Objectivo
Este artigo apresenta os "delegates" e as técnicas para expor e consumir eventos. No final será apresentado um caso prático, que consiste num simulador mais realista do processo de extração de bolas do "Euromilhões" (este exercício é uma evolução do apresentado no artigo sobre OOP disponível aqui)
"Delegates"
Um "Delegate" é um objecto que permite invocar um método indirectamente.
Os "Delegates" podem ser utilizados para invocar quer métodos estáticos quer métodos de instância.
Os "Delegates" são o motor de implementação interna dos eventos.
Exemplo de declaração de um "Delegate":
Delegate Sub DisplayMessage (ByVal msg as String)
Exemplo de instanciação de um objecto to tipo "Delegate":
Dim MyDelegate as DisplayMessage
MyDelegate= new DisplayMessage (AdressOf WriteToDebugWindow)
O procedimento utilizado neste exemplo "WriteToDebugWindow", tem de ter a mesma assinatura que o "delegate" utilizado ("DisplayMessage"):
' Exibe uma mensagem na janela de debug
Sub WriteToDebugWindow(ByVal msgText As String)
Debug.WriteLine(msgText)
End Sub
Exemplo de invocação de um objecto to tipo "Delegate":
Dim MyDelegate as DisplayMessage
MyDelegate= new DisplayMessage (AdressOf WriteToDebugWindow)
MyDelegate.Invoke ("teste")
A flexibilidade oferecida por um "delegate" permite alterar o comportamento de uma aplicação com alteração minima de código:
Dim MyDelegate as DisplayMessage
MyDelegate= new DisplayMessage (AdressOf WriteToPopUpWindow)
MyDelegate.Invoke ("teste")
Exemplo de implementação do método "WriteToPopUpWindow":
' Exibe uma mensagem numa janela pop up
Sub WriteToPopUpWindow(ByVal msgText As String)
MsgBox(msgText)
End Sub
Os eventos são processados internamente através de "Delegates". Uma classe que expõe um evento, define internamente um "delegate" privado que aponta para todos os clientes que subscreveram o evento. Quando o evento dispara, a infra-estrutura do .Net chama o método "invoke" do "delegate" levando a que todos os clientes sejam notificados do evento.
Tratamento de Eventos (Design Time)
Um "Handler" para um evento é criado automaticamente, por exemplo quando se clica no objecto do tipo Button:
Private Sub ButtonOk_Click (ByVal sender as object, ByVal e as eventArgs) Handles ButtonOk.Click
O "Handler" de um evento aceita sempre dois argumentos:
1 - Uma referência para o objecto que despoletou o evento.
2 - Um objecto derivado de "System.EventArgs" que possui informação sobre o próprio evento.
Exemplo: O "Handler" do Evento KeyPress recebe um objecto do tipo "KeyPressEventArgs", que por sua vez expõe as propriedades "KeyChar" e "Handled":
Private Textbox1_KeyPress(ByVal sender as object, ByVal e as KeyPressEventArgs) Handles TextBox1.KeyPress
If e.KeyChar=" " then e.Handled=true
É possível definir um único "Handler" para tratar eventos provenientes de vários controlos.
Exemplo: Um único "Handler" para alterar a cor do controlo que ganha o focus:
Private Sub Control_Enter (ByVal sender as object, ByVal e as eventArgs) Handles TxtFirstName.Enter, TxtLastName.Enter
Dim ctrl as control=Directcast(Sender, Control)
ctrl.BackColor=color.red
End Sub
Private Sub Control_Leave (ByVal sender as object, ByVal e as eventArgs) Handles TxtFirstName.Leave, TxtLastName.Leave
Dim ctrl as control=Directcast(Sender, Control)
ctrl.BackColor=SystemColors.Window
End Sub
Para que um evento de um objecto possa ser processado, utilizando a expressão "Handles", esse objecto teve de ser declarado com a expressão "WithEvents":
Public WithEvents FirstName as System.Windows.Forms.TextBox
Tratamento de Eventos (Run Time)
Para obter o máximo de flexibilidade no tratamento de eventos, deve utilizar-se o operador "AddHandler". Nomeadamente para que seja possível:
- Tratar eventos de objectos gerados em "Run Time"
- Tratar eventos estáticos (Shared)
- Tratar eventos de colecções e "Arrays" de objectos
AddHandler Object.Event, Delegate
Exemplo:
AddHandler TextBox1.Enter, New EventHandler (AddressOf Control_Enter)
O VB consegue determinar o "Delegate EventHandler" automaticamente, por isso pode ser utilizada a versão simplificada:
AddHandler TextBox1.Enter, AddressOf Control_Enter
Neste exemplo o método "Control_Enter" tem de ser implementado, respeitando a assinatura do "Delegate EventHandler"
Inversamente, para deixar de receber as notificações de um evento, utiliza-se o operador "RemoveHandler":
RemoveHandler TextBox1.Enter, AddressOf Control_Enter
O operador "removeHandler" deve ser utilizado sempre que tenha sido utilizado previamente o operador "AddHandler", de forma a impedir que os objectos "Delegate" fiquem a consumir recursos após a destruição do objecto.
Exemplo:
Public Class Form1
Dim MyButton As New Button
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
MyButton.Text = "Teste"
MyButton.Height = 100
MyButton.Width = 100
Me.Controls.Add(MyButton)
AddHandler MyButton.Click, AddressOf ShowMessage
End Sub
Private Sub ShowMessage(ByVal sender As Object, ByVal e As EventArgs)
MsgBox("O botão foi Clicado ")
RemoveHandler MyButton.Click, AddressOf ShowMessage
End Sub
End Class
Exemplo de utilização do operador "AddHandler" para tratar eventos estáticos:
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
AddHandler Application.Idle, AddressOf Application_Idle
End Sub
Private Sub Application_Idle(ByVal sender As Object, ByVal e As EventArgs)
Label1.Text = "Caracteres: " & TextBox1.TextLength.ToString
End Sub
End Class
O operador "AddHandler" pode ainda ser utilizado para tratar eventos de colecções ou "arrays" objectos.
Exemplo:
Public Class Form1
Dim MyButton As Button
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
For x As Integer = 1 To 10
MyButton = New Button
MyButton.Name = x.ToString
MyButton.Text = x.ToString
MyButton.Height = 50
MyButton.Width = 50
MyButton.Left = x * 50 + 10
Me.Controls.Add(MyButton)
AddHandler MyButton.Click, AddressOf ShowClickedButton
Next
End Sub
Private Sub ShowClickedButton(ByVal sender As Object, ByVal e As EventArgs)
Label1.Text = "Clicou no botão:" & CType(sender, Button).Text
End Sub
End Class
Despoletar eventos
Os eventos, tal como os campos, propriedades e métodos, fazem parte do interface que um tipo expõe aos seus clientes.
Os eventos são especiais, pois são activados pelo tipo e não pelo cliente.
Exemplo:
Public Event NameChanged (ByVal sender as object, ByVal e as EventArgs)
Exemplo de uma classe que expõe um evento:
Public Class User
Public Event NameChanged(ByVal sender As Object, ByVal e As EventArgs)
Private _Name As String
Public Property Name() As String
Get
Return _Name
End Get
Set(ByVal Value As String)
If _Name <> Value Then
_Name = Value
RaiseEvent NameChanged(Me, EventArgs.Empty)
End If
End Set
End Property
End Class
Para tornar o código mais claro e aumentar a performance, deve ser utilizada a forma alternativa, que permite definir um evento em função de um "Delegate" especifico:
Public Event NameChanged As EventHandler
Event Args
Todos os eventos trabalham com dois argumentos: Uma referência para o objecto que lançou o evento e um objecto do tipo "System.EventArgs" (ou derivado, o nome termina sempre em "EventArgs") que expõe argumentos do evento através de campos ou propriedades.
Imports System.ComponentModel
Public Class User
Public Event BeforeLogin As CancelEventHandler
Public Event AfterLogin As EventHandler
Public Sub Login()
Dim e As New CancelEventArgs
RaiseEvent BeforeLogin(Me, e)
If e.Cancel Then Exit Sub
'Código para implementar o Login
RaiseEvent AfterLogin(Me, EventArgs.Empty)
End Sub
End Class
Derivando de EventArgs
Quando não existe nenhuma classe derivada de "System.EventArgs" que exponha as propriedades desejadas é necessário criar uma classe personalizada:
Imports System.componentmodel
Public Class NameChangingEventArgs
Inherits CancelEventArgs
Private _ProposedValue As String
Public Sub New(ByVal ProposedValue As String)
_ProposedValue = ProposedValue
End Sub
Public ReadOnly Property ProposedValue() As String
Get
Return _ProposedValue
End Get
End Property
End Class
Caso prático
O objectivo é criar um modelo realista do funcionamento de um mecanismo de extracção de bolas para o jogo "EuroMilhões", utilizando as técnicas estudadas para manipulação de objectos e eventos.
A lógica da aplicação é a seguinte:
- Cada bola possui um relógio interno que simula o tempo em segundos que a bola demora a passar junto a porta de saída.
- Cada vez que a bola passar junto a porta, lança um evento indicando que esta pronta a sair.
- A tômbola possui um mecanismo de abertura da porta, também dependente de um relógio interno.
- Ao ser iniciado um jogo, a tômbola procede a criação do número de bolas necessário, subscrevendo os seus eventos.
- Quando for recebido um evento de que uma bola esta pronta a sair, esta deve ser retirada, apenas se a porta estiver aberta.
- A porta da tômbola deve ser mantida fechada enquanto esta a ser processada uma bola.
- Quando a tômbola terminar de extrair todas as bolas necessárias, deve emitir um evento indicando que o jogo terminou.
Regras para desenvolvimento
- Criar um novo projecto com o template "Windows Application"
-
Criar uma classe "bola" com as seguintes propriedades
- Valor numérico da bola
- Cor (respeitando as regras do LAB 2)
- Ciclo (propriedade só de leitura, indicando o ciclo da bola em segundos, ou seja, de quanto em quanto tempo passa pela porta da tômbola)
-
O construtor da classe bola aceita como parâmetro o valor e procede aos seguintes passos:
- Atribui uma cor (pelas regras já definidas)
- Define aleatoriamente o ciclo da bola
- A classe bola deve expor o evento "QueroSair" sempre que atingir o seu ciclo.
-
Criar uma classe "Tômbola" com as seguintes propriedades:
- Chave (Propriedade só de leitura que contem a chave encontrada, através de um "array" de inteiros. A propriedade não é indexada.)
-
O construtor da classe "Tômbola" deve aceitar como argumentos:
- A quantidade de bolas necessárias para o jogo
- A quantidade de bolas a extrair
-
O construtor da classe tômbola deverá ainda realizar os passos:
- Criar as bolas necessárias, subscrevendo os seus eventos
- Iniciar o relógio que controla a abertura da porta
-
Sempre que a classe "Tômbola" receber um pedido de saída de uma bola, deverá:
- Aceitar a saída caso a porta esteja aberta
- Guardar o valor da bola
- Destruir a bola (e deixar de receber os seus eventos)
- Determinar se foram retiradas todas as bolas necessárias, e se sim, destruir as restantes bolas e fazer o "Raise" de um evento "Fimdejogo"
- No form, criar duas instâncias do objecto "tômbola"
-
Ao receber a notificação de ambas as tômbolas de que o jogo terminou, apresentar a chave no ecrã.
Solução
