8. Eventi utente
Nel capitolo precedente abbiamo discusso il concetto di eventi associati ai componenti dei moduli. Ora vedremo come creare eventi nelle nostre classi.
8.1. Oggetti delegati predefiniti
Il concetto di oggetto delegato è stato introdotto nel capitolo precedente, ma all'epoca era stato trattato solo superficialmente. Quando abbiamo esaminato come vengono dichiarati i gestori di eventi dei componenti di un modulo, ci siamo imbattuti in un codice simile al seguente:
this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);
dove buttonAfficher era un componente [Button]. Questa classe ha un evento Click definito come segue:
![]() |
- [1]: la classe [Button]
- [2]: i suoi eventi
- [3,4]: l'evento Click
- [5]: la dichiarazione dell'evento [Control.Click] [4].
- EventHandler è un prototipo (un modello) di un metodo chiamato delegato.
- event è una parola chiave che limita la funzionalità del delegato EventHandler: un oggetto delegato ha funzionalità più ricche rispetto a un evento.
Visita il delegato EventHandler è definito come segue:
![]() |
Il delegato Visit EventHandler designa un modello di metodo:
- con tipo come primo parametro Object
- il cui secondo parametro è un EventArgs
- che non restituisce alcun risultato
Un metodo corrispondente al modello definito da EventHandler potrebbe essere il seguente:
private void buttonAfficher_Click(object sender, EventArgs e);
Per creare un EventHandler, procedere come segue:
EventHandler evtHandler=new EventHandler(méthode correspondant au prototype défini par le type EventHandler);
Possiamo quindi scrivere:
Una variabile di tipo delegato è infatti un elenco di riferimenti ai metodi corrispondenti al delegato. Per aggiungere un nuovo metodo M alla variabile evtHandler sopra riportata, utilizziamo la sintassi:
La notazione += può essere utilizzata anche se evtHandler è un elenco vuoto.
Istruzioni:
this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);
aggiunge un EventHandler all'elenco dei metodi di evento buttonAfficher.Click. Quando si verifica l'evento Click sul componente buttonAfficher, VB esegue il:
dove:
- source è l'oggetto alla base dell'evento
- evt è di tipo EventArgs e non contiene informazioni
Tutti i metodi con firma void M(object, EventArgs) associati all'evento Click da:
this.buttonAfficher.Click += new System.EventHandler(M);
verranno chiamati con i parametri (source, evt) trasmessi da VB.
8.2. Definizione di oggetti delegati
Istruzione
public delegate int Opération(int n1, int n2);
definisce un tipo chiamato Operation che rappresenta un prototipo di funzione che accetta due numeri interi e restituisce un numero intero. È la parola chiave delegate che rende Operation una definizione di prototipo di funzione.
Una variabile op di tipo Operation registrerà un elenco di funzioni corrispondenti al prototipo Operation :
La registrazione di un metodo fi nella variabile op avviene tramite op=new Operation(fi) o semplicemente op=fi. Per aggiungere un metodo fj all'elenco delle funzioni registrate, si scrive op+= fj. Per rimuovere un metodo fk si scrive op-=fk. Se, nel nostro esempio, scriviamo n=op(n1,n2), l'insieme dei metodi memorizzati in op verrà eseguito con i parametri n1 e n2. Il risultato n ottenuto sarà quello dell'ultimo metodo eseguito. Non è possibile ottenere i risultati prodotti da tutti i metodi. Per questo motivo, se si memorizza un elenco di metodi in una funzione delegata, questi restituiranno solitamente un risultato di tipo void.
Si consideri il seguente esempio:
using System;
namespace Chap6 {
class Class1 {
// function prototype definition
// accepts 2 integers as parameters and returns an integer
public delegate int Opération(int n1, int n2);
// two instance methods corresponding to the prototype
public int Ajouter(int n1, int n2) {
Console.WriteLine("Ajouter(" + n1 + "," + n2 + ")");
return n1 + n2;
}//add
public int Soustraire(int n1, int n2) {
Console.WriteLine("Soustraire(" + n1 + "," + n2 + ")");
return n1 - n2;
}//subtract
// a static method corresponding to the prototype
public static int Augmenter(int n1, int n2) {
Console.WriteLine("Augmenter(" + n1 + "," + n2 + ")");
return n1 + 2 * n2;
}//increase
static void Main(string[] args) {
// define an operation object to store functions
// we register the static function increase
Opération op = Augmenter;
// the delegate is executed
int n = op(4, 7);
Console.WriteLine("n=" + n);
// creation of a c1 object of type class1
Class1 c1 = new Class1();
// we register c1's add method in the delegate
op = c1.Ajouter;
// execution of delegated object
n = op(2, 3);
Console.WriteLine("n=" + n);
// the subtract method of c1 is registered in the delegate
op = c1.Soustraire;
n = op(2, 3);
Console.WriteLine("n=" + n);
//registration of two functions in the delegate
op = c1.Ajouter;
op += c1.Soustraire;
// execution of delegated object
op(0, 0);
// remove a function from the delegate
op -= c1.Soustraire;
// the delegate is executed
op(1, 1);
}
}
}
- riga 3: definisce una classe Class1.
- riga 6: definizione del delegato Operation: un prototipo di metodi che accettano due parametri di tipo int e restituiscono un int
- righe 9-12: il metodo di istanza Add alla firma del delegato Operation.
- righe 14-17: il metodo di istanza S subtract alla firma del delegato Operation.
- righe 20-23: il metodo di classe Ancrease alla firma del delegato Operation.
- riga 25: viene eseguito il metodo Main
- riga 20: la variabile op è di tipo delegato Operation. Conterrà un elenco di metodi con la firma di tipo delegato Operation. Le viene assegnato un primo riferimento a un metodo, quello del metodo statico Class1.Augmenter.
- riga 31: il delegato op viene eseguito: tutti i metodi a cui fa riferimento op verranno eseguiti. Verranno eseguiti con i parametri passati al delegato op. In questo caso, verrà eseguito solo il metodo statico Class1.Augmenter.
- riga 35: viene creata un'istanza c1 della classe Class1.
- riga 37: il metodo di istanza c1.Ajouter viene assegnato al delegato op. Increase era un metodo statico, Add è un metodo di istanza. Volevamo dimostrare che non aveva importanza.
- riga 39: viene eseguito il delegato op: Add verrà eseguito con i parametri passati al delegato op.
- riga 42: fare lo stesso con il metodo di istanza Subtract.
- righe 46-47: metodi Add e Subtract nel delegato op.
- riga 49: il delegato op viene eseguito: sia Add che Subtract verranno eseguiti con i parametri passati al delegato op.
- riga 51: il metodo Subtract viene rimosso dall'operazione del delegato.
- riga 53: viene eseguita l'operazione delegata: verrà eseguito il restante Add.
I risultati sono i seguenti:
8.3. Delegati o interfacce?
I concetti di delegati e interfacce possono sembrare piuttosto simili, e ci si potrebbe chiedere quali siano esattamente le differenze tra questi due concetti. Prendiamo il seguente esempio, simile a uno che abbiamo già studiato:
using System;
namespace Chap6 {
class Program1 {
// function prototype definition
// accepts 2 integers as parameters and returns an integer
public delegate int Opération(int n1, int n2);
// two instance methods corresponding to the prototype
public static int Ajouter(int n1, int n2) {
Console.WriteLine("Ajouter(" + n1 + "," + n2 + ")");
return n1 + n2;
}//add
public static int Soustraire(int n1, int n2) {
Console.WriteLine("Soustraire(" + n1 + "," + n2 + ")");
return n1 - n2;
}//subtract
// Executing a delegate
public static int Execute(Opération op, int n1, int n2){
return op(n1, n2);
}
static void Main(string[] args) {
// delegate execution Add
Console.WriteLine(Execute(Ajouter, 2, 3));
// delegate execution Subtract
Console.WriteLine(Execute(Soustraire, 2, 3));
// executing a multicast delegate
Opération op = Ajouter;
op += Soustraire;
Console.WriteLine(Execute(op, 2, 3));
// remove a function from the delegate
op -= Soustraire;
// the delegate is executed
Console.WriteLine(Execute(op, 2, 3));
}
}
}
Riga 20, il metodo Execute si aspetta un riferimento a un oggetto di tipo delegato Operation definito alla riga 6. Questo passa a Execute, metodi diversi (righe 26, 28, 32 e 36). Questa proprietà di polimorfismo può essere ottenuta anche con un :
using System;
namespace Chap6 {
// interface IOperation
public interface IOperation {
int operation(int n1, int n2);
}
// class Add
public class Ajouter : IOperation {
public int operation(int n1, int n2) {
Console.WriteLine("Ajouter(" + n1 + "," + n2 + ")");
return n1 + n2;
}
}
// class Subtract
public class Soustraire : IOperation {
public int operation(int n1, int n2) {
Console.WriteLine("Soustraire(" + n1 + "," + n2 + ")");
return n1 - n2;
}
}
// test class
public static class Program2 {
// Executing the single method of the IOperation interface
public static int Execute(IOperation op, int n1, int n2) {
return op.operation(n1, n2);
}
public static void Main() {
// delegate execution Add
Console.WriteLine(Execute(new Ajouter(), 2, 3));
// delegate execution Subtract
Console.WriteLine(Execute(new Soustraire(), 2, 3));
}
}
}
- righe 6-8: l'interfaccia [IOperation] definisce un metodo operation.
- righe 11-16 e 19-24: le classi [Add] e [Subtract] implementano l'interfaccia [IOperation].
- righe 29-31: il metodo Execute il cui primo parametro è di tipo interfaccia IOperation. Il metodo Execute riceverà successivamente, come primo parametro, un'istanza della classe Add e poi un'istanza della classe Subtract.
L'aspetto polimorfico del delegato dell'esempio precedente. Allo stesso tempo, entrambi gli esempi mostrano le differenze tra queste due nozioni.
I tipi delegato e interfaccia sono intercambiabili
- se l'interfaccia ha un solo metodo. Infatti, il delegato è un wrapper per un singolo metodo, mentre l'interfaccia può definire diversi metodi.
- se non viene utilizzato l'aspetto multicast del delegato. Il concetto di multicast non esiste nell'interfaccia.
Se queste due condizioni sono soddisfatte, allora possiamo scegliere tra le seguenti due firme per il metodo Execute :
int Execute(IOperation op, int n1, int n2)
int Execute(Opération op, int n1, int n2)
La seconda, che utilizza il delegato, può essere utilizzata in modo più flessibile. Nella prima firma, il primo parametro del metodo deve implementare l'interfaccia IOperation. Ciò richiede la creazione di una classe per definire il metodo da passare come primo parametro a Execute. Nella seconda firma, è sufficiente qualsiasi metodo esistente con la firma corretta. Non è richiesta alcuna creazione aggiuntiva.
8.4. Gestione degli eventi
Le classi delegate degli oggetti possono essere utilizzate per definire eventi. Una classe C1 può definire un evento evt come segue:
- un delegato di tipo viene definito all'interno o all'esterno della classe C1:
- la classe C1 definisce un delegato Evt :
- quando un'istanza c1 della classe C1 desidera segnalare un evento, eseguirà il proprio delegato Evt1 passando i parametri definiti dal delegato Evt. Tutti i metodi registrati nel delegato Evt1 verranno quindi eseguiti con questi parametri. Si può dire che sono stati avvisati dell'evento Evt1.
- Se un oggetto c2 che utilizza c1 desidera essere avvisato quando si verifica un evento Evt1 sull'oggetto c1, registrerà uno dei suoi c2.M nell'oggetto delegato c1.Evt1 dell'oggetto c1. Il suo c2.M verrà eseguito ogni volta che l'evento Evt1 si verificherà sull'oggetto c1. Può anche annullare l'iscrizione se non desidera più essere avvisato dell'evento.
- Poiché l'oggetto delegato c1.Evt1 può registrare diversi metodi, diversi ci possono registrarsi con c1.Evt1 per essere avvisati dell'evento Evt1 su c1.
In questo scenario, abbiamo:
- una classe che segnala un evento
- classi che vengono notificate di questo evento. Si dice che si iscrivono all'evento.
- un delegato di tipo che definisce la firma dei metodi che saranno notificati dell'evento
Il framework .NET definisce:
- una firma standard del delegato un evento
- source: l'oggetto che ha segnalato l'evento
- evtInfo: un oggetto di tipo EventArgs o derivato che fornisce informazioni sull'evento
- il nome del delegato deve terminare con EventHandler
- un modo standard per dichiarare un MyEventHandler in una classe:
Il tipo Evt1 è un delegato. La parola chiave event serve a limitare le operazioni che possono essere eseguite su di esso:
- dall'esterno della classe C1, sono possibili solo le operazioni += e -=. Ciò impedisce che i metodi sottoscritti all'evento vengano eliminati (ad esempio a causa di un errore dello sviluppatore). È possibile semplicemente sottoscrivere (+=) o annullare la sottoscrizione (-=) all'evento.
- Solo un'istanza di tipo C1 può eseguire la chiamata Evt1(source,evtInfo) che attiva l'esecuzione dei metodi sottoscritti a Evt1.
Il framework .NET fornisce un metodo generico che soddisfa la firma del delegato di un evento:
public delegate void EventHandler<TEventArgs>(object source, TEventArgs evtInfo) where TEventArgs : EventArgs
- il delegato EventHandler utilizza il tipo generico TEventArgs, che è il tipo del suo secondo parametro
- il tipo TEventArgs deve derivare da EventArgs (dove TEventArgs : EventArgs)
Con questo delegato generico, la dichiarazione di un evento X nella classe C seguirà lo schema raccomandato seguente:
- definire un tipo XEventArgs derivato da EventArgs per incapsulare le informazioni sull'evento X
- definire nella classe C un EventHandler<XEventArgs>.
- definire nella classe C un metodo protetto
per "pubblicare" l'evento X agli abbonati.
Si consideri il seguente esempio:
- una classe Transmitter incapsula una temperatura. Questa temperatura viene monitorata. Quando supera una certa soglia, viene attivato un evento. Chiameremo questo evento TemperatureTropHaute. Le informazioni relative a questo evento saranno incapsulate in un tipo TemperatureTropHauteEventArgs.
- Una classe Underwriter si abbona all'evento precedente. Quando riceve la notifica dell'evento, visualizza un messaggio sulla console.
- un programma console crea un trasmettitore e due abbonati. Inserisce le temperature dalla tastiera e le memorizza in un Transmitter. Se questa è troppo alta, il Transmitter pubblica l'evento TemperatureTropHaute.
Per conformarci al metodo raccomandato di gestione degli eventi, definiamo innanzitutto il tipo TemperatureTropHauteEventArgs per incapsulare le informazioni sull'evento:
using System;
namespace Chap6 {
public class TemperatureTropHauteEventArgs:EventArgs {
// temperature during evt
public decimal Temperature { get; set; }
// manufacturers
public TemperatureTropHauteEventArgs() {
}
public TemperatureTropHauteEventArgs(decimal temperature) {
Temperature = temperature;
}
}
}
- riga 6: l'informazione incapsulata da TemperatureTropHauteEventArgs è la temperatura che ha causato l'evento TemperatureTropHaute.
La classe Transmitter è la seguente:
using System;
namespace Chap6 {
public class Emetteur {
static decimal SEUIL = 19;
// observed temperature
private decimal temperature;
// name of source
public string Nom { get; set; }
// event reported
public event EventHandler<TemperatureTropHauteEventArgs> TemperatureTropHaute;
// read/write temperature
public decimal Temperature {
get {
return temperature;
}
set {
temperature = value;
if (temperature > SEUIL) {
// subscribers are notified of the event
OnTemperatureTropHaute(new TemperatureTropHauteEventArgs(temperature));
}
}
}
// reporting an event
protected virtual void OnTemperatureTropHaute(TemperatureTropHauteEventArgs evt) {
// issue of event TemperatureTropHaute to subscribers
TemperatureTropHaute(this, evt);
}
}
}
- riga 5: la soglia di temperatura al di sopra della quale verrà pubblicato l'evento TemperatureTropHaute.
- riga 10: il mittente ha un nome a scopo identificativo
- riga 12: l'evento TemperatureTropHaute.
- righe 15-26: il metodo get che rileva la temperatura e il metodo set che la registra. Questo è il set per pubblicare l'evento TemperatureTropHaute se la temperatura da registrare supera la soglia della riga 5. Pubblica l'evento utilizzando l'OnTemperatureTropHauteHandler della riga 29 passando un TemperatureTropHauteEventArgs in cui è stata registrata la temperatura che ha superato la soglia.
- righe 29-32: l'evento TemperatureTropHaute viene pubblicato con il mittente stesso come primo parametro e l'oggetto TemperatureTropHauteEventArgs ricevuto come parametro.
La classe Underwriter che si abbonerà all'evento TemperatureTropHaute è la seguente:
using System;
namespace Chap6 {
public class Souscripteur {
// name
public string Nom { get; set; }
// event manager TemperatureTropHaute
public void EvtTemperatureTropHaute(object source, TemperatureTropHauteEventArgs e) {
// operator console display
Console.WriteLine("Souscripteur [{0}] : la source [{1}] a signalé une température trop haute : [{2}]", Nom, ((Emetteur)source).Nom, e.Temperature);
}
}
}
- riga 6: ogni sottoscrittore è identificato dal nome.
- righe 9-12: il metodo da associare all'evento TemperatureTropHaute. Ha la firma del tipo delegato EventHandler<TEventArgs> che un gestore di eventi deve avere. Il metodo visualizza sulla console: il nome dell'abbonato che visualizza il messaggio, il nome del mittente che ha segnalato l'evento, la temperatura che ha attivato l'evento.
- L'iscrizione all'evento TemperatureTropHaute di un oggetto Transmitter non viene effettuata nella classe Underwriter. Sarà effettuata da una classe esterna.
Il programma [Program.cs] collega tutti questi elementi tra loro:
using System;
namespace Chap6 {
class Program {
static void Main(string[] args) {
// creation of an evts transmitter
Emetteur e1 = new Emetteur() { Nom = "e" };
// creation of a table of 2 subscribers
Souscripteur[] souscripteurs = new Souscripteur[2];
for (int i = 0; i < souscripteurs.Length; i++) {
// creation subscriber
souscripteurs[i] = new Souscripteur() { Nom = "s" + i };
// we subscribe him to e1's TemperatureTropHaute event
e1.TemperatureTropHaute += souscripteurs[i].EvtTemperatureTropHaute;
}
// temperatures are read from the keyboard
decimal temperature;
Console.Write("Température (rien pour arrêter) : ");
string saisie = Console.ReadLine().Trim();
// as long as the line entered is not empty
while (saisie != "") {
// is the input a decimal number?
if (decimal.TryParse(saisie, out temperature)) {
// correct temperature - recorded
e1.Temperature = temperature;
} else {
// on signale l'erreur
Console.WriteLine("Température incorrecte");
}
// new entry
Console.Write("Température (rien pour arrêter) : ");
saisie = Console.ReadLine().Trim();
}//while
}
}
}
- riga 6: creazione del trasmettitore
- righe 8-14: creazione di due sottoscrittori all'evento TemperatureTropHaute del trasmettitore.
- righe 20-32: ciclo di immissione della temperatura da tastiera
- riga 24: se la temperatura inserita è corretta, viene trasmessa all'oggetto Transmitter e1 che attiverà l'evento TemperatureTropHaute se la temperatura è superiore a 19 °C.
I risultati sono i seguenti:

