Skip to content

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

    Delegate Function opération(ByVal n1 As Integer, ByVal n2 As Integer) As Integer

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:

augmenter(4,7)
n=18
ajouter(2,3)
n=5
soustraire(2,3)
n=-1

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

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

AddHandler event, AddressOf eventhandler
  • 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]:

RaiseEvent eventname[( argumentlist )]
  • 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) :