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

Ed ecco il codice:

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:
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:Event [Nome evento]As [Tipo]
'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:Questa classe rappresenta una collezione generica di 'elementi che può essere ordinata con l'algoritmo 'Bubble Sort già analizzato Public Class BubbleCollection(Of TAs 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 _TimeElapsedAs Single = 0Public ReadOnly Property TimeElapsed()As Single Get Return _TimeElapsedEnd 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 BeforeSortingAs 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 AfterSortingAs EventHandler 'Riprende la procedura di scambio in lista analizzata 'nella lezione sulle ListView Private Sub SwapInList(ByVal IndexAs Int32)Dim TempAs 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 OccurrencesAs Int32 = 0Dim TimeAs New Stopwatch Time.Start()Do Occurrences = 0For IAs Int32 = 0To Me .Count - 1If I =Me .Count - 1Then Continue For End If If Me(I).CompareTo(Me(I + 1)) = 1Then SwapInList(I) Occurrences += 1End If Next Loop Until Occurrences = 0 Time.Stop() _TimeElapsed = Time.ElapsedMillisecondsEnd 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ì:Public Class BubbleCollection(Of TAs IComparable) '... Public Shadows Sub Sort()Dim OccurrencesAs Int32 = 0Dim TimeAs 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 eAs 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.CancelThen Exit Sub End If Time.Start()Do Occurrences = 0For IAs Int32 = 0To Me .Count - 1If I =Me .Count - 1Then Continue For End If If Me(I).CompareTo(Me(I + 1)) = 1Then SwapInList(I) Occurrences += 1End 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
BubbleCollection
Ed ecco il codice:
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: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 ExampleAs New BubbleCollection(Of Int32)Private Sub cmdGenerate_Click(ByVal senderAs Object , _ByVal eAs EventArgs)Handles cmdGenerate.Click 'Svuota la lista Example.Clear() 'La riempie con nuovi elementi Dim RndAs New RandomFor IAs Int32 = 1To 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 senderAs Object , _ByVal eAs EventArgs)Handles cmdSort.Click 'Ordina la lista Example.Sort()End Sub Private Sub Example_BeforeSorting(ByVal senderAs Object , _ByVal eAs 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 = 0Then e.Cancel = True MessageBox.Show("La lista è vuota!", "BubbleCollection", _ MessageBoxButtons.OK, MessageBoxIcon.Exclamation)End If End Sub Private Sub Example_AfterSorting(ByVal senderAs Object , _ByVal eAs 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
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 Form1Inherits System.Windows.Forms.Form 'Form overrides dispose to clean up the component list. <System.Diagnostics.DebuggerNonUserCode()> _Protected Overrides Sub Dispose(ByVal disposingAs Boolean )If disposingAndAlso componentsIsNot Nothing Then components.Dispose()End If My Base.Dispose(disposing)End Sub 'Required by the Windows Form Designer Private componentsAs 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.ContainerDim resourcesAs System.ComponentModel.ComponentResourceManager = _New System.ComponentModel.ComponentResourceManager(GetType(Form1))Me .Label1 =New System.Windows.Forms.LabelMe .nudNumber =New System.Windows.Forms.NumericUpDownMe .cmdGenerate =New System.Windows.Forms.ButtonMe .lblTime =New System.Windows.Forms.LabelMe .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 = TrueMe .Label1.Location =New System.Drawing.Point(12, 14)Me .Label1.Name = "Label1"Me .Label1.Size =New System.Drawing.Size(100, 13)Me .Label1.TabIndex = 0Me .Label1.Text = "Numero di elementi:" 'Eccetera... Me .AutoScaleDimensions =New System.Drawing.SizeF(6.0!, 13.0!)Me .AutoScaleMode = System.Windows.Forms.AutoScaleMode.FontMe .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.FixedSingleMe .MaximizeBox = FalseMe .Name = "Form1"Me .SizeGripStyle = System.Windows.Forms.SizeGripStyle.HideMe .StartPosition = System.Windows.Forms.FormStartPosition.CenterScreenMe .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 Label1As System.Windows.Forms.LabelFriend WithEvents nudNumberAs System.Windows.Forms.NumericUpDownFriend WithEvents cmdGenerateAs System.Windows.Forms.ButtonFriend WithEvents lblTimeAs System.Windows.Forms.LabelFriend WithEvents cmdSortAs System.Windows.Forms.ButtonEnd 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:
'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: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.CancelEventArgsPrivate _NameAs String Public ReadOnly Property Name()As String Get Return _NameEnd Get End Property Sub New (ByVal NameAs String ) _Name = NameEnd Sub End Class 'Questa classe memorizza due informazioni su un cliente e 'genera un codice seriale associato Public Class ClientSerialCodePrivate _NameAs String Private _SerialCodeAs String 'Ecco il nuovo tipo degli eventi Public Delegate Sub NameChangingEventHandler(ByVal senderAs Object , _ByVal eAs NameChangingEventArgs)Public Event NameChangingAs NameChangingEventHandler 'Quando il nome è stato cambiato, è accessibile dalla 'proprietà relativa: inutile scrivere un altro delegate 'e di conseguenza un altra classe Args Public Event NameChangedAs EventHandlerPublic Property Name()As String Get Return _NameEnd Get Set (ByVal ValueAs String )Dim eAs New NameChangingEventArgs(Value) 'Il nome sta cambiando RaiseEvent NameChanging(Me, e)If e.CancelThen 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 _SerialCodeEnd Get End Property 'La routine per la generazione del codice Public Sub GenerateCode() 'Genera il codice solo se non esiste If Me .SerialCodeIs Nothing Then _SerialCode = System.Guid.NewGuid.ToStringEnd If End Sub End Class
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.



