Skip to content

7. Graphical interfaces with C# and VS.NET

7.1. The basics of graphical user interfaces

7.1.1. A first project

Let's build a first "Windows Application" project:

  • [1]: Create a new project
  • [2] : Windows Application type
  • [3]: the name of the project is not important at the moment
  • [4]: the project created
  • [5]: save the current solution
  • [6]: project name
  • [7]: solution file
  • [8]: solution name
  • [9]: a folder will be created for the [Chap5] solution. Its projects will be in sub-folders.
  • [10]: project [01] in solution [Chap5] :
  • [Program.cs] is the project's main class
  • [Form1.cs] is the source file that manages the window's behavior [11]
  • [Form1.Designer.cs] is the source file that encapsulates information about the window's components [11]
  • [11]: file [Form1.cs] in design mode
  • [12]: the generated application can be run by (Ctrl-F5). The [Form1] window is displayed. It can be moved, resized and closed. The basic elements of a graphical window are now available.

The main class [Program.cs] is as follows:


using System;
using System.Windows.Forms;
 
namespace Chap5 {
    static class Program {
         /// <summary>
        /// The main entry point for the application.
         /// </summary>
        [STAThread]
        static void Main() {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}
  • line 2: applications with forms use the namespace System.Windows.Forms.
  • line 4: the original namespace has been renamed to Chap5.
  • line 10: when the project is run (Ctrl-F5), the [Main] method is executed.
  • lines 11-13: the classroom Application belongs to the System.Windows.Forms. It contains static methods for starting and stopping Windows graphics applications.
  • line 11: optional - allows you to give different visual styles to controls placed on a form
  • line 12: optional - sets the rendering engine for control texts: GDI+ (true), GDI (false)
  • line 13: the only essential line in the [Main] method: instantiates the [Form1] class, which is the form class, and instructs it to run.

The source file [Form1.cs] is as follows:


using System;
using System.Windows.Forms;
 
namespace Chap5 {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }
    }
}
  • line 5: the classroom Form1 derives from the [System.Windows.Forms.Form] class, which is the parent class of all windows. The keyword partial indicates that the class is partial and can be completed by other source files. This is the case ici, where the class Form1 is divided into two files:
  • [Form1.cs]: where you'll find the form's behavior, including its event handlers
  • [Form1.Designer.cs]: contains the form components and their properties. This file is regenerated each time the user modifies the window in [design] mode.
  • lines 6-8: class constructor Form1
  • line 7: calls the InitializeComponent. This method is not present in [Form1.cs]. It is found in [Form1.Designer.cs].

The source file [Form1.Designer.cs] is as follows:


namespace Chap5 {
    partial class Form1 {
         // <summary>
         // Required designer variable.
         // </summary>
        private System.ComponentModel.IContainer components = null;
 
        //tax <summary>
        //tax Clean up any resources being used.
        //tax </summary>
        //tax <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing) {
            if (disposing && (components != null)) {
                components.Dispose();
            }
            base.Dispose(disposing);
        }
 
        #region Windows Form Designer generated code
 
        //tax <summary>
        //a IImpot object Required method for Designer support - do not modify
        //Tax the contents of this method with the code editor.
         /// </summary>
        private void InitializeComponent() {
            this.SuspendLayout();
            ///
            ///
            ///
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(196, 98);
            this.Name = "Form1";
            this.Text = "Form1";
            this.ResumeLayout(false);
 
        }
 
        #endregion
 
    }
}
  • line 2: the class is always Form1. Note that it no longer needs to be repeated that it derives from the Form.
  • lines 25-37: the method InitializeComponent called by the [Form1] class constructor. This method creates and initializes all form components. It is regenerated each time the form is changed in [design] mode. A section called region, is created to delimit it on lines 19-39. The developer must not add any code to this region: it will be overwritten the next time it is regenerated.

At first, it's simpler to ignore the [Form1.Designer.cs] code. It is automatically generated and is the translation into C# language of the choices made by the developer in [design] mode. Let's take a first example:

  • [1]: Select [design] mode by double-clicking on the file [Form1.cs]
  • [2]: right-click on the form and select [Properties]
  • [3]: [Form1] properties window
  • [4]: the [Text] property represents the window title
  • [5]: the change in the [Text] property is taken into account in [design] mode as well as in the [Form1.Designer.cs] source code:

        private void InitializeComponent() {
            this.SuspendLayout();
...
            this.Text = "Mon 1er formulaire";
...
}

7.1.2. A second project

7.1.2.1. The form

We're starting a new project called 02. To do this, we follow the procedure described above for creating a project. The window to be created is as follows:

The form components are as follows:

name
type
role
1
labelSaisie
Label
a wording
2
textBoxSaisie
TextBox
an input field
3
buttonAfficher
Button
to display the contents of the textBoxSaisie input field in a dialog box

To build this window, proceed as follows:

  • [1]: right-click on the form outside any component and select option [Properties]
  • [2]: the window properties sheet appears in the bottom right-hand corner of Visual studio

Form properties include :

BackColor
to set the window background color
ForeColor
to set the color of drawings or text in the window
Menu
to associate a menu with the window
Text
to give the window a title
FormBorderStyle
to set the window type
Font
to set the font for writing in the
Name
to set the window name

Ici, we set the properties Text and Name :

Text
Inputs and buttons - 1
Name
frmSaisiesBoutons
  • [1]: choose the [Common Controls] toolbox from among the toolboxes available in Visual Studio
  • [2, 3, 4]: double-click the [Label], [Button] and [TextBox] components in succession
  • [5]: the three components are on the form

To align and size components correctly, you can use the toolbar items :

 
 

The principle of formatting is as follows:

  1. select the components to be formatted together (hold down the Ctrl key while clicking to select components)
  2. select the desired formatting type :
  • (continued)
    • options Align allow components to be aligned top, bottom, left, right, center, etc
    • options Make Same Size allow components to have the same height or width
    • the option Horizontal Spacing allows components to be aligned horizontally, with intervals between them of the same width. Ditto for option Vertical Spacing to align vertically.
    • the option Center to center a component horizontally (Horizontally) or vertically (Vertically) in the

Once the components have been placed, we set their properties. To do this, right-click on the component and select option Properties :

  • [1] : select the component to open its properties window. In this window, modify the following properties: name : labelSaisie, text : Input
  • [2]: proceed in the same way: name : textBoxSaisie, text : put nothing on
  • [3] : name : buttonAfficher, text : View
  • [4]: the window itself: name : frmSaisiesBoutons, text : Inputs and buttons - 1
  • [5]: execute (Ctrl-F5) the project to get a first glimpse of the window in action.

What was done in [design] mode has been translated into [Form1.Designer.cs] code:


namespace Chap5 {
    partial class frmSaisiesBoutons {
...
        private System.ComponentModel.IContainer components = null;
...
        private void InitializeComponent() {
            this.labelSaisie = new System.Windows.Forms.Label();
            this.buttonAfficher = new System.Windows.Forms.Button();
            this.textBoxSaisie = new System.Windows.Forms.TextBox();
            this.SuspendLayout();
            ///
            ///
            ///
            this.labelSaisie.AutoSize = true;
            this.labelSaisie.Location = new System.Drawing.Point(12, 19);
            this.labelSaisie.Name = "labelSaisie";
            this.labelSaisie.Size = new System.Drawing.Size(35, 13);
            this.labelSaisie.TabIndex = 0;
            this.labelSaisie.Text = "Saisie";
            ///
            ///
            ///
            this.buttonAfficher.Location = new System.Drawing.Point(80, 49);
            this.buttonAfficher.Name = "buttonAfficher";
            this.buttonAfficher.Size = new System.Drawing.Size(75, 23);
            this.buttonAfficher.TabIndex = 1;
            this.buttonAfficher.Text = "Afficher";
            this.buttonAfficher.UseVisualStyleBackColor = true;
            this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);
            ///
             // Form1
             // labelSaisie
            this.textBoxSaisie.Location = new System.Drawing.Point(80, 19);
            this.textBoxSaisie.Name = "textBoxSaisie";
            this.textBoxSaisie.Size = new System.Drawing.Size(100, 20);
            this.textBoxSaisie.TabIndex = 2;
             // buttonAfficher
             // textBoxSaisie
             // frmSaisiesBoutons
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(292, 118);
            this.Controls.Add(this.textBoxSaisie);
            this.Controls.Add(this.buttonAfficher);
            this.Controls.Add(this.labelSaisie);
            this.Name = "frmSaisiesBoutons";
            this.Text = "Saisies et boutons - 1";
            this.ResumeLayout(false);
            this.PerformLayout();
 
        }
 
        private System.Windows.Forms.Label labelSaisie;
        private System.Windows.Forms.Button buttonAfficher;
        private System.Windows.Forms.TextBox textBoxSaisie;
 
    }
}
  • lines 53-55: the three components have given rise to three private fields in the [Form1] class. Note that the names of these fields are the names given to the components in [design] mode. This is also the case for line 2, which is the class itself.
  • lines 7-9: the three objects of type [Label], [TextBox] and [Button] are created. These are used to manage visual components.
  • lines 14-19: label configuration labelSaisie
  • lines 23-29: button configuration buttonAfficher
  • lines 33-36: input field configuration textBoxSaisie
  • lines 40-47: form configuration frmSaisiesBoutons. Lines 43-45 show how to add components to the form.

This code is easy to understand. It is therefore possible to build forms by code without using the [design] mode. Numerous examples of this are given in the MSDN Visual Studio documentation. Mastering this code allows you to create forms at runtime: for example, create a form on the fly to update a database table, with the table structure only being discovered at runtime.

All that remains is to write the procedure for managing a click on the View. Select the button to access its properties window. This window has several tabs:

  • [1]: list of properties in alphabetical order
  • [2]: control events

Control properties and events can be accessed by category or alphabetically:

  • [3]: Properties or events by category
  • [4]: Properties or events in alphabetical order

The Events in Categories for the buttonAfficher is as follows:

  • [1]: the left-hand column of the window lists the possible events on the button. A click on a button corresponds to the event Click.
  • [2]: the right-hand column contains the name of the procedure called when the corresponding event occurs.
  • [3]: if you double-click on the event cell Click, we automatically switch to the code window to write the event handler Click button buttonAfficher :

using System;
using System.Windows.Forms;
 
namespace Chap5 {
    public partial class frmSaisiesBoutons : Form {
        public frmSaisiesBoutons() {
            InitializeComponent();
        }
 
        private void buttonAfficher_Click(object sender, EventArgs e) {
 
        }
    }
}
  • lines 10-12: the event handler skeleton Click on the button named buttonAfficher. The following points should be noted:
    • the method is named as follows eventName_ComponentName
    • the method is private. It receives two parameters:
    • sender : is the object that triggered the event. If the procedure is executed following a click on the buttonAfficher, sender will be equal to buttonAfficher. It is conceivable that the buttonAfficher_Click is executed from another procedure. This procedure would then be free to set the object sender of its choice.
    • EventArgs : an object containing information about the event. For an event Click, it contains nothing. For a mouse movement event, it will contain the mouse's (X,Y) coordinates.
    • we won't use any of these ici parameters.

Writing an event handler involves completing the previous code skeleton. with Ici, we want to present a dialog box with the contents of the textBoxSaisie if non-empty [1], an error message otherwise [2]:

The code to achieve this could be as follows:


        private void buttonAfficher_Click(object sender, EventArgs e) {
            // displays the text entered in the TextBox textboxSaisie
            string texte = textBoxSaisie.Text.Trim();
            if (texte.Length != 0) {
                MessageBox.Show("Texte saisi= " + texte, "Vérification de la saisie", MessageBoxButtons.OK, MessageBoxIcon.Information);
            } else {
                MessageBox.Show("Saissez un texte...", "Vérification de la saisie", MessageBoxButtons.OK, MessageBoxIcon.Error);
}

The class MessageBox is used to display messages in a window. We've used ici method Show next :


public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon);

with

text
the message to be displayed
caption
window title
buttons
buttons in the window
icon
the icon in the

The buttons can take its values from the following constants (prefixed by MessageBoxButtons as shown in line 7) above:

constante
buttons
   AbortRetryIgnore 
  OK 
    OKCancel 
    RetryCancel 
    YesNo 
    YesNoCancel 

The icon can take its values from the following constants (prefixed by MessageBoxIcon as shown in line 10) above:

Asterisk
Error
idem Stop
Exclamation
idem Warning
Hand
Information
idem Asterisk
None
Question
Stop
idem Hand
Warning

 

The method Show is a static method that returns a result of type [System.Windows.Forms.DialogResult] which is an enumeration :

Image

To find out which button the user pressed to close the MessageBox we write :

DialogResult res=MessageBox.Show(..);
if (res==DialogResult.Yes){ // il a appuyé sur le bouton oui...}

7.1.2.2. Event management code

In addition to the buttonAfficher_Click we have written, Visual studio has generated in the method InitializeComponents of [Form1.Designer.cs], which creates and initializes the form components, the following line :


            this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);

Click is a event class Button [1, 2, 3] :

  • [5]: the declaration of event [Control.Click] [4]. This shows that the event Click is not specific to the [Button] class. It belongs to the [Control] class, the parent class of the [Button] class.
    • EventHandler is a prototype (a model) of a method called delegate. We'll come back to this later.
    • event is a keyword that restricts the functionality of the delegate EventHandler : an object delegate has richer functionality than a event.

Visit delegate EventHandler is defined as follows:

 

Visit delegate EventHandler designates a method model :

  • with type as 1st parameter Object
  • whose 2nd parameter is a EventArgs
  • not returning any results

This is the case of the method for managing clicks on the buttonAfficher generated by Visual Studio :


        private void buttonAfficher_Click(object sender, EventArgs e);

The buttonAfficher_Click corresponds to the prototype defined by the EventHandler. To create a EventHandler, proceed as follows:

EventHandler evtHandler=new EventHandler(méthode correspondant au prototype  défini par le type EventHandler);

Since the buttonAfficher_Click corresponds to the prototype defined by the EventHandler, we can write :

EventHandler evtHandler=new EventHandler(buttonAfficher_Click);

A variable of type delegate is in fact a list of references to methods such as the delegate. To add a new method M to the variable evtHandler above, we use the syntax :

evtHandler+=new EvtHandler(M);

The += notation can be used even if evtHandler is an empty list.

Let's go back to the line in [InitializeComponent] that adds an event handler to the event Click object buttonAfficher :


            this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);

This instruction adds a EventHandler to the list of methods in the buttonAfficher.Click. These methods will be called each time the Click on the component buttonAfficher will be detected. There is often only one. It's called the "event handler".

Let's go back to the signing of EventHandler :


        private delegate void EventHandler(object sender, EventArgs e);

The second parameter of the delegate is an object of type EventArgs or a derived class. The type EventArgs is very general and doesn't actually provide any information about the event that occurred. For a click on a button, this is sufficient. For a mouse movement on a form, we'd have a MouseMove of class [Form] defined by :

public event MouseEventHandler MouseMove;

Visit delegate MouseEventHandler is defined as :

 

This is a delegated signature function void f (object, MouseEventArgs). The class MouseEventArgs is defined by :

The class MouseEventArgs is richer than the EventArgs. For example, we can find out the coordinates of the mouse X and Y at the moment the event occurs.

7.1.2.3. Conclusion

From the two projects studied, we can conclude that once the GUI has been built with Visual Studio, the developer's job is mainly to write the event handlers he wants to manage for this GUI. Code is generated automatically by Visual Studio. This code, which can be complex, can be ignored at first. Later, however, its study may provide a better understanding of how to create and manage forms.

7.2. Basic components

We now present a number of applications involving the most common components, in order to discover their main methods and properties. For each application, we present the graphical interface and the code of interest, mainly that of the event handlers.

7.2.1. Form Form

We'll start by presenting the essential component, the form on which you place components. We've already presented some of its basic properties. We'll now ici a few of the form's most important events.

Load
the form is loading
Closing
the form is being closed
Closed
the form is closed

The event Load event occurs before the form is displayed. The event Closing occurs when the form is being closed. It is also possible to stop this closing by programming.

We build a name form Form1 without component :

  • [1] : the form
  • [2]: the three events covered

The code for [Form1.cs] is as follows:


using System;
using System.Windows.Forms;
 
namespace Chap5 {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e) {
             // initial form loading
            MessageBox.Show("Evt Load", "Load");
        }
 
        private void Form1_FormClosing(object sender, FormClosingEventArgs e) {
            // the form is closing
            MessageBox.Show("Evt FormClosing", "FormClosing");
             // confirmation requested
            DialogResult réponse = MessageBox.Show("Voulez-vous vraiment quitter l'application", "Closing", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
            if (réponse == DialogResult.No)
                e.Cancel = true;
        }
 
        private void Form1_FormClosed(object sender, FormClosedEventArgs e) {
            // the form will be closed
            MessageBox.Show("Evt FormClosed", "FormClosed");
        }
    }
}

We use the MessageBox to be notified of events.

line 10: The event Load will occur when the
the application before the form is even displayed:
  
line 15: The event FormClosing will occur when
the user closes the window.
line 19: We then ask him if he really wants to leave
the :
line 20: If he answers No, we set the property Cancel from
the event CancelEventArgs e that the method received in
parameter. If we set this property to False, closing
of the window is aborted, otherwise it continues. The event
FormClosed will then occur:

7.2.2. Label labels and entry boxes TextBox

We've already come across these two components. Label is a text component and TextBox an input field component. Their main property is Text which designates either the content of the input field or the text of the label. This property is read/write.

The event usually used for TextBox is TextChanged which signals that the user has modified the input field. Here's an example using the TextChanged to track changes in an input field :

type
name
role
1
TextBox
textBoxSaisie
input field
2
Label
labelControle
displays text from 1 in real time
AutoSize=False, Text=(rien)
3
Button
buttonEffacer
to delete fields 1 and 2
4
Button
buttonQuitter
to quit the application

The code for this application is :


using System.Windows.Forms;
 
namespace Chap5 {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }
 
        private void textBoxSaisie_TextChanged(object sender, System.EventArgs e) {
            // the content of TextBox has changed - copy it to Label labelControle
            labelControle.Text = textBoxSaisie.Text;
        }
 
        private void buttonEffacer_Click(object sender, System.EventArgs e) {
            // delete the contents of the input box
            textBoxSaisie.Text = "";
        }
 
        private void buttonQuitter_Click(object sender, System.EventArgs e) {
            // click on the Quit button - exit the application
            Application.Exit();
        }
 
        private void Form1_Shown(object sender, System.EventArgs e) {
            // focus on the input field
            textBoxSaisie.Focus();
        }
    }
}
  • line 24: the event [Form].Shown takes place when the form is displayed
  • line 26: the focus (for input) is placed on the component textBoxSaisie.
  • line 9: the event [TextBox].TextChanged occurs whenever the contents of a component TextBox change
  • line 11: the contents of component [TextBox] are copied to component [Label]
  • line 14: manages the click on the [Delete] button
  • line 16: we put the empty string in component [TextBox]
  • line 19: manages the click on the [Quit] button
  • line 21: to stop the running application. Remember that the Application is used to launch the application in the [Main] method of [Form1.cs] :

        static void Main() {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
}

The following example uses a TextBox multiline :

The list of controls is as follows:

type
name
role
1
TextBox
textBoxLignes
multi-line input field
Multiline=true, ScrollBars=Both, AcceptReturn=True, AcceptTab=True
2
TextBox
textBoxLigne
single-line input field
3
Button
buttonAjouter
Adds contents from 2 to 1

To make a TextBox multiline, set the following control properties:

Multiline=true
to accept several lines of text
ScrollBars=( None, Horizontal, Vertical, Both)
to request that the control have scrollbars (Horizontal, Vertical, Both) or not (None)
AcceptReturn=(True, False)
if equal to true, the Enter key will jump to the line
AcceptTab=(True, False)
if true, the Tab key will generate a tab in the text

The application allows lines to be typed directly into [1] or added via [2] and [3].

The application code is as follows:


using System.Windows.Forms;
using System;
 
namespace Chap5 {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }
 
        private void buttonAjouter_Click(object sender, System.EventArgs e) {
            // add the content of textBoxLigne to that of textBoxLignes
            textBoxLignes.Text += textBoxLigne.Text+Environment.NewLine;
            textBoxLigne.Text = "";
        }
 
        private void Form1_Shown(object sender, EventArgs e) {
            // focus on the input field
            textBoxLigne.Focus();
        }
    }
}
  • line 18: when the form is displayed (evt Shown), focus on the input field textBoxLigne
  • line 10: manages the click on the [Add] button
  • line 12: input field text textBoxLigne is added to the text in the input field textBoxLignes followed by a line feed.
  • line 13: the input field textBoxLigne is deleted

7.2.3. Drop-down lists ComboBox

We create the following form:

type
name
role
1
ComboBox
comboNombres
contains character strings
DropDownStyle=DropDownList

A component ComboBox is a drop-down list with an input field: the user can either select an item in (2) or type text in (1). There are three types of ComboBox set by the property DropDownStyle :

Simple
non-drop-down list with edit zone
DropDown
drop-down list with edit zone
DropDownList
drop-down list without edit zone

By default, the type of a ComboBox is DropDown.

The class ComboBox to a single manufacturer:

new ComboBox()
creates an empty combo

The elements of ComboBox are available in the property Items :

public ComboBox.ObjectCollection Items {get;}

This is an indexed property, Items[i] designating the i element of the Combo. It is read-only.

Or C a combo and C.Items its list of elements. We have the following properties:

C.Items.Count
number of combo elements
C.Items[i]
element i of the combo
C.Add(object o)
adds object o as last element of combo
C.AddRange(object[] objets)
adds an array of objects at the end of a combo
C.Insert(int i, object o)
adds object o to position i of combo
C.RemoveAt(int i)
removes element i from combo
C.Remove(object o)
removes object o from combo
C.Clear()
deletes all elements of the combo
C.IndexOf(object o)
renders the position i of object o in the combo
C.SelectedIndex
index of selected item
C.SelectedItem
selected item
C.SelectedItem.Text
text displayed for selected item
C.Text
text displayed for selected item

It may come as a surprise that a combo can contain objects while visually displaying strings. If a ComboBox contains an object obj, it displays the string obj.ToString(). Remember that every object has a ToString inherited from the object which renders a character string "representative" of the object.

The Item selected in combo C is C.SelectedItem or C.Items[C.SelectedIndex] where C.SelectedIndex is the number of the selected element, starting from zero for the first element. The selected text can be obtained in various ways: C.SelectedItem.Text, C.Text

When an item is selected from the drop-down list, the event SelectedIndexChanged event, which can then be used to notify the user of a selection change in the combo. In the following application, we use this event to display the item that has been selected in the list.

 

The application code is as follows:


using System.Windows.Forms;
 
namespace Chap5 {
    public partial class Form1 : Form {
        private int previousSelectedIndex=0;
 
        public Form1() {
            InitializeComponent();
             // combo filling
            comboBoxNombres.Items.AddRange(new string[] { "zéro", "un", "deux", "trois", "quatre" });
             // select item no. 0
            comboBoxNombres.SelectedIndex = 0;
        }
 
        private void comboBoxNombres_SelectedIndexChanged(object sender, System.EventArgs e) {
            int newSelectedIndex = comboBoxNombres.SelectedIndex;
            if (newSelectedIndex != previousSelectedIndex) {
                // the selected item has changed - it is displayed
                MessageBox.Show(string.Format("Elément sélectionné : ({0},{1})", comboBoxNombres.Text, newSelectedIndex), "Combo", MessageBoxButtons.OK, MessageBoxIcon.Information);
                // note the new index
                previousSelectedIndex = newSelectedIndex;
            }
        }
    }
}
  • line 5: previousSelectedIndex stores the last index selected in the combo
  • line 10: fill the combo with an array of character strings
  • line 12: the 1st item is selected
  • line 15: the method executed each time the user selects an item from the combo. Contrary to what the name might suggest, this event takes place even if the item selected is the same as the previous one.
  • line 16: notes the index of the selected element
  • line 17: if different from above
  • line 19: displays the number and text of the selected item
  • line 21: note the new index

7.2.4. Component ListBox

We propose to build the following interface:

The components of this window are as follows:

type
name
role/properties
0
Form
Form1
form
FormBorderStyle=FixedSingle (cadre non redimensionable)
1
TextBox
textBoxSaisie
input field
2
Button
buttonAjouter
button to add the contents of input field [1] to list [3]
3
ListBox
listBox1
list 1
SelectionMode=MultiExtended :
4
ListBox
listBox2
list 2
SelectionMode=MultiSimple :
5
Button
button1to2
transfers selected items from list 1 to list 2
6
Button
button2to1
does the opposite
7
Button
buttonEffacer1
empties list 1
8
Button
buttonEffacer2
empties list 2

The components ListBox have a selection mode of their elements which is defined by their property SelectionMode :

One
only one item can be selected
MultiExtended
multi-selection possible: holding down the SHIFT key and clicking on an element extends the selection from the previously selected element to the current element.
MultiSimple
multi-selection possible: an element can be selected or deselected with a mouse click or by pressing the space bar.
  • The user types text in field 1 and adds it to list 1 using the Add (2). The input field (1) is then emptied and the user can add a new element.
  • It can transfer items from one list to another by selecting the item to be transferred in one of the lists and choosing the appropriate transfer button 5 or 6. The transferred item is added to the end of the destination list and removed from the source list.
  • He can double-click on an item in list 1. This item is then transferred to the edit box and removed from list 1.

The buttons are switched on or off according to the following rules:

  • the button Add is only lit if there is non-empty text in the input field
  • button [5] for transferring list 1 to list 2 is only lit if there is an item selected in list 1
  • the button [6] for transferring list 2 to list 1 is only lit if there is an item selected in list 2
  • buttons [7] and [8] for deleting lists 1 and 2 are only lit if the list to be deleted contains items.

Under the above conditions, all buttons must be switched off when the application is started. This is the Enabled buttons which must then be positioned at false. This can be done at the design stage, which will generate the corresponding code in the InitializeComponent or do it ourselves in the builder as shown below:


        public Form1() {
            InitializeComponent();
            // --- initialisations complémentaires ---
            // on inhibe un certain nombre de boutons
            buttonAjouter.Enabled = false;
            button1vers2.Enabled = false;
            button2vers1.Enabled = false;
            buttonEffacer1.Enabled = false;
            buttonEffacer2.Enabled = false;
}

Button status Add is controlled by the content of the input field. This is the TextChanged which allows us to track changes in this content:


        private void textBoxSaisie_TextChanged(object sender, System.EventArgs e) {
            // the content of textBoxSaisie has changed
            // the Add button is only lit if the entry is non-empty
            buttonAjouter.Enabled = textBoxSaisie.Text.Trim() != "";
        }
 

The state of the transfer buttons depends on whether or not an item has been selected in the list they control:


        private void listBox1_SelectedIndexChanged(object sender, System.EventArgs e) {
             // an item has been selected
            // switch on the 1 to 2 transfer button
            button1vers2.Enabled = true;
        }
 
        private void listBox2_SelectedIndexChanged(object sender, System.EventArgs e) {
             // an item has been selected
            // switch on the 2 to 1 transfer button
            button2vers1.Enabled = true;
}

The code associated with clicking on the Add is as follows:


        private void buttonAjouter_Click(object sender, System.EventArgs e) {
             // add a new element to list 1
            listBox1.Items.Add(textBoxSaisie.Text.Trim());
             // raz de la saisie
            textBoxSaisie.Text = "";
            // List 1 is not empty
            buttonEffacer1.Enabled = true;
            // return focus to input box
            textBoxSaisie.Focus();
}

Note the Focus to focus on a form control. The code associated with button clicks Delete :


        private void buttonEffacer1_Click(object sender, System.EventArgs e) {
             // delete list 1
            listBox1.Items.Clear();
             // delete button
            buttonEffacer1.Enabled = false;
        }
 
        private void buttonEffacer2_Click(object sender, System.EventArgs e) {
             // delete list 2
            listBox2.Items.Clear();
             // delete button
            buttonEffacer2.Enabled = false;
}

The code for transferring selected items from one list to another :


        private void button1vers2_Click(object sender, System.EventArgs e) {
            // transfer the item selected in List 1 to List 2
            transfert(listBox1, button1vers2, buttonEffacer1, listBox2, button2vers1, buttonEffacer2);
        }
 
        private void button2vers1_Click(object sender, System.EventArgs e) {
            // transfer the item selected in List 2 to List 1
            transfert(listBox2, button2vers1, buttonEffacer2, listBox1, button1vers2, buttonEffacer1);
        }
 

The two methods above delegate the transfer of selected items from one list to another to a single private method called transfer :


         // transfer
        private void transfert(ListBox l1, Button button1vers2, Button buttonEffacer1, ListBox l2, Button button2vers1, Button buttonEffacer2) {
            // transfer selected items from list l1 to list l2
            for (int i = l1.SelectedIndices.Count - 1; i >= 0; i--) {
                 // index of selected item
                int index = l1.SelectedIndices[i];
                 // addition to l2
                l2.Items.Add(l1.Items[index]);
                // deletion in l1
                l1.Items.RemoveAt(index);
            }
             // delete buttons
            buttonEffacer2.Enabled = l2.Items.Count != 0;
            buttonEffacer1.Enabled = l1.Items.Count != 0;
             // transfer buttons
            button1vers2.Enabled = false;
}
  • line b: the method transfer receives six parameters:
  • a reference to the list containing the selected items called ici l1. When the application is run, l1 is either listBox1 or listBox2. Examples of calls are shown on lines 3 and 8 of the transfer procedures buttonXversY_Click.
  • a reference to the transfer button linked to the list l1. For example, if l1 is listBox2, it will be button2to1(see call line 8)
  • a reference to the delete list button l1. For example, if l1 is listBox1, it will be buttonEffacer1(see call line 3)
  • the other three references are similar but refer to the l2.
  • line d: the [ListBox] collection.SelectedIndices represents the indices of the elements selected in component [ListBox]. This is a :
  • [ListBox].SelectedIndices.Count is the number of items in this collection
  • [ListBox].SelectedIndices[i] is item no. i in this collection

We go through the collection in reverse order, starting at the end and ending at the beginning. We'll explain why.

  • line f: index of a selected item in the list l1
  • line h: this item is added to the list l2
  • line j: and deleted from the list l1. Because it has been deleted, it is no longer selected. The collection l1.SelectedIndices of line d will be recalculated. It will lose the element just deleted. All subsequent elements will see their number change from n to n-1.
  • if the loop in line (d) is increasing and has just processed element no. 0, it will then process element no. 1. Or the element which was n° 1 before the deletion of element n° 0, will then be n° 0. It will then be forgotten by the loop.
  • if the loop in line (d) is descending and has just processed element n° n, it will then process element n° n-1. After deleting element n° n, element n° n-1 does not change n°. It is therefore processed in the next loop.
  • lines m-n: the state of the [Delete] buttons depends on whether or not items are present in the associated lists
  • line p: the list l2 has no more selected items: turn off its transfer button.

7.2.5. Checkboxes CheckBox, radio buttons ButtonRadio

We propose to write the following application:

The window components are as follows:

type
name
role
1
GroupBox
cf [6]
groupBox1
a component container. Other components can be dropped in.
Text=Boutons radio
2
RadioButton
radioButton1
radioButton2
radioButton3
3 radio buttons - radioButton1 owns Checked=True and the Text=1 - radioButton2 owns Text=2 - radioButton3 owns Text=3
Radio buttons in the same container, ici the GroupBox, are exclusive one from the other: only one of them is lit.
3
GroupBox
groupBox2
 
4
CheckBox
checkBox1
checkBox2
checkBox3
3 checkboxes. chechBox1 owns Checked=True and the Text=A - chechBox2 owns Text=B - chechBox3 owns Text=C
5
ListBox
listBoxValeurs
a list that displays the values of radio buttons and checkboxes whenever a change occurs.
6
  
shows where to find the container GroupBox

The event of interest for these six controls is the CheckChanged indicating that the state of the checkbox or radio button has changed. In both cases, this state is represented by the Boolean property Checked which real means that the control is checked. We'll use ici only one method to handle all six events CheckChanged, the method poster. To ensure that the six events CheckChanged are managed by the same method poster, you can proceed as follows:

Select the component radioButton1 and right-click on it to access its properties:

In the events [1], we associate the poster [2] to the event CheckChanged. This means that we want the click on the option to be a option A1 is processed by a method called poster. Visual studio automatically generates the poster in the code window:


private void affiche(object sender, EventArgs e) {
        }

The method poster is a EventHandler.

For the other five components, we does the same. For example, let's select the option CheckBox1 and its events [3]. Facing the event Click, we have a drop-down list [4] containing the existing methods that can process this event. in the case of Ici, only the method poster. We select it. Repeat this process for all other components.

Dn the method InitializeComponent code has been generated. The poster has been declared as the handler of the six events CheckedChanged as follows:


this.radioButton1.CheckedChanged += new System.EventHandler(this.affiche);
this.radioButton2.CheckedChanged += new System.EventHandler(this.affiche);
this.radioButton3.CheckedChanged += new System.EventHandler(this.affiche);
this.checkBox1.CheckedChanged += new System.EventHandler(this.affiche);
this.checkBox2.CheckedChanged += new System.EventHandler(this.affiche);
this.checkBox3.CheckedChanged += new System.EventHandler(this.affiche);

The method poster is completed as follows:


        private void affiche(object sender, System.EventArgs e) {
            // displays radio button or checkbox status
            // is it a checkbox?
            if (sender is CheckBox) {
                CheckBox chk = (CheckBox)sender;
                listBoxvaleurs.Items.Add(chk.Name + "=" + chk.Checked);
            }
            // is it a radiobutton?
            if (sender is RadioButton) {
                RadioButton rdb = (RadioButton)sender;
                listBoxvaleurs.Items.Add(rdb.Name + "=" + rdb.Checked);
            }
}

The syntax


            if (sender is CheckBox) {

is used to check that the sender type is CheckBox. This then allows us to transtype to the exact type of sender. The method poster written in the listBoxValeurs the name of the component causing the event and its property value Checked. At runtime [7], a click on a radio button triggers two events CheckChanged : one on the old checked button, which changes to "unchecked", and the other on the new button, which changes to "checked".

7.2.6. Inverters ScrollBar

There are several types of drive:
the horizontal drive (HscrollBar),
the vertical drive (VscrollBar),
the incrementer (NumericUpDown).

Let's run the following application:

type
name
role
1
hScrollBar
hScrollBar1
a horizontal drive
2
hScrollBar
hScrollBar2
a horizontal variator that follows the variations of variator 1
3
Label
labelValeurHS1
displays the value of the horizontal drive
4
NumericUpDown
numericUpDown2
to set the value of controller 2

An inverter ScrollBar allows the user to select a value from a range of integer values symbolized by the drive "band" over which a cursor moves. The drive value is available in its Value.

  • For a horizontal drive, the left-hand end represents the minimum value of the range, the right-hand end the maximum value, and the cursor the current selected value. For a vertical drive, the minimum is represented by the upper end, the maximum by the lower end. These values are represented by the properties Minimum and Maximum and default to 0 and 100.
  • Clicking on the drive ends causes the value to change by one increment (positive or negative) depending on which end is clicked SmallChange which defaults to 1.
  • Clicking on either side of the cursor changes the value by one increment (positive or negative), depending on which end is clicked LargeChange which defaults to 10.
  • When the upper end of a vertical dimmer is clicked, its value decreases. This may come as a surprise to the average user, who normally expects the value to "go up". This problem is solved by giving a negative value to the properties SmallChange and LargeChange
  • These five properties (Value, Minimum, Maximum, SmallChange, LargeChange) are accessible for reading and writing.
  • The drive's main event is the one that signals a change of value: the Scroll.

A component NumericUpDown is similar to that of the drive: it also has the following properties Minimum, Maximum and Value, defaults to 0, 100, 0. But ici, the Value is displayed in an input box that is an integral part of the control. The user himself can modify this value unless the property ReadOnly control to true. The increment value is set by the Incrementally, default 1. The component's main event NumericUpDown is the one that signals a change in value: the event ValueChanged

The application code is as follows:


using System.Windows.Forms;
 
namespace Chap5 {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
            // set the characteristics of drive 1
            hScrollBar1.Value = 7;
            hScrollBar1.Minimum = 1;
            hScrollBar1.Maximum = 130;
            hScrollBar1.LargeChange = 11;
            hScrollBar1.SmallChange = 1;
            // drive 2 is given the same characteristics as drive 1
            hScrollBar2.Value = hScrollBar1.Value;
            hScrollBar2.Minimum = hScrollBar1.Minimum;
            hScrollBar2.Maximum = hScrollBar1.Maximum;
            hScrollBar2.LargeChange = hScrollBar1.LargeChange;
            hScrollBar2.SmallChange = hScrollBar1.SmallChange;
            // ditto for the incrementer
            numericUpDown2.Value = hScrollBar1.Value;
            numericUpDown2.Minimum = hScrollBar1.Minimum;
            numericUpDown2.Maximum = hScrollBar1.Maximum;
            numericUpDown2.Increment = hScrollBar1.SmallChange;
 
            // the Label is given the value of drive 1
            labelValeurHS1.Text = hScrollBar1.Value.ToString();
        }
 
        private void hScrollBar1_Scroll(object sender, ScrollEventArgs e) {
            // value change on drive 1
            // its value is passed on to drive 2 and to the label
            hScrollBar2.Value = hScrollBar1.Value;
            labelValeurHS1.Text = hScrollBar1.Value.ToString();
        }
 
        private void numericUpDown2_ValueChanged(object sender, System.EventArgs e) {
            // incrementer has changed value
            // set the value of controller 2
            hScrollBar2.Value = (int)numericUpDown2.Value;
        }
    }
}

7.3. Events mouse

When drawing in a container, it's important to know the position of the mouse so that, for example, a point can be displayed when clicked. Mouse movements trigger events in the container in which it moves.

  • [1]: events occurring when the mouse is moved over a form or control
  • [2]: events occurring during drag & drop (Drag'nDrop)
MouseEnter
the mouse has entered the field of control
MouseLeave
the mouse has just left the control domain
MouseMove
the mouse is on the move in the control sector
MouseDown
Press left mouse button
MouseUp
Release left mouse button
DragDrop
user drops an object on the control
DragEnter
the user enters the control domain by pulling an object
DragLeave
the user leaves the control domain by pulling an object
DragOver
the user passes over the control domain by pulling an object

Here's an application to help you understand when different mouse events occur:

type
name
role
1
Label
lblPositionSouris
to display the mouse position in form 1, list 2 or button 3
2
ListBox
listBoxEvts
to display mouse events other than MouseMove
3
Button
buttonEffacer
to delete the contents of 2

To track mouse movements on the three controls, we write a single handler, the poster :

The procedure code poster is as follows:


        private void affiche(object sender, MouseEventArgs e) {
             // mvt mouse - displays its (X,Y) coordinates
            labelPositionSouris.Text = "(" + e.X + "," + e.Y + ")";
}

Each time the mouse enters the domain of a control, its coordinate system changes. Its origin (0,0) is the top left corner of the control it's on. So at runtime, when you move the mouse from the form to the button, you can clearly see the change in coordinates. To better see these changes in the mouse's domain, you can use the Cursor [1] controls :

This property is used to set the shape of the mouse cursor when it enters the control domain. In our example, we set the cursor to Default for the form itself [2], Hand for list 2 [3] and Cross for button 3 [4].

Furthermore, to detect mouse entry and exit on list 2, we process the events MouseEnter and MouseLeave from the same list:


        private void listBoxEvts_MouseEnter(object sender, System.EventArgs e) {
            // the event
            listBoxEvts.Items.Insert(0, string.Format("MouseEnter à {0:hh:mm:ss}",DateTime.Now));
        }
 
        private void listBoxEvts_MouseLeave(object sender, EventArgs e) {
            // the event
            listBoxEvts.Items.Insert(0, string.Format("MouseLeave à {0:hh:mm:ss}", DateTime.Now));
}

To process clicks on the form, we process the following events MouseDown and MouseUp :


        private void listBoxEvts_MouseDown(object sender, MouseEventArgs e) {
            // the event
            listBoxEvts.Items.Insert(0, string.Format("MouseDown à {0:hh:mm:ss}", DateTime.Now));
        }
 
        private void listBoxEvts_MouseUp(object sender, MouseEventArgs e) {
            // the event
            listBoxEvts.Items.Insert(0, string.Format("MouseUp à {0:hh:mm:ss}", DateTime.Now));
}
  • lines 3 and 8: messages are placed in 1st position in the ListBox so that the most recent events are listed first.
 

Finally, the code for the button click handler Delete :


        private void buttonEffacer_Click(object sender, EventArgs e) {
            listBoxEvts.Items.Clear();
}

7.4. Create a window with menu

Now let's see how to create a window with a menu. We'll create the following window:

To create a menu, select the "MenuStrip"in the barMenus & Tollbars" :

  • [1]: selection of component [MenuStrip]
  • [2]: a menu appears on the form, with empty boxes labelled "Type Here". All you have to do is indicate the various menu options.
  • [3]: the "Options A" label has been typed. We move on to label [4].
  • [5]: Option A labels have been entered. Move on to label [6]
  • [6]: the first B options
  • [7]: under B1, a separator is used. This is available in a combo associated with the text "Type Here"
  • [8]: to make a sub-menu, use arrow [8] and type the sub-menu in [9]

All that remains is to name the various components of the form:

type
nom(s)
role
1
Label
labelStatut
to display the text of the option menu item clicked
2
toolStripMenuItem
toolStripMenuItemOptionsA
toolStripMenuItemA1
toolStripMenuItemA2
toolStripMenuItemA3
menu options under the main option "Options A"
3
toolStripMenuItem
toolStripMenuItemOptionsB
toolStripMenuItemB1
toolStripMenuItemB2
toolStripMenuItemB3
menu options under the main option "Options B"
4
toolStripMenuItem
toolStripMenuItemB31
toolStripMenuItemB32
menu options under the main option "B3"

Menu options are controls like other visual components, and have properties and events. For example, the properties of the option menu item are as follows A1 are as follows:

 

Two properties are used in our example:

Name
menu control name
Text
the option menu label

In the menu structure, select option A1 and right-click to access the control's properties:

In the events [1], we associate the poster [2] to the event Click. This means that we want the click on the option to be a option A1 is processed by a method called poster. Visual studio automatically generates the poster in the code window:


private void affiche(object sender, EventArgs e) {
        }

In this method, we'll simply display in the label labelStatut the property Text of the option menu item that has been clicked:


private void affiche(object sender, EventArgs e) {
            // displays the name of the selected submenu in the TextBox
            labelStatut.Text = ((ToolStripMenuItem)sender).Text;
}

The source of the event sender type is object. The menu options are ToolStripMenuItem, so we are obliged to transtype the object to ToolStripMenuItem.

For all menu options, we set the click handler to the method poster [3,4].

Let's run the application and select a menu item:

 

7.5. Non-visual components

We're now turning our attention to a number of non-visual components: they're used during design, but not seen at runtime.

7.5.1. Dialog boxes OpenFileDialog and SaveFileDialog

We're going to build the following application:

The controls are as follows:

type
name
role
1
TextBox
TextBoxLignes
text typed by the user or loaded from a file
MultiLine=True, ScrollBars=Both, AccepReturn=True, AcceptTab=True
2
Button
buttonSauvegarder
saves the text of [1] in a text file
3
Button
buttonCharger
loads the contents of a text file into [1]
4
Button
buttonEffacer
deletes the contents of [1]
5
SaveFileDialog
saveFileDialog1
component for choosing the name and location of the backup file for [1]. This component is taken from the toolbar [7] and simply placed on the form. It is then saved, but does not take up any space on the form. It is a non-visual component.
6
OpenFileDialog
openFileDialog1
component for selecting the file to be loaded into [1].

The code associated with the Delete is simple:


        private void buttonEffacer_Click(object sender, EventArgs e) {
            // we put the empty string in the TexBox
            textBoxLignes.Text = "";
}

We will use the following properties and methods of the SaveFileDialog :

Field
Type
Role
string Filter
Property
the file types offered in the file type drop-down list in the
int FilterIndex
Property
the number of the file type proposed by default in the list above. Starts at 0.
string InitialDirectory
Property
the folder originally presented for saving the file
string FileName
Property
the name of the backup file specified by the user
DialogResult.ShowDialog()
Method
method that displays the save dialog box. Returns a result of type DialogResult.

The method ShowDialog displays a dialog box similar to the one below:

1
drop-down list built from the Filter. The default file type is set by FilterIndex
2
current file, set by InitialDirectory if this property has been set
3
file name chosen or typed directly by the user. Will be available in the FileName
4
buttons Save/Cancel. If the Register is used, the ShowDialog makes the result DialogResult.OK

The safeguard procedure can be written as follows:


private void buttonSauvegarder_Click(object sender, System.EventArgs e) {
            // save the input box in a text file
            // set the savefileDialog1 dialog box
            saveFileDialog1.InitialDirectory = Application.ExecutablePath;
            saveFileDialog1.Filter = "Fichiers texte (*.txt)|*.txt|Tous les fichiers (*.*)|*.*";
            saveFileDialog1.FilterIndex = 0;
            // display the dialog box and retrieve the result
            if (saveFileDialog1.ShowDialog() == DialogResult.OK) {
                // retrieve the file name
                string nomFichier = saveFileDialog1.FileName;
                StreamWriter fichier = null;
                try {
                    // open the file for writing
                    fichier = new StreamWriter(nomFichier);
                    // we write the text inside
                    fichier.Write(textBoxLignes.Text);
                } catch (Exception ex) {
                     // problem
                    MessageBox.Show("Problème à l'écriture du fichier (" +
                    ex.Message + ")", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return;
                } finally {
                    // close the file
                    if (fichier != null) {
                        fichier.Dispose();
                    }
                }
            }
        }
  • line 4: set the initial file (InitialDirectory) to the file (Application.ExecutablePath) which contains the application executable.
  • line 5: set the file types to be displayed. Note the filter syntax: filter1|filter2|..|filtren with filteri= Text|file model. Ici the user will be able to choose between the following files *.txt and *.*.
  • line 6: set the file type to be presented first to the user. Ici index 0 designates *.txt files.
  • line 8: the dialog box is displayed and its result retrieved. While the dialog box is displayed, the user no longer has access to the main form (modal dialog box). The user sets the name of the file to be saved and exits the dialog box either by clicking on the Register, or via the Cancel, or by closing the box. The result of the ShowDialog is DialogResult.OK only if the user has used the Register to exit the dialog box.
  • Once this has been done, the name of the file to be created is now in the FileName object saveFileDialog1. This brings us back to the classic creation of a text file. The contents of the TextBox : textBoxLignes.Text while managing any exceptions that may occur.

The class OpenFileDialog is very close to the SaveFileDialog. We'll use the same methods and properties as above. The method ShowDialog displays a dialog box similar to the one below:

1
drop-down list built from the Filter. The default file type is set by FilterIndex
2
current file, set by InitialDirectory if this property has been set
3
file name chosen or typed directly by the user. Will be available in the FileName
4
buttons Open/Cancel. If the Open is used, the ShowDialog makes the result DialogResult.OK

The procedure for loading the text file can be written as follows:


private void buttonCharger_Click(object sender, EventArgs e) {
            // load a text file into the input box
            // set the openfileDialog1 dialog box
            openFileDialog1.InitialDirectory = Application.ExecutablePath;
            openFileDialog1.Filter = "Fichiers texte (*.txt)|*.txt|Tous les fichiers (*.*)|*.*";
            openFileDialog1.FilterIndex = 0;
            // display the dialog box and retrieve the result
            if (openFileDialog1.ShowDialog() == DialogResult.OK) {
                //
                string nomFichier = openFileDialog1.FileName;
                StreamReader fichier = null;
                try {
                    // retrieve the file name
                    fichier = new StreamReader(nomFichier);
                    // open the file in read mode
                    textBoxLignes.Text = fichier.ReadToEnd();
                } catch (Exception ex) {
                    // read the entire file and put it in the TextBox
                    MessageBox.Show("Problème à la lecture du fichier (" +
                    ex.Message + ")", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return;
                } finally {
                     // problem
                    if (fichier != null) {
                        fichier.Dispose();
                    }
                }// close the file
             }//finally
        }
  • line 4: set the initial file (InitialDirectory) to the file (Application.ExecutablePath) which contains the application executable.
  • line 5: set the file types to be displayed. Note the filter syntax: filter1|filter2|..|filtren with filteri= Text|file model. Ici the user will be able to choose between the following files *.txt and *.*.
  • line 6: set the file type to be presented first to the user. Ici index 0 designates *.txt files.
  • line 8: the dialog box is displayed and its result retrieved. While the dialog box is displayed, the user no longer has access to the main form (modal dialog box). The user sets the name of the file to be saved and exits the dialog box either by clicking on the Open, or via the Cancel, or by closing the box. The result of the ShowDialog is DialogResult.OK only if the user has used the Register to exit the dialog box.
  • Once this has been done, the name of the file to be created is now in the FileName object openFileDialog1. This brings us back to the classic reading of a text file. Note, on line 16, the method for reading an entire file.

7.5.2. FontColor and ColorDialog dialog boxes

We continue the previous example, adding two new buttons and two new non-visual controls:

67

type
name
role
1
Button
buttonCouleur
to set the character color of the TextBox
2
Button
buttonPolice
to set the font for TextBox
3
ColorDialog
colorDialog1
the component for selecting a color - taken from the toolbox [5].
4
FontDialog
colorDialog1
the font selection component - taken from the toolbox [5].

The classes FontDialog and ColorDialog have a method ShowDialog similar to the ShowDialog classes OpenFileDialog and SaveFileDialog.

The method ShowDialog class ColorDialog to select a color [1]. The class FontDialog to select a font [2] :

  • [1]: if the user exits the dialog box with the OK, the result of the method ShowDialog is DialogResult.OK and the chosen color is in the Color object ColorDialog used.
  • [2]: if the user exits the dialog box with the OK, the result of the method ShowDialog is DialogResult.OK and the chosen font is in the Font object FontDialog used.

We now have the elements we need to process button clicks Color and Police :


         private void buttonCouleur_Click(object sender, EventArgs e) {//if
            if (colorDialog1.ShowDialog() == DialogResult.OK) {
                 // choice of text color
                textBoxLignes.ForeColor = colorDialog1.Color;
            }// change the Forecolor property of TextBox
        }
 
        private void buttonPolice_Click(object sender, EventArgs e) {
             //if
            if (fontDialog1.ShowDialog() == DialogResult.OK) {
                 // font selection
                textBoxLignes.Font = fontDialog1.Font;
}
  • line [4]: the [ForeColor] property of a component TextBox designates the [Color] type color of the characters in the TextBox. Ici this color is the one chosen by the user in the [ColorDialog] dialog box.
  • line [12]: the [Font] property of a component TextBox designates the [Font] typeface of the characters in the TextBox. Ici this font is the one chosen by the user in the [FontDialog] dialog box.

7.5.3. Timer

We propose ici to write the following application:

Type
Name
Role
1
Label
labelChrono
displays a stopwatch
2
Button
buttonArretMarche
stop/start button
3
Timer
timer1
component emitting ici an event every second

In [4] we see the stopwatch running, in [5] the stopwatch stopped.

To change the Label content every second LabelChrono, we need a component that generates an event every second, which we can intercept to update the stopwatch display. This component is the Timer [1] available in the toolbox Components [2] :

The properties of the Timer component used ici will be as follows:

Interval
number of milliseconds after which an event Tick is emitted.
Tick
the event produced at the end of Interval milliseconds
Enabled
makes the timer active (true) or inactive (false)

In our example, the timer is called timer1 and timer1.Interval is set to 1000 ms (1s). The event Tick will therefore occur every second. Clicking on the Stop/Start button is processed by the procedure buttonArretMarche_Click next :


using System;
using System.Windows.Forms;
 
namespace Chap5 {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }
 
        // change the Font property of TextBox
        private DateTime début = DateTime.Now;
...
        private void buttonArretMarche_Click(object sender, EventArgs e) {
             // variable instance
            if (buttonArretMarche.Text == "Marche") {
                // off or on?
                début = DateTime.Now;
                // we note the start time
                labelChrono.Text = "00:00:00";
                 // we display it
                timer1.Enabled = true;
                 // start timer
                buttonArretMarche.Text = "Arrêt";
                // change the button label
                return;
             }// end
            if (buttonArretMarche.Text == "Arrêt") {
                 // timer off
                timer1.Enabled = false;
                // change the button label
                buttonArretMarche.Text = "Marche";
                 // end
                return;
            }
        }
 
    }
}
  • line 13: the procedure that processes the click on the off/On button.
  • line 15: the Stop/Start button label is either "Stop" or "Start". We therefore need to test this label to know what to do.
  • line 17: in the case of "Marche", we note the start time in a variable start which is a global variable (line 11) of the form object
  • line 19: initializes label content LabelChrono
  • line 21: timer started (Enabled=true)
  • line 23: button label changed to "Stop".
  • line 27: in the case of "Arrêt" (Stop)
  • line 29: timer stopped (Enabled=false)
  • line 31: change button label to "On".

We still have to deal with the event Tick on the object timer1, an event that occurs every second:


private void timer1_Tick(object sender, EventArgs e) {
             // a second has passed
            DateTime maintenant = DateTime.Now;
            TimeSpan durée = maintenant - début;
            // update the stopwatch
            labelChrono.Text = durée.Hours.ToString("d2") + ":" + durée.Minutes.ToString("d2") + ":" + durée.Seconds.ToString("d2");
        }
  • line 3: note the time of day
  • line 4: calculates time elapsed since stopwatch start time. The result is an object of type TimeSpan which represents a duration in time.
  • line 6: this must be displayed in the stopwatch as hh:mm:ss. To do this, we use the Hours, Minutes, Second object TimeSPan which respectively represent the hours, minutes and seconds of the duration that we display in the format ToString("d2") to display 2 digits.

7.6. Application example - version 6

We take the example application IMPOTS. The latest version was studied in paragraph 6.4. It was the following three-coat application:

  • the [metier] and [dao] layers were encapsulated in DLL
  • the [ui] layer was a [console] layer
  • layer instantiation and integration into the application were handled by Spring.

In this new version, the [ui] layer will be provided by the following graphical interface:

 

7.6.1. The Visual Studio solution

The Visual Studio solution consists of the following components:

  • [1]: the project consists of the following elements:
  • [Program.cs]: the class that launches the application
  • [Form1.cs]: 1st form class
  • [Form2]: the class of a 2nd form
  • [lib] detailed in [2]: all the DLL required for the project have been included:
  • [ImpotsV5-dao.dll]: the DLL of the [dao] layer generated in paragraph 6.4.3;
  • [ImpotsV5-metier.dll]: the DLL of the [dao] layer generated in paragraph 6.4.4;
  • [Spring.Core.dll], [Common.Logging.dll], [antlr.runtime.dll]: the DLL of Spring already used in the previous version (see paragraph 6.4.6).
  • [references] detailed in [3]: project references. A reference has been added for each DLL in the [lib] file
  • [App.config]: the project configuration file. It is identical to that of the previous version described in paragraph 6.4.6;
  • [DataImpot.txt]: the tax bracket file configured to be automatically copied to the project execution folder [4]

Form [Form1] is the form for entering tax calculation parameters [A] already presented above. Form [Form2] [B] is used to display an error message:

7.6.2. The [ classProgram.cs]

The [Program.cs] class launches the application. Its code is as follows:


using System;
using System.Windows.Forms;
using Spring.Context;
using Spring.Context.Support;
using Metier;
using System.Text;
 
namespace Chap5 {
    static class Program {
         /// <summary>
        /// The main entry point for the application.
         /// </summary>
        [STAThread]
        static void Main() {
             // code generated by Vs
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
 
             // --------------- Developer code
             // instantiations [metier] and [dao] layers
            IApplicationContext ctx = null;
            Exception ex = null;
            IImpotMetier metier = null;
            try {
                 // spring context
                ctx = ContextRegistry.GetContext();
                 // a reference is requested on the [metier] layer
                metier = (IImpotMetier)ctx.GetObject("metier");
            } catch (Exception e1) {
                 // memory exception
                ex = e1;
            }
             // form to display
            Form form = null;
             // was there an exception?
            if (ex != null) {
                 // yes - create the error message to be displayed
                StringBuilder msgErreur = new StringBuilder(String.Format("Chaîne des exceptions : {0}{1}", "".PadLeft(40, '-'), Environment.NewLine));
                Exception e = ex;
                while (e != null) {
                    msgErreur.Append(String.Format("{0}: {1}{2}", e.GetType().FullName, e.Message, Environment.NewLine));
                    msgErreur.Append(String.Format("{0}{1}", "".PadLeft(40, '-'), Environment.NewLine));
                    e = e.InnerException;
                }
                 // creation of an error window to which the error message to be displayed is passed
                Form2 form2 = new Form2();
                form2.MsgErreur = msgErreur.ToString();
                 // this will be the window to display
                form = form2;
            } else {
                 // all went well
                 // creation of a graphical interface [Form1] to which we pass the reference on the [metier] layer
                Form1 form1 = new Form1();
                form1.Metier = metier;
                 // this will be the window to display
                form = form1;
            }
             // window display
            Application.Run(form);
        }
    }
}

The code generated by Visual Studio has been completed from line 19. The application uses the [App.config] next :


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 
    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>
 
    <spring>
        <context>
            <resource uri="config://spring/objects" />
        </context>
        <objects xmlns="http://www.springframework.net">
            <object name="dao" type="Dao.FileImpot, ImpotsV5-dao">
                <constructor-arg index="0" value="DataImpot.txt"/>
            </object>
            <object name="metier" type="Metier.ImpotMetier, ImpotsV5-metier">
                <constructor-arg index="0" ref="dao"/>
            </object>
        </objects>
    </spring>
</configuration>
  • lines 24-32: use of previous [App.config] file to instantiate [metier] and [dao] layers
  • line 26: use of file [App.config]
  • line 28: retrieve a reference from the [metier] layer
  • line 31: exception memorized
  • line 34: the reference form will designate the form to be displayed (form1 or form2)
  • lines 36-50: if an exception has been raised, we prepare to display a form of type [Form2]
  • lines 38-44: create the error message to be displayed. It is made up of the concatenation of error messages from the various exceptions present in the exception chain.
  • line 46: a form of type [Form2] is created.
  • line 47: as we shall see later, this form is a public property MsgErreur which is the error message to be displayed:

        public string MsgErreur { private get; set; }

This property is entered.

  • line 49: the reference form which designates the window to be displayed, is initialized. Note the polymorphism at work. form2 is not of type [Form] but of type [Form2], a type derived from [Form].
  • lines 50-57: no exception. We're getting ready to display a form of type [Form1].
  • line 53: a form of type [Form1] is created.
  • line 54: as we will see later, this form is a public property Trade which is a reference to the [metier] layer:

                public IImpotMetier Metier { private get; set; }

This property is entered.

  • line 56: the reference form which designates the window to be displayed, is initialized. Once again, polymorphism is at work. form1 is not of type [Form] but of type [Form1], a type derived from [Form].
  • line 59: the window referenced by form is displayed.

7.6.3. The [Form1] form

In [design] mode, the [Form1] form is as follows:

The controls are as follows

type
name
role
0
GroupBox
groupBox1
Text=Etes-vous marié(e)
1
RadioButton
radioButtonOui
checked if married
2
RadioButton
radioButtonNon
checked if not married
Checked=True
3
NumericUpDown
numericUpDownEnfants
number of children
Minimum=0, Maximum=20, Increment=1
4
TextBox
textSalaire
taxpayer's annual salary in euros
5
Label
labelImpot
amount of tax payable
BorderStyle=Fixed3D
6
Button
buttonCalculate
launches tax calculation
7
Button
buttonDelete
returns the form to the state it was in when loaded
8
Button
buttonExit
to quit the application

Form operating rules

  • the button Calculate remains off as long as there is nothing in the wage field
  • if, when the calculation is run, it turns out that the salary is incorrect, the error is reported [9]

The class code is as follows:


using System.Windows.Forms;
using Metier;
using System;
 
namespace Chap5 {
    public partial class Form1 : Form {
         // business] layer
        public IImpotMetier Metier { private get; set; }
 
        public Form1() {
            InitializeComponent();
        }
 
        private void buttonCalculer_Click(object sender, System.EventArgs e) {
            // is the salary correct?
            int salaire;
            bool ok=int.TryParse(textSalaire.Text.Trim(), out salaire);
            if (! ok  || salaire < 0) {
                // error msg
                MessageBox.Show("Salaire incorrect", "Erreur de saisie", MessageBoxButtons.OK, MessageBoxIcon.Error);
                // back to the wrong field
                textSalaire.Focus();
                // select text for input field
                textSalaire.SelectAll();
                 // back to input interface
                return;
            }
            // salary is correct - tax can be calculated
            labelImpot.Text = Metier.CalculerImpot(radioButtonOui.Checked, (int)numericUpDownEnfants.Value, salaire).ToString();
        }
 
        private void buttonQuitter_Click(object sender, System.EventArgs e) {
            Environment.Exit(0);
        }
 
        private void buttonEffacer_Click(object sender, System.EventArgs e) {
             // raz form
            labelImpot.Text = "";
            numericUpDownEnfants.Value = 0;
            textSalaire.Text = "";
            radioButtonNon.Checked = true;
        }
 
        private void textSalaire_TextChanged(object sender, EventArgs e) {
            // calculate] button status
            buttonCalculer.Enabled=textSalaire.Text.Trim()!="";
        }
 
    }
}

We comment only on the important parts:

  • line [8]: public property Trade which allows the launch class [Program.cs] to inject a reference to the [metier] layer into [Form1].
  • line [14]: tax calculation procedure
  • lines 15-27: check validity of salary (an integer >=0).
  • line 29: tax calculation using the [CalculerImpot] method of the [metier] layer. Note the simplicity of this operation, achieved by encapsulating the [metier] layer in a DLL.

7.6.4. The [ formForm2]

In [design] mode, the [Form2] form is as follows:

The controls are as follows

type
name
role
1
TextBox
textBoxErreur
Multiline=True, Scrollbars=Both

The class code is as follows:


using System.Windows.Forms;
 
namespace Chap5 {
    public partial class Form2 : Form {
        // error msg
        public string MsgErreur { private get; set; }
 
        public Form2() {
            InitializeComponent();
        }
 
        private void Form2_Load(object sender, System.EventArgs e) {
            // error msg is displayed
            textBoxErreur.Text = MsgErreur;
             // deselect all text
            textBoxErreur.Select(0, 0);
        }
    }
}
  • line 6: public property MsgErreur which allows the launch class [Program.cs] to inject the error message to be displayed into [Form2]. This message is displayed when the Load, lines 12-16.
  • line 14: the error message is put in the TextBox
  • line 16: the selection made in the previous operation is removed. [TextBox].Select(début,longueur) select (highlight) length characters from character no start. [TextBox].Select(0,0) is equivalent to deselecting all text.

7.6.5. Conclusion

Let's look back at the three-layer architecture used:

This architecture enabled us to replace the existing [ui] layer with a graphical implementation, without changing the [metier] and [dao] layers. We were able to concentrate on the [ui] layer without worrying about the possible impact on the other layers. This is the main advantage of 3-layer architectures. We'll see another example later on, when the [dao] layer, which currently uses data from a text file, is replaced by a [dao] layer, which uses data from a database. As we shall see, this will have no impact on the [ui] and [metier] layers.