6. Gestione degli eventi
Nel capitolo precedente abbiamo affrontato il concetto di eventi legati ai componenti. Vediamo ora come creare eventi nelle nostre classi. Si tratta di un concetto complesso che non interesserà gli sviluppatori principianti, ai quali si consiglia di passare direttamente al capitolo successivo.
6.1. Oggetti delegate
L'istruzione
definisce un tipo denominato opération, che è in realtà un prototipo di funzione che accetta due interi e restituisce un intero. È la parola chiave delegate a rendere opération una definizione di prototipo di funzione. Il tipo [opération] può essere assegnato a una variabile nel modo seguente:
Dim op As New opération(AddressOf [fonction])
Una variabile di tipo [délégué] viene creata come un oggetto. Il parametro del costruttore è la funzione associata all’istanza del delegato. In VB, questa funzione è specificata nella forma AddressOf funzione, dove [fonction] è il nome della funzione. Una volta creata l'istanza di un delegato, questa può essere utilizzata come una funzione:
' si esegue la funzione delegata
Dim n As Integer = op(4, 7)
Il delegato [opération] è stato definito in modo da ricevere due parametri di tipo [integer] e restituire un risultato dello stesso tipo. Pertanto, la sua istanza [op] sopra riportata viene utilizzata come una funzione di questo tipo. Consideriamo il seguente esempio:
' funzioni delegate
Imports System
Public Class delegate1
' definizione di un prototipo di funzione
' accetta 2 interi come parametri e restituisce un intero
Delegate Function opération(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
' due metodi di istanza corrispondenti al prototipo
' aggiungere
Public Function ajouter(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
Console.Out.WriteLine(("ajouter(" & n1 & "," & n2 & ")"))
Return n1 + n2
End Function
' sottrarre
Public Function soustraire(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
Console.Out.WriteLine(("soustraire(" & n1 & "," & n2 & ")"))
Return n1 - n2
End Function
' un metodo statico corrispondente al prototipo
Public Shared Function augmenter(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
Console.Out.WriteLine(("augmenter(" & n1 & "," & n2 & ")"))
Return n1 + 2 * n2
End Function
' programma di test
Public Shared Sub Main()
' si definisce un oggetto di tipo operazione per registrarvi delle funzioni
' si registra la funzione statica «aumentare»
Dim op As New opération(AddressOf delegate1.augmenter)
' si esegue il delegato
Dim n As Integer = op(4, 7)
Console.Out.WriteLine(("n=" & n))
' creazione di un oggetto c1 di tipo class1
Dim c1 As New delegate1
' si registra nel delegato il metodo «aggiungere» di c1
op = New delegate1.opération(AddressOf c1.ajouter)
' esecuzione dell'oggetto delegato
n = op(2, 3)
Console.Out.WriteLine(("n=" & n))
' si registra nel delegato il metodo "sottrai" di c1
op = New delegate1.opération(AddressOf c1.soustraire)
n = op(2, 3)
Console.Out.WriteLine(("n=" & n))
End Sub
End Class
I risultati dell’esecuzione sono i seguenti:
6.2. Gestione degli eventi
A cosa può servire un oggetto di tipo delegate?
Come vedremo nell’esempio seguente, serve soprattutto per la gestione degli eventi. Una classe C1 può generare eventi evti. Durante un evento evti, un oggetto di tipo C1 avvierà l’esecuzione di un oggetto evtiDéclenché di tipo delegate. Verranno quindi eseguite tutte le funzioni registrate nell’oggetto delegato evtiDéclenché. Se un oggetto C2 che utilizza un oggetto C1 desidera essere avvisato del verificarsi dell’evento evti sull’oggetto C1, registrerà uno dei suoi metodi C2.f nell’oggetto delegato C1.evtiDéclenché dell’oggetto C1, in modo che il metodo C2.f venga eseguito ogni volta che l’evento evti si verifichi sull'oggetto C1. Poiché l'oggetto delegato C1.evtiDéclenché può registrare più funzioni, diversi oggetti Ci potranno registrarsi presso il delegato C1.evtiDéclenché per essere avvisati dell’evento evti su C1.
6.2.1. Notifica di un evento
Un evento viene dichiarato nel modo seguente (1):
Delegate Sub proc(liste d'arguments)
Public Event événement as proc
oppure (2)
Public Event événement(liste d'arguments)
È la parola chiave [Event] che definisce un dato come evento. All'evento viene associata la firma che dovranno avere i gestori dello stesso. È possibile definire questa firma tramite un delegato (metodo 1) oppure direttamente (metodo 2).
6.2.2. Definizione dei gestori di un evento
Quando un evento viene attivato dall'istruzione [RaiseEvent], è necessario elaborarlo. Le procedure incaricate di gestire gli eventi sono denominate gestori di eventi. Al momento della creazione di un oggetto [Event], viene dichiarata solo la firma dei gestori dell’evento. Tale firma è quella dell’oggetto [delegate] associato all’evento. Fatto ciò, sarà possibile associare dei gestori all’evento. Si tratta di procedure che devono avere la stessa firma dell’oggetto [delegate] associato all’evento. Per associare un gestore di evento a un evento, si utilizza l’istruzione [AddHandler] con la seguente sintassi:
- event: variabile evento a cui si associa un nuovo gestore di eventi
- eventhandler: nome del gestore di eventi - deve avere la firma del [délégué] associato all'evento
È possibile associare a un evento tutti i gestori che si desidera. Verranno tutti eseguiti quando verrà attivato l’evento a cui sono associati.
6.2.3. Attivazione di un evento
Per attivare un evento tramite programma, si utilizza l’istruzione [RaiseEvent]:
- eventname: variabile evento (dichiarata con la parola chiave Event)
- argumentlist: argomenti del delegato associato all’evento.
Verranno chiamati tutti i gestori che sono stati associati all'evento tramite l'istruzione AddHandler. Essi riceveranno come parametri quelli definiti nella firma dell'evento.
6.2.4. Un esempio
Consideriamo il seguente esempio:
'Gestione degli eventi
Imports System
Public Class myEventArgs
Inherits EventArgs
' la classe di un evento
' attributo
Private _saisie As String
' costruttore
Public Sub New(ByVal saisie As String)
_saisie = saisie
End Sub
' proprietà di sola lettura
Public Overrides Function ToString() As String
Return _saisie
End Function
End Class
Public Class émetteur
' classe emittente di un evento
' attributo
Private _nom As String ' nom de l'émetteur
' costruttore
Public Sub New(ByVal nom As String)
_nom = nom
End Sub
' ToString
Public Overrides Function ToString() As String
Return _nom
End Function
' il prototipo delle funzioni incaricate di gestire l'evento
Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)
' il pool dei gestori di eventi
Public Event evtHandler As _evtHandler
' metodo per richiedere l'emissione di un evento
Public Sub envoyerEvt(ByVal evt As myEventArgs)
' si avvisano tutti gli abbonati
' si simula che l'evento provenga da qui
RaiseEvent evtHandler(Me, evt)
End Sub
End Class
' una classe di gestione dell'evento
Public Class souscripteur
' attributo
Private nom As String ' nom du souscripteur
Private sender As émetteur ' l'émetteur des évts
' costruttore
Public Sub New(ByVal nom As String, ByVal e As émetteur)
' si annota il nome del sottoscrittore
Me.nom = nom
' e l'emittente degli eventi
Me.sender = e
' ci si iscrive per ricevere gli eventi dall'emittente e
AddHandler e.evtHandler, AddressOf traitementEvt
End Sub
' gestore di eventi
Public Sub traitementEvt(ByVal sender As Object, ByVal evt As myEventArgs)
' visualizzazione eventi
Console.Out.WriteLine(("L'objet [" + sender.ToString + "] a signalé la saisie erronée [" + evt.ToString + "] au souscripteur [" + nom + "]"))
End Sub
End Class
' un programma di test
Public Class test
Public Shared Sub Main()
' creazione di un generatore di eventi
Dim émetteur1 As New émetteur("émetteur1")
' creazione di un array di sottoscrittori
' per gli eventi emessi dall'emittente1
Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
' si legge una sequenza di numeri interi dalla tastiera
' non appena uno è errato, si emette un evento
Console.Out.Write("Nombre entier (rien pour arrêter) : ")
Dim saisie As String = Console.In.ReadLine().Trim()
' finché la riga inserita non è vuota
While saisie <> ""
' il valore immesso è un numero intero?
Try
Dim n As Integer = Integer.Parse(saisie)
Catch
' non è un numero intero
' si avvisa tutti
émetteur1.envoyerEvt(New myEventArgs(saisie))
End Try
' si avvisa tutti
' nuovo inserimento
Console.Out.Write("Nombre entier (rien pour arrêter) : ")
saisie = Console.In.ReadLine().Trim()
End While
' fine
Environment.Exit(0)
End Sub
End Class
Il codice precedente è piuttosto complesso. Analizziamolo in dettaglio. In una gestione degli eventi, c'è un emittente di eventi (sender) che invia i dettagli degli eventi (EventArgs) agli abbonati che hanno manifestato interesse per gli eventi in questione. La classe emittente degli eventi è la seguente:
Public Class émetteur
' la classe emittente di un evento
' attributo
Private _nom As String ' nom de l'émetteur
' costruttore
Public Sub New(ByVal nom As String)
_nom = nom
End Sub
' ToString
Public Overrides Function ToString() As String
Return _nom
End Function
' il prototipo delle funzioni incaricate di gestire l'evento
Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)
' il pool dei gestori di eventi
Public Event evtHandler As _evtHandler
' metodo per richiedere l'emissione di un evento
Public Sub envoyerEvt(ByVal evt As myEventArgs)
' si avvisano tutti gli abbonati
' si simula che l'evento provenga da qui
RaiseEvent evtHandler(Me, evt)
End Sub
End Class
Ogni oggetto émetteur ha un nom assegnato per impostazione predefinita. Il metodo ToString è stato ridefinito in modo tale che, quando si trasforma un oggetto émetteur in string,, venga recuperato il suo nome. La classe definisce un evento:
' il prototipo delle funzioni incaricate di gestire l'evento
Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)
' l'evento
Public Event evtHandler As _evtHandler
Il primo argomento di una funzione di gestione di un evento sarà l’oggetto che genera l’evento. Il secondo argomento sarà di tipo myEventArgs, un oggetto che fornirà dettagli sull’evento e sul quale torneremo in seguito. Per poter attivare un evento dall’esterno di un oggetto émetteur, aggiungiamo alla classe il metodo envoyerEvt:
' metodo per richiedere l'emissione di un evento
Public Sub envoyerEvt(ByVal evt As myEventArgs)
' si avvisano tutti gli abbonati
' si simula che l'evento provenga da qui
RaiseEvent evtHandler(Me, evt)
End Sub
Dobbiamo definire quale sarà il tipo di eventi attivati dalla classe émetteur. A tal fine abbiamo definito la classe myEventArgs:
Public Class myEventArgs
' la classe di un evento
' attributo
Private _saisie As String
' costruttore
Public Sub New(ByVal saisie As String)
_saisie = saisie
End Sub
' proprietà immessa in sola lettura
Public Overrides Function ToString() As String
Return _saisie
End Function
End Class
Gli eventi che ci interessano sono gli inserimenti errati da tastiera. Chiederemo a un utente di digitare numeri interi sulla tastiera e, non appena digiterà una stringa che non rappresenta un numero intero, verrà attivato un evento. Come dettaglio dell’evento, ci limiteremo a fornire l’input errato. È questo il significato dell’attributo _saisie della classe. Viene quindi creato un oggetto myEventArgs con l’input errato come parametro. Ridefiniamo inoltre il metodo ToString affinché, quando si converte un oggetto myEventArgs in una stringa, si ottenga l’attributo _saisie.
Abbiamo definito il generatore degli eventi e il tipo di eventi che emette. Definiamo quindi il tipo di sottoscrittori interessati a tali eventi.
' una classe di gestione dell'evento
Public Class souscripteur
' attributo
Private nom As String ' nom du souscripteur
Private sender As émetteur ' l'émetteur des évts
' costruttore
Public Sub New(ByVal nom As String, ByVal e As émetteur)
' si registra il nome del sottoscrittore
Me.nom = nom
' e l'emittente degli eventi
Me.sender = e
' ci si iscrive per ricevere gli eventi dall'emittente e
AddHandler e.evtHandler, AddressOf traitementEvt
End Sub
' gestore di eventi
Public Sub traitementEvt(ByVal sender As Object, ByVal evt As myEventArgs)
' visualizzazione eventi
Console.Out.WriteLine(("L'objet [" + sender.ToString + "] a signalé la saisie erronée [" + evt.ToString + "] au souscripteur [" + nom + "]"))
End Sub
End Class
Un sottoscrittore sarà definito da due parametri: il suo nome (attributo nom) e l’oggetto émetteur di cui desidera elaborare gli eventi (attributo sender). Questi due parametri saranno passati al costruttore dell’oggetto. Nel corso della stessa creazione, il sottoscrittore si abbona agli eventi dell’emittente:
' ci si registra per ricevere gli eventi dall'emittente e
AddHandler e.evtHandler, AddressOf traitementEvt
La funzione registrata presso l’emittente è traitementEvt. Questo metodo della classe souscripteur visualizza i due argomenti che ha ricevuto (sender, evt) nonché il nome del ricevitore (nom). Sono stati definiti il tipo degli eventi generati, il tipo dell’emittente di tali eventi e il tipo dei sottoscrittori. Non resta che implementarli:
' un programma di test
Public Class test
Public Shared Sub Main()
' creazione di un emittente di eventi
Dim émetteur1 As New émetteur("émetteur1")
' creazione di un elenco di iscritti
' per gli eventi emessi dall'emittente1
Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
' si legge una sequenza di numeri interi dalla tastiera
' non appena uno è errato, si emette un evento
Console.Out.Write("Nombre entier (rien pour arrêter) : ")
Dim saisie As String = Console.In.ReadLine().Trim()
' finché la riga inserita non è vuota
While saisie <> ""
' il valore immesso è un numero intero?
Try
Dim n As Integer = Integer.Parse(saisie)
Catch
' non è un numero intero
' si avvisa tutti
émetteur1.envoyerEvt(New myEventArgs(saisie))
End Try
' si avvisa tutti
' nuovo inserimento
Console.Out.Write("Nombre entier (rien pour arrêter) : ")
saisie = Console.In.ReadLine().Trim()
End While
' fine
Environment.Exit(0)
End Sub
Creiamo un oggetto émetteur:
' creazione di un emittente di eventi
Dim émetteur1 As New émetteur("émetteur1")
Creiamo una tabella con due sottoscrittori per gli eventi emessi dall'oggetto émetteur1:
' creazione di un array di sottoscrittori
' per gli eventi emessi dall'emittente1
Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
Chiediamo all'utente di digitare numeri interi dalla tastiera. Non appena viene immesso un valore errato, chiediamo a émetteur1 di inviare un evento ai propri sottoscrittori:
' si avvisa tutti
émetteur1.envoyerEvt(New myEventArgs(saisie))
L'evento inviato è di tipo myEventArgs e contiene l'immissione errata. Entrambi i sottoscrittori dovrebbero ricevere questo evento e segnalarlo. È quanto mostra l'esecuzione che segue.
dos>evt1
Nombre entier (rien pour arrêter) : 4
Nombre entier (rien pour arrêter) : a
L'objet [émetteur1] a signalé la saisie erronée [a] au souscripteur [s1]
L'objet [émetteur1] a signalé la saisie erronée [a] au souscripteur [s2]
Nombre entier (rien pour arrêter) : 1.6
L'objet [émetteur1] a signalé la saisie erronée [1.6] au souscripteur [s1]
L'objet [émetteur1] a signalé la saisie erronée [1.6] au souscripteur [s2]
Nombre entier (rien pour arrêter) :