Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Cette rubrique montre comment inscrire et révoquer des délégués de gestion des événements à l’aide de C++/WinRT. Vous pouvez gérer un événement à l’aide de n’importe quel objet de fonction C++ standard.
Remarque
Pour plus d’informations sur l’installation et l’utilisation de l’extension Visual Studio C++/WinRT (VSIX) et du package NuGet (qui fournissent ensemble la prise en charge du modèle de projet et de la génération), consultez prise en charge de Visual Studio pour C++/WinRT.
Utilisation de Visual Studio pour ajouter un gestionnaire d’événements
Un moyen pratique d’ajouter un gestionnaire d’événements à votre projet consiste à utiliser l’interface utilisateur du concepteur XAML dans Visual Studio. Une fois votre page XAML ouverte dans le Concepteur XAML, sélectionnez le contrôle dont vous souhaitez gérer l’événement. Sur la page de propriétés de ce contrôle, cliquez sur l'icône en forme d'éclair pour répertorier tous les événements générés par ce contrôle. Ensuite, double-cliquez sur l’événement que vous souhaitez gérer ; par exemple, OnClicked.
Le Concepteur XAML ajoute le prototype de fonction de gestionnaire d’événements approprié (et une implémentation stub) à vos fichiers sources, prêt à être remplacé par votre propre implémentation.
Remarque
En règle générale, vos gestionnaires d’événements n’ont pas besoin d’être décrits dans votre fichier Midl (.idl
). Par conséquent, le Concepteur XAML n’ajoute pas de prototypes de fonction de gestionnaire d’événements à votre fichier Midl. Il les ajoute uniquement à vos fichiers .h
et .cpp
.
Inscrire un délégué pour gérer un événement
Un exemple simple consiste à gérer l’événement click d’un bouton. Il est courant d’utiliser le balisage XAML pour inscrire une fonction membre pour gérer l’événement, comme ceci.
// 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"));
}
Le code ci-dessus est extrait du projet Application vide (C++/WinRT) dans Visual Studio. Le code myButton()
appelle une fonction d’accesseur générée, qui retourne le bouton que nous avons nommé myButton. Si vous modifiez la x:Name
de cet élément Button, le nom de la fonction d’accesseur générée change également.
Remarque
Dans ce cas, la source d’événement (l’objet qui déclenche l’événement) est le bouton nommé myButton. Et le destinataire de l’événement (l’objet qui gère l’événement) est une instance de MainPage. Vous trouverez plus d’informations plus loin dans cette rubrique sur la gestion de la durée de vie des sources d’événements et des destinataires d’événements.
Au lieu de le faire de manière déclarative dans le balisage, vous pouvez inscrire impérativement une fonction membre pour gérer un événement. Cela peut ne pas être évident dans l'exemple de code ci-dessous, mais l’argument de l’appel ButtonBase::Click est une instance du délégué RoutedEventHandler. Dans ce cas, nous utilisons la surcharge de constructeur RoutedEventHandler qui prend un objet et un pointeur vers une fonction membre.
// MainPage.cpp
MainPage::MainPage()
{
InitializeComponent();
myButton().Click({ this, &MainPage::ClickHandler });
}
Important
Lors de l’inscription du délégué, l’exemple de code ci-dessus passe un pointeur brut cette (pointant vers l’objet courant). Pour savoir comment établir une référence forte ou faible à l’objet actuel, consultez Si vous utilisez une fonction membre en tant que délégué.
Voici un exemple qui utilise une fonction membre statique ; notez la syntaxe plus simple.
// 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 */) { ... }
Il existe d’autres façons de construire un RoutedEventHandler. Vous trouverez ci-dessous le bloc de syntaxe extrait de la rubrique de documentation pour RoutedEventHandler (choisissez C++/WinRT dans la liste déroulante Langue dans le coin supérieur droit de la page web). Notez les différents constructeurs : on prend une lambda ; une autre fonction libre ; et un autre (celui que nous avons utilisé ci-dessus) prend un objet et une fonction pointeur vers membre.
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;
};
La syntaxe de l’opérateur d’appel de fonction est également utile à examiner. Il vous indique les paramètres dont votre délégué a besoin. Comme vous pouvez le voir, dans ce cas, la syntaxe de l’opérateur d’appel de fonction correspond aux paramètres de notre MainPage ::ClickHandler.
Remarque
Pour tout événement donné, pour déterminer les détails de son délégué et les paramètres de ce délégué, accédez d’abord à la rubrique de documentation de l’événement lui-même. Prenons l'événement UIElement.KeyDown comme exemple. Visitez cette rubrique, puis choisissez C++/WinRT dans la liste déroulante Langue. Dans le bloc de syntaxe au début de la rubrique, vous verrez cela.
// Register
event_token KeyDown(KeyEventHandler const& handler) const;
Ces informations nous indiquent que l’événement UIElement.KeyDown (la rubrique que nous traitons) a un type délégué de KeyEventHandler, puisque c'est celui que vous utilisez lorsque vous inscrivez un délégué à cet événement. Suivez maintenant le lien sur la rubrique vers ce délégué de type KeyEventHandler . Ici, le bloc de syntaxe contient un opérateur d’appel de fonction. Et, comme mentionné ci-dessus, cela vous indique quels doivent être les paramètres de votre délégué.
void operator()(
winrt::Windows::Foundation::IInspectable const& sender,
winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e) const;
Comme vous pouvez le voir, le délégué doit être déclaré pour prendre un IInspectable en tant qu'expéditeur, et une instance de la classe KeyRoutedEventArgs en tant qu'argument.
Pour prendre un autre exemple, examinons l’événement Popup.Closed . Son type de délégué est EventHandler<>IInspectable . Par conséquent, votre délégué prendra un IInspectable comme expéditeur, et un autre IInspectable (car c'est le paramètre de type du EventHandler) comme argument.
Si vous ne faites pas beaucoup de travail dans votre gestionnaire d’événements, vous pouvez utiliser une fonction lambda au lieu d’une fonction membre. Là encore, il n’est peut-être pas évident dans l’exemple de code ci-dessous, mais un RoutedEventHandler délégué est construit à partir d’une fonction lambda qui, à nouveau, doit correspondre à la syntaxe de l’opérateur d’appel de fonction que nous avons mentionné ci-dessus.
MainPage::MainPage()
{
InitializeComponent();
myButton().Click([this](IInspectable const& /* sender */, RoutedEventArgs const& /* args */)
{
myButton().Content(box_value(L"Clicked"));
});
}
Vous pouvez choisir d’être un peu plus explicite lorsque vous construisez votre délégué. Par exemple, si vous souhaitez le transmettre ou l’utiliser plusieurs fois.
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);
}
Révoquer un délégué inscrit
Lorsque vous inscrivez un délégué, un jeton est généralement retourné à vous. Vous pouvez ensuite utiliser ce jeton pour révoquer votre délégué ; cela signifie que le délégué n’est pas inscrit à partir de l’événement et ne sera pas appelé si l’événement est à nouveau déclenché.
Par souci de simplicité, aucun des exemples de code ci-dessus n’a montré comment procéder. Toutefois, cet exemple de code suivant stocke le jeton dans le membre de données privées du struct et révoque son gestionnaire dans le destructeur.
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;
};
Au lieu d’une référence forte, comme dans l’exemple ci-dessus, vous pouvez stocker une référence faible au bouton (voir Les références fortes et faibles dans C++/WinRT).
Remarque
Lorsqu’une source d’événement déclenche ses événements de façon synchrone, vous pouvez révoquer votre gestionnaire et être certain que vous ne recevrez plus d’événements. Toutefois, pour les événements asynchrones, même après la révocation (et en particulier lors de la révocation dans le destructeur), un événement en cours d’exécution peut atteindre votre objet une fois qu’il a commencé à détruire. La recherche d’un emplacement pour se désabonner avant la destruction peut atténuer le problème, ou pour une solution robuste, consultez accéder en toute sécurité au ce pointeur avec un délégué de gestion des événements.
Vous pouvez également spécifier winrt ::auto_revoke (valeur de type winrt ::auto_revoke_t) pour demander un révoqueur d’événements (de type winrt ::event_revoker). Le révoqueur d’événements contient une référence faible à la source d’événement (l’objet qui déclenche l’événement) pour vous. Vous pouvez révoquer manuellement en appelant la fonction membre event_revoker::revoke ; mais le révoqueur d’événements appelle automatiquement cette fonction lorsqu’il sort de sa portée. La fonction de révocation vérifie si la source d’événement existe toujours et, le cas échéant, révoque votre délégué. Dans cet exemple, il n’est pas nécessaire de stocker la source d’événement et aucun besoin de destructeur.
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;
};
Voici le bloc de syntaxe extrait de la rubrique de documentation pour l’événement ButtonBase::Click. Il affiche les trois fonctions d’inscription et de révocation différentes. Vous pouvez voir exactement quel type de révoqueur d’événements vous devez déclarer à partir de la troisième surcharge. Vous pouvez également transmettre les mêmes types de délégués à la fois au registre et au révoquer avec les surcharges de fonction 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;
Remarque
Dans l’exemple de code ci-dessus, Button::Click_revoker
est un alias de type pour winrt::event_revoker<winrt::Windows::UI::Xaml::Controls::Primitives::IButtonBase>
. Un modèle similaire s’applique à tous les événements C++/WinRT. Chaque événement Windows Runtime a une fonction de révocation surchargée qui retourne un révoqueur d’événements, et le type de ce révoqueur est un membre de la source de l'événement. Par conséquent, pour prendre un autre exemple, l’événement CoreWindow::SizeChanged a une surcharge de la fonction d'enregistrement qui retourne une valeur de type CoreWindow::SizeChanged_revoker.
Vous pouvez envisager de révoquer des gestionnaires dans un scénario de navigation de page. Si vous naviguez à plusieurs reprises dans une page, puis revenez, vous pouvez révoquer tous les gestionnaires lorsque vous quittez la page. Sinon, si vous réutilisez la même instance de page, vérifiez la valeur de votre jeton et enregistrez-le uniquement s'il n'est pas encore défini (if (!m_token){ ... }
). Une troisième option consiste à stocker un révoqueur d’événements dans la page en tant que membre de données. Et une quatrième option, comme décrit plus loin dans cette rubrique, consiste à capturer une référence forte ou faible au cet objet dans votre fonction lambda.
Si votre délégué de révocation automatique ne parvient pas à s’enregistrer
Si vous essayez de spécifier winrt::auto_revoke lors de l'inscription d'un délégué, et que cela résulte en une exception winrt::hresult_no_interface, cela signifie généralement que la source de l'événement ne supporte pas les références faibles. C’est une situation courante dans l’espace de noms Windows.UI.Composition , par exemple. Dans ce cas, vous ne pouvez pas utiliser la fonctionnalité de révocation automatique. Vous devrez vous rabattre sur la révocation manuelle de vos gestionnaires d’événements.
Types délégués pour les actions et opérations asynchrones
Les exemples ci-dessus utilisent le type de délégué RoutedEventHandler , mais il existe bien sûr de nombreux autres types de délégués. Par exemple, les actions et opérations asynchrones (avec et sans progression) ont des événements de finalisation et/ou de progression qui attendent des délégués du type correspondant. Par exemple, l’événement de progression d’une opération asynchrone avec progression (qui est tout ce qui implémente IAsyncOperationWithProgress) nécessite un délégué de type AsyncOperationProgressHandler. Voici un exemple de code de création d’un délégué de ce type à l’aide d’une fonction lambda. L’exemple montre également comment créer un délégué 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 };
}
Comme le commentaire « coroutine » ci-dessus suggère, au lieu d’utiliser un délégué avec les événements terminés d’actions et d’opérations asynchrones, vous trouverez probablement plus naturel d’utiliser les coroutines. Pour plus d’informations et pour obtenir des exemples de code, consultez Accès concurrentiel et opérations asynchrones avec C++/WinRT.
Remarque
Il n'est pas correct d'implémenter plus d'un gestionnaire d'achèvement pour une action ou une opération asynchrone. Vous pouvez avoir soit un délégué unique lorsque l'événement est terminé, ou bien vous pouvez le co_await
. Si vous avez les deux, la seconde échoue.
Si vous restez avec des délégués au lieu d’une coroutine, vous pouvez opter pour une syntaxe plus simple.
async_op_with_progress.Completed(
[](auto&& /*sender*/, AsyncStatus const /* args */)
{
// ...
});
Types délégués qui retournent une valeur
Certains types de délégués doivent retourner eux-mêmes une valeur. Un exemple est ListViewItemToKeyHandler, qui retourne une chaîne. Voici un exemple de création d’un délégué de ce type (notez que la fonction lambda retourne une valeur).
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";
});
}
Accéder en toute sécurité à ce pointeur avec un délégué de gestion d'événements
Si vous gérez un événement avec la fonction membre d’un objet ou à partir d’une fonction lambda à l’intérieur de la fonction membre d’un objet, vous devez réfléchir aux durées de vie relatives du destinataire de l’événement (l’objet qui gère l’événement) et à la source d’événement (l’objet qui déclenche l’événement). Pour plus d’informations et d'exemples de code, consultez Références fortes et faibles en C++/WinRT.
API importantes
- winrt ::auto_revoke_t struct de marqueur
- fonction winrt::implements::get_weak
- fonction winrt::implements::get_strong