Partilhar via


Manipular eventos usando delegados em 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 padrão semelhante a uma função C++.

Observação

Para obter informações sobre como instalar e usar o C++/WinRT Visual Studio Extension (VSIX) e o pacote NuGet (que, juntos, fornecem suporte a modelo de projeto e compilação), 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 (UI) do Designer XAML no Visual Studio. Com sua 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 que são 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 seus arquivos de origem, pronto para ser substituído 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 de manipulador de eventos ao seu arquivo Midl. Apenas lhes adiciona os seus ficheiros .h e .cpp.

Registrar um delegado para gerir um evento

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

// 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 é retirado do projeto Blank App (C++/WinRT) no Visual Studio. O código myButton() chama uma função de acesso gerada, que retorna o Botão que nomeámos myButton. Se você alterar o x:Name do elemento Button, o nome da função de acesso gerada também será alterado.

Observação

Nesse caso, a fonte do evento (o objeto que gera o evento) é o Button chamado myButton. E o destinatário do evento (o objeto que manipula o evento) é uma instância de MainPage. Há mais informações mais adiante neste tópico sobre como gerenciar o tempo de vida de fontes e destinatários de eventos.

Em vez de fazê-lo declarativamente na marcação, você pode registrar imperativamente uma função de membro para manipular um evento. Pode não ser óbvio a partir do exemplo de código abaixo, mas o argumento da chamada ButtonBase::Click é uma instância do RoutedEventHandler delegate. Neste caso, estamos a usar a sobrecarga do construtor RoutedEventHandler que aceita um objeto e um ponteiro para função membro.

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

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

Importante

Ao registar o delegado, o exemplo de código acima passa diretamente um ponteiro bruto, que aponta para o objeto atual. Para saber como estabelecer uma referência forte ou fraca ao objeto atual, consulte se 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. Abaixo está o bloco de sintaxe retirado do tópico de documentação para RoutedEventHandler (escolha C++/WinRT no menu suspenso Language no canto superior direito da página web). Observe os vários construtores: um pega uma lambda; outro uma função livre; e outro (o que usamos acima) pega um objeto e uma função de ponteiro para 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 explica quais precisam ser os parâmetros do seu delegado. Como você pode ver, neste caso, a sintaxe do operador de chamada de função corresponde aos parâmetros de nossa MainPage::ClickHandler.

Observação

Para qualquer evento, para descobrir os detalhes de seu delegado e os parâmetros desse delegado, vá primeiro para o tópico de documentação do evento em si. Tomemos como exemplo o evento UIElement.KeyDown. Visite esse tópico e escolha C++/WinRT na lista suspensa Language. 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 tema que estamos abordando) possui um delegado do tipo KeyEventHandler, já que é esse o tipo passado ao registar um delegado com este tipo de evento. Então, agora siga o link sobre o tópico para que delegado KeyEventHandler tipo. Aqui, o bloco de sintaxe contém um operador de chamada de função. E, como mencionado acima, isso diz quais são os parâmetros do seu 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 ter um IInspectable como remetente e uma instância da classe KeyRoutedEventArgs como args.

Para dar outro exemplo, vejamos o evento Popup.Closed . O seu tipo de delegado é EventHandler<IInspectable>. Assim, seu delegado terá um IInspectable como remetente e outro IInspectable (porque esse é o parâmetro de tipo do EventHandler) como args.

Se você não estiver fazendo muito trabalho em seu manipulador de eventos, poderá usar uma função lambda em vez de uma função membro. Novamente, pode não ser óbvio a partir do exemplo de código abaixo, mas um delegado RoutedEventHandler está a ser construído a partir de uma função lambda que, novamente, precisa corresponder à sintaxe da 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 quiser partilhá-lo ou usá-lo mais do que 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 está registrado no evento e não será chamado caso o evento seja levantado novamente.

Por uma questão de simplicidade, 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 privados da estrutura e revoga o seu manipulador no destrutor.

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 eventos gera seus eventos de forma síncrona, você pode revogar seu manipulador e ter certeza de que não receberá mais eventos. Mas para eventos assíncronos, mesmo depois de revogar (e especialmente ao revogar dentro do destruidor), um evento em voo pode atingir seu objeto depois que ele começar a destruir. Encontrar um local para cancelar a inscrição antes da destruição pode atenuar o problema ou, para obter uma solução robusta, consulte Acesso seguro ao este ponteiro com um delegado 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 à fonte 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 fonte do evento ainda existe e, em caso afirmativo, revoga o delegado. Neste exemplo, não há necessidade de armazenar a fonte 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;
};

Abaixo está o bloco de código de sintaxe retirado do tópico de documentação para o evento ButtonBase::Click. Mostra as três diferentes funções de registo e revogação. Você pode ver exatamente que tipo de revocador de evento você precisa declarar na terceira sobrecarga. E você pode passar os mesmos tipos de delegados para o registrar e o revogar com event_revoker sobrecargas.

// 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 é 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 Tempo de Execução do Windows tem uma sobrecarga de função de revogação que retorna um revogador de eventos e esse tipo de revogador é um membro da fonte do evento. Então, para dar 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 estiver entrando repetidamente numa página e depois saindo novamente, poderá revogar quaisquer manipuladores ao sair da página. Como alternativa, se você estiver reutilizando a mesma instância de página, verifique o valor do seu 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 mais adiante neste tópico, é capturar uma referência forte ou fraca ao esse objeto na sua função lambda.

Se o seu delegado de revogação automática não conseguir registar-se

Se tentar especificar winrt::auto_revoke ao registar um delegado, e o resultado for uma exceção winrt::hresult_no_interface, isso geralmente significa que a origem do evento não suporta 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ê terá que recorrer a revogar manualmente os seus manipuladores de eventos.

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

Os exemplos acima usam o tipo de delegado RoutedEventHandler , mas é claro que há muitos outros tipos de delegado. Por exemplo, ações e operações assíncronas (com e sem progresso) têm eventos de conclusão e/ou 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 implemente 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 AsyncOperationWithProgressCompletedHandler delegado.

#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 utilizar um delegado com os eventos concluídos de ações e operações assíncronas, irá provavelmente achar mais natural usar coroutines. Para obter detalhes e exemplos de código, consulte Simultaneidade e operações assíncronas com C++/WinRT.

Observação

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

Se você ficar com delegados em vez de uma co-rotina, então você pode 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";
    });
}

Aceder com segurança a este ponteiro associado ao com um delegado de manipulação de eventos

Ao manipular um evento com a função de membro de um objeto, ou a partir de uma função lambda dentro de uma função de membro de um objeto, precisa considerar os ciclos de vida relativos do destinatário do evento (o objeto que manipula o evento) e da fonte do evento (o objeto que gera o evento). Para obter mais informações e exemplos de código, consulte Referências fortes e fracas em C++/WinRT.

APIs importantes