Skip to content

6. Gestão de eventos

No capítulo anterior, abordámos o conceito de eventos associados a componentes. Vamos agora ver como criar eventos nas nossas próprias classes. Trata-se de um conceito complexo que não interessará aos programadores principiantes, aos quais se recomenda que passem diretamente para o capítulo seguinte.

6.1. Objetos delegate

A instrução

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

define um tipo denominado opération, que é, na verdade, um protótipo de função que aceita dois inteiros e devolve um inteiro. É a palavra-chave delegate que faz com que opération seja uma definição de protótipo de função. O tipo [opération] pode ser atribuído a uma variável da seguinte forma:


        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, esta função é indicada na forma AddressOf função, em que [fonction] é o nome da função. Uma vez criada a instância de um delegado, esta pode ser utilizada 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 devolver um resultado do mesmo tipo. Assim, a sua instância [op] 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 devolve 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 registar funções
        ' regista-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
        ' regista-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))
        ' regista-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:

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

6.2. Gestão de eventos

Para que serve um objeto do tipo delegate?

Como veremos no exemplo seguinte, serve principalmente para a gestão 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 registadas no objeto delegado evtiDéclenché serão então executadas. Se um objeto C2 que utilize um objeto C1 quiser ser notificado da ocorrência do evento evti no objeto C1, 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 oevento evti ocorrer no objeto C1. Como o objeto delegado C1.evtiDéclenché pode registar várias funções, diferentes objetos Ci poderão registar-se junto do delegado C1.evtiDéclenché para serem notificados do evento evti no C1.

6.2.1. Declaração de 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] que define um dado como um evento. Ao evento é associada a assinatura que os gestores do mesmo deverão ter. Esta assinatura pode ser definida através de um delegado (método 1) ou diretamente (método 2).

6.2.2. Definir os gestores de um evento

Quando um evento é desencadeado pela instrução [RaiseEvent], é necessário processá-lo. Os procedimentos encarregados de processar os eventos são denominados gestores de eventos. Aquando da criação de um objeto [Event], apenas é declarada a assinatura dos gestores do evento. Esta assinatura é a do objeto [delegate] associado ao evento. Feito isto, será possível associar gestores ao evento. Estes são procedimentos que devem ter a mesma assinatura que o [delegate] associado ao evento. Para associar um gestor de eventos a um evento, utiliza-se a instrução [AddHandler] com a seguinte sintaxe:

AddHandler event, AddressOf eventhandler
  • event: variável de evento à qual se associa um novo gestor de eventos
  • eventhandler: nome do gestor de eventos — deve ter a assinatura do [délégué] associado ao evento

É possível associar quantos manipuladores se desejar 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 programa, utiliza-se a instrução [RaiseEvent]:

RaiseEvent eventname[( argumentlist )]
  • eventname: variável de evento (declarada com a palavra-chave Event)
  • argumentlist: argumentos do delegado associado ao evento.

Todos os gestores 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

Consideremos o seguinte exemplo:


'gestão 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 de entrada de leitura única
    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 gestores de eventos
    Public Event evtHandler As _evtHandler

    ' método para solicitar a emissão de um evento
    Public Sub envoyerEvt(ByVal evt As myEventArgs)
        ' notificam-se todos os subscritores
        ' faz-se como se o evento tivesse origem aqui
        RaiseEvent evtHandler(Me, evt)
    End Sub
End Class

' 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)
        ' anotamos o nome do subscritor
        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

    ' gestor de eventos
    Public Sub traitementEvt(ByVal sender As Object, ByVal evt As myEventArgs)
        ' visualizaçã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 subscritores
        ' para os eventos emitidos pelo emissor1
        Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
        ' leitura de uma sequência de números inteiros a partir do teclado
        ' assim que um valor estiver errado, emite-se um evento
        Console.Out.Write("Nombre entier (rien pour arrêter) : ")
        Dim saisie As String = Console.In.ReadLine().Trim()
        ' enquanto a linha introduzida 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 toda a gente
                émetteur1.envoyerEvt(New myEventArgs(saisie))
            End Try
            ' avisa-se toda a gente
            ' nova introdução
            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. Numa gestão de eventos, existe um emissor de eventos (sender) que envia os detalhes dos eventos (EventArgs) aos subscritores que manifestaram interesse 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 gestores de eventos
    Public Event evtHandler As _evtHandler

    ' método de solicitação de emissão de um evento
    Public Sub envoyerEvt(ByVal evt As myEventArgs)
        ' notificam-se todos os subscritores
        ' faz-se como se o evento tivesse origem aqui
        RaiseEvent evtHandler(Me, evt)
    End Sub
End Class

Cada objeto émetteur tem um nom definido por predefinição. O método ToString foi redefinido de forma a que, ao transformar um objeto émetteur num string,, seja o seu nome que se recupera. 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 ao qual voltaremos mais tarde. Para poder desencadear um evento a partir do exterior 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)
        ' notificamos todos os assinantes
        ' trata-se como se o evento tivesse origem aqui
        RaiseEvent evtHandler(Me, evt)
    End Sub

Temos de definir qual será o tipo de eventos desencadeados pela classe émetteur. Para tal, 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 introduzida como só de leitura
    Public Overrides Function ToString() As String
        Return _saisie
    End Function
End Class

Os eventos que nos interessam são as entradas erradas no teclado. Vamos pedir a um utilizador que introduza números inteiros no teclado e, assim que introduzir uma cadeia de caracteres que não represente um número inteiro, será acionado um evento. Como detalhe do evento, limitar-nos-emos a indicar a entrada incorreta. É esse o significado do atributo _saisie da classe. Assim, é criado um objeto myEventArgs com a entrada incorreta como parâmetro. Além disso, redefinimos o método ToString para que, ao converter um objeto myEventArgs numa cadeia de caracteres, se obtenha o atributo _saisie.

Definimos o emissor dos eventos e o tipo de eventos que este emite. Em seguida, definimos o tipo de subscritores 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)
            ' regista-se o nome do subscritor
            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

        ' gestor de eventos
        Public Sub traitementEvt(ByVal sender As Object, ByVal evt As myEventArgs)
            ' visualizaçã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 subscritor será definido por dois parâmetros: o seu nome (atributo nom) e o objeto émetteur cujos eventos pretende processar (atributo sender). Estes dois parâmetros serão passados ao construtor do objeto. Durante essa mesma criação, o subscritor subscreve os eventos do emissor:


            ' inscreve-se para receber os eventos do emissor e
            AddHandler e.evtHandler, AddressOf traitementEvt

A função registada junto do emissor é traitementEvt. Este método da classe souscripteur apresenta os dois argumentos que recebeu (sender, evt), bem como o nome do recetor (nom). Foram definidos o tipo dos eventos gerados, o tipo do emissor desses eventos e o tipo dos subscritores. Resta-nos apenas 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 subscritores
            ' 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 a partir do teclado
            ' assim que um valor estiver errado, emite-se um evento
            Console.Out.Write("Nombre entier (rien pour arrêter) : ")
            Dim saisie As String = Console.In.ReadLine().Trim()
            ' enquanto a linha introduzida 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 toda a gente
                    émetteur1.envoyerEvt(New myEventArgs(saisie))
                End Try
                ' avisa-se toda a gente
                ' nova introdução
                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 subscritores para os eventos emitidos pelo objeto émetteur1:


         ' criação de uma tabela de subscritores
         ' para os eventos emitidos pelo emissor1
        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 introduzido um valor incorreto, solicitamos ao émetteur1 que envie um evento aos seus subscritores:


                 ' avisa-se toda a gente
                émetteur1.envoyerEvt(New myEventArgs(saisie))

O evento enviado é do tipo myEventArgs e contém a entrada incorreta. Ambos os subscritores devem receber este evento e notificá-lo. É isso que mostra a execução que se segue.

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