Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Important
Construir com o SDK de Aplicações Windows? O código deste artigo utiliza espaços de nomes UWP(Windows.UI.Xaml). Se o seu projeto tem como alvo o WinUI 3 (SDK de Aplicações Windows), substitua Microsoft.UI.Xaml (e namespaces relacionadosMicrosoft.UI.*) ao longo de todo o processo. Consulte Mapeamento das APIs UWP para o SDK de Aplicações Windows para obter um mapeamento completo e guia de migração da interface do utilizador para obter detalhes adicionais.
Este tópico mostra como registar e revogar delegados que gerem eventos usando C++/WinRT. Podes gerir um evento usando qualquer objeto padrão tipo função C++.
Note
Para informações sobre a instalação e utilização da Extensão Visual Studio C++/WinRT (VSIX) e do pacote NuGet (que juntos fornecem suporte para templates de projeto e compilação), consulte o suporte Visual Studio para C++/WinRT.
Usar o Visual Studio para adicionar um gestor de eventos
Uma forma conveniente de adicionar um gestor de eventos ao seu projeto é usar a interface de utilizador (UI) do XAML Designer no Visual Studio. Com a sua página XAML aberta no XAML Designer, selecione o controlo cujo evento quer gerir. Na página de propriedades desse controlo, clique no ícone em forma de raio para listar todos os eventos gerados por esse controlo. Depois, clique duas vezes no evento que pretende tratar; por exemplo, OnClicked.
O XAML Designer adiciona o protótipo adequado da função handler de eventos (e uma implementação stub) aos seus ficheiros fonte, prontos para substituir pela sua própria implementação.
Note
Normalmente, os seus gestores de eventos não precisam de ser descritos no seu ficheiro Midl (.idl). Assim, o XAML Designer não adiciona protótipos de funções de gestor de eventos ao teu ficheiro Midl. Só os adiciona aos teus ficheiros .h e .cpp.
Registe um delegado para tratar de um evento
Um exemplo simples é lidar com o evento de clique de um botão. É comum usar marcação XAML para registar uma função membro para processar o evento, como esta.
// MainPage.xaml
<Button x:Name="myButton" Click="ClickHandler">Click Me</Button>
// MainPage.h
void ClickHandler(
winrt::Windows::Foundation::IInspectable const& sender,
winrt::Microsoft::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 foi retirado do projeto Blank App, Packaged (WinUI 3 in Desktop) no Visual Studio. O código myButton() chama uma função acessora gerada, que devolve o Button a que demos o nome myButton. Se alterar o x:Name desse elemento Botão, o nome da função de acesso gerada também muda.
Note
Neste caso, a fonte do evento (o objeto que levanta o evento) é o botão chamado myButton. E o destinatário do evento (o objeto que gere o evento) é uma instância do MainPage. Há mais informações mais adiante neste tópico sobre como gerir a vida útil das fontes e dos destinatários do evento.
Em vez de o fazer declarativamente em marcação, pode registar imperativamente uma função membro para gerir um evento. Pode não ser óbvio pelo exemplo de código abaixo, mas o argumento para a chamada ButtonBase::Click é uma instância do delegado RoutedEventHandler . Neste caso, estamos a usar a sobrecarga do construtor RoutedEventHandler que recebe um objeto e um ponteiro para uma função membro.
// MainPage.cpp
MainPage::MainPage()
{
InitializeComponent();
myButton().Click({ this, &MainPage::ClickHandler });
}
Important
Ao registar o delegado, o exemplo de código acima passa um ponteiro this em bruto (que aponta para o objeto atual). Para aprender a estabelecer uma referência forte ou fraca ao objeto atual, veja Se usa uma função membro como delegado.
Aqui está um exemplo que usa uma função de membro estática; Note a sintaxe mais simples.
// MainPage.h
static void ClickHandler(
winrt::Windows::Foundation::IInspectable const& sender,
winrt::Microsoft::UI::Xaml::RoutedEventArgs const& args);
// MainPage.cpp
MainPage::MainPage()
{
InitializeComponent();
myButton().Click( MainPage::ClickHandler );
}
void MainPage::ClickHandler(
IInspectable const& /* sender */,
RoutedEventArgs const& /* args */) { ... }
Existem outras formas de construir um RoutedEventHandler. Abaixo está o bloco de sintaxe extraído do tópico de documentação de RoutedEventHandler (escolha C++/WinRT na lista pendente Language no canto superior direito da página Web). Repare nos vários construtores: um recebe uma lambda; outro, uma função livre; e outro (o que usámos acima) recebe 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::Microsoft::UI::Xaml::RoutedEventArgs const& e) const;
};
A sintaxe do operador de chamada de função também é útil de observar. Diz-lhe quais devem ser os parâmetros do seu delegado. Como pode ver, neste caso a sintaxe do operador de chamada de função corresponde aos parâmetros da nossa Página Principal::ClickHandler.
Note
Para qualquer evento, para perceber os detalhes do seu delegado e os parâmetros desse delegado, vá primeiro ao tema da documentação do próprio evento. Tomemos o evento UIElement.KeyDown como exemplo. Aceda a esse tópico e escolha C++/WinRT na lista pendente Idioma. No bloco de sintaxe no início do tópico, verá o seguinte.
// Register
event_token KeyDown(KeyEventHandler const& handler) const;
Essa informação diz-nos que o evento UIElement.KeyDown (o tópico que estamos a abordar) tem um tipo de delegado KeyEventHandler, uma vez que é esse o tipo passado quando se regista um delegado para este tipo de evento. Portanto, agora siga o link do tema para esse tipo de delegado KeyEventHandler . Aqui, o bloco de sintaxe contém um operador de chamada de função. E, como mencionado acima, isso diz-lhe quais os parâmetros do seu delegado devem ser.
void operator()(
winrt::Windows::Foundation::IInspectable const& sender,
winrt::Microsoft::UI::Xaml::Input::KeyRoutedEventArgs const& e) const;
Como pode ver, o delegado precisa de ser declarado para assumir 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, o seu delegado aceitará um IInspectable como remetente e outro IInspectable (porque esse é o parâmetro de tipo do EventHandler) como argumentos.
Se não estiveres a fazer muito trabalho no teu gestor de eventos, podes usar uma função lambda em vez de uma função membro. Mais uma vez, pode não ser óbvio a partir do exemplo de código abaixo, mas está a ser construído um delegado RoutedEventHandler a partir de uma função lambda que, novamente, precisa de 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"));
});
}
Pode optar por ser um pouco mais explícito ao construir o seu delegado. Por exemplo, se quiseres passá-lo de mão em mão ou usá-lo mais do que uma vez.
MainPage::MainPage()
{
InitializeComponent();
auto click_handler = [](IInspectable const& sender, RoutedEventArgs const& /* args */)
{
sender.as<winrt::Microsoft::UI::Xaml::Controls::Button>().Content(box_value(L"Clicked"));
};
myButton().Click(click_handler);
AnotherButton().Click(click_handler);
}
Revogar um delegado registado
Quando regista um delegado, normalmente é-lhe devolvido um token. Pode posteriormente usar esse token para revogar o seu delegado; o que significa que o delegado não está registado no evento e não será chamado caso o evento seja novamente levantado.
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 do struct e revoga o seu handler no destruidor.
struct Example : ExampleT<Example>
{
Example(winrt::Microsoft::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::Microsoft::UI::Xaml::Controls::Button m_button;
winrt::event_token m_token;
};
Em vez de uma referência forte, como no exemplo acima, pode armazenar uma referência fraca ao botão (ver Referências fortes e fracas em C++/WinRT).
Note
Quando uma fonte de eventos eleva os seus eventos de forma síncrona, pode revogar o seu handler e ter a certeza de que não receberá mais eventos. Mas para eventos assíncronos, mesmo após a revogação (e especialmente ao revogar dentro do destruidor), um evento em voo pode chegar ao seu objeto depois de este ter começado a destruir. Encontrar uma forma de cancelar a subscrição antes da destruição poderá mitigar o problema ou, para uma solução robusta, veja Aceder em segurança ao ponteiro this com um delegado de tratamento de eventos.
Alternativamente, ao registar um delegado, pode especificar winrt::auto_revoke (que é um valor do tipo winrt::auto_revoke_t) para solicitar um revogador de eventos (do tipo winrt::event_revoker). O revogador de eventos mantém uma referência fraca à fonte do evento (o objeto que levanta o evento) para ti. Pode revogar manualmente chamando a função-membro event_revoker::revoke; mas o revogador do evento chama essa função automaticamente quando sai do escopo. A função revoke verifica se a origem do evento ainda existe e, nesse caso, revoga o seu delegado. Neste exemplo, não há necessidade de armazenar a fonte do evento, nem de um destruidor.
struct Example : ExampleT<Example>
{
Example(winrt::Microsoft::UI::Xaml::Controls::Button button)
{
m_event_revoker = button.Click(
winrt::auto_revoke,
[this](IInspectable const& /* sender */,
RoutedEventArgs const& /* args */)
{
// ...
});
}
private:
winrt::Microsoft::UI::Xaml::Controls::Button::Click_revoker m_event_revoker;
};
Abaixo está o bloco de sintaxe retirado do tópico de documentação do evento ButtonBase::Click . Mostra as três funções diferentes de registo e revogação. Podes ver exatamente que tipo de revogador de eventos precisas de declarar a partir da terceira sobrecarga. E podes passar os mesmos tipos de delegados tanto para as sobrecargas de register como para as de revoke with event_revoker.
// Register
winrt::event_token Click(winrt::Microsoft::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::Microsoft::UI::Xaml::RoutedEventHandler const& handler) const;
Note
No exemplo de código acima, Button::Click_revoker é um alias de tipo para winrt::event_revoker<winrt::Microsoft::UI::Xaml::Controls::Primitives::IButtonBase>. Um padrão semelhante aplica-se a todos os eventos C++/WinRT. Cada evento do Windows Runtime tem uma sobrecarga da função revoke que devolve um revogador de evento, e o tipo desse revogador é um membro da origem do evento. Assim, para dar outro exemplo, o evento Window::SizeChanged tem uma sobrecarga de função de registo que devolve um valor do tipo Window::SizeChanged_revoker.
Poderias considerar revogar handlers num cenário de navegação de páginas. Se estiveres a entrar repetidamente numa página e depois a sair dela, poderás revogar quaisquer manipuladores quando saíres da página. Alternativamente, se estiveres a reutilizar a mesma instância de página, verifica o valor do teu token e só regista se ainda não tiver sido definido (if (!m_token){ ... }). Uma terceira opção é armazenar um revogador de eventos na página como membro de dados. E uma quarta opção, conforme descrito mais adiante neste tópico, é capturar uma referência forte ou fraca a este objeto na sua função lambda.
Se o seu delegado de revogação automática não conseguir registar-se
Se tentares especificar winrt::auto_revoke ao registares um delegado, e o resultado for uma exceção winrt::hresult_no_interface , isso normalmente significa que a fonte do evento não suporta referências fracas. Esta é uma situação comum no espaço de nomes Microsoft.UI.Composition, por exemplo. Nesta situação, não pode usar a funcionalidade de revogação automática. Terás de optar por revogar manualmente os seus processadores de eventos.
Tipos de delegado para ações e operações assíncronas
Os exemplos acima usam o tipo de delegado RoutedEventHandler , mas existem, claro, muitos outros tipos de delegados. Por exemplo, ações e operações assíncronas (com e sem progresso) têm eventos de conclusão e/ou de progresso que requerem delegados do tipo correspondente. Por exemplo, o evento de progresso de uma operação assíncrona com progresso (ou seja, qualquer elemento que implemente IAsyncOperationWithProgress) requer um delegado do tipo AsyncOperationProgressHandler. Eis um exemplo de código de como definir 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 sugere o comentário sobre "corrotinas" acima, em vez de usar um delegado com os eventos concluídos das ações e operações assíncronas, provavelmente vai achar mais natural usar corrotinas. Para detalhes e exemplos de código, veja Concorrência e operações assíncronas com C++/WinRT.
Note
Não é correto implementar mais do que um handler de completação para uma ação ou operação assíncrona. Pode ter um único delegado para o evento concluído, ou co_await pode fazê-lo. Se tiveres ambos, o segundo falha.
Se optar por usar delegados em vez de uma corrotina, pode optar por uma sintaxe mais simples.
async_op_with_progress.Completed(
[](auto&& /*sender*/, AsyncStatus const /* args */)
{
// ...
});
Tipos de delegado que devolvem um valor
Alguns tipos de delegado devem eles próprios devolver um valor. Um exemplo é o ListViewItemToKeyHandler, que devolve uma cadeia. Aqui está um exemplo de criação de um delegado desse tipo (note que a função lambda devolve um valor).
using namespace winrt::Microsoft::UI::Xaml::Controls;
winrt::hstring f(ListView listview)
{
return ListViewPersistenceHelper::GetRelativeScrollPosition(listview, [](IInspectable const& item)
{
return L"key for item goes here";
});
}
Aceder em segurança ao ponteiro this com um delegado para processamento de eventos
Se processar um evento com o método de um objeto, ou a partir de uma função lambda no interior do método de um objeto, então tem de ter em conta os tempos de vida relativos do destinatário do evento (o objeto que processa o evento) e da origem do evento (o objeto que desencadeia o evento). Para mais informações e exemplos de código, veja Referências fortes e fracas em C++/WinRT.
APIs importantes
- winrt::auto_revoke_t estrutura de marcadores
- função winrt::implements::get_weak
- função winrt::implements::get_strong
Tópicos relacionados
Windows developer