Eventos (C++/CX)
Um tipo do Windows Runtime pode declarar (isto é, publicar) eventos, e o código do cliente no mesmo componente ou em outros componentes pode assinar esses eventos associando métodos chamados manipuladores de eventos a esse evento. Vários manipuladores de eventos podem ser associados a um único evento. Quando o objeto de publicação gera o evento, ele faz com que todos os manipuladores de eventos sejam invocados. Dessa forma, uma classe assinante poderá executar qualquer ação personalizada que seja apropriada quando o editor gerar o evento. Um evento possui um tipo delegate que especifica a assinatura que todos os manipuladores de eventos devem ter para assinar o evento.
Consumindo eventos em componentes do Windows
Muitos componentes no Windows Runtime expõem eventos. Por exemplo, um objeto LightSensor dispara um evento ReadingChanged quando o sensor relata um novo valor de luminescência. Ao usar um objeto LightSensor em seu programa, você pode definir um método que será chamado quando o evento ReadingChanged for acionado. O método pode fazer tudo o que você quiser; o único requisito é que sua assinatura corresponda à assinatura do delegado que é invocado. Para obter mais informações sobre como criar um manipulador de eventos delegado e assinar um evento, consulte Delegados.
Criando eventos personalizados
Declaração
Você pode declarar um evento em uma classe ref ou uma interface, e ele pode ter acessibilidade pública, interna (pública/privada, pública protegida, protegida, privada protegida ou privada. Quando você declara um evento, internamente o compilador cria um objeto que expõe dois métodos acessadores: add e remove. Ao assinar manipuladores de eventos de registro de objetos, o objeto de evento armazena-os em uma coleção. Quando um evento é acionado, o objeto de evento invoca todos os manipuladores em sua lista, um a um. Um evento trivial, como o do exemplo a seguir, tem um repositório de backup implícito, bem como métodos acessadores add
e remove
implícitos. Você também pode especificar seus próprios acessadores, da mesma forma que pode especificar acessadores get
e set
personalizados em uma propriedade. A classe de implementação não pode percorrer manualmente a lista de assinantes de evento em um evento trivial.
O exemplo de código a seguir mostra como declarar e disparar um evento. Observe que o evento tem um tipo delegate e é declarado com o símbolo “^”.
namespace EventTest
{
ref class Class1;
public delegate void SomethingHappenedEventHandler(Class1^ sender, Platform::String^ s);
public ref class Class1 sealed
{
public:
Class1(){}
event SomethingHappenedEventHandler^ SomethingHappened;
void DoSomething()
{
//Do something....
// ...then fire the event:
SomethingHappened(this, L"Something happened.");
}
};
}
Uso
O exemplo a seguir mostra como uma classe assinante usa o operador +=
para assinar o evento e fornecer um manipulador de eventos a ser invocado quando o evento for acionado. Observe que a função fornecida corresponde à assinatura do representante que é definido no lado do editor no namespace EventTest
.
namespace EventClient
{
using namespace EventTest;
namespace PC = Platform::Collections; //#include <collection.h>
public ref class Subscriber sealed
{
public:
Subscriber() : eventCount(0)
{
// Instantiate the class that publishes the event.
publisher= ref new EventTest::Class1();
// Subscribe to the event and provide a handler function.
publisher->SomethingHappened +=
ref new EventTest::SomethingHappenedEventHandler(
this,
&Subscriber::MyEventHandler);
eventLog = ref new PC::Map<int, Platform::String^>();
}
void SomeMethod()
{
publisher->DoSomething();
}
void MyEventHandler(EventTest::Class1^ mc, Platform::String^ msg)
{
// Our custom action: log the event.
eventLog->Insert(eventCount, msg);
eventCount++;
}
private:
PC::Map<int, Platform::String^>^ eventLog;
int eventCount;
EventTest::Class1^ publisher;
};
}
Aviso
Em geral, é melhor usar uma função nomeada, em vez de uma lambda, para um manipulador de eventos, a menos que você seja muito cuidadoso a fim de evitar referências circulares. Uma função nomeada captura o ponteiro "this" por referência fraca, enquanto uma lambda captura-o por referência forte e cria uma referência circular. Para obter mais informações, consulte Referências fracas e quebra de ciclos (C++/CX).
Métodos add e remove personalizados
Internamente, um evento tem um método add, um método remove e um método raise. Quando o código do cliente assina um evento, o método add é chamado e o representante que é transmitido é adicionado à lista de invocação de eventos. A classe de publicação invoca o evento, o que faz com que o método raise() seja chamado e cada representante na lista seja invocado um após o outro. Um assinante pode remover a si mesmo da lista, o que faz com que o método remove do evento seja chamado. O compilador fornecerá versões padrão desses métodos se você não defini-los no código; eles são conhecidos como eventos triviais. Em muitos casos, tudo o que se precisa é de um evento trivial.
Você pode especificar métodos add, remove e raise personalizados para qualquer evento se tiver que executar lógica personalizada em resposta à adição ou remoção de assinantes. Por exemplo, se você tiver um objeto caro que seja necessário somente para relatórios de eventos, poderá adiar a criação do objeto até um cliente realmente assinar o evento.
O exemplo a seguir mostra como adicionar métodos add, remove e raise personalizados a um evento:
namespace EventTest2
{
ref class Class1;
public delegate void SomethingHappenedEventHandler(Class1^ sender, Platform::String^ msg);
public ref class Class1 sealed
{
public:
Class1(){}
event SomethingHappenedEventHandler^ SomethingHappened;
void DoSomething(){/*...*/}
void MethodThatFires()
{
// Fire before doing something...
BeforeSomethingHappens(this, "Something's going to happen.");
DoSomething();
// ...then fire after doing something...
SomethingHappened(this, L"Something happened.");
}
event SomethingHappenedEventHandler^ _InternalHandler;
event SomethingHappenedEventHandler^ BeforeSomethingHappens
{
Windows::Foundation::EventRegistrationToken add(SomethingHappenedEventHandler^ handler)
{
// Add custom logic here:
//....
return _InternalHandler += handler;
}
void remove(Windows::Foundation::EventRegistrationToken token)
{
// Add custom logic here:
//....
_InternalHandler -= token;
}
void raise(Class1^ sender, Platform::String^ str)
{
// Add custom logic here:
//....
return _InternalHandler(sender, str);
}
}
};
}
Removendo um manipulador de eventos do lado do assinante
Em alguns casos raros, talvez você queira remover um manipulador de eventos para um evento ao qual tenha assinado anteriormente. Por exemplo, você pode querer substituí-lo por outro manipulador de eventos ou pode querer excluir alguns recursos que são mantidos por ele. Para remover um manipulador, você deve armazenar o EventRegistrationToken que é retornado da operação +=
. Em seguida, você pode usar o operador de -=
no token para remover um manipulador de eventos. No entanto, o manipulador original ainda pode ser invocado, mesmo depois de removido. Por exemplo, uma condição de corrida pode surgir quando a origem do evento obtém uma lista de manipuladores e começa a invocá-los. Se um manipulador de eventos for removido enquanto isso acontecer, a lista ficará desatualizada. Portanto, se você pretende remover um manipulador de eventos, crie um sinalizador de membro. Defina-o se o evento for removido e, no manipulador de eventos, verifique o sinalizador e retorne imediatamente se estiver definido. O exemplo a seguir mostra o padrão básico.
namespace EventClient2
{
using namespace EventTest2;
ref class Subscriber2 sealed
{
private:
bool handlerIsActive;
Platform::String^ lastMessage;
void TestMethod()
{
Class1^ c1 = ref new Class1();
handlerIsActive = true;
Windows::Foundation::EventRegistrationToken cookie =
c1->SomethingHappened +=
ref new EventTest2::SomethingHappenedEventHandler(this, &Subscriber2::MyEventHandler);
c1->DoSomething();
// Do some other work�..then remove the event handler and set the flag.
handlerIsActive = false;
c1->SomethingHappened -= cookie;
}
void MyEventHandler(Class1^ mc, Platform::String^ msg)
{
if (!handlerIsActive)
return;
lastMessage = msg;
}
};
}
Comentários
Vários manipuladores podem ser associados ao mesmo evento. A origem do evento é chamada sequencialmente em todos os manipuladores de evento do mesmo thread. Se um receptor de evento for bloqueado no método do manipulador de eventos, isso impedirá a origem do evento de acionar outros manipuladores de evento para esse evento.
A ordem na qual a origem do evento invoca manipuladores de evento em receptores de evento não é garantida e pode ser diferente de uma chamada para outra.
Confira também
Sistema de tipos
Representantes
Referência da linguagem C++/CX
Referência de namespaces