Skip to content

6. Gestione degli eventi

Nel capitolo precedente abbiamo discusso il concetto di eventi associati ai componenti. Vedremo 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 delegati

L'istruzione

    Delegate Function opération(ByVal n1 As Integer, ByVal n2 As Integer) As Integer

definisce un tipo denominato operation, che in realtà è un prototipo di funzione che accetta due interi e restituisce un intero. È la parola chiave delegate che rende operation una definizione di prototipo di funzione. Il tipo [operation] può essere assegnato a una variabile come segue:


        Dim op As New opération(AddressOf [fonction])

Una variabile di tipo [delegate] viene costruita come un oggetto. Il parametro del costruttore è la funzione associata all'istanza del delegato. In VB, questa funzione è specificata come AddressOf function, dove [function] è il nome della funzione. Una volta creata un'istanza del delegato, questa può essere utilizzata come una funzione:


        ' the delegate is executed
        Dim n As Integer = op(4, 7)

Il delegato [operation] è stato definito per accettare due parametri di tipo [integer] e restituire un risultato dello stesso tipo. Pertanto, la sua istanza [op] sopra riportata viene utilizzata come tale funzione. Si consideri il seguente esempio:


' delegated functions
Imports System

Public Class delegate1
    ' function prototype definition
    ' accepts 2 integers as parameters and returns an integer
    Delegate Function opération(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
 
    ' two instance methods corresponding to the prototype
    ' add
    Public Function ajouter(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
        Console.Out.WriteLine(("ajouter(" & n1 & "," & n2 & ")"))
        Return n1 + n2
    End Function
 
    ' subtract
    Public Function soustraire(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
        Console.Out.WriteLine(("soustraire(" & n1 & "," & n2 & ")"))
        Return n1 - n2
    End Function
 
    ' a static method corresponding to the prototype
    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
 
    ' test program
    Public Shared Sub Main()
        ' define an operation object to store functions
        ' we register the static function increase
        Dim op As New opération(AddressOf delegate1.augmenter)
        ' the delegate is executed
        Dim n As Integer = op(4, 7)
        Console.Out.WriteLine(("n=" & n))
        ' creation of a c1 object of type class1
        Dim c1 As New delegate1
        ' we register c1's add method in the delegate
        op = New delegate1.opération(AddressOf c1.ajouter)
        ' execution of delegated object
        n = op(2, 3)
        Console.Out.WriteLine(("n=" & n))
        ' we register the subtract method of c1 in the delegate
        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:

augmenter(4,7)
n=18
ajouter(2,3)
n=5
soustraire(2,3)
n=-1

6.2. Gestione degli eventi

A cosa può servire un oggetto delegato?

Come vedremo nell'esempio seguente, viene utilizzato principalmente per la gestione degli eventi. Una classe C1 può generare eventi evti. Quando si verifica un evento evti, un oggetto di tipo C1 attiverà l'esecuzione di un oggetto evtiDéclenché di tipo delegato. Verranno quindi eseguite tutte le funzioni registrate nell'oggetto delegato evtiDéclenché. Se un oggetto C2 che utilizza un oggetto C1 desidera essere informato del verificarsi dell'evento evti sull'oggetto C1, registrerà uno dei suoi metodi, C2.f, con l'oggetto delegato C1.evtiDéclenché dell'oggetto C1 in modo che il metodo C2.f venga eseguito ogni volta che l'evento evti si verifica sull'oggetto C1. Poiché l'oggetto delegato C1.evtiDéclenché può registrare più funzioni, diversi oggetti Ci possono registrarsi con il delegato C1.evtiDéclenché per essere avvisati dell'evento evti su C1.

6.2.1. Dichiarazione di un evento

Un evento viene dichiarato come segue (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] definisce un dato come evento. L'evento è associato alla firma che i suoi gestori devono avere. Questa firma può essere definita utilizzando un delegato (metodo 1) o direttamente (metodo 2).

6.2.2. Definizione dei gestori di eventi

Quando un evento viene attivato dall'istruzione [RaiseEvent], deve essere gestito. Le procedure responsabili della gestione degli eventi sono chiamate gestori di eventi. Quando si crea un oggetto [Event], viene dichiarata solo la firma dei gestori di eventi. Questa firma è quella dell'oggetto [delegate] associato all'evento. Una volta fatto ciò, è possibile associare i gestori all'evento. Si tratta di procedure che devono avere la stessa firma del [delegate] associato all'evento. Per associare un gestore di eventi a un evento, utilizzare l'istruzione [AddHandler] con la seguente sintassi:

AddHandler event, AddressOf eventhandler
  • event: variabile evento a cui è associato un nuovo gestore di eventi
  • gestoreEvento: nome del gestore di eventi — deve avere la stessa firma del [delegato] associato all'evento

È possibile associare a un evento tutti i gestori che si desidera. Verranno tutti eseguiti quando l'evento a cui sono associati viene attivato.

6.2.3. Attivazione di un evento

Per attivare un evento a livello di programmazione, utilizzare l'istruzione [RaiseEvent]:

RaiseEvent eventname[( argumentlist )]
  • nome_evento: variabile evento (dichiarata con la parola chiave Event)
  • elencoargomenti: argomenti del delegato associato all'evento.

Verranno chiamati tutti gli handler 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

Si consideri il seguente esempio:


'event management
Imports System
 
Public Class myEventArgs
    Inherits EventArgs
    ' the class of an evt
    ' attribute
    Private _saisie As String
 
    ' manufacturer
    Public Sub New(ByVal saisie As String)
        _saisie = saisie
    End Sub
 
    ' read-only property
    Public Overrides Function ToString() As String
        Return _saisie
    End Function
End Class
 
Public Class émetteur
    ' the class issuing an event
    ' attribute
    Private _nom As String    ' name of issuer
 
    ' manufacturer
    Public Sub New(ByVal nom As String)
        _nom = nom
    End Sub
 
    ' ToString
    Public Overrides Function ToString() As String
        Return _nom
    End Function
 
    ' the prototype of the functions responsible for processing the event
    Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)
 
    ' pool of event managers
    Public Event evtHandler As _evtHandler
 
    ' method of requesting the issue of an evt
    Public Sub envoyerEvt(ByVal evt As myEventArgs)
        ' all subscribers are notified
        ' we act as if the event originated here
        RaiseEvent evtHandler(Me, evt)
    End Sub
End Class
 
' an evt treatment class
Public Class souscripteur
    ' attribute
    Private nom As String    ' name of subscriber
    Private sender As émetteur    ' evts sender
 
    ' manufacturer
    Public Sub New(ByVal nom As String, ByVal e As émetteur)
        ' we note the name of the subscriber
        Me.nom = nom
        ' and the sender of the evts
        Me.sender = e
        ' register to receive news from the transmitter e
        AddHandler e.evtHandler, AddressOf traitementEvt
    End Sub
 
    ' event manager
    Public Sub traitementEvt(ByVal sender As Object, ByVal evt As myEventArgs)
        ' evt display
        Console.Out.WriteLine(("L'objet [" + sender.ToString + "] a signalé la saisie erronée [" + evt.ToString + "] au souscripteur [" + nom + "]"))
    End Sub
End Class
 
' a test program
Public Class test
    Public Shared Sub Main()
        ' creation of an evts transmitter
        Dim émetteur1 As New émetteur("émetteur1")
        ' create a table of subscribers
        ' for events issued by issuer1
        Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
        ' read a sequence of integers from the keyboard
        ' as soon as one is wrong, we issue an evt
        Console.Out.Write("Nombre entier (rien pour arrêter) : ")
        Dim saisie As String = Console.In.ReadLine().Trim()
        ' as long as the line entered is not empty
        While saisie <> ""
            ' is the input a whole number?
            Try
                Dim n As Integer = Integer.Parse(saisie)
            Catch
                ' not an integer
                ' we warn everyone
                émetteur1.envoyerEvt(New myEventArgs(saisie))
            End Try
            ' we warn everyone
            ' new entry
            Console.Out.Write("Nombre entier (rien pour arrêter) : ")
            saisie = Console.In.ReadLine().Trim()
        End While
        ' end
        Environment.Exit(0)
    End Sub
End Class

Il codice sopra riportato è piuttosto complesso. Analizziamolo. Nella gestione degli eventi, esiste un mittente dell'evento che invia i dettagli dell'evento (EventArgs) agli iscritti che hanno manifestato interesse per gli eventi in questione. La classe che invia l'evento in questo caso è la seguente:


Public Class émetteur
    ' the class issuing an event
    ' attribute
    Private _nom As String    ' name of issuer
 
    ' manufacturer
    Public Sub New(ByVal nom As String)
        _nom = nom
    End Sub
 
    ' ToString
    Public Overrides Function ToString() As String
        Return _nom
    End Function
 
    ' the prototype of the functions responsible for processing the event
    Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)
 
    ' pool of event managers
    Public Event evtHandler As _evtHandler
 
    ' method of requesting the issue of an evt
    Public Sub envoyerEvt(ByVal evt As myEventArgs)
        ' all subscribers are notified
        ' we act as if the event originated here
        RaiseEvent evtHandler(Me, evt)
    End Sub
End Class

Ogni oggetto emettitore ha un nome impostato per impostazione predefinita. Il metodo ToString è stato ridefinito in modo che, quando un oggetto emettitore viene convertito in una stringa, venga restituito il suo nome. La classe definisce un evento:


        ' the prototype of the functions responsible for processing the event
        Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)
 
        ' the event
        Public Event evtHandler As _evtHandler

Il primo argomento di una funzione gestore di eventi sarà l'oggetto che emette l'evento. Il secondo argomento sarà di tipo myEventArgs, un oggetto che fornisce dettagli sull'evento, di cui parleremo più avanti. Per attivare un evento dall'esterno di un oggetto emittente, aggiungiamo il metodo envoyerEvt alla classe:


    ' method of requesting the issue of an evt
    Public Sub envoyerEvt(ByVal evt As myEventArgs)
        ' all subscribers are notified
        ' we act as if the event originated here
        RaiseEvent evtHandler(Me, evt)
    End Sub

Dobbiamo definire il tipo di eventi generati dalla classe emittente. A tal fine, abbiamo definito la classe myEventArgs:


Public Class myEventArgs
    ' the class of an evt
    ' attribute
    Private _saisie As String
 
    ' manufacturer
    Public Sub New(ByVal saisie As String)
        _saisie = saisie
    End Sub
 
    ' read-only property
    Public Overrides Function ToString() As String
        Return _saisie
    End Function
End Class

Gli eventi che ci interessano sono gli inserimenti errati dalla tastiera. Chiederemo all'utente di digitare numeri interi sulla tastiera e, non appena digiterà una stringa che non rappresenta un numero intero, attiveremo un evento. Come dettaglio dell'evento, forniremo semplicemente l'inserimento errato. Questo è lo scopo dell'attributo _input della classe. Viene quindi creato un oggetto myEventArgs con l'inserimento errato come parametro. Sovrascriviamo anche il metodo ToString in modo che, quando convertiamo un oggetto myEventArgs in una stringa, otteniamo la proprietà _input.

Abbiamo definito l'emittente di eventi e il tipo di eventi che emette. Successivamente, definiamo il tipo di sottoscrittori interessati a questi eventi.


    ' an evt treatment class
    Public Class souscripteur
        ' attribute
        Private nom As String     ' name of subscriber
        Private sender As émetteur     ' evts sender
 
        ' manufacturer
        Public Sub New(ByVal nom As String, ByVal e As émetteur)
            ' we note the name of the subscriber
            Me.nom = nom
            ' and the sender of the evts
            Me.sender = e
            ' register to receive news from the transmitter e
            AddHandler e.evtHandler, AddressOf traitementEvt
        End Sub
 
        ' event manager
        Public Sub traitementEvt(ByVal sender As Object, ByVal evt As myEventArgs)
            ' evt display
            Console.Out.WriteLine(("L'objet [" + sender.ToString + "] a signalé la saisie erronée [" + evt.ToString + "] au souscripteur [" + nom + "]"))
        End Sub
    End Class

Un sottoscrittore è definito da due parametri: il suo nome (attributo name) e l'oggetto emittente di cui desidera gestire gli eventi (attributo sender). Questi due parametri vengono passati al costruttore dell'oggetto. Durante questa stessa costruzione, il sottoscrittore si abbona agli eventi dell'emittente:


            ' on s'inscrit pour recevoir les evts de l'émetteur e
            AddHandler e.evtHandler, AddressOf traitementEvt

La funzione registrata presso l'emittente è traitementEvt. Questo metodo della classe dell'abbonato visualizza i due argomenti ricevuti (sender, evt) nonché il nome del destinatario (nom). I tipi degli eventi generati, il tipo dell'emittente di tali eventi e i tipi degli abbonati sono stati definiti. Non resta che implementarli:


    ' a test program
    Public Class test
        Public Shared Sub Main()
            ' creation of an evts transmitter
            Dim émetteur1 As New émetteur("émetteur1")
            ' create a table of subscribers
            ' for events issued by issuer1
            Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
            ' read a sequence of integers from the keyboard
            ' as soon as one is wrong, we issue an evt
            Console.Out.Write("Nombre entier (rien pour arrêter) : ")
            Dim saisie As String = Console.In.ReadLine().Trim()
            ' as long as the line entered is not empty
            While saisie <> ""
                ' is the input a whole number?
                Try
                    Dim n As Integer = Integer.Parse(saisie)
                Catch
                    ' not an integer
                    ' we warn everyone
                    émetteur1.envoyerEvt(New myEventArgs(saisie))
                End Try
                ' we warn everyone
                ' new entry
                Console.Out.Write("Nombre entier (rien pour arrêter) : ")
                saisie = Console.In.ReadLine().Trim()
            End While
            ' end
            Environment.Exit(0)
        End Sub

Creiamo un oggetto emettitore di eventi:


        ' creation of an evts transmitter
        Dim émetteur1 As New émetteur("émetteur1")

Creiamo un array di due subscriber per gli eventi emessi dall'oggetto emetteur1:


        ' create a table of subscribers
        ' for events issued by issuer1
        Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}

Chiediamo all'utente di inserire numeri interi tramite la tastiera. Non appena viene effettuato un inserimento errato, chiediamo a issuer1 di inviare un evento ai propri iscritti:


                ' on prévient tout le monde
                émetteur1.envoyerEvt(New myEventArgs(saisie))

L'evento inviato è di tipo myEventArgs e contiene l'input errato. Entrambi gli iscritti dovrebbero ricevere questo evento e segnalarlo. Ciò è illustrato nell'esecuzione seguente.

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