Skip to content

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

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

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:

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

AddHandler event, AddressOf eventhandler
  • 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]:

RaiseEvent eventname[( argumentlist )]
  • 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) :