Passa ai contenuti principali

VB.NET for Dummies: Gli Eventi

Vorrei iniziare con questo post una serie dedicata ad aspetti di VB.NET di base che possono essere utile a coloro che si avvicinano al mondo .NET e che, in genere, non vengono trattati a livello base.

La serie di post non ha la pretesa di essere assolutamente esaustivi sugli argomenti che tratterò

In questo primo post parleremo degli eventi.

Cosa sono e a cosa servono

Un evento è la “notifica” dell’accadimento di qualcosa.

Quando, ad esempio, premiamo un bottone della finestra di un’applicazione, dietro le quinte, il bottone stesso “notifica” al mondo circostante che qualcuno, in quell’istante, lo ha premuto.

Sta, poi, al mondo circostante preoccuparsi di “intercettare” l’evento stesso per gestirlo (tramite un gestore di evento).

Attenzione a non confondere la pressione del tasto con la “notifica” della pressione del tasto: l’evento è la “notifica” dell’accadimento, non l’accadimento stesso.

Ma a cosa serve tutto ciò?

Utilizzare gli eventi è un modo per disaccoppiare due o più attori del nostro dominio.

Sempre tornando al bottone di cui sopra, infatti, lo stesso bottone non è assolutamente a conoscenza di chi gestirà la sua “notifica”. Semplicemente comunica tale notifica. Questo ci permette di far gestire l’evento del bottone da qualsiasi classe senza minimamente modificare il bottone stesso.

Tanto per fare un’altro esempio, immaginate una newsletter: l’invio della newsletter è un evento, se voi la leggete avete gestito l’evento!

Come si definisce un evento

Un evento si definisce utilizzando la seguente sintassi:

  1. Public Event <NomeEvento>(<parameterList>)

dove <NomeEvento> è il nome che vogliamo assegnare all’evento (ad esempio Click per la pressione di un Button) e <parameterList> è la lista di argomenti che vogliamo trasferire a chi si è sottoscritto per ricevere la “notifica”.

Lo standard utilizzato all’interno del framework è il seguente:

  1. Public Event <NomeEvento>(sender as object, e as MyArgs)

dove

  • sender è l’istanza della classe (oggetto) che ha sollevato l’evento;
  • MyArgs è una classe che deriva da System.EventArgs (o da sue derivate) che contiene l’argomento dell’evento e che ci serve per trasportare oggetti all’indirizzo di chi deve gestire l’evento.

Il sender tra gli argomenti permette, a chi si sottoscrive, di poter gestire gli eventi di più oggetti con lo stesso tipo di evento in un unico punto (gestore).

Per esemplificare ulteriormente cominciamo con il realizzare una classe che si occuperà di simulare un lavoro di lunga durata:

  1. Public Class LongRunJob
  2.  
  3.     Public Event JobCompleted(ByVal sender As Object, ByVal e As EventArgs)
  4.  
  5.     Public Sub DoWork(ByVal nSec As Integer)
  6.         .
  7.         .
  8.     End Sub
  9.     
  10. End Class

Abbiamo definito un evento che segnalerà il completamento del lavoro. Il metodo DoWork, come vedremo più avanti, si occuperà di avviare un thread che, banalmente, dopo nSec secondi segnalerà l’avvenuto completamento.

Come si solleva un evento

Per poter scatenare un evento è sufficiente eseguire l’istruzione:

  1. RaiseEvent <NomeEvento>(<ParameterList>)

In particolare, per sollevare l’evento JobCompleted() della classe LongRunJob, possiamo scrivere:

  1. RaiseEvent JobCompleted(Me, EventArgs.Empty)

Completiamo, quindi, la nostra classe LongRunJob con il thread che esegue il lavoro:

  1. Public Class LongRunJob
  2.  
  3.     Public Event JobCompleted(ByVal sender As Object, ByVal e As EventArgs)
  4.  
  5.     Public Sub DoWork(ByVal nSec As Integer)
  6.         Dim threadStart = New Threading.ParameterizedThreadStart(AddressOf DoWorkCode)
  7.         Dim thread = New Threading.Thread(threadStart)
  8.         thread.Start(nSec)
  9.     End Sub
  10.  
  11.     Private Sub DoWorkCode(ByVal data As Object)
  12.         Dim nSec = CType(data, Int32)
  13.         System.Threading.Thread.Sleep(nSec * 1000)
  14.         RaiseEvent JobCompleted(Me, EventArgs.Empty)
  15.     End Sub
  16.  
  17. End Class

Come si gestisce un evento

La nostra classe comunica, quindi, al mondo circostante di aver completato il proprio lavoro, ma come facciamo ad intercettare l’evento in un’altra classe?

Per poterci “sottoscrivere” alla ricezione di un evento utiliziamo la parola chiave AddHandler:

  1. AddHandler calc.JobCompleted, AddressOf LongRunJobCompletedHandler

In questo modo comunichiamo all’istanza della classe LongRunJob (vedremo più avanti in che modo) quale metodo deve essere richiamato nel momento in cui viene sollevato l’evento. E’ la classe che vuole essere informata della notifica che ha il dovere di sottoscriversi.

Il metodo che gestirà l’evento deve avere la stessa firma dell’evento stesso (Stessi parametri per tipo, ordine e numero).

Nel momento in cui non abbianmo più necessità di ricevere le notifiche dell’evento, possiamo “sganciare” il gestore di evento con la parola chiave RemoveHandler:

  1. RemoveHandler calc.JobCompleted, AddressOf LongRunJobCompletedHandler

Un esempio di gestione dell’evento è il seguente:

  1. Module Module1
  2.  
  3.     Sub Main()
  4.         Dim calc = New LongRunJob()
  5.         AddHandler calc.JobCompleted, AddressOf LongRunJobCompletedHandler
  6.         Console.WriteLine("Inizio lavoro!")
  7.         calc.DoWork(10)
  8.         Console.ReadLine()
  9.         RemoveHandler calc.JobCompleted, AddressOf LongRunJobCompletedHandler
  10.     End Sub
  11.  
  12.     Public Sub LongRunJobCompletedHandler(ByVal sender As Object, ByVal e As EventArgs)
  13.         Console.WriteLine("Lavoro completato!")
  14.     End Sub
  15.  
  16. End Module

Cosa c’è dietro le quinte

Perchè in C#, per implementare un evento, si deve definire un delegate, definire l’evento come membro della classe di tipo delegate e via dicendo mentre VB, essendo comunque un linguaggio del framework, non ha la necessità di scrivere tutto ciò?

La risposta è che il compilatore VB semplifica la vita e crea per noi la struttura del delegate che gestisce l’evento.

In particolare apriamo la nostra classe LongRunJob con Reflector e vediamo esattamente come si presenta:

imageOsserviamo che il compilatore, dietro le quinte, crea un delegate e definisce l’evento con lo stesso delegate:

  1. Public Delegate Sub JobCompletedEventHandler(ByVal sender As Object, ByVal e As EventArgs)
  2.  
  3. Public Custom Event JobCompleted As JobCompletedEventHandler

Inoltre, la parola chiave RaieseEvent (che viene da noi invocata senza controllare se c’è qualcuno in ascolto) viene compilata nel seguente modo:

  1. Private Sub DoWorkCode(ByVal data As Object)
  2.     Thread.Sleep(CInt((Conversions.ToInteger(data) * 1000)))
  3.     Dim VB$t_ref$S0 As JobCompletedDelegate = Me.JobCompletedEvent
  4.     If (Not VB$t_ref$S0 Is Nothing) Then
  5.         VB$t_ref$S0.Invoke(Me, EventArgs.Empty)
  6.     End If
  7. End Sub

IL compilatore, quindi, genera un codice del tutto simile a quello che scriverebbe un programmatore C#: si controlla se c’è qualcuno in ascolto e, solo in quel caso, si solleva l’evento (metodo Invoke).

Osserviamo, inoltre, che l’evento, nella classe compilata, viene generato come evento custom (ci occuperemo in dettaglio degli eventi custom in un prossimo post). Vengono generati i due metodi richiamati per l’aggancio del gestore di evento e per lo sgancio dello stesso:

  1. Public Sub add_JobCompleted(ByVal obj As JobCompletedEventHandler)
  2.     Me.JobCompletedEvent = DirectCast(Delegate.Combine(Me.JobCompletedEvent, obj), _
  3.         JobCompletedEventHandler)
  4. End Sub
  5.  
  6. Public Sub remove_JobCompleted(ByVal obj As JobCompletedEventHandler)
  7.     Me.JobCompletedEvent = DirectCast(Delegate.Remove(Me.JobCompletedEvent, obj), _
  8.         JobCompletedEventHandler)
  9. End Sub

Di fatto, i gestori di evento agganciati al nostro evento sono “memorizzati” nell’attributo privato JobCompletedEvent (generato dal compilatore). JobCompletedEvent è un oggetto di classe MultiCastDelegate, ovvero un oggetto in grado di avere collegata una lista di gestori di eventi (delegate) detta lista di invocazione. I metodi Combine e Remove del MulticastDelegate consentono di aggiungere e rimuovere un gestore di evento dalla lista di invocazione.

Ultima annotazione importante è la presenza dell’attributo JobCompletedEvent di tipo JobCompletedEventHandler. Questo è presente all’interno della nostra classe pur non essendo visibile all’intellisense. Di fatto possiamo utilizzarlo per capire se qualcuno si è agganciato al nostro evento:

  1. Public Function IsJobCompletedListening() As Boolean
  2.     Return (Me.JobCompletedEvent IsNot Nothing)
  3. End Function

Alla fine, sfruttando le osservazioni fatte, possiamo scrivere la nostra classe “alla C#”, definendo, cioè tutto quello che ci serve:

  1. Public Class LongRunJobCSLike
  2.  
  3.     Public Delegate Sub JobCompletedDelegate(ByVal sender As Object, ByVal e As EventArgs)
  4.  
  5.     Public Event JobCompleted As JobCompletedDelegate
  6.  
  7.     Public Sub DoWork(ByVal nSec As Integer)
  8.         Dim threadStart = New Threading.ParameterizedThreadStart(AddressOf DoWorkCode)
  9.         Dim thread = New Threading.Thread(threadStart)
  10.         thread.Start(nSec)
  11.     End Sub
  12.  
  13.     Private Sub DoWorkCode(ByVal data As Object)
  14.         Dim nSec = CType(data, Int32)
  15.         System.Threading.Thread.Sleep(nSec * 1000)
  16.         If Me.JobCompletedEvent IsNot Nothing Then
  17.             Me.JobCompletedEvent.Invoke(Me, EventArgs.Empty)
  18.         End If
  19.     End Sub
  20.  
  21. End Class

In questo caso il compilatore non definisce il delegate in quanto lo abbiamo già fatto noi.

Commenti

Post popolari in questo blog

VB.NET: Convertire un file DOC in RTF e PDF con office interop

In questo post vorrei proporvi del codice per poter convertire un file .doc in un file .rtf oppure .pdf utilizzando le API di interoperabilità di Office.Creeremo una classe, DocConverter, che esporrà le due funzionalità sopra citate.Cominciamo con il prevedere un attributo privato della classe che rappresenterà l’applicazione Word che utilizzeremo per la conversione. Creeremo l’istanza dell’attributo privato all’interno del costruttore della classe:PublicSubNew()
IfNot CreateWordApp() Then
ThrowNew ApplicationException("Assembly di interoperabilità con Office non trovato!")
EndIf
EndSub
Private _wordApp As Word.ApplicationClass
ProtectedFunction CreateWordApp() AsBoolean
Dim retval = True
Try
_wordApp = New Word.ApplicationClass()
_wordApp.Visible = False
Catch ex As System.Exception
_wordApp = Nothing
retval = False
EndTry
Return retval
EndFunction

La conversione del file doc sarà effettuata aprendo il file stesso ed eseguendo un’operazione di SaveAs:

Pr…

Tascam DR-05 registratore digitale per tutti

Diverso tempo fa ho acquistato il registratore digitale Tascam DR-05 e, ora, dopo diversi mesi di utilizzo, posso dire la mia a proposito.

Si tratta di un ottimo registratore digitale con microfono stereo che permette di ottenere registrazioni di ottima qualitaà anche in ambienti non perfetti dal punto di vista acustico.

Interessante la possibilità di utilizzare un cavalletto di piccole dimensioni come HAMA Mini treppiede o Mini Cavalletto Universale per tenere il microfono sollevato dal tavolo in fase di registrazione grazie allàattacco universale per macchina fotografica che il microfono ha nella parte inferiore.

Da segnalare anche il menu’ ben fatto e la possibilita’ di utilizzare una scheda SD esterna per memorizzare i file audio. Anche a livello di consumo batterie non e’ niente male. Consiglio l’uso delle alcaline non ricaricabili.

Il mio utilizzo e’ stato prettamente di registrazione di podcast vocali (no musica) ma le recensioni confermano che se la cava egregiamente con la mu…

Cambiare la lingua di Visual Studio 2008

Oggi ho avuto qualche problema installando Windows Mobile 6 Professional SDK Refresh e Windows Mobile 6 Standard SDK Refresh.Scaricati i file di installazione e installati, ho provato a creare un progetto di tipo Windows Mobile 6.0 e mi sono beccato questo errore:Dopo qualche smanettamento abbiamo scoperto (e ringrazio il mitico Matteo per l’aiuto) che il mio Visual Studio 2008, pur essendo in Inglese (prova ne era il fatto che gli hotfix e la SP installata erano nella lingua di Albione) aveva come lingua impostata quella del sistema operativo (italiano).Ovviamente, non avrebbe mai potuto trovare la cartella 1040 (italiano) visto che l’installazione dell’SDK aveva supposto che la lingua del Visual Studio fosse Inglese (1033).La soluzione del problema è duplice:1) Duplicate la cartella 1033 presente nel percorso evidenziato dall’errore e la rinominate 10402) cambiate la lingua di Visual Studio.Per questa ultima eventualità basta andare nel menù Strumenti/Opzioni:e cambiare il linguaggi…