Compartilhar via


Eventos personalizados e acessadores de evento em componentes do Windows Runtime

O suporte do .NET para componentes do Tempo de Execução do Windows facilita a declaração de componentes de eventos, ocultando as diferenças entre o padrão de evento da Plataforma Universal do Windows (UWP) e o padrão de evento do .NET. No entanto, ao declarar acessadores de eventos personalizados em um componente do Tempo de Execução do Windows, você deve seguir o padrão usado na UWP.

Registrando eventos

Quando você se registra para manipular um evento na UWP, o acessador add retorna um token. Para cancelar o registro, passe esse token para o acessador de remoção. Isso significa que os acessadores de adição e remoção para eventos UWP têm assinaturas diferentes dos acessadores com os quais você está acostumado.

Felizmente, os compiladores Visual Basic e C# simplificam esse processo: quando você declara um evento com acessadores personalizados em um componente do Tempo de Execução do Windows, os compiladores usam automaticamente o padrão UWP. Por exemplo, você receberá um erro do compilador se o acessador add não retornar um token. O .NET fornece dois tipos para dar suporte à implementação:

  • A estrutura EventRegistrationToken representa o token.
  • A classe T> EventRegistrationTokenTable<cria tokens e mantém um mapeamento entre tokens e manipuladores de eventos. O argumento de tipo genérico é o tipo de argumento de evento. Você cria uma instância dessa classe para cada evento, na primeira vez que um manipulador de eventos é registrado para esse evento.

O código a seguir para o evento NumberChanged mostra o padrão básico para eventos UWP. Neste exemplo, o construtor do objeto de argumento de evento, NumberChangedEventArgs, usa um único parâmetro inteiro que representa o valor numérico alterado.

Observação Esse é o mesmo padrão que os compiladores usam para eventos comuns que você declara em um componente do Tempo de Execução do Windows.

 

private EventRegistrationTokenTable<EventHandler<NumberChangedEventArgs>>
    m_NumberChangedTokenTable = null;

public event EventHandler<NumberChangedEventArgs> NumberChanged
{
    add
    {
        return EventRegistrationTokenTable<EventHandler<NumberChangedEventArgs>>
            .GetOrCreateEventRegistrationTokenTable(ref m_NumberChangedTokenTable)
            .AddEventHandler(value);
    }
    remove
    {
        EventRegistrationTokenTable<EventHandler<NumberChangedEventArgs>>
            .GetOrCreateEventRegistrationTokenTable(ref m_NumberChangedTokenTable)
            .RemoveEventHandler(value);
    }
}

internal void OnNumberChanged(int newValue)
{
    EventHandler<NumberChangedEventArgs> temp =
        EventRegistrationTokenTable<EventHandler<NumberChangedEventArgs>>
        .GetOrCreateEventRegistrationTokenTable(ref m_NumberChangedTokenTable)
        .InvocationList;
    if (temp != null)
    {
        temp(this, new NumberChangedEventArgs(newValue));
    }
}
Private m_NumberChangedTokenTable As  _
    EventRegistrationTokenTable(Of EventHandler(Of NumberChangedEventArgs))

Public Custom Event NumberChanged As EventHandler(Of NumberChangedEventArgs)

    AddHandler(ByVal handler As EventHandler(Of NumberChangedEventArgs))
        Return EventRegistrationTokenTable(Of EventHandler(Of NumberChangedEventArgs)).
            GetOrCreateEventRegistrationTokenTable(m_NumberChangedTokenTable).
            AddEventHandler(handler)
    End AddHandler

    RemoveHandler(ByVal token As EventRegistrationToken)
        EventRegistrationTokenTable(Of EventHandler(Of NumberChangedEventArgs)).
            GetOrCreateEventRegistrationTokenTable(m_NumberChangedTokenTable).
            RemoveEventHandler(token)
    End RemoveHandler

    RaiseEvent(ByVal sender As Class1, ByVal args As NumberChangedEventArgs)
        Dim temp As EventHandler(Of NumberChangedEventArgs) = _
            EventRegistrationTokenTable(Of EventHandler(Of NumberChangedEventArgs)).
            GetOrCreateEventRegistrationTokenTable(m_NumberChangedTokenTable).
            InvocationList
        If temp IsNot Nothing Then
            temp(sender, args)
        End If
    End RaiseEvent
End Event

O método estático (Shared no Visual Basic) GetOrCreateEventRegistrationTokenTable cria a instância do evento do objeto T> EventRegistrationTokenTable<lentamente. Passe o campo de nível de classe que conterá a instância da tabela de token para esse método. Se o campo estiver vazio, o método criará a tabela, armazenará uma referência à tabela no campo e retornará uma referência à tabela. Se o campo já contiver uma referência de tabela de token, o método apenas retornará essa referência.

Importante Para garantir a segurança do thread, o campo que contém a instância do evento de EventRegistrationTokenTable<T> deve ser um campo de nível de classe. Se for um campo de nível de classe, o método GetOrCreateEventRegistrationTokenTable garante que, quando vários threads tentarem criar a tabela de tokens, todos os threads obtenham a mesma instância da tabela. Para um determinado evento, todas as chamadas para o método GetOrCreateEventRegistrationTokenTable devem usar o mesmo campo de nível de classe.

Chamar o método GetOrCreateEventRegistrationTokenTable no acessador remove e no método RaiseEvent (o método OnRaiseEvent em C#) garante que nenhuma exceção ocorra se esses métodos forem chamados antes que qualquer representante do manipulador de eventos tenha sido adicionado.

Os outros membros da classe T> EventRegistrationTokenTable<usados no padrão de evento UWP incluem o seguinte:

  • O método AddEventHandler gera um token para o delegado do manipulador de eventos, armazena o delegado na tabela, adiciona-o à lista de invocação e retorna o token.

  • A sobrecarga do método RemoveEventHandler(EventRegistrationToken) remove o delegado da tabela e da lista de invocação.

    Observação Os métodos AddEventHandler e RemoveEventHandler(EventRegistrationToken) bloqueiam a tabela para ajudar a garantir a segurança do thread.

  • A propriedade InvocationList retorna um delegado que inclui todos os manipuladores de eventos que estão atualmente registrados para manipular o evento. Use esse delegado para gerar o evento ou use os métodos da classe Delegate para invocar os manipuladores individualmente.

    Observação Recomendamos que você siga o padrão mostrado no exemplo fornecido anteriormente neste artigo e copie o delegado para uma variável temporária antes de invocá-lo. Isso evita uma condição de corrida na qual um thread remove o último manipulador, reduzindo o delegado a nulo pouco antes de outro thread tentar invocar o delegado. Os delegados são imutáveis, portanto, a cópia ainda é válida.

Coloque seu próprio código nos acessadores conforme apropriado. Se a segurança do thread for um problema, você deverá fornecer seu próprio bloqueio para o código.

Usuários do C#: quando você escreve acessadores de eventos personalizados no padrão de evento UWP, o compilador não fornece os atalhos sintáticos usuais. Ele gera erros se você usar o nome do evento em seu código.

Usuários do Visual Basic: no .NET, um evento é apenas um delegado multicast que representa todos os manipuladores de eventos registrados. Levantar o evento significa apenas invocar o delegado. A sintaxe do Visual Basic geralmente oculta as interações com o delegado e o compilador copia o delegado antes de invocá-lo, conforme descrito na observação sobre segurança de thread. Ao criar um evento personalizado em um componente do Tempo de Execução do Windows, você precisa lidar diretamente com o delegado. Isso também significa que você pode, por exemplo, usar o método MulticastDelegate.GetInvocationList para obter uma matriz que contém um delegado separado para cada manipulador de eventos, se quiser invocar os manipuladores separadamente.