Casino online











Mercato forex






B18. Gli Eventi


Gli eventi vengono generati, come già detto in precedenza, ogniqualvolta accada qualcosa nel programma che modifichi lo stato di un controllo o il suo contenuto. Anche durante la scrittura di una classe è possibile scrivere nuovi eventi e generarli nel caso ce ne sia bisogno: infatti i controlli altro non sono che classi e i loro eventi, membri di classe. La sintassi generale con cui si dichiara un membro di questo tipo è:
Event [Nome evento] As [Tipo] 
Nel capitolo sui Delegate si è accennato a come essi siano parte integrante del sistema di gestione degli eventi: per questo motivo, il tipo da associare a un evento non è altro che un delegate. Ad esempio:
'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 inizia 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
    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

    'Riprende la procedura di scambio in lista analizzata
    'nella lezione sulle ListView
    Private Sub SwapInList(ByVal Index As Int32)
        Dim Temp As T = Me(Index + 1)
        Me.RemoveAt(Index + 1)
        Me.Insert(Index, Temp)
    End Sub

    'È già dichiarato un metodo Sort nella lista base,
    'perciò bisogna oscurarlo con Shadows (in quanto non
    'è sovrascrivibile con il polimorfismo)
    Public Shadows Sub Sort()
        Dim Occurrences As Int32 = 0
        Dim Time As New Stopwatch

        Time.Start()
        Do
            Occurrences = 0
            For I As Int32 = 0 To Me.Count - 1
                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
        Loop Until Occurrences = 0
        Time.Stop()
        _TimeElapsed = Time.ElapsedMilliseconds
    End Sub
End Class 
La classe sembrerebbe a posto così com'è, ma mancano ancora molte cose. Infatti, sono stati dichiarati degli eventi, ma non sono stati utilizzati all'interno della procedura Sort come ci si era proposti di fare. Per invocare un evento bisogna utilizzare la keyword RaiseEvent e specificare subito dopo quale di quelli disponibili si debba lanciare. In questo caso si genereranno BeforeSorting prima di fare partire l'algoritmo e AfterSorting dopo aver completato il tutto. Nel primo caso, però, il delegate associato a BeforeSorting potrebbe anche incontrare delle condizioni particolari per cui porre e.Cancel = True e annullare l'operazione: perciò è necessario porre attenzione anche al probelma di controllare "e" prima di partire. L'unico modo consiste nel dichiarare una variabile locale da passare in seconda istanza come parametro dell'evento:
Public Class BubbleCollection(Of T As IComparable)
    '...
    Public Shadows Sub Sort()
        Dim Occurrences As Int32 = 0
        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: in questo punto di codice, il
        'programma richiama tutti i metodi associati all'evento
        'dal delegate d'evento (che può essere anche multicast) e
        'aspetta che tutti siano terminati prima di passare oltre.
        'Si è quindi sicuri che e abbia un valore esatto
        RaiseEvent BeforeSorting(Me, e)
        'Se e.Cancel = True si cancella l'operazione
        If e.Cancel Then
            Exit Sub
        End If

        Time.Start()
        Do
            Occurrences = 0
            For I As Int32 = 0 To Me.Count - 1
                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
        Loop Until Occurrences = 0
        Time.Stop()
        _TimeElapsed = Time.ElapsedMilliseconds

        'Qui genera semplicemente l'evento
        RaiseEvent AfterSorting(Me, EventArgs.Empty)
    End Sub
End Class 
Ora non resta che provare la classe in un'applicazione di prova. Si aggiungano controlli in modo che la finestra risulti così:


BubbleCollection

Ed ecco il codice:
Class Form1
    'Un oggetto di tipo BubbleCollection di interi a 32-bit.
    'La keyword WithEvents specifica che debbano essere rilevati anche
    'i suoi eventi: di default i campi non dichiarati WithEvents 
    'possono generare eventi, ma questi non vengono considerati
    Private WithEvents Example As New BubbleCollection(Of Int32)

    Private Sub cmdGenerate_Click(ByVal sender As Object, _ 
        ByVal e As EventArgs) Handles cmdGenerate.Click
        'Svuota la lista
        Example.Clear()
        'La riempie con nuovi elementi
        Dim Rnd As New Random
        For I As Int32 = 1 To nudNumber.Value
            'Aggiunge un numero qualsiasi estratto tra il
            'valore minimo e il valore massimo nel range
            'di valor possibili per un Int32
            Example.Add(Rnd.Next(Int32.MinValue, Int32.MaxValue))
        Next
        'Comunica che la lista è pronta
        MessageBox.Show("La lista è pronta!", "BubbleCollection", _
            MessageBoxButtons.OK, MessageBoxIcon.Information)
    End Sub

    Private Sub cmdSort_Click(ByVal sender As Object, _ 
        ByVal e As EventArgs) Handles cmdSort.Click
        'Ordina la lista
        Example.Sort()
    End Sub

    Private Sub Example_BeforeSorting(ByVal sender As Object, _
        ByVal e As CancelEventArgs) Handles Example.BeforeSorting
        'Ma potrebbe darsi che l'utente si sia dimenticato di riempire
        'la lista prima di cliccare sort... Bisogna controllare quanti
        'elementi ci sono e se non ce ne sono, fermare il processo
        If Example.Count = 0 Then
            e.Cancel = True
            MessageBox.Show("La lista è vuota!", "BubbleCollection", _
            MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
        End If
    End Sub

    Private Sub Example_AfterSorting(ByVal sender As Object, _ 
        ByVal e As EventArgs) Handles Example.AfterSorting
        'Finito di ordinare, si prende il tempo
        lblTime.Text = String.Format("Tempo impiegato: {0}ms", Example.TimeElapsed)
    End Sub
End Class 
Ora basta testare l'applicazione e osservarne il funzionamento: sul mio computer, 1000 elementi vengono ordinati in 370ms e 500 in 59ms. Prima di concludere, si osservi il comportamento della keyword WithEvents: se un campo viene specificato con questa parola riservata, che letteramente significa "con eventi", tutti gli eventi da esso generati verranno catturati ed eventualmente gestiti dai delegate associati. Anche tutti i controlli di un form sono WithEvents: a testimonianza di ciò, si apra la finestra del codice di designer:


Magia!

Dopo aver premuto il pulsante "Show all files", che rivela tutti i file nascosti usati dal compilatore, selezionare ed aprire Form1.Designer.vb per osservare il codice di progettazione:
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
'Ricordate? I Form sono clasi Partial: vengono definiti in parte 
'dall'utente, come nel codice sopra, e in parte dal compilatore, 
'come in questo codice
Partial Class Form1
    Inherits System.Windows.Forms.Form

    'Form overrides dispose to clean up the component list.
    <System.Diagnostics.DebuggerNonUserCode()> _
    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing AndAlso components IsNot Nothing Then
            components.Dispose()
        End If
        MyBase.Dispose(disposing)
    End Sub

    'Required by the Windows Form Designer
    Private components As System.ComponentModel.IContainer

    'NOTE: The following procedure is required by the Windows Form 
    'Designer. It can be modified using the Windows Form Designer.  
    'Do not modify it using the code editor.
    <System.Diagnostics.DebuggerStepThrough()> _
    Private Sub InitializeComponent()
        Me.components = New System.ComponentModel.Container
        Dim resources As System.ComponentModel.ComponentResourceManager = _ 
        New System.ComponentModel.ComponentResourceManager(GetType(Form1))
        Me.Label1 = New System.Windows.Forms.Label
        Me.nudNumber = New System.Windows.Forms.NumericUpDown
        Me.cmdGenerate = New System.Windows.Forms.Button
        Me.lblTime = New System.Windows.Forms.Label
        Me.cmdSort = New System.Windows.Forms.Button
        CType(Me.nudNumber, System.ComponentModel.ISupportInitialize).BeginInit()
        Me.SuspendLayout()
        'Qui vengono dichiarate tutte le proprietà...
        '
        'Label1
        '
        Me.Label1.AutoSize = True
        Me.Label1.Location = New System.Drawing.Point(12, 14)
        Me.Label1.Name = "Label1"
        Me.Label1.Size = New System.Drawing.Size(100, 13)
        Me.Label1.TabIndex = 0
        Me.Label1.Text = "Numero di elementi:"
        'Eccetera...
        
        Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
        Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
        Me.ClientSize = New System.Drawing.Size(296, 112)
        Me.Controls.Add(Me.cmdSort)
        Me.Controls.Add(Me.lblTime)
        Me.Controls.Add(Me.cmdGenerate)
        Me.Controls.Add(Me.nudNumber)
        Me.Controls.Add(Me.Label1)
        Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle
        Me.MaximizeBox = False
        Me.Name = "Form1"
        Me.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide
        Me.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen
        Me.Text = "BubbleCollection"
        CType(Me.nudNumber, System.ComponentModel.ISupportInitialize).EndInit()
        Me.ResumeLayout(False)
        Me.PerformLayout()
    End Sub
    
    'Ecco qui! Tutti i controlli sono dichiarati WithEvents!
    Friend WithEvents Label1 As System.Windows.Forms.Label
    Friend WithEvents nudNumber As System.Windows.Forms.NumericUpDown
    Friend WithEvents cmdGenerate As System.Windows.Forms.Button
    Friend WithEvents lblTime As System.Windows.Forms.Label
    Friend WithEvents cmdSort As System.Windows.Forms.Button
End Class 


Eventi personalizzati
È pur vero che sono disponibili migliaia di tipi delegate per gestire gli eventi, ma non sempre questi coincidono con quello che esige il programmatore. Per questo è possibile anche scrivere dei nuovi tipi di evento: dopotutto, sono sempre delegate, e noi sappiamo come dichiarare nuovi delegate. Per questo motivo, non si pone il problema di quale sia il tipo delegate associato, ma piuttosto quale sia il tipo di "e", secondo parametro classico della signature. Bisogna creare una nuova classe tutta per "e". Ecco un esempio:
'Ecco il nuovo tipo di e, che sarà usato per monitorare
'i cambiamenti della proprietà Name della prossima classe
Public Class NameChangingEventArgs
    'Eredita da CancelEventArgs perchè si può anche annullare
    'l'operazione (e per non riscrivere la proprietà Cancel XD)
    Inherits System.ComponentModel.CancelEventArgs

    Private _Name As String

    Public ReadOnly Property Name() As String
        Get
            Return _Name
        End Get
    End Property

    Sub New(ByVal Name As String)
        _Name = Name
    End Sub
End Class

'Questa classe memorizza due informazioni su un cliente e
'genera un codice seriale associato
Public Class ClientSerialCode
    Private _Name As String
    Private _SerialCode As String

    'Ecco il nuovo tipo degli eventi
    Public Delegate Sub NameChangingEventHandler(ByVal sender As Object, _
        ByVal e As NameChangingEventArgs)

    Public Event NameChanging As NameChangingEventHandler
    'Quando il nome è stato cambiato, è accessibile dalla
    'proprietà relativa: inutile scrivere un altro delegate
    'e di conseguenza un altra classe Args
    Public Event NameChanged As EventHandler

    Public Property Name() As String
        Get
            Return _Name
        End Get
        Set(ByVal Value As String)
            Dim e As New NameChangingEventArgs(Value)
            'Il nome sta cambiando
            RaiseEvent NameChanging(Me, e)
            If e.Cancel Then
                Exit Property
            End If
            _Name = Value
            'Il nome è cambiato
            RaiseEvent NameChanged(Me, EventArgs.Empty)
        End Set
    End Property

    Public ReadOnly Property SerialCode() As String
        Get
            Return _SerialCode
        End Get
    End Property

    'La routine per la generazione del codice
    Public Sub GenerateCode()
        'Genera il codice solo se non esiste
        If Me.SerialCode Is Nothing Then
            _SerialCode = System.Guid.NewGuid.ToString
        End If
    End Sub
End Class 
Nel codice si è usato un nuovo tipo, Guid, non ancora analizzato. È molto semplice: non dispone di alcuna proprietà, ma solo di pochi metodi, uno dei quali è statico. NewGuid è una funzione shared che serve per generare un nuovo Guid: ToString e ToByteArray convertono il Guid rispettivamente in una stringa o in un array di bytes. Il Guid è molto usato in ambiente .Net e contraddistingue ogni oggetto con un identificativo esadecimale univoco. Per questo motivo calza perfettamente nell'esempio proposto. Ecco alcuni esempi di codici Guid:
8ffa79e8-800b-4b98-a378-e8d33ab1d907
11402f67-9bfe-4a64-9663-e05b4bb63188
ed2408f4-ff97-4212-acba-b126097d066a 




 

The Totem's Lair - Copyright (C) 2009
È vietata la riproduzione sia totale che parziale del sito.