Skip to content

7. Interfacce grafiche con C# e VS.NET

7.1. Nozioni di base sulle interfacce grafiche utente

7.1.1. Un primo progetto

Creiamo un primo progetto di "Applicazione Windows":

  • [1]: Crea un nuovo progetto
  • [2]: Tipo di applicazione Windows
  • [3]: il nome del progetto non è importante in questo momento
  • [4]: il progetto è stato creato
  • [5]: salva la soluzione corrente
  • [6]: nome del progetto
  • [7]: file della soluzione
  • [8]: nome della soluzione
  • [9]: verrà creata una cartella per la soluzione [Chap5]. I suoi progetti saranno contenuti in sottocartelle.
  • [10]: progetto [01] nella soluzione [Chap5] :
  • [Program.cs] è la classe principale del progetto
  • [Form1.cs] è il file sorgente che gestisce il comportamento della finestra [11]
  • [Form1.Designer.cs] è il file sorgente che incapsula le informazioni sui componenti della finestra [11]
  • [11]: file [Form1.cs] in modalità progettazione
  • [12]: l'applicazione generata può essere eseguita con (Ctrl-F5). Viene visualizzata la finestra [Form1]. È possibile spostarla, ridimensionarla e chiuderla. Gli elementi di base di una finestra grafica sono ora disponibili.

La classe principale [Program.cs] è la seguente:


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());
        }
    }
}
  • riga 2: le applicazioni con moduli utilizzano lo spazio dei nomi System.Windows.Forms.
  • riga 4: lo spazio dei nomi originale è stato rinominato Chap5.
  • riga 10: quando il progetto viene eseguito (Ctrl-F5), viene eseguito il metodo [Main].
  • righe 11-13: l'applicazione Classroom appartiene a System.Windows.Forms. Contiene metodi statici per l'avvio e l'arresto delle applicazioni grafiche di Windows.
  • riga 11: opzionale - consente di assegnare diversi stili visivi ai controlli posizionati su un form
  • riga 12: opzionale - imposta il motore di rendering per i testi dei controlli: GDI+ (true), GDI (false)
  • riga 13: l'unica riga essenziale nel metodo [Main]: istanzia la classe [Form1], che è la classe del modulo, e le ordina di essere eseguita.

Il file sorgente [Form1.cs] è il seguente:


using System;
using System.Windows.Forms;
 
namespace Chap5 {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }
    }
}
  • riga 5: il modulo Form1 deriva dalla classe [System.Windows.Forms.Form], che è la classe padre di tutte le finestre. La parola chiave partial indica che la classe è parziale e può essere completata da altri file sorgente. È il caso qui, dove la classe Form1 è suddivisa in due file:
  • [Form1.cs]: dove si trova il comportamento del form, inclusi i gestori di eventi
  • [Form1.Designer.cs]: contiene i componenti del modulo e le loro proprietà. Questo file viene rigenerato ogni volta che l'utente modifica la finestra in modalità [design].
  • righe 6-8: costruttore della classe Form1
  • riga 7: chiama InitializeComponent. Questo metodo non è presente in [Form1.cs]. Si trova in [Form1.Designer.cs].

Il file sorgente [Form1.Designer.cs] è il seguente:


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
 
    }
}
  • riga 2: la classe è sempre Form1. Si noti che non è più necessario ripetere che essa deriva da Form.
  • righe 25-37: il metodo InitializeComponent chiamato dal costruttore della classe [Form1]. Questo metodo crea e inizializza tutti i componenti del modulo. Viene rigenerato ogni volta che il modulo viene modificato in modalità [design]. Per delimitarlo, alle righe 19-39 viene creata una sezione denominata region. Lo sviluppatore non deve aggiungere alcun codice a questa regione: verrà sovrascritto alla successiva rigenerazione.

All'inizio, è più semplice ignorare il codice [Form1.Designer.cs]. Viene generato automaticamente ed è la traduzione in linguaggio C# delle scelte effettuate dallo sviluppatore in modalità [design]. Prendiamo un primo esempio:

  • [1]: Selezionare la modalità [design] facendo doppio clic sul file [Form1.cs]
  • [2]: clicca con il tasto destro del mouse sul form e seleziona [Proprietà]
  • [3]: Finestra delle proprietà di [Form1]
  • [4]: la proprietà [Text] rappresenta il titolo della finestra
  • [5]: la modifica alla proprietà [Text] viene presa in considerazione sia in modalità [design] che nel codice sorgente [Form1.Designer.cs]:

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

7.1.2. Un secondo progetto

7.1.2.1. Il modulo

Stiamo avviando un nuovo progetto chiamato 02. Per farlo, seguiamo la procedura descritta sopra per la creazione di un progetto. La finestra da creare è la seguente:

I componenti del modulo sono i seguenti:

nome
tipo
ruolo
1
etichettaSaisie
Etichetta
una dicitura
2
casella di testo
Casella di testo
un campo di immissione
3
pulsanteVisualizza
Pulsante
per visualizzare il contenuto del campo di immissione textBoxSaisie in una finestra di dialogo

Per creare questa finestra, procedere come segue:

  • [1]: fare clic con il tasto destro del mouse sul modulo al di fuori di qualsiasi componente e selezionare l'opzione [Proprietà]
  • [2]: la finestra delle proprietà della finestra appare nell'angolo in basso a destra di Visual Studio

Le proprietà del modulo includono:

Colore di sfondo
per impostare il colore di sfondo della finestra
ForeColor
per impostare il colore dei disegni o del testo nella finestra
Menu
per associare un menu alla finestra
Text
per assegnare un titolo alla finestra
FormBorderStyle
per impostare il tipo di finestra
Font
per impostare il carattere per la scrittura nel
Name
per impostare il nome della finestra

Qui, impostiamo le proprietà Testo e Nome :

Testo
Campi di immissione e pulsanti - 1
Nome
frmSaisiesBoutons
  • [1]: selezionare la casella degli strumenti [Common Controls] tra quelle disponibili in Visual Studio
  • [2, 3, 4]: fare doppio clic in successione sui componenti [Label], [Button] e [TextBox]
  • [5]: i tre componenti sono presenti nel modulo

Per allineare e ridimensionare correttamente i componenti, è possibile utilizzare le voci della barra degli strumenti:

 
  
   

Il principio di formattazione è il seguente:

  1. selezionare i componenti da formattare insieme (tenere premuto il tasto Ctrl mentre si fa clic per selezionare i componenti)
  2. selezionare il tipo di formattazione desiderato:
  • (continua)
    • le opzioni Allinea consentono di allineare i componenti in alto, in basso, a sinistra, a destra, al centro, ecc.
    • le opzioni "Usa stessa dimensione" consentono di impostare la stessa altezza o larghezza per i componenti
    • l'opzione Spaziatura orizzontale consente di allineare i componenti orizzontalmente, con intervalli tra loro della stessa larghezza. Lo stesso vale per l'opzione Spaziatura verticale per allineare verticalmente.
    • L'opzione "Al centro" consente di centrare un componente orizzontalmente (Orizzontalmente) o verticalmente (Verticalmente) nel

Una volta posizionati i componenti, ne impostiamo le proprietà. Per farlo, clicca con il tasto destro del mouse sul componente e seleziona l'opzione Proprietà:

  • [1]: selezionare il componente per aprire la finestra delle proprietà. In questa finestra, modificare le seguenti proprietà: nome: labelSaisie, testo: Input
  • [2]: procedere allo stesso modo: nome: textBoxSaisie, testo: non inserire nulla
  • [3] : nome : buttonAfficher, testo : Visualizza
  • [4]: la finestra stessa: nome: frmSaisiesBoutons, testo: Inserimenti e pulsanti - 1
  • [5]: eseguire (Ctrl-F5) il progetto per avere una prima anteprima della finestra in azione.

Ciò che è stato fatto in modalità [design] è stato tradotto nel codice [Form1.Designer.cs]:


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;
 
    }
}
  • righe 53-55: i tre componenti hanno dato origine a tre campi privati nella classe [Form1]. Si noti che i nomi di questi campi sono quelli assegnati ai componenti in modalità [design]. Lo stesso vale per la riga 2, che è la classe stessa.
  • righe 7-9: vengono creati i tre oggetti di tipo [Label], [TextBox] e [Button]. Questi servono a gestire i componenti visivi.
  • righe 14-19: configurazione dell'etichetta labelSaisie
  • righe 23-29: configurazione del pulsante buttonAfficher
  • righe 33-36: configurazione del campo di immissione textBoxSaisie
  • righe 40-47: configurazione del modulo frmSaisiesBoutons. Le righe 43-45 mostrano come aggiungere componenti al modulo.

Questo codice è di facile comprensione. È quindi possibile creare moduli tramite codice senza ricorrere alla modalità [design]. Nella documentazione MSDN di Visual Studio sono riportati numerosi esempi al riguardo. Padroneggiare questo codice consente di creare moduli in fase di esecuzione: ad esempio, è possibile creare un modulo al volo per aggiornare una tabella di database, la cui struttura viene individuata solo in fase di esecuzione.

Non resta che scrivere la procedura per gestire un clic sulla vista. Selezionare il pulsante per accedere alla finestra delle proprietà. Questa finestra presenta diverse schede:

  • [1]: elenco delle proprietà in ordine alfabetico
  • [2]: eventi di controllo

È possibile accedere alle proprietà e agli eventi di controllo per categoria o in ordine alfabetico:

  • [3]: Proprietà o eventi per categoria
  • [4]: Proprietà o eventi in ordine alfabetico

Gli eventi nelle categorie per il pulsante Afficher sono i seguenti:

  • [1]: la colonna di sinistra della finestra elenca i possibili eventi relativi al pulsante. Un clic su un pulsante corrisponde all'evento Click.
  • [2]: la colonna di destra contiene il nome della procedura chiamata quando si verifica l'evento corrispondente.
  • [3]: se si fa doppio clic sulla cella dell'evento Click, si passa automaticamente alla finestra del codice per scrivere il gestore dell'evento Click del pulsante 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) {
 
        }
    }
}
  • righe 10-12: lo scheletro del gestore di eventi Fare clic sul pulsante denominato buttonAfficher. È necessario tenere presente quanto segue:
    • il metodo è denominato come segue: eventName_ComponentName
    • il metodo è privato. Riceve due parametri:
    • sender : è l'oggetto che ha generato l'evento. Se la procedura viene eseguita in seguito a un clic su buttonAfficher, sender sarà uguale a buttonAfficher. È ipotizzabile che buttonAfficher_Click venga eseguito da un'altra procedura. Questa procedura sarebbe quindi libera di impostare l'oggetto sender di sua scelta.
    • EventArgs : un oggetto contenente informazioni sull'evento. Per un evento Click, non contiene nulla. Per un evento di movimento del mouse, conterrà le coordinate (X,Y) del mouse.
    • Qui non useremo nessuno di questi parametri.

Per scrivere un gestore di eventi occorre completare lo scheletro di codice precedente. Con Ici, vogliamo visualizzare una finestra di dialogo con il contenuto della casella di testo textBoxSaisie se questa non è vuota [1], altrimenti un messaggio di errore [2]:

Il codice per ottenere questo risultato potrebbe essere il seguente:


        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);
}

La classe MessageBox viene utilizzata per visualizzare messaggi in una finestra. Abbiamo utilizzato qui il metodo Show:


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

con

testo
il messaggio da visualizzare
didascalia
titolo della finestra
pulsanti
pulsanti nella finestra
icona
l'icona nella

I pulsanti possono assumere i valori delle seguenti costanti (prefissate da MessageBoxButtons come mostrato nella riga 7) sopra:

constante
pulsanti
   AnnullaRiprovaIgnora
  OK
    OKAnnulla
    RiprovaAnnulla
    SìNo
    SìNoAnnulla

L'icona può assumere i valori delle seguenti costanti (precedute dal prefisso MessageBoxIcon, come mostrato alla riga 10) riportate sopra:

Asterisco
Errore
idem Stop
Esclamazione
idem Avviso
Mano
Informazioni
idem Asterisco
Nessuna
Domanda
Stop
idem Mano
Avviso

 

Il metodo Show è un metodo statico che restituisce un risultato di tipo [System.Windows.Forms.DialogResult], che è un'enumerazione:

Image

Per scoprire quale pulsante l'utente ha premuto per chiudere la finestra di messaggio, scriviamo:

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

7.1.2.2. Codice di gestione degli eventi

Oltre al pulsante Afficher_Click che abbiamo scritto, Visual Studio ha generato nel metodo InitializeComponents di [Form1.Designer.cs], che crea e inizializza i componenti del modulo, la seguente riga:


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

Click è una classe di evento del pulsante [1, 2, 3]:

  • [5]: la dichiarazione dell'evento [Control.Click] [4]. Ciò dimostra che l'evento Click non è specifico della classe [Button]. Appartiene alla classe [Control], la classe padre della classe [Button].
    • EventHandler è un prototipo (un modello) di un metodo chiamato delegato. Torneremo su questo argomento più avanti.
    • event è una parola chiave che limita le funzionalità del delegato EventHandler: un oggetto delegato ha funzionalità più avanzate rispetto a un evento.

Visita delegate EventHandler è definito come segue:

 

Visita il delegato EventHandler, che designa un modello di metodo:

  • con il tipo come primo parametro Object
  • il cui secondo parametro è un EventArgs
  • che non restituisce alcun risultato

Questo è il caso del metodo per la gestione dei clic sul pulsante Afficher generato da Visual Studio:


        private void buttonAfficher_Click(object sender, EventArgs e);

buttonAfficher_Click corrisponde al prototipo definito da EventHandler. Per creare un EventHandler, procedere come segue:

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

Poiché il pulsante Afficher_Click corrisponde al prototipo definito dall'EventHandler, possiamo scrivere:

EventHandler evtHandler=new EventHandler(buttonAfficher_Click);

Una variabile di tipo delegato è infatti un elenco di riferimenti a metodi come il delegato. Per aggiungere un nuovo metodo M alla variabile evtHandler sopra, usiamo la sintassi:

evtHandler+=new EvtHandler(M);

La notazione += può essere utilizzata anche se evtHandler è un elenco vuoto.

Torniamo alla riga in [InitializeComponent] che aggiunge un gestore di eventi all'evento Click dell'oggetto buttonAfficher:


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

Questa istruzione aggiunge un EventHandler all'elenco dei metodi in buttonAfficher.Click. Questi metodi verranno chiamati ogni volta che verrà rilevato il clic sul componente buttonAfficher. Spesso ce n'è solo uno. Si chiama "gestore di eventi".

Torniamo alla firma di EventHandler:


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

Il secondo parametro del delegato è un oggetto di tipo EventArgs o di una classe derivata. Il tipo EventArgs è molto generico e in realtà non fornisce alcuna informazione sull'evento che si è verificato. Per un clic su un pulsante, questo è sufficiente. Per un movimento del mouse su un form, avremmo un MouseMove della classe [Form] definito da:

public event MouseEventHandler MouseMove;

Il delegato MouseEventHandler è definito come:

 

Questa è una funzione di firma delegata void f (object, MouseEventArgs). La classe MouseEventArgs è definita da:

La classe MouseEventArgs è più ricca rispetto a EventArgs. Ad esempio, possiamo ricavare le coordinate X e Y del mouse nel momento in cui si verifica l'evento.

7.1.2.3. Conclusione

Dai due progetti studiati, possiamo concludere che una volta che la GUI è stata realizzata con Visual Studio, il compito dello sviluppatore consiste principalmente nello scrivere i gestori di eventi che desidera gestire per questa GUI. Il codice viene generato automaticamente da Visual Studio. Questo codice, che può essere complesso, può essere ignorato inizialmente. In seguito, tuttavia, il suo studio può fornire una migliore comprensione di come creare e gestire i moduli.

7.2. Componenti di base

Presentiamo ora una serie di applicazioni che coinvolgono i componenti più comuni, al fine di scoprirne i metodi e le proprietà principali. Per ogni applicazione, presentiamo l'interfaccia grafica e il codice di interesse, principalmente quello dei gestori di eventi.

7.2.1. Modulo Modulo

Inizieremo presentando il componente essenziale, il form su cui si posizionano i componenti. Abbiamo già presentato alcune delle sue proprietà di base. Ora vedremo alcuni degli eventi più importanti del form.

Caricamento
il modulo è in fase di caricamento
Chiusura
il modulo sta per essere chiuso
Chiuso
il modulo è chiuso

L'evento Load si verifica prima che il modulo venga visualizzato. L'evento Closing si verifica quando il modulo viene chiuso. È anche possibile interrompere questa chiusura tramite programmazione.

Creiamo un modulo denominato Form1 senza componenti:

  • [1]: il modulo
  • [2]: i tre eventi trattati

Il codice per [Form1.cs] è il seguente:


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");
        }
    }
}

Utilizziamo MessageBox per ricevere notifiche sugli eventi.

riga 10: L'evento Load si verificherà quando l'
l'applicazione prima ancora che il modulo venga visualizzato:
  
riga 15: L'evento FormClosing si verificherà quando
l'utente chiuderà la finestra.
riga 19: Gli chiediamo quindi se vuole davvero uscire
il :
riga 20: Se risponde No, impostiamo la proprietà Cancel dell'
l'evento CancelEventArgs e che il metodo ha ricevuto nel
parametro. Se impostiamo questa proprietà su False, la chiusura
della finestra viene interrotta, altrimenti procede. Si verificherà quindi l'evento
FormClosed:

7.2.2. Etichette e caselle di immissione TextBox

Abbiamo già incontrato questi due componenti. Label è un componente di testo e TextBox un componente campo di immissione. La loro proprietà principale è Text, che indica il contenuto del campo di immissione o il testo dell’etichetta. Questa proprietà è di tipo lettura/scrittura.

L'evento solitamente utilizzato per TextBox è TextChanged, che segnala che l'utente ha modificato il campo di immissione. Ecco un esempio che utilizza TextChanged per monitorare le modifiche in un campo di immissione:

tipo
nome
ruolo
1
Casella di testo
textBoxSaisie
campo di immissione
2
Etichetta
labelControle
visualizza il testo di 1 in tempo reale
AutoSize=False, Testo=(nulla)
3
Pulsante
pulsanteCancella
per cancellare i campi 1 e 2
4
Pulsante
pulsanteQuitter
per chiudere l'applicazione

Il codice di questa applicazione è:


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();
        }
    }
}
  • riga 24: l'evento [Form].Shown si verifica quando il modulo viene visualizzato
  • riga 26: il focus (per l'immissione) viene posizionato sul componente textBoxSaisie.
  • riga 9: l'evento [TextBox].TextChanged si verifica ogni volta che il contenuto di un componente TextBox cambia
  • riga 11: il contenuto del componente [TextBox] viene copiato nel componente [Label]
  • riga 14: gestisce il clic sul pulsante [Delete]
  • riga 16: inseriamo la stringa vuota nel componente [TextBox]
  • riga 19: gestisce il clic sul pulsante [Quit]
  • riga 21: per arrestare l'applicazione in esecuzione. Ricordiamo che l'oggetto Application viene utilizzato per avviare l'applicazione nel metodo [Main] di [Form1.cs]:

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

L'esempio seguente utilizza una casella di testo multilinea:

L'elenco dei controlli è il seguente:

tipo
nome
ruolo
1
Casella di testo
textBoxLines
campo di immissione multilinea
Multiline=true, ScrollBars=Both, AcceptReturn=True, AcceptTab=True
2
TextBox
textBoxLigne
campo di immissione a riga singola
3
Pulsante
buttonAjouter
Aggiunge i contenuti da 2 a 1

Per rendere una casella di testo multilinea, impostare le seguenti proprietà del controllo:

Multiline=true
per accettare più righe di testo
ScrollBars=( None, Horizontal, Vertical, Both)
per richiedere che il controllo abbia le barre di scorrimento (Orizzontale, Verticale, Entrambe) o meno (Nessuna)
AcceptReturn=(True, False)
se uguale a true, il tasto Invio passerà alla riga
AcceptTab=(True, False)
se vero, il tasto Tab genererà un tabulatore nel testo

L'applicazione consente di digitare le righe direttamente in [1] o di aggiungerle tramite [2] e [3].

Il codice dell'applicazione è il seguente:


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();
        }
    }
}
  • riga 18: quando il modulo viene visualizzato (evt Shown), mette a fuoco il campo di immissione textBoxLigne
  • riga 10: gestisce il clic sul pulsante [Aggiungi]
  • riga 12: il testo del campo di immissione textBoxLigne viene aggiunto al testo nel campo di immissione textBoxLignes seguito da un avanzamento riga.
  • riga 13: il testo del campo di immissione textBoxLigne viene cancellato

7.2.3. Elenchi a discesa ComboBox

Creiamo il seguente modulo:

tipo
nome
ruolo
1
Casella combinata
comboNombres
contiene stringhe di caratteri
DropDownStyle=DropDownList

Un componente ComboBox è un elenco a discesa con un campo di immissione: l'utente può selezionare una voce in (2) oppure digitare del testo in (1). Esistono tre tipi di ComboBox definiti dalla proprietà DropDownStyle :

Semplice
elenco non a discesa con zona di modifica
A tendina
elenco a discesa con zona di modifica
DropDownList
elenco a discesa senza area di modifica

Per impostazione predefinita, il tipo di una ComboBox è DropDown.

La classe ComboBox di un unico produttore:

new ComboBox()
crea una casella combinata vuota

Gli elementi di ComboBox sono disponibili nella proprietà Items :

public ComboBox.ObjectCollection Items {get;}

Si tratta di una proprietà indicizzata, dove Items[i] indica l'elemento i della ComboBox. È di sola lettura.

Oppure C è una casella combinata e C.Items è il suo elenco di elementi. Abbiamo le seguenti proprietà:

C.Items.Count
numero di elementi del combo
C.Items[i]
elemento i della casella combinata
C.Add(oggetto o)
aggiunge l'oggetto o come ultimo elemento del combo
C.AddRange(object[] objets)
aggiunge un array di oggetti alla fine di un menu a tendina
C.Insert(int i, object o)
aggiunge l'oggetto o alla posizione i del combo
C.RemoveAt(int i)
rimuove l'elemento i dal menu a discesa
C.Remove(object o)
rimuove l'oggetto o dal combo
C.Clear()
elimina tutti gli elementi del combo
C.IndexOf(object o)
restituisce la posizione i dell'oggetto o nel menu a discesa
C.SelectedIndex
indice dell'elemento selezionato
C.SelectedItem
elemento selezionato
C.SelectedItem.Text
testo visualizzato per l'elemento selezionato
C.Text
testo visualizzato per l'elemento selezionato

Potrebbe sorprendere il fatto che una casella combinata possa contenere oggetti pur visualizzando stringhe. Se una casella combinata contiene un oggetto obj, visualizza la stringa obj.ToString(). Ricordate che ogni oggetto ha un metodo ToString ereditato dall'oggetto che rende una stringa di caratteri "rappresentativa" dell'oggetto.

L'elemento selezionato nel menu a discesa C è C.SelectedItem o C.Items[C.SelectedIndex], dove C.SelectedIndex è il numero dell'elemento selezionato, a partire da zero per il primo elemento. Il testo selezionato può essere ottenuto in vari modi: C.SelectedItem.Text, C.Text

Quando un elemento viene selezionato dall'elenco a discesa, si verifica l'evento SelectedIndexChanged, che può quindi essere utilizzato per notificare all'utente un cambiamento di selezione nel combo. Nell'applicazione seguente, utilizziamo questo evento per visualizzare l'elemento che è stato selezionato nell'elenco.

 

Il codice dell'applicazione è il seguente:


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;
            }
        }
    }
}
  • riga 5: previousSelectedIndex memorizza l'ultimo indice selezionato nel menu a discesa
  • riga 10: riempie il menu a tendina con un array di stringhe di caratteri
  • riga 12: viene selezionato il primo elemento
  • riga 15: il metodo viene eseguito ogni volta che l'utente seleziona un elemento dal menu a discesa. Contrariamente a quanto potrebbe suggerire il nome, questo evento si verifica anche se l'elemento selezionato è lo stesso di quello precedente.
  • riga 16: registra l'indice dell'elemento selezionato
  • riga 17: se diverso da quello precedente
  • riga 19: visualizza il numero e il testo dell'elemento selezionato
  • riga 21: registra il nuovo indice

7.2.4. Componente ListBox

Proponiamo di realizzare la seguente interfaccia:

I componenti di questa finestra sono i seguenti:

tipo
nome
ruolo/proprietà
0
Modulo
Form1
modulo
FormBorderStyle=FixedSingle (bordo non ridimensionabile)
1
Casella di testo
casella di testo
campo di immissione
2
Pulsante
pulsanteAggiungi
pulsante per aggiungere il contenuto del campo di immissione [1] all'elenco [3]
3
Casella di riepilogo
listBox1
elenco 1
SelectionMode=MultiExtended :
4
Casella di riepilogo
listBox2
elenco 2
ModalitàSelezione=MultiSemplice :
5
Pulsante
pulsante1a2
trasferisce gli elementi selezionati dall'elenco 1 all'elenco 2
6
Pulsante
pulsante2a1
fa l'opposto
7
Pulsante
pulsanteCancella1
svuota la lista 1
8
Pulsante
pulsanteCancella2
svuota la lista 2

I componenti ListBox dispongono di una modalità di selezione dei propri elementi definita dalla proprietà SelectionMode:

Solo
È possibile selezionare un solo elemento
MultiExtended
è possibile la selezione multipla: tenendo premuto il tasto SHIFT e cliccando su un elemento si estende la selezione dall'elemento precedentemente selezionato all'elemento corrente.
MultiSimple
possibilità di selezione multipla: un elemento può essere selezionato o deselezionato con un clic del mouse o premendo la barra spaziatrice.
  • L'utente digita il testo nel campo 1 e lo aggiunge alla lista 1 utilizzando il pulsante Aggiungi (2). Il campo di immissione (1) viene quindi svuotato e l'utente può aggiungere un nuovo elemento.
  • È possibile trasferire elementi da un elenco all'altro selezionando l'elemento da trasferire in uno degli elenchi e scegliendo l'apposito pulsante di trasferimento 5 o 6. L'elemento trasferito viene aggiunto alla fine dell'elenco di destinazione e rimosso dall'elenco di origine.
  • È possibile fare doppio clic su un elemento nell'elenco 1. Questo elemento viene quindi trasferito nella casella di modifica e rimosso dall'elenco 1.

I pulsanti vengono attivati o disattivati secondo le seguenti regole:

  • il pulsante Aggiungi è illuminato solo se nel campo di immissione è presente del testo
  • il pulsante [5] per trasferire l'elenco 1 all'elenco 2 è illuminato solo se c'è una voce selezionata nell'elenco 1
  • il pulsante [6] per trasferire l'elenco 2 all'elenco 1 è illuminato solo se nell'elenco 2 è selezionato un elemento
  • i pulsanti [7] e [8] per l'eliminazione delle liste 1 e 2 sono accesi solo se la lista da eliminare contiene voci.

In base alle condizioni di cui sopra, tutti i pulsanti devono essere disattivati all'avvio dell'applicazione. Si tratta dei pulsanti Abilitati che devono quindi essere impostati su false. Ciò può essere fatto in fase di progettazione, il che genererà il codice corrispondente in InitializeComponent, oppure può essere fatto manualmente nel builder come mostrato di seguito:


        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;
}

Lo stato del pulsante Aggiungi è controllato dal contenuto del campo di immissione. Questo è il TextChanged che ci permette di monitorare le modifiche a questo contenuto:


        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() != "";
        }
 

Lo stato dei pulsanti di trasferimento dipende dal fatto che sia stato selezionato o meno un elemento nell'elenco che controllano:


        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;
}

Il codice associato al clic su Aggiungi è il seguente:


        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();
}

Si noti il comando Focus per mettere a fuoco un controllo del modulo. Il codice associato al clic sul pulsante Elimina:


        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;
}

Il codice per trasferire gli elementi selezionati da un elenco all'altro:


        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);
        }
 

I due metodi sopra riportati delegano il trasferimento degli elementi selezionati da un elenco all'altro a un unico metodo privato denominato 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;
}
  • riga b: il metodo transfer riceve sei parametri:
  • un riferimento alla lista contenente gli elementi selezionati, qui denominata l1. All'esecuzione dell'applicazione, l1 è listBox1 o listBox2. Esempi di chiamate sono riportati alle righe 3 e 8 delle procedure di trasferimento buttonXversY_Click.
  • un riferimento al pulsante di trasferimento collegato alla lista l1. Ad esempio, se l1 è listBox2, sarà button2to1 (vedi chiamata alla riga 8)
  • un riferimento al pulsante di eliminazione dell'elenco l1. Ad esempio, se l1 è listBox1, sarà buttonEffacer1 (vedi chiamata alla riga 3)
  • gli altri tre riferimenti sono simili ma si riferiscono a l2.
  • riga d: la collezione [ListBox].SelectedIndices rappresenta gli indici degli elementi selezionati nel componente [ListBox]. Si tratta di un :
  • [ListBox].SelectedIndices.Count è il numero di elementi in questa collezione
  • [ListBox].SelectedIndices[i] è l'elemento n. i in questa collezione

Percorriamo la collezione in ordine inverso, partendo dalla fine e terminando all'inizio. Spiegheremo il perché.

  • riga f: indice di un elemento selezionato nell'elenco l1
  • riga h: questo elemento viene aggiunto all'elenco l2
  • riga j: ed è stato eliminato dalla lista l1. Poiché è stato eliminato, non è più selezionato. La collezione l1.SelectedIndices della riga d verrà ricalcolata. Perderà l'elemento appena eliminato. Tutti gli elementi successivi vedranno il proprio numero cambiare da n a n-1.
  • se il ciclo nella riga (d) è crescente e ha appena elaborato l'elemento n. 0, elaborerà quindi l'elemento n. 1. Oppure l'elemento che era n. 1 prima dell'eliminazione dell'elemento n. 0, sarà quindi n. 0. Sarà quindi dimenticato dal ciclo.
  • Se il ciclo nella riga (d) è discendente e ha appena elaborato l'elemento n° n, elaborerà quindi l'elemento n° n-1. Dopo aver eliminato l'elemento n° n, l'elemento n° n-1 non cambia numero. Viene quindi elaborato nel ciclo successivo.
  • righe m-n: lo stato dei pulsanti [Elimina] dipende dalla presenza o meno di elementi nelle liste associate
  • riga p: l'elenco l2 non ha più elementi selezionati: disattiva il suo pulsante di trasferimento.

7.2.5. Caselle di controllo CheckBox, pulsanti di opzione ButtonRadio

Proponiamo di scrivere la seguente applicazione:

I componenti della finestra sono i seguenti:

tipo
nome
ruolo
1
GroupBox
cf [6]
groupBox1
un contenitore di componenti. È possibile trascinarvi altri componenti.
Testo=Pulsanti radio
2
RadioButton
radioButton1
radioButton2
radioButton3
3 pulsanti di opzione - radioButton1 ha Checked=True e Text=1 - radioButton2 ha Text=2 - radioButton3 ha Text=3
I pulsanti di opzione nello stesso contenitore, in questo caso il GroupBox, sono esclusivi l'uno rispetto all'altro: solo uno di essi è selezionato.
3
GroupBox
groupBox2
 
4
Casella di controllo
checkBox1
checkBox2
casella di selezione3
3 caselle di controllo. checkBox1 ha Checked=True e Text=A - checkBox2 ha Text=B - checkBox3 ha Text=C
5
Casella di riepilogo
listBoxValori
un elenco che visualizza i valori dei pulsanti di opzione e delle caselle di controllo ogni volta che si verifica una modifica.
6
  
indica dove trovare il contenitore GroupBox

L'evento di interesse per questi sei controlli è CheckChanged, che indica che lo stato della casella di controllo o del pulsante di opzione è cambiato. In entrambi i casi, questo stato è rappresentato dalla proprietà booleana Checked, che indica effettivamente che il controllo è selezionato. Useremo qui un unico metodo per gestire tutti e sei gli eventi CheckChanged, il metodo poster. Per garantire che i sei eventi CheckChanged siano gestiti dallo stesso metodo poster, è possibile procedere come segue:

Seleziona il componente radioButton1 e fai clic con il pulsante destro del mouse per accedere alle sue proprietà:

Negli eventi [1], associamo il poster [2] all'evento CheckChanged. Ciò significa che vogliamo che il clic sull'opzione A1 venga elaborato da un metodo chiamato poster. Visual Studio genera automaticamente il poster nella finestra del codice:


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

Il metodo poster è un EventHandler.

Per gli altri cinque componenti, procediamo allo stesso modo. Ad esempio, selezioniamo l'opzione CheckBox1 e i suoi eventi [3]. Di fronte all'evento Click, abbiamo un elenco a discesa [4] contenente i metodi esistenti in grado di gestire questo evento. In questo caso, solo il metodo affiche. Lo selezioniamo. Ripetiamo questa procedura per tutti gli altri componenti.

Nel metodo InitializeComponent è stato generato il codice. Il poster è stato dichiarato come gestore dei sei eventi CheckedChanged come segue:


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);

Il metodo poster è completato come segue:


        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);
            }
}

La sintassi


            if (sender is CheckBox) {

viene utilizzata per verificare che il tipo del mittente sia CheckBox. Questo ci permette quindi di effettuare il transtype al tipo esatto del mittente. Il metodo poster inserisce in listBoxValeurs il nome del componente che ha generato l'evento e il valore della sua proprietà Checked . In fase di esecuzione [7], un clic su un pulsante di opzione attiva due eventi CheckChanged: uno sul vecchio pulsante selezionato, che passa a "deselezionato", e l'altro sul nuovo pulsante, che passa a "selezionato".

7.2.6. Invertitori ScrollBar

Esistono diversi tipi di barra di scorrimento:
la barra di scorrimento orizzontale (HscrollBar),
la barra di scorrimento verticale (VscrollBar),
l'incrementatore (NumericUpDown).

Eseguiamo la seguente applicazione:

tipo
nome
ruolo
1
hScrollBar
hScrollBar1
un'unità orizzontale
2
hScrollBar
hScrollBar2
un variatore orizzontale che segue le variazioni del variatore 1
3
Etichetta
labelValeurHS1
visualizza il valore dell'azionamento orizzontale
4
NumericUpDown
numericUpDown2
per impostare il valore del controller 2

Una barra di scorrimento (ScrollBar) consente all'utente di selezionare un valore da un intervallo di valori interi rappresentato dalla "fascia" di regolazione su cui si sposta il cursore. Il valore di regolazione è disponibile nella sua proprietà Value.

  • Per un drive orizzontale, l'estremità sinistra rappresenta il valore minimo dell'intervallo, l'estremità destra il valore massimo e il cursore il valore attualmente selezionato. Per un drive verticale, il minimo è rappresentato dall'estremità superiore, il massimo dall'estremità inferiore. Questi valori sono rappresentati dalle proprietà Minimum e Maximum e hanno come valori predefiniti 0 e 100.
  • Facendo clic sulle estremità del drive il valore cambia di un incremento (positivo o negativo) a seconda di quale estremità viene cliccata SmallChange, il cui valore predefinito è 1.
  • Cliccando su uno dei due lati del cursore, il valore cambia di un incremento (positivo o negativo), a seconda di quale estremità viene cliccata (LargeChange), il cui valore predefinito è 10.
  • Quando si clicca sull'estremità superiore di un dimmer verticale, il suo valore diminuisce. Questo può sorprendere l'utente medio, che normalmente si aspetta che il valore "aumenti". Questo problema viene risolto assegnando un valore negativo alle proprietà SmallChange e LargeChange
  • Queste cinque proprietà (Value, Minimum, Maximum, SmallChange, LargeChange) sono accessibili in lettura e scrittura.
  • L'evento principale del drive è quello che segnala un cambiamento di valore: lo Scroll.

Un componente NumericUpDown è simile a quello del drive: ha anch'esso le seguenti proprietà Minimum, Maximum e Value, i cui valori predefiniti sono 0, 100, 0. Ma qui, il valore (Value) viene visualizzato in una casella di immissione che è parte integrante del controllo. L'utente stesso può modificare questo valore a meno che la proprietà ReadOnly del controllo non sia impostata su true. Il valore di incremento è impostato da Incrementally, il cui valore predefinito è 1. L'evento principale del componente NumericUpDown è quello che segnala un cambiamento di valore: l'evento ValueChanged

Il codice dell'applicazione è il seguente:


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. Eventi mouse

Quando si disegna in un contenitore, è importante conoscere la posizione del mouse in modo che, ad esempio, sia possibile visualizzare un punto quando si fa clic. I movimenti del mouse attivano eventi nel contenitore in cui si muove.

  • [1]: eventi che si verificano quando il mouse viene spostato su un modulo o un controllo
  • [2]: eventi che si verificano durante il trascinamento (Drag'nDrop)
MouseEnter
il mouse è entrato nell'area del controllo
MouseLeave
il mouse ha appena lasciato l'area del controllo
MouseMove
il mouse si sta spostando all'interno dell'area del controllo
MouseDown
Premere il tasto sinistro del mouse
MouseUp
Rilasciare il tasto sinistro del mouse
DragDrop
l'utente rilascia un oggetto sul controllo
DragEnter
l'utente entra nell'area del controllo trascinando un oggetto
DragLeave
l'utente esce dall'area del controllo trascinando un oggetto
DragOver
l'utente passa sopra l'area di controllo trascinando un oggetto

Ecco un'applicazione che ti aiuterà a capire quando si verificano i diversi eventi del mouse:

tipo
nome
ruolo
1
Etichetta
lblPosizioneMouse
per visualizzare la posizione del mouse nel modulo 1, nell'elenco 2 o nel pulsante 3
2
ListBox
listBoxEvts
per visualizzare eventi del mouse diversi da MouseMove
3
Pulsante
buttonEffacer
per cancellare il contenuto di 2

Per tracciare i movimenti del mouse sui tre controlli, scriviamo un unico gestore, il poster :

Il codice della procedura poster è il seguente:


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

Ogni volta che il mouse entra nell'area di un controllo, il suo sistema di coordinate cambia. La sua origine (0,0) è l'angolo in alto a sinistra del controllo su cui si trova. Quindi, in fase di esecuzione, quando si sposta il mouse dal modulo al pulsante, è possibile vedere chiaramente il cambiamento delle coordinate. Per vedere meglio questi cambiamenti nell'area del mouse, è possibile utilizzare i controlli Cursor [1] :

Questa proprietà viene utilizzata per impostare la forma del cursore del mouse quando entra nell'area del controllo. Nel nostro esempio, impostiamo il cursore su Default per il modulo stesso [2], Mano per l'elenco 2 [3] e Croce per il pulsante 3 [4].

Inoltre, per rilevare l'ingresso e l'uscita del mouse dall'elenco 2, elaboriamo gli eventi MouseEnter e MouseLeave provenienti dallo stesso elenco:


        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));
}

Per gestire i clic sul modulo, gestiamo i seguenti eventi: MouseDown e 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));
}
  • righe 3 e 8: i messaggi vengono inseriti in prima posizione nella ListBox in modo che gli eventi più recenti vengano elencati per primi.
 

Infine, il codice per il gestore di clic del pulsante Elimina:


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

7.4. Crea una finestra con un menu

Ora vediamo come creare una finestra con un menu. Creeremo la seguente finestra:

Per creare un menu, seleziona "MenuStrip" nella barra "Menu e barre degli strumenti":

  • [1]: selezione del componente [MenuStrip]
  • [2]: sul modulo appare un menu con caselle vuote contrassegnate dalla scritta "Digita qui". Basta indicare le varie opzioni del menu.
  • [3]: l'etichetta "Opzioni A" è stata digitata. Passiamo all'etichetta [4].
  • [5]: sono state inserite le etichette dell'Opzione A. Passiamo all'etichetta [6]
  • [6]: le prime opzioni B
  • [7]: sotto B1, viene utilizzato un separatore. Questo è disponibile in un menu a tendina associato al testo "Digita qui"
  • [8]: per creare un sottomenu, utilizzare la freccia [8] e digitare il sottomenu in [9]

Non resta che assegnare un nome ai vari componenti del modulo:

n.
tipo
nome/i
ruolo
1
Etichetta
labelStatut
per visualizzare il testo della voce del menu delle opzioni selezionata
2
toolStripMenuItem
toolStripMenuItemOptionsA
toolStripMenuItemA1
toolStripMenuItemA2
toolStripMenuItemA3
opzioni di menu sotto l'opzione principale "Opzioni A"
3
Voce di menu della barra degli strumenti
toolStripMenuItemOpzioniB
toolStripMenuItemB1
toolStripMenuItemB2
toolStripMenuItemB3
opzioni di menu sotto l'opzione principale "Opzioni B"
4
Voce di menu della barra degli strumenti
toolStripMenuItemB31
toolStripMenuItemB32
opzioni di menu sotto l'opzione principale "B3"

Le opzioni di menu sono controlli come gli altri componenti visivi e dispongono di proprietà ed eventi. Ad esempio, le proprietà della voce di menu A1 sono le seguenti:

 

Nel nostro esempio vengono utilizzate due proprietà:

Nome
nome del controllo menu
Testo
etichetta del menu delle opzioni

Nella struttura del menu, selezionare l'opzione A1 e fare clic con il pulsante destro del mouse per accedere alle proprietà del controllo:

Negli eventi [1], associamo il poster [2] all'evento Click. Ciò significa che vogliamo che il clic sull'opzione A1 venga elaborato da un metodo chiamato poster. Visual Studio genera automaticamente il poster nella finestra del codice:


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

In questo metodo, visualizzeremo semplicemente nell'etichetta labelStatut la proprietà Text della voce di menu dell'opzione su cui è stato cliccato:


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

Il tipo di origine dell'evento sender è object. Le opzioni del menu sono ToolStripMenuItem, quindi siamo obbligati a convertire l'oggetto in ToolStripMenuItem.

Per tutte le opzioni di menu, impostiamo il gestore di clic sul metodo poster [3,4].

Eseguiamo l'applicazione e selezioniamo una voce di menu:

 

7.5. Componenti non visive

Passiamo ora a esaminare una serie di componenti non visibili: vengono utilizzati durante la progettazione, ma non sono visibili in fase di esecuzione.

7.5.1. Finestre di dialogo OpenFileDialog e SaveFileDialog

Realizzeremo la seguente applicazione:

I controlli sono i seguenti:

tipo
nome
ruolo
1
Casella di testo
TextBoxLines
testo digitato dall'utente o caricato da un file
MultiLine=True, ScrollBars=Both, AccepReturn=True, AcceptTab=True
2
Pulsante
pulsanteSalva
salva il testo di [1] in un file di testo
3
Pulsante
pulsanteCharger
carica il contenuto di un file di testo in [1]
4
Pulsante
pulsanteEffacer
cancella il contenuto di [1]
5
SaveFileDialog
saveFileDialog1
componente per la scelta del nome e della posizione del file di backup per [1]. Questo componente viene prelevato dalla barra degli strumenti [7] e semplicemente inserito nel modulo. Viene quindi salvato, ma non occupa spazio nel modulo. Si tratta di un componente non visivo.
6
OpenFileDialog
openFileDialog1
per selezionare il file da caricare in [1].

Il codice associato al comando Elimina è semplice:


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

Useremo le seguenti proprietà e metodi di SaveFileDialog :

Campo
Tipo
Ruolo
string Filtro
Proprietà
i tipi di file disponibili nell'elenco a discesa dei tipi di file nel
int FilterIndex
Proprietà
il numero del tipo di file proposto per impostazione predefinita nell'elenco sopra. Inizia da 0.
stringa InitialDirectory
Proprietà
la cartella originariamente presentata per il salvataggio del file
stringa FileName
Proprietà
il nome del file di backup specificato dall'utente
DialogResult.ShowDialog()
Metodo
metodo che visualizza la finestra di dialogo di salvataggio. Restituisce un risultato di tipo DialogResult.

Il metodo ShowDialog visualizza una finestra di dialogo simile a quella riportata di seguito:

1
elenco a discesa generato dal filtro. Il tipo di file predefinito è impostato da FilterIndex
2
file corrente, impostato da InitialDirectory se questa proprietà è stata impostata
3
nome del file scelto o digitato direttamente dall'utente. Sarà disponibile in FileName
4
Pulsanti Salva/Annulla. Se si utilizza Register, ShowDialog imposta il risultato su DialogResult.OK

La procedura di salvaguardia può essere scritta come segue:


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();
                    }
                }
            }
        }
  • riga 4: imposta il file iniziale (InitialDirectory) sul file (Application.ExecutablePath) che contiene l'eseguibile dell'applicazione.
  • riga 5: imposta i tipi di file da visualizzare. Nota la sintassi del filtro: filtro1|filtro2|..|filtro n con filtro i = Testo|modello file. Qui l'utente potrà scegliere tra i seguenti file *.txt e *.*.
  • riga 6: imposta il tipo di file da presentare per primo all'utente. Qui l'indice 0 indica i file *.txt.
  • riga 8: viene visualizzata la finestra di dialogo e ne viene recuperato il risultato. Mentre la finestra di dialogo è visualizzata, l'utente non ha più accesso al form principale (finestra di dialogo modale). L'utente imposta il nome del file da salvare ed esce dalla finestra di dialogo cliccando su Salva, su Annulla o chiudendo la finestra. Il risultato di ShowDialog è DialogResult.OK solo se l'utente ha utilizzato il pulsante Salva per uscire dalla finestra di dialogo.
  • Una volta fatto ciò, il nome del file da creare si trova ora nell'oggetto FileName saveFileDialog1. Questo ci riporta alla classica creazione di un file di testo. Il contenuto della TextBox: textBoxLignes.Text, gestendo al contempo eventuali eccezioni che potrebbero verificarsi.

La classe OpenFileDialog è molto simile a SaveFileDialog. Useremo gli stessi metodi e proprietà di cui sopra. Il metodo ShowDialog visualizza una finestra di dialogo simile a quella qui sotto:

1
elenco a discesa generato dal filtro. Il tipo di file predefinito è impostato da FilterIndex
2
file corrente, determinato da InitialDirectory se questa proprietà è stata impostata
3
nome del file scelto o digitato direttamente dall'utente. Sarà disponibile in FileName
4
pulsanti Apri/Annulla. Se si utilizza Apri, ShowDialog imposta il risultato su DialogResult.OK

La procedura per caricare il file di testo può essere scritta come segue:


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
        }
  • riga 4: imposta il file iniziale (InitialDirectory) sul file (Application.ExecutablePath) che contiene l'eseguibile dell'applicazione.
  • riga 5: imposta i tipi di file da visualizzare. Nota la sintassi del filtro: filtro1|filtro2|..|filtro n con filtro i = Testo|modello di file. Qui l'utente potrà scegliere tra i seguenti file *.txt e *.*.
  • riga 6: imposta il tipo di file da presentare per primo all'utente. Qui l'indice 0 indica i file *.txt.
  • riga 8: viene visualizzata la finestra di dialogo e ne viene recuperato il risultato. Mentre la finestra di dialogo è visualizzata, l'utente non ha più accesso al form principale (finestra di dialogo modale). L'utente imposta il nome del file da salvare ed esce dalla finestra di dialogo cliccando su Apri, oppure su Annulla, o chiudendo la finestra. Il risultato di ShowDialog è DialogResult.OK solo se l'utente ha utilizzato il pulsante Apri per uscire dalla finestra di dialogo.
  • Una volta fatto ciò, il nome del file da creare si trova ora nell'oggetto FileName openFileDialog1. Questo ci riporta alla classica lettura di un file di testo. Si noti, alla riga 16, il metodo per leggere un intero file.

7.5.2. Finestre di dialogo FontColor e ColorDialog

Continuiamo l'esempio precedente, aggiungendo due nuovi pulsanti e due nuovi controlli non visivi:

67

tipo
nome
ruolo
1
Pulsante
colorePulsante
per impostare il colore dei caratteri della casella di testo
2
Pulsante
pesoPulsante
per impostare il carattere della casella di testo
3
ColorDialog
colorDialog1
il componente per la selezione di un colore - tratto dalla casella degli strumenti [5].
4
FontDialog
colorDialog1
il componente per la selezione del font - preso dalla casella degli strumenti [5].

Le classi FontDialog e ColorDialog dispongono di un metodo ShowDialog simile a quello delle classi OpenFileDialog e SaveFileDialog.

Il metodo ShowDialog della classe ColorDialog consente di selezionare un colore [1]. La classe FontDialog consente di selezionare un font [2]:

  • [1]: se l'utente chiude la finestra di dialogo con OK, il risultato del metodo ShowDialog è DialogResult.OK e il colore scelto si trova nell'oggetto Color utilizzato da ColorDialog.
  • [2]: se l'utente chiude la finestra di dialogo con il pulsante OK, il risultato del metodo ShowDialog è DialogResult.OK e il font scelto si trova nell'oggetto Font utilizzato da FontDialog.

Ora disponiamo degli elementi necessari per elaborare i clic sui pulsanti Colore e Carattere:


         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;
}
  • riga [4]: la proprietà [ForeColor] di un componente TextBox indica il colore [Color] dei caratteri presenti nella TextBox. In questo caso, il colore è quello scelto dall'utente nella finestra di dialogo [ColorDialog].
  • riga [12]: la proprietà [Font] di un componente TextBox indica il carattere [Font] dei caratteri nella TextBox. Qui questo carattere è quello scelto dall'utente nella finestra di dialogo [FontDialog].

7.5.3. Timer

Proponiamo qui di scrivere la seguente applicazione:

n.
Tipo
Nome
Ruolo
1
Etichetta
labelChrono
visualizza un cronometro
2
Pulsante
pulsanteArretMarche
pulsante di avvio/arresto
3
Timer
timer1
componente che emette qui un evento ogni secondo

In [4] vediamo il cronometro in funzione, in [5] il cronometro si è fermato.

Per modificare il contenuto dell'etichetta ogni secondo LabelChrono, abbiamo bisogno di un componente che generi un evento ogni secondo, che possiamo intercettare per aggiornare la visualizzazione del cronometro. Questo componente è il Timer [1] disponibile nella casella degli strumenti Componenti [2] :

Le proprietà del componente Timer utilizzato qui saranno le seguenti:

Intervallo
numero di millisecondi dopo i quali viene emesso un evento Tick.
Tick
l'evento generato al termine dei millisecondi specificati in Intervallo
Abilitato
rende il timer attivo (true) o inattivo (false)

Nel nostro esempio, il timer si chiama timer1 e timer1.Interval è impostato su 1000 ms (1 s). L'evento Tick si verificherà quindi ogni secondo. Il clic sul pulsante Stop/Start viene gestito dalla procedura buttonArretMarche_Click:


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;
            }
        }
 
    }
}
  • riga 13: la procedura che gestisce il clic sul pulsante Off/On.
  • riga 15: l'etichetta del pulsante Stop/Start è "Stop" o "Start". Dobbiamo quindi verificare questa etichetta per sapere cosa fare.
  • riga 17: nel caso di "Marche", annotiamo l'ora di inizio in una variabile start che è una variabile globale (riga 11) dell'oggetto form
  • riga 19: inizializza il contenuto dell'etichetta LabelChrono
  • riga 21: timer avviato (Enabled=true)
  • riga 23: l'etichetta del pulsante è cambiata in "Stop".
  • riga 27: nel caso di "Arrêt" (Stop)
  • riga 29: timer fermato (Enabled=false)
  • riga 31: cambia l'etichetta del pulsante in "On".

Dobbiamo ancora occuparci dell'evento Tick sull'oggetto timer1, un evento che si verifica ogni secondo:


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");
        }
  • riga 3: registra l'ora del giorno
  • riga 4: calcola il tempo trascorso dall'ora di avvio del cronometro. Il risultato è un oggetto di tipo TimeSpan che rappresenta una durata nel tempo.
  • riga 6: questo deve essere visualizzato nel cronometro come hh:mm:ss. Per farlo, utilizziamo gli oggetti Hours, Minutes, Second di TimeSpan che rappresentano rispettivamente le ore, i minuti e i secondi della durata che visualizziamo nel formato ToString("d2") per visualizzare 2 cifre.

7.6. Esempio di applicazione - versione 6

Prendiamo l'applicazione di esempio IMPOTS. L'ultima versione è stata studiata nel paragrafo 6.4. Si trattava della seguente applicazione a tre livelli:

  • i livelli [metier] e [dao] erano incapsulati in DLL
  • il livello [ui] era un livello [console]
  • L'istanziazione dei livelli e l'integrazione nell'applicazione sono state gestite da Spring.

In questa nuova versione, il livello [ui] sarà fornito dalla seguente interfaccia grafica:

 

7.6.1. La soluzione Visual Studio

La soluzione Visual Studio è composta dai seguenti componenti:

  • [1]: il progetto è costituito dai seguenti elementi:
  • [Program.cs]: la classe che avvia l'applicazione
  • [Form1.cs]: prima classe del modulo
  • [Form2]: la classe del secondo form
  • [lib] descritta in [2]: sono state incluse tutte le DLL necessarie per il progetto:
  • [ImpotsV5-dao.dll]: la DLL del livello [dao] generata al paragrafo 6.4.3;
  • [ImpotsV5-metier.dll]: la DLL del livello [dao] generata nel paragrafo 6.4.4;
  • [Spring.Core.dll], [Common.Logging.dll], [antlr.runtime.dll]: le DLL di Spring già utilizzate nella versione precedente (vedi paragrafo 6.4.6).
  • [riferimenti] descritti in [3]: riferimenti del progetto. È stato aggiunto un riferimento per ogni DLL nel file [lib]
  • [App.config]: il file di configurazione del progetto. È identico a quello della versione precedente descritta al paragrafo 6.4.6;
  • [DataImpot.txt]: il file delle fasce di imposta configurato per essere copiato automaticamente nella cartella di esecuzione del progetto [4]

Il modulo [Form1] è il modulo per l'inserimento dei parametri di calcolo delle imposte [A] già presentato in precedenza. Il modulo [Form2] [B] viene utilizzato per visualizzare un messaggio di errore:

7.6.2. La [classe Program.cs]

La classe [Program.cs] avvia l'applicazione. Il suo codice è il seguente:


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);
        }
    }
}

Il codice generato da Visual Studio è stato completato a partire dalla riga 19. L'applicazione utilizza il file [ App.config] successivo:


<?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>
  • righe 24-32: utilizzo del precedente file [App.config] per istanziare i livelli [metier] e [dao]
  • riga 26: utilizzo del file [App.config]
  • riga 28: recupero di un riferimento dal livello [metier]
  • riga 31: eccezione memorizzata
  • riga 34: il modulo di riferimento indicherà il modulo da visualizzare (form1 o form2)
  • righe 36-50: se è stata generata un'eccezione, ci prepariamo a visualizzare un modulo di tipo [Form2]
  • righe 38-44: creazione del messaggio di errore da visualizzare. È costituito dalla concatenazione dei messaggi di errore delle varie eccezioni presenti nella catena di eccezioni.
  • riga 46: viene creato un modulo di tipo [Form2].
  • riga 47: come vedremo più avanti, questo modulo è una proprietà pubblica MsgErreur che rappresenta il messaggio di errore da visualizzare:

        public string MsgErreur { private get; set; }

Viene inserita questa proprietà.

  • riga 49: viene inizializzata la forma di riferimento che designa la finestra da visualizzare. Si noti il polimorfismo in atto. form2 non è di tipo [Form] ma di tipo [Form2], un tipo derivato da [Form].
  • righe 50-57: nessuna eccezione. Ci stiamo preparando a visualizzare un form di tipo [Form1].
  • riga 53: viene creato un modulo di tipo [Form1].
  • riga 54: come vedremo più avanti, questo modulo è una proprietà pubblica Trade che è un riferimento al livello [metier]:

                public IImpotMetier Metier { private get; set; }

Questa proprietà viene inserita.

  • riga 56: viene inizializzata la forma di riferimento che designa la finestra da visualizzare. Ancora una volta, è in atto il polimorfismo. form1 non è di tipo [Form] ma di tipo [Form1], un tipo derivato da [Form].
  • riga 59: viene visualizzata la finestra a cui fa riferimento form.

7.6.3. Il modulo [Form1]

In modalità [design], il modulo [Form1] è il seguente:

I controlli sono i seguenti

tipo
nome
ruolo
0
GroupBox
groupBox1
Testo=È sposato/a?
1
Pulsante di opzione
radioButtonOui
selezionato se sposato
2
RadioButton
radioButtonNo
selezionato se non sposato
Selezionato=True
3
NumericUpDown
numericUpDownEnfants
numero di figli
Minimo=0, Massimo=20, Incremento=1
4
Casella di testo
textSalaire
stipendio annuo del contribuente in euro
5
Etichetta
labelImpot
importo dell'imposta dovuta
BorderStyle=Fixed3D
6
Pulsante
pulsanteCalcola
avvia il calcolo dell'imposta
7
Pulsante
pulsanteElimina
riporta il modulo allo stato in cui si trovava al momento del caricamento
8
Pulsante
pulsanteEsci
per uscire dall'applicazione

Regole di funzionamento del modulo

  • il pulsante Calcola rimane disattivato finché il campo dello stipendio è vuoto
  • se, una volta eseguito il calcolo, risulta che lo stipendio è errato, viene segnalato l'errore [9]

Il codice della classe è il seguente:


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()!="";
        }
 
    }
}

Commentiamo solo le parti importanti:

  • riga [8]: proprietà pubblica Trade che consente alla classe di avvio [Program.cs] di iniettare un riferimento al livello [metier] in [Form1].
  • riga [14]: procedura di calcolo delle imposte
  • righe 15-27: verifica della validità dello stipendio (un numero intero >=0).
  • riga 29: calcolo delle imposte utilizzando il metodo [CalculerImpot] del livello [metier]. Si noti la semplicità di questa operazione, ottenuta incapsulando il livello [metier] in una DLL.

7.6.4. Il [modulo Form2]

In modalità [progettazione], il modulo [Form2] è il seguente:

I controlli sono i seguenti

tipo
nome
ruolo
1
Casella di testo
casella di testo Errore
Multiline=True, Scrollbars=Both

Il codice della classe è il seguente:


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);
        }
    }
}
  • riga 6: proprietà pubblica MsgErreur che consente alla classe di avvio [Program.cs] di inserire il messaggio di errore da visualizzare in [Form2]. Questo messaggio viene visualizzato quando viene eseguito il Load, righe 12-16.
  • riga 14: il messaggio di errore viene inserito nella TextBox
  • riga 16: la selezione effettuata nell'operazione precedente viene rimossa. [TextBox].Select(début,longueur) seleziona (evidenzia) lunghezza caratteri a partire dal carattere n. inizio. [TextBox].Select(0,0) equivale a deselezionare tutto il testo.

7.6.5. Conclusione

Rivediamo l'architettura a tre livelli utilizzata:

Questa architettura ci ha permesso di sostituire il livello [ui] esistente con un'implementazione grafica, senza modificare i livelli [metier] e [dao]. Abbiamo potuto concentrarci sul livello [ui] senza preoccuparci del possibile impatto sugli altri livelli. Questo è il vantaggio principale delle architetture a tre livelli. Vedremo un altro esempio più avanti, quando il livello [dao], che attualmente utilizza dati provenienti da un file di testo, verrà sostituito da un livello [dao] che utilizza dati provenienti da un database. Come vedremo, ciò non avrà alcun impatto sui livelli [ui] e [metier].