6. Gerenciamento de eventos
No capítulo anterior, abordamos o conceito de eventos vinculados a componentes. Veremos agora como criar eventos em nossas próprias classes. Trata-se de um conceito complexo que não interessará aos desenvolvedores iniciantes, aos quais recomendamos passar diretamente para o próximo capítulo.
6.1. Objetos delegate
A instrução
define um tipo chamado opération, que é, na verdade, um protótipo de função que aceita dois inteiros e retorna um inteiro. É a palavra-chave delegate que faz de opération uma definição de protótipo de função. O tipo [opération] pode ser atribuído a uma variável da seguinte maneira:
Dim op As New opération(AddressOf [fonction])
Uma variável do tipo [délégué] é criada como um objeto. O parâmetro do construtor é a função associada à instância do delegado. Em VB, essa função é fornecida na forma AddressOf função, onde [fonction] é o nome da função. Uma vez criada a instância de um delegado, ela pode ser usada como uma função:
' executa-se a função delegada
Dim n As Integer = op(4, 7)
O delegado [opération] foi definido para receber dois parâmetros do tipo [integer] e retornar um resultado do mesmo tipo. Portanto, sua instância [op], apresentada acima, é utilizada como tal função. Consideremos o seguinte exemplo:
' funções delegadas
Imports System
Public Class delegate1
' definição de um protótipo de função
' aceita 2 inteiros como parâmetros e retorna um inteiro
Delegate Function opération(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
' dois métodos de instância correspondentes ao protótipo
' adicionar
Public Function ajouter(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
Console.Out.WriteLine(("ajouter(" & n1 & "," & n2 & ")"))
Return n1 + n2
End Function
' subtrair
Public Function soustraire(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
Console.Out.WriteLine(("soustraire(" & n1 & "," & n2 & ")"))
Return n1 - n2
End Function
' um método estático correspondente ao protótipo
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
' programa de teste
Public Shared Sub Main()
' define-se um objeto do tipo operação para registrar funções nele
' registra-se a função estática aumentar
Dim op As New opération(AddressOf delegate1.augmenter)
' executa-se o delegado
Dim n As Integer = op(4, 7)
Console.Out.WriteLine(("n=" & n))
' criação de um objeto c1 do tipo class1
Dim c1 As New delegate1
' registra-se no delegado o método “adicionar” de c1
op = New delegate1.opération(AddressOf c1.ajouter)
' execução do objeto delegado
n = op(2, 3)
Console.Out.WriteLine(("n=" & n))
' registra-se no delegado o método “subtrair” de c1
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. Gerenciamento de eventos
Para que serve um objeto do tipo delegate?
Como veremos no exemplo a seguir, ele serve principalmente para o gerenciamento de eventos. Uma classe C1 pode gerar eventos evti. Durante um evento evti, um objeto do tipo C1 iniciará a execução de um objeto evtiDéclenché do tipo delegate. Todas as funções registradas no objeto delegado evtiDéclenché serão então executadas. Se um objeto C2 que utiliza um objeto C1 quiser ser notificado sobre a ocorrência do evento evti no objeto C1, ele registrará um de 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 oevento evti ocorrer no objeto C1. Como o objeto delegado C1.evtiDéclenché pode registrar várias funções, diferentes objetos Ci poderão se registrar junto ao delegado C1.evtiDéclenché para serem notificados sobre o evento evti no C1.
6.2.1. Declaração de um evento
Um evento é declarado da seguinte maneira (1):
Delegate Sub proc(liste d'arguments)
Public Event événement as proc
ou (2)
Public Event événement(liste d'arguments)
A palavra-chave [Event] define um dado como um evento. Associamos ao evento a assinatura que os gerenciadores do mesmo deverão ter. É possível definir essa assinatura por meio de um delegado (método 1) ou diretamente (método 2).
6.2.2. Definir os manipuladores de um evento
Quando um evento é acionado pela instrução [RaiseEvent], ele deve ser processado. Os procedimentos encarregados de processar os eventos são chamados de manipuladores de eventos. Ao criar um objeto [Event], apenas a assinatura dos manipuladores do evento é declarada. Essa assinatura é a do objeto [delegate] associado ao evento. Feito isso, será possível associar manipuladores ao evento. Esses manipuladores são procedimentos que devem ter a mesma assinatura que o [delegate] associado ao evento. Para associar um manipulador de evento a um evento, utiliza-se a instrução [AddHandler] com a seguinte sintaxe:
- event: variável de evento à qual se associa um novo manipulador de eventos
- eventhandler: nome do manipulador de eventos — deve ter a assinatura do [délégué] associado ao evento
É possível associar quantos manipuladores de eventos forem desejados a um evento. Todos eles serão executados quando o evento ao qual estão associados for acionado.
6.2.3. Disparar um evento
Para acionar um evento por meio de programa, utiliza-se a instrução [RaiseEvent]:
- eventname: variável de evento (declarada com a palavra-chave Event)
- argumentlist: argumentos do delegado associado ao evento.
Todos os manipuladores que foram associados ao evento pela instrução AddHandler serão chamados. Eles receberão como parâmetros os parâmetros definidos na assinatura do evento.
6.2.4. Um exemplo
Consideremos o seguinte exemplo:
'gerenciamento de eventos
Imports System
Public Class myEventArgs
Inherits EventArgs
' a classe de um evento
' atributo
Private _saisie As String
' construtor
Public Sub New(ByVal saisie As String)
_saisie = saisie
End Sub
' propriedade inserida como somente leitura
Public Overrides Function ToString() As String
Return _saisie
End Function
End Class
Public Class émetteur
' a classe emissora de um evento
' atributo
Private _nom As String ' nom de l'émetteur
' construtor
Public Sub New(ByVal nom As String)
_nom = nom
End Sub
' ToString
Public Overrides Function ToString() As String
Return _nom
End Function
' o protótipo das funções responsáveis pelo tratamento do evento
Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)
' o conjunto de gerenciadores de eventos
Public Event evtHandler As _evtHandler
' método para solicitar a emissão de um evento
Public Sub envoyerEvt(ByVal evt As myEventArgs)
' notificamos todos os assinantes
' age-se como se o evento tivesse origem aqui
RaiseEvent evtHandler(Me, evt)
End Sub
End Class
' uma classe de processamento do evento
Public Class souscripteur
' atributo
Private nom As String ' nom du souscripteur
Private sender As émetteur ' l'émetteur des évts
' construtor
Public Sub New(ByVal nom As String, ByVal e As émetteur)
' registramos o nome do assinante
Me.nom = nom
' e o emissor dos eventos
Me.sender = e
' inscreve-se para receber os eventos do emissor e
AddHandler e.evtHandler, AddressOf traitementEvt
End Sub
' gerenciador de eventos
Public Sub traitementEvt(ByVal sender As Object, ByVal evt As myEventArgs)
' exibição de eventos
Console.Out.WriteLine(("L'objet [" + sender.ToString + "] a signalé la saisie erronée [" + evt.ToString + "] au souscripteur [" + nom + "]"))
End Sub
End Class
' um programa de teste
Public Class test
Public Shared Sub Main()
' criação de um emissor de eventos
Dim émetteur1 As New émetteur("émetteur1")
' criação de uma tabela de assinantes
' para os eventos emitidos pelo emissor1
Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
' lê-se uma sequência de números inteiros do teclado
' assim que um deles estiver incorreto, emite-se um evento
Console.Out.Write("Nombre entier (rien pour arrêter) : ")
Dim saisie As String = Console.In.ReadLine().Trim()
' enquanto a linha inserida não estiver vazia
While saisie <> ""
' a entrada é um número inteiro?
Try
Dim n As Integer = Integer.Parse(saisie)
Catch
' não é um número inteiro
' avisa-se a todos
émetteur1.envoyerEvt(New myEventArgs(saisie))
End Try
' avisa-se a todos
' nova entrada
Console.Out.Write("Nombre entier (rien pour arrêter) : ")
saisie = Console.In.ReadLine().Trim()
End While
' fim
Environment.Exit(0)
End Sub
End Class
O código anterior é bastante complexo. Vamos detalhá-lo. Em um gerenciamento de eventos, há um emissor de eventos (sender) que envia os detalhes dos eventos (EventArgs) aos assinantes que se declararam interessados nos eventos em questão. A classe emissora dos eventos é, neste caso, a seguinte:
Public Class émetteur
' a classe emissora de um evento
' atributo
Private _nom As String ' nom de l'émetteur
' construtor
Public Sub New(ByVal nom As String)
_nom = nom
End Sub
' ToString
Public Overrides Function ToString() As String
Return _nom
End Function
' o protótipo das funções responsáveis pelo tratamento do evento
Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)
' o conjunto de gerenciadores de eventos
Public Event evtHandler As _evtHandler
' método para solicitar a emissão de um evento
Public Sub envoyerEvt(ByVal evt As myEventArgs)
' notificamos todos os assinantes
' age-se como se o evento tivesse origem aqui
RaiseEvent evtHandler(Me, evt)
End Sub
End Class
Cada objeto émetteur possui um nom definido por padrão. O método ToString foi redefinido de forma que, ao transformar um objeto émetteur em string,, seja recuperado o nome deste último. A classe define um evento:
' o protótipo das funções responsáveis pelo tratamento do evento
Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)
' o evento
Public Event evtHandler As _evtHandler
O primeiro argumento de uma função de tratamento de um evento será o objeto que emite o evento. O segundo argumento será do tipo myEventArgs, um objeto que fornecerá detalhes sobre o evento e sobre o qual voltaremos a falar. Para poder acionar um evento de fora de um objeto émetteur, adicionamos à classe o método envoyerEvt:
' método para solicitar a emissão de um evento
Public Sub envoyerEvt(ByVal evt As myEventArgs)
' avisamos todos os assinantes
' trata-se como se o evento tivesse origem aqui
RaiseEvent evtHandler(Me, evt)
End Sub
Precisamos definir quais serão os tipos de eventos acionados pela classe émetteur. Para isso, definimos a classe myEventArgs:
Public Class myEventArgs
' a classe de um evento
' atributo
Private _saisie As String
' construtor
Public Sub New(ByVal saisie As String)
_saisie = saisie
End Sub
' propriedade inserida como somente leitura
Public Overrides Function ToString() As String
Return _saisie
End Function
End Class
Os eventos que nos interessam são entradas incorretas no teclado. Solicitaremos que um usuário digite números inteiros no teclado e, assim que ele digitar uma sequência que não represente um número inteiro, um evento será acionado. Como detalhe do evento, nos limitaremos a fornecer a entrada incorreta. Esse é o significado do atributo _saisie da classe. Um objeto myEventArgs é, portanto, criado com a entrada incorreta como parâmetro. Além disso, redefinimos o método ToString para que, ao converter um objeto myEventArgs em string, obtenhamos o atributo _saisie.
Definimos o emissor dos eventos e o tipo de eventos que ele emite. Em seguida, definimos o tipo de assinantes interessados nesses eventos.
' uma classe de tratamento do evento
Public Class souscripteur
' atributo
Private nom As String ' nom du souscripteur
Private sender As émetteur ' l'émetteur des évts
' construtor
Public Sub New(ByVal nom As String, ByVal e As émetteur)
' registra-se o nome do assinante
Me.nom = nom
' e o emissor dos eventos
Me.sender = e
' inscreve-se para receber os eventos do emissor e
AddHandler e.evtHandler, AddressOf traitementEvt
End Sub
' gerenciador de eventos
Public Sub traitementEvt(ByVal sender As Object, ByVal evt As myEventArgs)
' exibição de eventos
Console.Out.WriteLine(("L'objet [" + sender.ToString + "] a signalé la saisie erronée [" + evt.ToString + "] au souscripteur [" + nom + "]"))
End Sub
End Class
Um assinante será definido por dois parâmetros: seu nome (atributo nom) e o objeto émetteur cujos eventos ele deseja processar (atributo sender). Esses dois parâmetros serão passados ao construtor do objeto. Durante essa mesma criação, o assinante se inscreve para receber os eventos do emissor:
' inscreve-se para receber os eventos do emissor e
AddHandler e.evtHandler, AddressOf traitementEvt
A função registrada junto ao emissor é traitementEvt. Este método da classe souscripteur exibe os dois argumentos que recebeu (sender, evt), bem como o nome do receptor (nom). Foram definidos o tipo dos eventos gerados, o tipo do emissor desses eventos e o tipo dos assinantes. Agora, basta implementá-los:
' um programa de teste
Public Class test
Public Shared Sub Main()
' criação de um emissor de eventos
Dim émetteur1 As New émetteur("émetteur1")
' criação de uma tabela de assinantes
' para os eventos emitidos pelo emissor1
Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
' lê-se uma sequência de números inteiros do teclado
' assim que um deles estiver incorreto, emite-se um evento
Console.Out.Write("Nombre entier (rien pour arrêter) : ")
Dim saisie As String = Console.In.ReadLine().Trim()
' enquanto a linha inserida não estiver vazia
While saisie <> ""
' a entrada é um número inteiro?
Try
Dim n As Integer = Integer.Parse(saisie)
Catch
' não é um número inteiro
' avisa-se a todos
émetteur1.envoyerEvt(New myEventArgs(saisie))
End Try
' avisa-se a todos
' nova entrada
Console.Out.Write("Nombre entier (rien pour arrêter) : ")
saisie = Console.In.ReadLine().Trim()
End While
' fim
Environment.Exit(0)
End Sub
Criamos um objeto émetteur:
' criação de um emissor de eventos
Dim émetteur1 As New émetteur("émetteur1")
Criamos uma tabela com dois assinantes para os eventos emitidos pelo objeto émetteur1:
' criação de uma tabela de assinantes
' para os eventos emitidos pelo emissor1
Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
Solicitamos ao usuário que digite números inteiros no teclado. Assim que ocorrer uma entrada incorreta, solicitamos que o émetteur1 envie um evento aos seus assinantes:
' avisamos a todos
émetteur1.envoyerEvt(New myEventArgs(saisie))
O evento enviado é do tipo myEventArgs e contém a entrada incorreta. Os dois assinantes devem receber esse evento e sinalizá-lo. É o que mostra a execução a seguir.
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) :