Oppure

Loading

 

Cosa sono

Ora che stiamo per entrare nel mondo della programmazione visuale, è necessario allontanarsi da quello stereotipo di applicazione che ho usato fin dall'inizio della guida. In questo nuovo contesto, "non esiste" una Sub Main (o, per meglio dire, esiste ma possiede una semplice funzione di inizializzazione): il codice da eseguire, quindi, non viene posto in un singolo blocco ed eseguito dall'inizio alla fine seguendo un flusso ben definito. Piuttosto, esiste un oggetto standard, la Form - nome tecnico della finestra - che viene creato all'avvio dell'applicazione e che l'utente può vedere e manipolare a suo piacimento. L'approccio cambia: il programmatore non vincola il flusso di esecuzione, ma dice semplicemente al programma "come comportarsi" in reazione all'input dell'utente. Ad esempio, viene premuto un certo pulsante: bene, al click esegui questo codice; viene inserito un testo in una casella di testo: quando l'utente digita un carattere, esegui quest'altro codice, e così via... Il codice viene scritto, quindi, per eventi. Volendo dare una definizione teorico-concettuale di evento, potremmo dire che è un qualsiasi atto che modifica lo stato attuale di un oggetto. Ho di proposito detto "oggetto", poiché le Forms non sono le uniche entità a possedere eventi. Passando ad un ambito più formale e rigoroso, infatti, un evento non è altro che una speciale variabile di tipo delegate (multicast). Essendo di tipo delegate, tale variabile può contenere riferimenti a uno o più metodi, i quali vengono comunemente chiamati gestori d'evento (o event's handler). La programmazione visuale, in sostanza, richiede di scrivere tanti gestori d'evento quanti sono gli eventi che vogliamo gestire e, quindi, tanti quanti possono essere le azioni che il nostro programma consente all'utente di eseguire. Mediante queste definizioni, delineamo il comportamento di tutta l'applicazione.

Sintassi e invocazione degli eventi

Le facilitazioni che l'IDE mette a disposizione per la scrittura dei gestori d'evento portano spesso i programmatori novelli a non sapere cosa siano e come funzionino realmente gli eventi, anche a causa di una considerevole presenza di tutorial del tipo HOW-TO che spiegano in due o tre passaggi come costruire "il tuo primo programma". Inutile dire che queste scorciatie fanno più male che bene. Per questo motivo ho deciso di introdurre l'argomento quanto prima, per mettervi subito al corrente di come stanno le cose.
Iniziamo con l'introdurre la sintassi con cui si dichiara un evento:

Event [Nome] As [Tipo]

Dove [Nome] è il nome dell'evento e [Tipo] il suo tipo. Data la natura di ciò che staimo definendo, il tipo sarà sempre un tipo delegate. Possiamo scegliere di utilizzare un delegate già definito nelle librerie standard del Framework - come il classico EventHandler - oppure decidere di scriverne uno noi al momento. Scegliendo il secondo caso, tuttavia, si devono rispettare delle convenzioni:

  • Il nome del delegate deve terminare con la parola "Handler";
  • Il delegate deve esporre solo due parametri;
  • Il primo parametro è solitamente chiamato "sender" ed è comunemente di tipo Object. Questa convenzione è abbastanza restrittiva e non è necessario seguirla sempre;
  • Il secondo parametro è solitamente chiamato "e" ed il suo tipo è una classe che eredita da System.EventArgs. Allo stesso modo, possiamo definire un nuovo tipo derivato da tale classe per il nuovo delegate, ma il nome di questo tipo deve terminare con "EventArgs".

Come avrete notato sono un po' fissato sulle convenzioni. Servono a rendere il codice più chiaro e "standard" (quando non ci sono regole da seguire, ognuno fa come meglio crede: vedi, ad esempio, i compilatori C). Ad ogni modo, sender rappresenta l'oggetto che ha generato l'evento, mentre e contiene tutte le informazioni relative alle circostanze in cui questo evento si è verificato. Se e è di tipo EventArgs, non contiene alcun membro: il fatto che l'evento sia stato generato è di per sé significativo. Ad esempio, per un ipotetico evento Click non avremmo bisogno di conoscere nessun'altra informazione: ci basta sapere che è stato fatto click col mouse. Invece, per l'evento KeyDown (pressione di un tasto sulla tastiera) sarebbe interessante sapere quale tasto è stato premuto, il codice associato ad esso ed eventualmente il carattere. Ma ora passiamo a un piccolo esempio sul primo caso, mantenendoci ancora per qualche riga in una Console Application:

Module Module1
 
    'Questa classe rappresenta una collezione generica di
    'elementi che può essere ordinata con l'algoritmo
    'Bubble Sort già analizzato
    Public Class BubbleCollection(Of T As IComparable)
        'Eredita tutti i membri pubblici e protected della classe
        'a tipizzazione forte List(Of T), il che consente di
        'disporre di tutti i metodi delle liste scrivendo
        'solo una linea di codice
        Inherits List(Of T)
 
        'Questo campo indica il numero di millisecondi impiegati
        'ad ordinare tutta la collezione
        Private _TimeElapsed As Single = 0
 
        Public ReadOnly Property TimeElapsed() As Single
            Get
                Return _TimeElapsed
            End Get
        End Property
 
        'Ecco gli eventi:
        'Il primo viene lanciato prima che inizi la procedura di
        'ordinamento, e per tale motivo è di tipo
        'CancelEventHandler. Questo delegate espone come
        'secondo parametro della signature una variabile "e"
        'al cui intero è disponibile una proprietà
        'Cancel che indica se cancellare oppure no l'operazione.
        'Se si volesse cancellare l'operazione sarebbe possibile
        'farlo nell'evento BeforeSorting.
        'In genere, si usa il tipo CancelEventHandler o un suo
        'derivato ogni volta che bisogna gestire un evento
        'che inizia un'operazione annullabile. 
        Event BeforeSorting As System.ComponentModel.CancelEventHandler
        'Il secondo viene lanciato dopo aver terminato la procedura
        'di ordinamento e serve solo a notificare un'azione
        'avvenuta. Il tipo è un semplicissimo EventHandler
        Event AfterSorting As EventHandler
 
        'Scambia l'elemento alla posizione Index con il suo
        'successivo
        Private Sub SwapInList(ByVal Index As Int32)
            Dim Temp As T = Me(Index + 1)
            Me.RemoveAt(Index + 1)
            Me.INSERT IGNORE(Index, Temp)
        End Sub
 
        'In List(Of T) è già presente un metodo Sort,
        'perciò bisogna oscurarlo con Shadows (in quanto non
        'è sovrascrivibile con il polimorfismo)
        Public Shadows Sub Sort()
            Dim Occurrences As Int32
            Dim J As Int32
            Dim Time As New Stopwatch
            'Attenzione! non bisogna confondere EventHandlers con
            'EventArgs: il primo è un tipo delegate e costituisce
            'il tipo dell'evento; il secondo è un normale tipo
            'reference e rappresenta tutti gli argomenti opzionali
            'inerenti alle operazioni svolte
            Dim e As New System.ComponentModel.CancelEventArgs
 
            'Viene generato l'evento. RaiseEvent si occupa di
            'richiamare tutti i gestori d'evento memorizzati 
            'nell'evento BeforeSorting (che, ricordo, è un
            'delegate multicast). A tutti i gestori d'evento
            'vengono passati i parametri Me ed e. Al termine
            'di questa operazione, se un gestore d'evento ha
            'modificato una qualsiasi proprietà di e (e volendo,
            'anche di quest'oggetto), possiamo sfruttare tale
            'conoscenza per agire in modi diversi.
            RaiseEvent BeforeSorting(Me, e)
            'In questo caso, se e.Cancel = True si
            'cancella l'operazione
            If e.Cancel Then
                Exit Sub
            End If
 
            Time.Start()
            J = 0
            Do
                Occurrences = 0
                For I As Int32 = 0 To Me.Count - 1 - J
                    If I = Me.Count - 1 Then
                        Continue For
                    End If
                    If Me(I).CompareTo(Me(I + 1)) = 1 Then
                        SwapInList(I)
                        Occurrences += 1
                    End If
                Next
                J += 1
            Loop Until Occurrences = 0
            Time.Stop()
            _TimeElapsed = Time.ElapsedMilliseconds
 
            'Qui genera semplicemente l'evento
            RaiseEvent AfterSorting(Me, EventArgs.Empty)
        End Sub
 
    End Class
    
    '...
    
End Module

Questo codice mostra anche l'uso dell'istruzione RaiseEvent, usata per generare un evento. Essa non fa altro che scorrere tutta l'invocation list di tale evento ed invocare tutti i gestori d'evento ivi contenuti. Le invocazioni si svolgono, di norma, una dopo l'altra (sono sincrone).
Ora che abbiamo terminato la classe, tuttavia, bisognerebbe anche poterla usare, ma mancano ancora due importanti informazioni per essere in grado di gestirla correttamente. Prima di tutto, la variabile che useremo per contenere l'unica istanza di BubbleCollection deve essere dichiarata in modo diverso dal solito. Se normalmente potremmo scrivere:

Dim Bubble As New BubbleCollection(Of Int32)

in questo caso, non basta. Per poter usare gli eventi di un oggetto, è necessario comunicarlo esplicitamente al compilatore usando la keyword WithEvents, da anteporre (o sostituire) a Dim:

WithEvents Bubble As New BubbleCollection(Of Int32)

Infine, dobbiamo associare dei gestori d'evento ai due eventi che Bubble espone (NB: non è obbligatorio associare handler a tutti gli eventi di un oggetto, ma basta farlo per quelli che ci interessano). Per associare un metodo a un evento e farlo diventare gestore di quell'evento, si usa la clausola Handles, molto simile come sintassi alla clausola Implements analizzata nei capitoli sulle interfacce.

Sub [Nome Gestore](ByVal sender As Object, ByVal e As [Tipo]) Handles [Oggetto].[Evento]
    '...
End Sub

I gestori d'evento sono sempre procedure e mai funzioni: questo è ovvio, poiché eseguono solo istruzioni e nessuno richiede alcun valore in ritorno da loro. Ecco l'esempio completo:

Module Module1
 
    Public Class BubbleCollection(Of T As IComparable)
        Inherits List(Of T)
 
        Private _TimeElapsed As Single = 0
 
        Public ReadOnly Property TimeElapsed() As Single
            Get
                Return _TimeElapsed
            End Get
        End Property
 
        Event BeforeSorting As System.ComponentModel.CancelEventHandler
        Event AfterSorting As EventHandler
 
        Private Sub SwapInList(ByVal Index As Int32)
            Dim Temp As T = Me(Index + 1)
            Me.RemoveAt(Index + 1)
            Me.INSERT IGNORE(Index, Temp)
        End Sub
 
        Public Shadows Sub Sort()
            Dim Occurrences As Int32
            Dim J As Int32
            Dim Time As New Stopwatch
            Dim e As New System.ComponentModel.CancelEventArgs
            
            RaiseEvent BeforeSorting(Me, e)
            If e.Cancel Then
                Exit Sub
            End If
 
            Time.Start()
            J = 0
            Do
                Occurrences = 0
                For I As Int32 = 0 To Me.Count - 1 - J
                    If I = Me.Count - 1 Then
                        Continue For
                    End If
                    If Me(I).CompareTo(Me(I + 1)) = 1 Then
                        SwapInList(I)
                        Occurrences += 1
                    End If
                Next
                J += 1
            Loop Until Occurrences = 0
            Time.Stop()
            _TimeElapsed = Time.ElapsedMilliseconds
 
            'Qui genera semplicemente l'evento
            RaiseEvent AfterSorting(Me, EventArgs.Empty)
        End Sub
 
    End Class
 
    'Bubble è WithEvents poiché ne utilizzeremo
    'gli eventi
    WithEvents Bubble As New BubbleCollection(Of Int32)
    Sub Main()
        Dim I As Int32
 
        Console.WriteLine("Inserire degli interi (0 per terminare):")
        I = Console.ReadLine
        Do While I <> 0
            Bubble.Add(I)
            I = Console.ReadLine
        Loop
 
        'Il corpo di Main termina con l'esecuzione di Sort, ma
        'il programma non finisce qui, poiché Sort
        'scatena due eventi, BeforeSorting e AfterSorting.
        'Questi comportano l'esecuzione prima del metodo
        'Bubble_BeforeSorting e poi di Bubble_AfterSorting.
        'Vedrete bene il risultato eseguendo il programma
        Bubble.Sort()
    End Sub
 
    Private Sub Bubble_BeforeSorting(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles Bubble.BeforeSorting
        If Bubble.Count = 0 Then
            e.Cancel = True
            Console.WriteLine("Lista vuota!")
            Console.ReadKey()
        End If
    End Sub
 
    Private Sub Bubble_AfterSorting(ByVal sender As Object, ByVal e As EventArgs) Handles Bubble.AfterSorting
        Console.WriteLine("Lista ordinata:")
        'Scrive a schermo tutti gli elementi di Bubble
        'mediante un delegate generico.
        Bubble.ForEach(AddressOf Console.WriteLine)
        Console.WriteLine("Tempo impiegato: {0} ms", Bubble.TimeElapsed)
        Console.ReadKey()
    End Sub
    
    'Handles significa "gestisce". In questo come in molti altri
    'casi, il codice è molto simile al linguaggio.
    'Ad esempio, traducendo in italiano si avrebbe:
    '  Bubble_AfterSorting gestisce Bubble.AfterSorting
    'Il VB è molto chiaro nelle keywords
End Module

Anche per i nomi dei gestori d'evento c'è questa convenzione: "[Oggetto che genera l'evento]_[Evento gestito]".
Ciò che abbiamo appena visto consente di eseguire una sorta di early binding, ossia legare l'evento a un gestore durante la scrittura del codice. C'è, parimenti, una tecnica parallela più simile al late binding, che consente di associare un gestore ad un evento dinamicamente. La sintassi è:

'Add Handler = Aggiungi Gestore; molto intuitiva come keyword
AddHandler [Oggetto].[Evento], AddressOf [Gestore]
'E per rimuovere il gestore dall'invocation list:
RemoveHandler [Oggetto].[Evento], AddressOf [Gestore]

Il codice sopra potrebbe essere stato modificato come segue:

Module Module1
 
    '...
    
    WithEvents Bubble As New BubbleCollection(Of Int32)
    Sub Main()
        Dim I As Int32
 
        AddHandler Bubble.BeforeSorting, AddressOf Bubble_BeforeSorting
        AddHandler Bubble.AfterSorting, AddressOf Bubble_AfterSorting
        
        Console.WriteLine("Inserire degli interi (0 per terminare):")
        I = Console.ReadLine
        Do While I <> 0
            Bubble.Add(I)
            I = Console.ReadLine
        Loop
 
        'Il corpo di Main termina con l'esecuzione di Sort, ma
        'il programma non finisce qui, poiché Sort
        'scatena due eventi, BeforeSorting e AfterSorting.
        'Questi comportano l'esecuzione prima del metodo
        'Bubble_BeforeSorting e poi di Bubble_AfterSorting.
        'Vedrete bene il risultato eseguendo il programma
        Bubble.Sort()
    End Sub
 
    Private Sub Bubble_BeforeSorting(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs)
        If Bubble.Count = 0 Then
            e.Cancel = True
            Console.WriteLine("Lista vuota!")
            Console.ReadKey()
        End If
    End Sub
 
    Private Sub Bubble_AfterSorting(ByVal sender As Object, ByVal e As EventArgs)
        Console.WriteLine("Lista ordinata:")
        Bubble.ForEach(AddressOf Console.WriteLine)
        Console.WriteLine("Tempo impiegato: {0} ms", Bubble.TimeElapsed)
        Console.ReadKey()
    End Sub
End Module

Ovviamente se usate questo metodo, non potrete usare allo stesso tempo anche Handles, o aggiungereste due volte lo stesso gestore!

A cura di: Il Totem