6. Tratamento de eventos
No capítulo anterior, discutimos o conceito de eventos associados a componentes. Vamos agora ver como criar eventos nas nossas próprias classes. Este é um conceito difícil que não será do interesse de programadores iniciantes, aos quais se recomenda que passem diretamente para o próximo capítulo.
6.1. Objetos delegados
A instrução
define um tipo chamado operation, que é, na verdade, um protótipo de função que aceita dois inteiros e devolve um inteiro. É a palavra-chave delegate que torna operation uma definição de protótipo de função. O tipo [operation] pode ser atribuído a uma variável da seguinte forma:
Dim op As New opération(AddressOf [fonction])
Uma variável do tipo [delegate] é construída como um objeto. O parâmetro do construtor é a função associada à instância do delegado. Em VB, esta função é especificada como AddressOf function, onde [function] é o nome da função. Uma vez criada uma instância do delegado, esta pode ser utilizada como uma função:
' the delegate is executed
Dim n As Integer = op(4, 7)
O delegado [operation] foi definido para receber dois parâmetros do tipo [integer] e devolver um resultado do mesmo tipo. Por conseguinte, a sua instância [op] acima é utilizada como tal função. Considere o seguinte exemplo:
' delegated functions
Imports System
Public Class delegate1
' function prototype definition
' accepts 2 integers as parameters and returns an integer
Delegate Function opération(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
' two instance methods corresponding to the prototype
' add
Public Function ajouter(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
Console.Out.WriteLine(("ajouter(" & n1 & "," & n2 & ")"))
Return n1 + n2
End Function
' subtract
Public Function soustraire(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
Console.Out.WriteLine(("soustraire(" & n1 & "," & n2 & ")"))
Return n1 - n2
End Function
' a static method corresponding to the prototype
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
' test program
Public Shared Sub Main()
' define an operation object to store functions
' we register the static function increase
Dim op As New opération(AddressOf delegate1.augmenter)
' the delegate is executed
Dim n As Integer = op(4, 7)
Console.Out.WriteLine(("n=" & n))
' creation of a c1 object of type class1
Dim c1 As New delegate1
' we register c1's add method in the delegate
op = New delegate1.opération(AddressOf c1.ajouter)
' execution of delegated object
n = op(2, 3)
Console.Out.WriteLine(("n=" & n))
' we register the subtract method of c1 in the delegate
op = New delegate1.opération(AddressOf c1.soustraire)
n = op(2, 3)
Console.Out.WriteLine(("n=" & n))
End Sub
End Class
Os resultados da execução são os seguintes:
6.2. Tratamento de eventos
Para que pode ser utilizado um objeto delegado?
Como veremos no exemplo seguinte, é utilizado principalmente para o tratamento de eventos. Uma classe C1 pode gerar eventos evti. Quando ocorre um evento evti, um objeto do tipo C1 desencadeia a execução de um objeto evtiDéclenché do tipo delegado. Todas as funções registadas no objeto delegado evtiDéclenché serão então executadas. Se um objeto C2 que utiliza um objeto C1 quiser ser notificado da ocorrência do evento evti no objeto C1, irá registar um dos seus métodos, C2.f, no objeto delegado C1.evtiDéclenché do objeto C1, para que o método C2.f seja executado sempre que o evento evti ocorrer no objeto C1. Uma vez que o objeto delegado C1.evtiDéclenché pode registar múltiplas funções, diferentes objetos Ci podem registar-se junto do delegado C1.evtiDéclenché para serem notificados do evento evti no C1.
6.2.1. Declarar um evento
Um evento é declarado da seguinte forma (1):
ou (2)
A palavra-chave [Event] define um dado como um evento. O evento está associado à assinatura que os seus manipuladores devem ter. Esta assinatura pode ser definida utilizando um delegado (método 1) ou diretamente (método 2).
6.2.2. Definir manipuladores de eventos
Quando um evento é acionado pela instrução [RaiseEvent], ele deve ser tratado. Os procedimentos responsáveis pelo tratamento de eventos são chamados de manipuladores de eventos. Ao criar um objeto [Event], apenas a assinatura dos manipuladores de eventos é declarada. Esta assinatura é a do objeto [delegate] associado ao evento. Uma vez feito isto, os manipuladores podem ser associados ao evento. Estes são procedimentos que devem ter a mesma assinatura que o [delegate] associado ao evento. Para associar um manipulador de eventos a um evento, utilize a instrução [AddHandler] com a seguinte sintaxe:
- event: variável de evento à qual um novo manipulador de eventos é associado
- manipulador de eventos: nome do manipulador de eventos — deve ter a mesma assinatura que o [delegado] associado ao evento
Pode associar quantos manipuladores quiser a um evento. Todos eles serão executados quando o evento ao qual estão associados for acionado.
6.2.3. Acionar um evento
Para acionar um evento programaticamente, utilize a instrução [RaiseEvent]:
- nome do evento: variável de evento (declarada com a palavra-chave Event)
- lista de argumentos: argumentos do delegado associado ao evento.
Todos os manipuladores que tenham sido associados ao evento através da instrução AddHandler serão chamados. Receberão como parâmetros os parâmetros definidos na assinatura do evento.
6.2.4. Um exemplo
Considere o seguinte exemplo:
'event management
Imports System
Public Class myEventArgs
Inherits EventArgs
' the class of an evt
' attribute
Private _saisie As String
' manufacturer
Public Sub New(ByVal saisie As String)
_saisie = saisie
End Sub
' read-only property
Public Overrides Function ToString() As String
Return _saisie
End Function
End Class
Public Class émetteur
' the class issuing an event
' attribute
Private _nom As String ' name of issuer
' manufacturer
Public Sub New(ByVal nom As String)
_nom = nom
End Sub
' ToString
Public Overrides Function ToString() As String
Return _nom
End Function
' the prototype of the functions responsible for processing the event
Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)
' pool of event managers
Public Event evtHandler As _evtHandler
' method of requesting the issue of an evt
Public Sub envoyerEvt(ByVal evt As myEventArgs)
' all subscribers are notified
' we act as if the event originated here
RaiseEvent evtHandler(Me, evt)
End Sub
End Class
' an evt treatment class
Public Class souscripteur
' attribute
Private nom As String ' name of subscriber
Private sender As émetteur ' evts sender
' manufacturer
Public Sub New(ByVal nom As String, ByVal e As émetteur)
' we note the name of the subscriber
Me.nom = nom
' and the sender of the evts
Me.sender = e
' register to receive news from the transmitter e
AddHandler e.evtHandler, AddressOf traitementEvt
End Sub
' event manager
Public Sub traitementEvt(ByVal sender As Object, ByVal evt As myEventArgs)
' evt display
Console.Out.WriteLine(("L'objet [" + sender.ToString + "] a signalé la saisie erronée [" + evt.ToString + "] au souscripteur [" + nom + "]"))
End Sub
End Class
' a test program
Public Class test
Public Shared Sub Main()
' creation of an evts transmitter
Dim émetteur1 As New émetteur("émetteur1")
' create a table of subscribers
' for events issued by issuer1
Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
' read a sequence of integers from the keyboard
' as soon as one is wrong, we issue an evt
Console.Out.Write("Nombre entier (rien pour arrêter) : ")
Dim saisie As String = Console.In.ReadLine().Trim()
' as long as the line entered is not empty
While saisie <> ""
' is the input a whole number?
Try
Dim n As Integer = Integer.Parse(saisie)
Catch
' not an integer
' we warn everyone
émetteur1.envoyerEvt(New myEventArgs(saisie))
End Try
' we warn everyone
' new entry
Console.Out.Write("Nombre entier (rien pour arrêter) : ")
saisie = Console.In.ReadLine().Trim()
End While
' end
Environment.Exit(0)
End Sub
End Class
O código acima é bastante complexo. Vamos analisá-lo. No tratamento de eventos, existe um emissor de eventos que envia detalhes do evento (EventArgs) aos subscritores que manifestaram interesse nos eventos em questão. A classe de envio de eventos aqui é a seguinte:
Public Class émetteur
' the class issuing an event
' attribute
Private _nom As String ' name of issuer
' manufacturer
Public Sub New(ByVal nom As String)
_nom = nom
End Sub
' ToString
Public Overrides Function ToString() As String
Return _nom
End Function
' the prototype of the functions responsible for processing the event
Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)
' pool of event managers
Public Event evtHandler As _evtHandler
' method of requesting the issue of an evt
Public Sub envoyerEvt(ByVal evt As myEventArgs)
' all subscribers are notified
' we act as if the event originated here
RaiseEvent evtHandler(Me, evt)
End Sub
End Class
Cada objeto emissor tem um nome definido por predefinição. O método ToString foi redefinido para que, quando um objeto emissor é convertido numa cadeia de caracteres, o seu nome seja devolvido. A classe define um evento:
' the prototype of the functions responsible for processing the event
Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)
' the event
Public Event evtHandler As _evtHandler
O primeiro argumento de uma função de manipulador de eventos será o objeto que emite o evento. O segundo argumento será do tipo myEventArgs, um objeto que fornece detalhes sobre o evento, o que discutiremos mais tarde. Para acionar um evento a partir de fora de um objeto emissor, adicionamos o método envoyerEvt à classe:
' method of requesting the issue of an evt
Public Sub envoyerEvt(ByVal evt As myEventArgs)
' all subscribers are notified
' we act as if the event originated here
RaiseEvent evtHandler(Me, evt)
End Sub
Precisamos de definir o tipo de eventos acionados pela classe emissora. Para tal, definimos a classe myEventArgs:
Public Class myEventArgs
' the class of an evt
' attribute
Private _saisie As String
' manufacturer
Public Sub New(ByVal saisie As String)
_saisie = saisie
End Sub
' read-only property
Public Overrides Function ToString() As String
Return _saisie
End Function
End Class
Os eventos que nos interessam são entradas incorretas no teclado. Pediremos ao utilizador que digite números inteiros no teclado e, assim que digitar uma cadeia de caracteres que não represente um número inteiro, acionaremos um evento. Como detalhe do evento, forneceremos simplesmente a entrada incorreta. Este é o objetivo do atributo _input da classe. Assim, é criado um objeto myEventArgs com a entrada incorreta como seu parâmetro. Também reescrevemos o método ToString para que, quando convertermos um objeto myEventArgs numa cadeia de caracteres, obtenhamos a propriedade _input.
Definimos o emissor de eventos e o tipo de eventos que este emite. A seguir, definimos o tipo de subscritores interessados nestes eventos.
' an evt treatment class
Public Class souscripteur
' attribute
Private nom As String ' name of subscriber
Private sender As émetteur ' evts sender
' manufacturer
Public Sub New(ByVal nom As String, ByVal e As émetteur)
' we note the name of the subscriber
Me.nom = nom
' and the sender of the evts
Me.sender = e
' register to receive news from the transmitter e
AddHandler e.evtHandler, AddressOf traitementEvt
End Sub
' event manager
Public Sub traitementEvt(ByVal sender As Object, ByVal evt As myEventArgs)
' evt display
Console.Out.WriteLine(("L'objet [" + sender.ToString + "] a signalé la saisie erronée [" + evt.ToString + "] au souscripteur [" + nom + "]"))
End Sub
End Class
Um subscritor é definido por dois parâmetros: o seu nome (atributo name) e o objeto emissor cujos eventos pretende tratar (atributo sender). Estes dois parâmetros são passados ao construtor do objeto. Durante esta mesma construção, o subscritor subscreve os eventos do emissor:
' on s'inscrit pour recevoir les evts de l'émetteur e
AddHandler e.evtHandler, AddressOf traitementEvt
A função registada junto do emissor é traitementEvt. Este método da classe de assinante apresenta os dois argumentos que recebeu (sender, evt), bem como o nome do destinatário (nom). Os tipos dos eventos produzidos, o tipo do emissor desses eventos e os tipos dos assinantes já foram definidos. Resta apenas implementá-los:
' a test program
Public Class test
Public Shared Sub Main()
' creation of an evts transmitter
Dim émetteur1 As New émetteur("émetteur1")
' create a table of subscribers
' for events issued by issuer1
Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
' read a sequence of integers from the keyboard
' as soon as one is wrong, we issue an evt
Console.Out.Write("Nombre entier (rien pour arrêter) : ")
Dim saisie As String = Console.In.ReadLine().Trim()
' as long as the line entered is not empty
While saisie <> ""
' is the input a whole number?
Try
Dim n As Integer = Integer.Parse(saisie)
Catch
' not an integer
' we warn everyone
émetteur1.envoyerEvt(New myEventArgs(saisie))
End Try
' we warn everyone
' new entry
Console.Out.Write("Nombre entier (rien pour arrêter) : ")
saisie = Console.In.ReadLine().Trim()
End While
' end
Environment.Exit(0)
End Sub
Criamos um objeto emissor de eventos:
' creation of an evts transmitter
Dim émetteur1 As New émetteur("émetteur1")
Criamos uma matriz com dois assinantes para os eventos emitidos pelo objeto emetteur1:
' create a table of subscribers
' for events issued by issuer1
Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
Pedimos ao utilizador que introduza números inteiros através do teclado. Assim que for introduzida uma entrada incorreta, pedimos ao emissor1 que envie um evento aos seus subscritores:
' on prévient tout le monde
émetteur1.envoyerEvt(New myEventArgs(saisie))
O evento enviado é do tipo myEventArgs e contém a entrada incorreta. Ambos os assinantes devem receber este evento e reportá-lo. Isto é demonstrado na execução seguinte.
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) :