8. Eventos do utilizador
No capítulo anterior, discutimos o conceito de eventos associados a componentes de formulário. Agora, vamos ver como criar eventos nas nossas próprias classes.
8.1. Objetos delegados predefinidos
A noção de objeto delegado foi abordada no capítulo anterior, mas foi apenas mencionada de passagem na altura. Quando analisámos como os manipuladores de eventos dos componentes de um formulário eram declarados, deparámo-nos com código semelhante ao seguinte:
this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);
onde buttonAfficher era um componente [Button]. Esta classe tem um Click definido da seguinte forma:
![]() |
- [1]: a classe [Button]
- [2]: os seus eventos
- [3,4]: o evento Click
- [5]: a declaração do evento [Control.Click] [4].
- EventHandler é um protótipo (um modelo) de um método chamado delegado.
- event é uma palavra-chave que restringe a funcionalidade do delegado EventHandler: um objeto delegado tem funcionalidades mais ricas do que um evento.
Visite o delegado EventHandler, que é definido da seguinte forma:
![]() |
O delegado VisitEventHandler designa um modelo de método:
- com o tipo como primeiro parâmetro Object
- cujo segundo parâmetro é um EventArgs
- que não retorna nenhum resultado
Um método correspondente ao modelo definido por EventHandler poderia ser o seguinte:
private void buttonAfficher_Click(object sender, EventArgs e);
Para criar um EventHandler, proceda da seguinte forma:
EventHandler evtHandler=new EventHandler(méthode correspondant au prototype défini par le type EventHandler);
Podemos, assim, escrever:
Uma variável do tipo delegado é, na verdade, uma lista de referências a métodos correspondentes ao delegado. Para adicionar um novo método M à variável evtHandler acima, utilizamos a sintaxe:
A notação += pode ser utilizada mesmo que evtHandler seja uma lista vazia.
Instruções:
this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);
adiciona um EventHandler à lista de métodos de evento buttonAfficher.Click. Quando ocorre o evento Click no componente buttonAfficher, o VB executa o:
onde:
- source é o objeto por trás do evento
- evt é do tipo EventArgs e não contém informações
Todos os métodos com assinatura void M(object, EventArgs) associados ao evento Click por:
this.buttonAfficher.Click += new System.EventHandler(M);
serão chamados com os parâmetros (source, evt) transmitidos pelo VB.
8.2. Definir objetos delegados
Instrução
public delegate int Opération(int n1, int n2);
define um tipo chamado Operation que representa 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.
Uma variável op do tipo Operation irá registar uma lista de funções correspondentes ao protótipo Operation :
O registo de um método fi na variável op é feito através de op=new Operation(fi) ou simplesmente op=fi. Para adicionar um método fj à lista de funções registadas, escrevemos op+= fj. Para remover um método fk, escrevemos op-=fk. Se, no nosso exemplo, escrevermos n=op(n1,n2), o conjunto de métodos armazenados em op será executado com os parâmetros n1 e n2. O resultado n obtido será o do último método executado. Não é possível obter os resultados produzidos por todos os métodos. Por esta razão, se armazenar uma lista de métodos numa função delegada, estes irão normalmente devolver um resultado do tipo void.
Considere o seguinte exemplo:
using System;
namespace Chap6 {
class Class1 {
// function prototype definition
// accepts 2 integers as parameters and returns an integer
public delegate int Opération(int n1, int n2);
// two instance methods corresponding to the prototype
public int Ajouter(int n1, int n2) {
Console.WriteLine("Ajouter(" + n1 + "," + n2 + ")");
return n1 + n2;
}//add
public int Soustraire(int n1, int n2) {
Console.WriteLine("Soustraire(" + n1 + "," + n2 + ")");
return n1 - n2;
}//subtract
// a static method corresponding to the prototype
public static int Augmenter(int n1, int n2) {
Console.WriteLine("Augmenter(" + n1 + "," + n2 + ")");
return n1 + 2 * n2;
}//increase
static void Main(string[] args) {
// define an operation object to store functions
// we register the static function increase
Opération op = Augmenter;
// the delegate is executed
int n = op(4, 7);
Console.WriteLine("n=" + n);
// creation of a c1 object of type class1
Class1 c1 = new Class1();
// we register c1's add method in the delegate
op = c1.Ajouter;
// execution of delegated object
n = op(2, 3);
Console.WriteLine("n=" + n);
// the subtract method of c1 is registered in the delegate
op = c1.Soustraire;
n = op(2, 3);
Console.WriteLine("n=" + n);
//registration of two functions in the delegate
op = c1.Ajouter;
op += c1.Soustraire;
// execution of delegated object
op(0, 0);
// remove a function from the delegate
op -= c1.Soustraire;
// the delegate is executed
op(1, 1);
}
}
}
- linha 3: define uma classe Class1.
- linha 6: definição do delegado Operation: um protótipo de métodos que aceitam dois parâmetros do tipo int e devolvem um int
- linhas 9-12: o método de instância Add à assinatura do delegado Operation.
- linhas 14-17: o método de instância S subtract à assinatura do delegado Operation.
- linhas 20-23: o método de classe Ancrease à assinatura do delegado Operation.
- linha 25: o método Main é executado
- linha 20: a variável op é do tipo delegado Operation. Ela conterá uma lista de métodos com a assinatura de tipo delegado Operation. É-lhe atribuída uma primeira referência de método, a do método estático Class1.Augmenter.
- linha 31: o delegado op é executado: todos os métodos referenciados por op serão executados. Serão executados com os parâmetros passados ao delegado op. Aqui, apenas o método estático Class1.Augmenter será executado.
- linha 35: é criada uma instância c1 da classe Class1.
- linha 37: o método de instância c1.Ajouter é atribuído ao delegado op. Increase era um método estático, Add é um método de instância. Queríamos mostrar que isso não importava.
- linha 39: o delegado op é executado: o Add será executado com os parâmetros passados para o delegado op.
- linha 42: faça o mesmo com o método de instância Subtract.
- linhas 46-47: métodos Add e Subtract no delegado op.
- linha 49: o delegado op é executado: tanto Add como Subtract serão executados com os parâmetros passados para o delegado op.
- linha 51: o método Subtract é removido da operação delegada.
- linha 53: a operação delegada é executada: o Add restante será executado.
Os resultados são os seguintes:
8.3. Delegados ou interfaces?
Os conceitos de delegados e interfaces podem parecer bastante semelhantes, e podemos questionar-nos sobre quais são exatamente as diferenças entre estes dois conceitos. Vejamos o seguinte exemplo, semelhante a um que já estudámos:
using System;
namespace Chap6 {
class Program1 {
// function prototype definition
// accepts 2 integers as parameters and returns an integer
public delegate int Opération(int n1, int n2);
// two instance methods corresponding to the prototype
public static int Ajouter(int n1, int n2) {
Console.WriteLine("Ajouter(" + n1 + "," + n2 + ")");
return n1 + n2;
}//add
public static int Soustraire(int n1, int n2) {
Console.WriteLine("Soustraire(" + n1 + "," + n2 + ")");
return n1 - n2;
}//subtract
// Executing a delegate
public static int Execute(Opération op, int n1, int n2){
return op(n1, n2);
}
static void Main(string[] args) {
// delegate execution Add
Console.WriteLine(Execute(Ajouter, 2, 3));
// delegate execution Subtract
Console.WriteLine(Execute(Soustraire, 2, 3));
// executing a multicast delegate
Opération op = Ajouter;
op += Soustraire;
Console.WriteLine(Execute(op, 2, 3));
// remove a function from the delegate
op -= Soustraire;
// the delegate is executed
Console.WriteLine(Execute(op, 2, 3));
}
}
}
Na linha 20, o método Execute espera uma referência a um objeto do tipo delegado Operation definido na linha 6. Isto muda para o Execute, métodos diferentes (linhas 26, 28, 32 e 36). Esta propriedade de polimorfismo também pode ser alcançada com um :
using System;
namespace Chap6 {
// interface IOperation
public interface IOperation {
int operation(int n1, int n2);
}
// class Add
public class Ajouter : IOperation {
public int operation(int n1, int n2) {
Console.WriteLine("Ajouter(" + n1 + "," + n2 + ")");
return n1 + n2;
}
}
// class Subtract
public class Soustraire : IOperation {
public int operation(int n1, int n2) {
Console.WriteLine("Soustraire(" + n1 + "," + n2 + ")");
return n1 - n2;
}
}
// test class
public static class Program2 {
// Executing the single method of the IOperation interface
public static int Execute(IOperation op, int n1, int n2) {
return op.operation(n1, n2);
}
public static void Main() {
// delegate execution Add
Console.WriteLine(Execute(new Ajouter(), 2, 3));
// delegate execution Subtract
Console.WriteLine(Execute(new Soustraire(), 2, 3));
}
}
}
- linhas 6-8: a interface [IOperation] define um método operation.
- linhas 11-16 e 19-24: as classes [Add] e [Subtract] implementam a interface [IOperation].
- linhas 29-31: o método Execute, cujo primeiro parâmetro é do tipo de interface IOperation. O método Execute receberá sucessivamente, como primeiro parâmetro, uma instância da classe Add e, em seguida, uma instância da classe Subtract.
O aspeto polimórfico do delegado do exemplo anterior. Ao mesmo tempo, ambos os exemplos mostram as diferenças entre estas duas noções.
Os tipos delegado e interface são intercambiáveis
- se a interface tiver apenas um método. Na verdade, o delegado é um invólucro para um único método, enquanto a interface pode definir vários métodos.
- se o aspeto multicast do delegado não for utilizado. A noção de multicast não existe na interface.
Se estas duas condições forem satisfeitas, podemos escolher entre as duas assinaturas seguintes para o método Execute:
int Execute(IOperation op, int n1, int n2)
int Execute(Opération op, int n1, int n2)
A segunda, que utiliza o delegado, pode ser utilizada de forma mais flexível. Na primeira assinatura, o primeiro parâmetro do método deve implementar a interface IOperation. Isto requer a criação de uma classe para definir o método a ser passado como primeiro parâmetro para o Execute. Na segunda assinatura, qualquer método existente com a assinatura correta serve. Não é necessária nenhuma construção adicional.
8.4. Gestão de eventos
As classes de delegados de objetos podem ser utilizadas para definir eventos. Uma classe C1 pode definir um evento evt da seguinte forma:
- um tipo delegado é definido dentro ou fora da classe C1:
- a classe C1 define um delegado Evt :
- Quando uma instância c1 da classe C1 quiser reportar um evento, irá executar o seu delegado Evt1, passando-lhe os parâmetros definidos pelo delegado Evt. Todos os métodos registados no delegado Evt1 serão então executados com esses parâmetros. Podemos dizer que foram notificados do evento Evt1.
- Se um objeto c2 que utiliza um c1 quiser ser notificado quando ocorrer um evento Evt1 no objeto c1, irá registar um dos seus c2.M no objeto delegado c1.Evt1 do objeto c1. O seu c2.M será executado sempre que o evento Evt1 ocorrer no objeto c1. Também podem cancelar a subscrição se já não desejarem ser notificados do evento.
- Como o objeto delegado c1.Evt1 pode registar vários métodos, diferentes ci podem registar-se no c1.Evt1 para serem notificados do evento Evt1 no c1.
Neste cenário, temos:
- uma classe que sinaliza um evento
- classes que são notificadas deste evento. Diz-se que estas se subscrevem ao evento.
- um tipo delegado que define a assinatura dos métodos que serão notificados do evento
O .NET Framework define:
- uma assinatura padrão do delegado de um evento
- source: o objeto que comunicou o evento
- evtInfo: um objeto do tipo EventArgs ou derivado que fornece informações sobre o evento
- o nome do delegado deve terminar em EventHandler
- uma forma padrão de declarar um MyEventHandler numa classe:
O tipo Evt1 é um delegado. A palavra-chave event existe para restringir as operações que podem ser realizadas sobre ele:
- fora da classe C1, apenas as operações += e -= são possíveis. Isto impede que os métodos subscritos ao evento sejam eliminados (por erro do programador, por exemplo). Pode simplesmente subscrever (+=) ou cancelar a subscrição (-=) do evento.
- apenas uma instância do tipo C1 pode efetuar a chamada Evt1(source,evtInfo), que desencadeia a execução dos métodos subscritos ao Evt1.
O .NET Framework fornece um método genérico que satisfaz a assinatura do delegado de um evento:
public delegate void EventHandler<TEventArgs>(object source, TEventArgs evtInfo) where TEventArgs : EventArgs
- o delegado EventHandler utiliza o tipo genérico TEventArgs, que é o tipo do seu segundo parâmetro
- o tipo TEventArgs deve ser derivado de EventArgs (onde TEventArgs : EventArgs)
Com este delegado genérico, a declaração de um evento X na classe C seguirá o seguinte esquema recomendado:
- definir um tipo XEventArgs derivado de EventArgs para encapsular as informações do evento X
- definir na classe C um EventHandler<XEventArgs>.
- definir na classe C um método protegido
para «publicar» o evento X aos subscritores.
Considere o seguinte exemplo:
- uma classe Transmitter encapsula uma temperatura. Esta temperatura é monitorizada. Quando esta temperatura excede um determinado limiar, é acionado um evento. Vamos chamar a este evento TemperatureTropHaute. A informação sobre este evento será encapsulada num tipo TemperatureTropHauteEventArgs.
- uma classe Underwriter subscreve o evento anterior. Quando notificada do evento, exibe uma mensagem na consola.
- Um programa de consola cria um transmissor e dois subscritores. Introduz as temperaturas a partir do teclado e armazena-as num Transmitter. Se esta for demasiado elevada, o Transmitter publica o evento TemperatureTropHaute.
Para estar em conformidade com o método recomendado de gestão de eventos, definimos primeiro o tipo TemperatureTropHauteEventArgs para encapsular as informações do evento:
using System;
namespace Chap6 {
public class TemperatureTropHauteEventArgs:EventArgs {
// temperature during evt
public decimal Temperature { get; set; }
// manufacturers
public TemperatureTropHauteEventArgs() {
}
public TemperatureTropHauteEventArgs(decimal temperature) {
Temperature = temperature;
}
}
}
- linha 6: a informação encapsulada pelo TemperatureTropHauteEventArgs é a temperatura que causou o evento TemperatureTropHaute.
A classe Transmitter é a seguinte:
using System;
namespace Chap6 {
public class Emetteur {
static decimal SEUIL = 19;
// observed temperature
private decimal temperature;
// name of source
public string Nom { get; set; }
// event reported
public event EventHandler<TemperatureTropHauteEventArgs> TemperatureTropHaute;
// read/write temperature
public decimal Temperature {
get {
return temperature;
}
set {
temperature = value;
if (temperature > SEUIL) {
// subscribers are notified of the event
OnTemperatureTropHaute(new TemperatureTropHauteEventArgs(temperature));
}
}
}
// reporting an event
protected virtual void OnTemperatureTropHaute(TemperatureTropHauteEventArgs evt) {
// issue of event TemperatureTropHaute to subscribers
TemperatureTropHaute(this, evt);
}
}
}
- linha 5: o limiar de temperatura acima do qual o evento TemperatureTropHaute será publicado.
- linha 10: o remetente tem um nome para fins de identificação
- linha 12: o evento TemperatureTropHaute.
- linhas 15-26: o método get que obtém a temperatura e o método set que a regista. Este é o conjunto para publicar o evento TemperatureTropHaute se a temperatura a registar exceder o limiar na linha 5. Publica o evento utilizando o OnTemperatureTropHauteHandler da linha 29, passando um TemperatureTropHauteEventArgs no qual foi registada a temperatura que excedeu o limiar.
- linhas 29-32: o evento TemperatureTropHaute é publicado com o próprio remetente como primeiro parâmetro e o objeto TemperatureTropHauteEventArgs recebido como parâmetro.
A classe Underwriter que irá subscrever o evento TemperatureTropHaute é a seguinte:
using System;
namespace Chap6 {
public class Souscripteur {
// name
public string Nom { get; set; }
// event manager TemperatureTropHaute
public void EvtTemperatureTropHaute(object source, TemperatureTropHauteEventArgs e) {
// operator console display
Console.WriteLine("Souscripteur [{0}] : la source [{1}] a signalé une température trop haute : [{2}]", Nom, ((Emetteur)source).Nom, e.Temperature);
}
}
}
- linha 6: cada assinante é identificado pelo nome.
- linhas 9-12: o método a ser associado ao evento TemperatureTropHaute. Tem a assinatura do tipo delegado EventHandler<TEventArgs> que um gestor de eventos deve possuir. O método apresenta no console: o nome do subscritor que exibe a mensagem, o nome do remetente que comunicou o evento e a temperatura que desencadeou o evento.
- A subscrição do evento TemperatureTropHaute de um objeto Transmitter não é feita na classe Underwriter. Será feita por uma classe externa.
O programa [Program.cs] liga todos estes elementos:
using System;
namespace Chap6 {
class Program {
static void Main(string[] args) {
// creation of an evts transmitter
Emetteur e1 = new Emetteur() { Nom = "e" };
// creation of a table of 2 subscribers
Souscripteur[] souscripteurs = new Souscripteur[2];
for (int i = 0; i < souscripteurs.Length; i++) {
// creation subscriber
souscripteurs[i] = new Souscripteur() { Nom = "s" + i };
// we subscribe him to e1's TemperatureTropHaute event
e1.TemperatureTropHaute += souscripteurs[i].EvtTemperatureTropHaute;
}
// temperatures are read from the keyboard
decimal temperature;
Console.Write("Température (rien pour arrêter) : ");
string saisie = Console.ReadLine().Trim();
// as long as the line entered is not empty
while (saisie != "") {
// is the input a decimal number?
if (decimal.TryParse(saisie, out temperature)) {
// correct temperature - recorded
e1.Temperature = temperature;
} else {
// on signale l'erreur
Console.WriteLine("Température incorrecte");
}
// new entry
Console.Write("Température (rien pour arrêter) : ");
saisie = Console.ReadLine().Trim();
}//while
}
}
}
- linha 6: criação do transmissor
- linhas 8-14: criação de dois assinantes do evento TemperatureTropHaute do transmissor.
- linhas 20-32: loop de entrada de temperatura pelo teclado
- linha 24: se a temperatura introduzida estiver correta, é transmitida ao objeto Transmitter e1, que acionará o TemperatureTropHaute se a temperatura for superior a 19 °C.
Os resultados são os seguintes:

