Compartilhar via


Manipular eventos usando delegados no C++/WinRT

Este tópico mostra como registrar e revogar delegados de manipulação de eventos usando C++/WinRT. Você pode manipular um evento usando qualquer objeto de função C++ padrão.

Observação

Para obter informações sobre como instalar e usar a Extensão C++/WinRT para Visual Studio (VSIX) e o pacote NuGet (que juntos fornecem suporte ao modelo de projeto e ao build), consulte suporte do Visual Studio para C++/WinRT.

Usando o Visual Studio para adicionar um manipulador de eventos

Uma maneira conveniente de adicionar um manipulador de eventos ao seu projeto é usando a interface do usuário (interface do usuário) do Designer XAML no Visual Studio. Com a página XAML aberta no Designer XAML, selecione o controle cujo evento você deseja manipular. Na página de propriedades desse controle, clique no ícone de raio para listar todos os eventos originados por esse controle. Em seguida, clique duas vezes no evento que você deseja manipular; por exemplo, OnClicked .

O Designer XAML adiciona o protótipo de função do manipulador de eventos apropriado (e uma implementação de stub) aos arquivos de origem, pronto para que você substitua por sua própria implementação.

Observação

Normalmente, os manipuladores de eventos não precisam ser descritos no arquivo Midl (.idl). Portanto, o Designer XAML não adiciona protótipos de função do manipulador de eventos ao arquivo Midl. Ele só adiciona seus arquivos .h e .cpp.

Cadastrar um delegado para gerenciar um evento

Um exemplo simples é lidar com o evento de clique de um botão. É comum usar a marcação XAML para registrar uma função membro para lidar com o evento, como mostrado a seguir.

// MainPage.xaml
<Button x:Name="myButton" Click="ClickHandler">Click Me</Button>
// MainPage.h
void ClickHandler(
    winrt::Windows::Foundation::IInspectable const& sender,
    winrt::Windows::UI::Xaml::RoutedEventArgs const& args);

// MainPage.cpp
void MainPage::ClickHandler(
    IInspectable const& /* sender */,
    RoutedEventArgs const& /* args */)
{
    myButton().Content(box_value(L"Clicked"));
}

O código acima é obtido do projeto App em Branco (C++/WinRT) no Visual Studio. O código myButton() chama uma função de acessador gerada, que retorna o botão que chamamos de myButton. Se você alterar o elemento x:NameButton, o nome da função de acesso gerada também será alterado.

Observação

Nesse caso, a origem do evento (o objeto que aciona o evento) é o botão chamado myButton. E o destinatário do evento (o objeto que está tratando o evento) é uma instância de MainPage. Há mais informações posteriormente neste tópico sobre como gerenciar o tempo de vida de fontes de eventos e destinatários de eventos.

Em vez de fazer isso declarativamente na marcação, você pode registrar de maneira imperativa uma função membro para lidar com um evento. Pode não ser óbvio no exemplo de código abaixo, mas o argumento para a chamada ButtonBase::Click é uma instância do delegado RoutedEventHandler. Nesse caso, estamos usando a sobrecarga do construtor RoutedEventHandler que usa um objeto e uma função de ponteiro para membro.

// MainPage.cpp
MainPage::MainPage()
{
    InitializeComponent();

    myButton().Click({ this, &MainPage::ClickHandler });
}

Importante

Ao registrar o delegado, o exemplo de código acima passa um bruto neste ponteiro de (apontando para o objeto atual). Para saber como estabelecer uma referência forte ou fraca ao objeto atual, consulte se você usar uma função de membro como delegado.

Aqui está um exemplo que usa uma função de membro estático; observe a sintaxe mais simples.

// MainPage.h
static void ClickHandler(
    winrt::Windows::Foundation::IInspectable const& sender,
    winrt::Windows::UI::Xaml::RoutedEventArgs const& args);

// MainPage.cpp
MainPage::MainPage()
{
    InitializeComponent();

    myButton().Click( MainPage::ClickHandler );
}
void MainPage::ClickHandler(
    IInspectable const& /* sender */,
    RoutedEventArgs const& /* args */) { ... }

Há outras maneiras de construir um RoutedEventHandler. Veja abaixo o bloco de sintaxe retirado do tópico de documentação do RoutedEventHandler (escolha C++/WinRT na lista suspensa Idioma no canto superior direito da página da Web). Observe os vários construtores: um usa um lambda; outro uma função livre; e outro (aquele que usamos acima) usa um objeto e um ponteiro para função-membro.

struct RoutedEventHandler : winrt::Windows::Foundation::IUnknown
{
    RoutedEventHandler(std::nullptr_t = nullptr) noexcept;
    template <typename L> RoutedEventHandler(L lambda);
    template <typename F> RoutedEventHandler(F* function);
    template <typename O, typename M> RoutedEventHandler(O* object, M method);
    /* ... other constructors ... */
    void operator()(winrt::Windows::Foundation::IInspectable const& sender,
        winrt::Windows::UI::Xaml::RoutedEventArgs const& e) const;
};

A sintaxe do operador de chamada de função também é útil de ver. Ele informa quais precisam ser os parâmetros do delegado. Como você pode ver, nesse caso, a sintaxe do operador de chamada de função corresponde aos parâmetros de nosso MainPage::ClickHandler.

Observação

Para qualquer evento específico, para descobrir os detalhes de seu representante e os parâmetros desse delegado, vá primeiro para o tópico de documentação do próprio evento. Vamos usar o evento UIElement.KeyDown como exemplo. Visite esse tópico e escolha C++/WinRT na lista suspensa Idioma. No bloco de sintaxe no início do tópico, você verá isso.

// Register
event_token KeyDown(KeyEventHandler const& handler) const;

Essas informações nos dizem que o evento UIElement.KeyDown (o tópico que estamos discutindo) tem um tipo delegado de KeyEventHandler, pois esse é o tipo que você passa quando registra um delegado com esse tipo de evento. Portanto, agora siga o link no tópico para aquele tipo de delegado KeyEventHandler. Aqui, o bloco de sintaxe contém um operador de chamada de função. E, conforme mencionado acima, isso informa quais são os parâmetros do delegado.

void operator()(
  winrt::Windows::Foundation::IInspectable const& sender,
  winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e) const;

Como você pode ver, o delegado precisa ser declarado para receber um IInspectable como remetente e uma instância da classe KeyRoutedEventArgs como os argumentos.

Para dar outro exemplo, vamos examinar o evento Popup.Closed. O tipo de delegado é EventHandler<IInspectable>. Portanto, seu delegado usará um IInspectable como remetente e outro IInspectable (porque esse é o parâmetro de tipo do EventHandler) como os argumentos.

Se você não estiver fazendo muito trabalho no manipulador de eventos, poderá usar uma função lambda em vez de uma função de membro. Novamente, pode não ser óbvio no exemplo de código abaixo, mas um delegado RoutedEventHandler está sendo construído a partir de uma função lambda que, novamente, precisa corresponder à sintaxe do operador de chamada de função que discutimos acima.

MainPage::MainPage()
{
    InitializeComponent();

    myButton().Click([this](IInspectable const& /* sender */, RoutedEventArgs const& /* args */)
    {
        myButton().Content(box_value(L"Clicked"));
    });
}

Você pode optar por ser um pouco mais explícito ao construir seu delegado. Por exemplo, se você quiser passá-lo adiante ou usá-lo mais de uma vez.

MainPage::MainPage()
{
    InitializeComponent();

    auto click_handler = [](IInspectable const& sender, RoutedEventArgs const& /* args */)
    {
        sender.as<winrt::Windows::UI::Xaml::Controls::Button>().Content(box_value(L"Clicked"));
    };
    myButton().Click(click_handler);
    AnotherButton().Click(click_handler);
}

Revogar um delegado registrado

Quando você registra um delegado, normalmente um token é retornado para você. Posteriormente, você pode usar esse token para revogar seu delegado; o que significa que o delegado não foi registrado do evento e não será chamado caso o evento seja acionado novamente.

Para simplificar, nenhum dos exemplos de código acima mostrou como fazer isso. Mas este próximo exemplo de código armazena o token no membro de dados privado da estrutura e revoga o seu manipulador no destruidor.

struct Example : ExampleT<Example>
{
    Example(winrt::Windows::UI::Xaml::Controls::Button const& button) : m_button(button)
    {
        m_token = m_button.Click([this](IInspectable const&, RoutedEventArgs const&)
        {
            // ...
        });
    }
    ~Example()
    {
        m_button.Click(m_token);
    }

private:
    winrt::Windows::UI::Xaml::Controls::Button m_button;
    winrt::event_token m_token;
};

Em vez de uma referência forte, como no exemplo acima, você pode armazenar uma referência fraca ao botão (consulte referências fortes e fracas em C++/WinRT).

Observação

Quando uma fonte de evento gera seus eventos de forma síncrona, você pode revogar seu manipulador e ter certeza de que não receberá mais eventos. No entanto, para eventos assíncronos, mesmo após a revogação (e especialmente ao revogar dentro do destruidor), um evento em andamento pode atingir seu objeto depois de ele ter começado a ser destruído. Encontrar um local para cancelar a assinatura antes da destruição pode atenuar o problema ou, para uma solução robusta, veja Acessar com segurança o esse ponteiro com um representante de manipulação de eventos.

Como alternativa, ao registrar um delegado, você pode especificar winrt::auto_revoke (que é um valor do tipo winrt::auto_revoke_t) para solicitar um revogador de evento (do tipo winrt::event_revoker). O revogador de eventos contém uma referência fraca à origem do evento (o objeto que gera o evento) para você. Você pode revogar manualmente chamando a função event_revoker::revoke membro; mas o revogador de eventos chama essa função automaticamente quando ela sai do escopo. A função revogar verifica se a origem do evento ainda existe e, nesse caso, revoga seu delegado. Neste exemplo, não é necessário armazenar a origem do evento e não há necessidade de um destruidor.

struct Example : ExampleT<Example>
{
    Example(winrt::Windows::UI::Xaml::Controls::Button button)
    {
        m_event_revoker = button.Click(
            winrt::auto_revoke,
            [this](IInspectable const& /* sender */,
            RoutedEventArgs const& /* args */)
        {
            // ...
        });
    }

private:
    winrt::Windows::UI::Xaml::Controls::Button::Click_revoker m_event_revoker;
};

O bloco de sintaxe abaixo foi retirado do tópico de documentação relacionado ao evento ButtonBase::Click. Ele mostra as três funções de registro e revogação diferentes. Você pode ver exatamente que tipo de revogador de eventos você precisa declarar da terceira sobrecarga. E você pode passar os mesmos tipos de delegados para o registro e o revogar com sobrecargas de event_revoker.

// Register
winrt::event_token Click(winrt::Windows::UI::Xaml::RoutedEventHandler const& handler) const;

// Revoke with event_token
void Click(winrt::event_token const& token) const;

// Revoke with event_revoker
Button::Click_revoker Click(winrt::auto_revoke_t,
    winrt::Windows::UI::Xaml::RoutedEventHandler const& handler) const;

Observação

No exemplo de código acima, Button::Click_revoker há um alias de tipo para winrt::event_revoker<winrt::Windows::UI::Xaml::Controls::Primitives::IButtonBase>. Um padrão semelhante se aplica a todos os eventos C++/WinRT. Cada evento do Windows Runtime tem uma sobrecarga de função de revogação que retorna um revogador de eventos, e o tipo desse revogador é um membro da origem do evento. Portanto, para obter outro exemplo, o evento CoreWindow::SizeChanged tem uma sobrecarga de função de registro que retorna um valor do tipo CoreWindow::SizeChanged_revoker.

Você pode considerar a revogação de manipuladores em um cenário de navegação de página. Se você estiver navegando repetidamente em uma página e depois saindo dela, poderá revogar todos os manipuladores ao sair da página. Como alternativa, se você estiver usando novamente a mesma instância de página, verifique o valor do token e registre-se apenas se ele ainda não tiver sido definido (if (!m_token){ ... }). Uma terceira opção é armazenar um revogador de eventos na página como um membro de dados. E uma quarta opção, conforme descrito posteriormente neste tópico, é capturar uma referência forte ou fraca ao este objeto em sua função lambda.

Se o delegado de revogação automática não for registrado

Se você tentar especificar winrt::auto_revoke ao registrar um delegado e o resultado for uma exceção winrt::hresult_no_interface, isso geralmente significa que a origem do evento não dá suporte a referências fracas. Essa é uma situação comum no namespace Windows.UI.Composition , por exemplo. Nessa situação, você não pode usar o recurso de revogação automática. Você precisará voltar a revogar manualmente seus manipuladores de eventos.

Delegar tipos para operações e ações assíncronas

Os exemplos acima usam o tipo de delegado RoutedEventHandler, mas há, naturalmente, muitos outros tipos de delegado. Por exemplo, ações e operações assíncronas (com e sem progresso) se completaram e/ou têm eventos de progresso que esperam delegados do tipo correspondente. Por exemplo, o evento de progresso de uma operação assíncrona com progresso (que é qualquer coisa que implementa IAsyncOperationWithProgress ) requer um delegado do tipo AsyncOperationProgressHandler. Aqui está um exemplo de código de criação de um delegado desse tipo usando uma função lambda. O exemplo também mostra como criar um delegado AsyncOperationWithProgressCompletedHandler.

#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Web.Syndication.h>

using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;

void ProcessFeedAsync()
{
    Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
    SyndicationClient syndicationClient;

    auto async_op_with_progress = syndicationClient.RetrieveFeedAsync(rssFeedUri);

    async_op_with_progress.Progress(
        [](
            IAsyncOperationWithProgress<SyndicationFeed,
            RetrievalProgress> const& /* sender */,
            RetrievalProgress const& args)
        {
            uint32_t bytes_retrieved = args.BytesRetrieved;
            // use bytes_retrieved;
        });

    async_op_with_progress.Completed(
        [](
            IAsyncOperationWithProgress<SyndicationFeed,
            RetrievalProgress> const& sender,
            AsyncStatus const /* asyncStatus */)
        {
            SyndicationFeed syndicationFeed = sender.GetResults();
            // use syndicationFeed;
        });

    // or (but this function must then be a coroutine, and return IAsyncAction)
    // SyndicationFeed syndicationFeed{ co_await async_op_with_progress };
}

Como o comentário "coroutine" acima sugere, em vez de usar um delegado com os eventos concluídos de ações e operações assíncronas, você provavelmente achará mais natural usar coroutines. Para obter detalhes e exemplos de código, consulte operações simultâneas e assíncronas comC++/WinRT.

Observação

Não é correto implementar mais de um handler de conclusão para uma ação ou operação assíncrona. Você pode ter um único delegado para o evento concluído ou pode co_await-lo. Se você tiver os dois, o segundo falhará.

Se você ficar com delegados em vez de uma coroutina, poderá optar por uma sintaxe mais simples.

async_op_with_progress.Completed(
    [](auto&& /*sender*/, AsyncStatus const /* args */)
{
    // ...
});

Tipos de delegado que retornam um valor

Alguns tipos de delegados devem, por si mesmos, retornar um valor. Um exemplo é ListViewItemToKeyHandler, que retorna uma cadeia de caracteres. Aqui está um exemplo de criação de um delegado desse tipo (observe que a função lambda retorna um valor).

using namespace winrt::Windows::UI::Xaml::Controls;

winrt::hstring f(ListView listview)
{
    return ListViewPersistenceHelper::GetRelativeScrollPosition(listview, [](IInspectable const& item)
    {
        return L"key for item goes here";
    });
}

Acessando com segurança o com esse ponteiro usando um delegado de tratamento de eventos

Se você lida com um evento através de uma função-membro de um objeto, ou a partir de uma função lambda dentro de uma função-membro de um objeto, será necessário pensar sobre os tempos de vida relativos do destinatário do evento (o objeto que está tratando o evento) e a origem do evento (o objeto que aciona o evento). Para obter mais informações e exemplos de código, consulte Referências fortes e fracas no C++/WinRT.

APIs importantes