Remarque
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.
Important
Construire avec le SDK d'application Windows ? Le code de cet article utilise des espaces de noms UWP (Windows.UI.Xaml). Si votre projet cible WinUI 3 (SDK d'application Windows), remplacez Microsoft.UI.Xaml (et les espaces de noms associésMicrosoft.UI.*) dans l’ensemble. Pour plus d’informations, consultez mappage des API UWP à l’SDK d'application Windows pour obtenir un guide complet de mappage et de migration de l’interface utilisateur.
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.
Note
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 un modèle de projet et une prise en charge de build), consultez Visual Studio prise en charge de 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. Votre page XAML étant ouverte dans le Concepteur XAML, sélectionnez le contrôle dont vous souhaitez gérer un événement. Dans la page de propriétés de ce contrôle, cliquez sur l’icône en forme d’éclair pour afficher la liste de 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.
Note
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::Microsoft::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, Empaqueté (WinUI 3 in Desktop) 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 l’élément x:NameButton , le nom de la fonction d’accesseur générée change également.
Note
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. Il 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 accepte 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 transmet un pointeur brut this (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::Microsoft::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::Microsoft::UI::Xaml::RoutedEventArgs const& e) const;
};
La syntaxe de l’opérateur d’appel de fonction est également utile à connaître. Il vous indique quels doivent être les paramètres de votre délégué. 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.
Note
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 et 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 consultons) a pour type de délégué KeyEventHandler, puisque c’est le type que vous passez lorsque vous associez un délégué à ce type d’événement. Maintenant, suivez le lien dans la rubrique vers ce type de délégué KeyEventHandler. Ici, le bloc de syntaxe contient un opérateur d’appel de fonction. Et, comme indiqué 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::Microsoft::UI::Xaml::Input::KeyRoutedEventArgs const& e) const;
Comme vous pouvez le voir, le délégué doit être déclaré de sorte à accepter un IInspectable comme expéditeur, et une instance de la classe KeyRoutedEventArgs comme 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é prend un IInspectable en tant qu’expéditeur, et un autre IInspectable (car c’est le paramètre de type de EventHandler) comme arguments.
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 de l’exemple de code ci-dessous, mais un délégué RoutedEventHandler est construit à partir d’une fonction lambda qui, à nouveau, doit correspondre à la syntaxe de l’opérateur d’appel de fonction que nous avons abordé 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::Microsoft::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, l'exemple de code suivant stocke le jeton dans le membre de données privé de la structure et révoque le gestionnaire associé dans le destructeur.
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;
};
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).
Note
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. Trouver où se désabonner avant la destruction de l’objet peut atténuer le problème ou, pour une solution robuste, consultez Accéder en toute sécurité au pointeur this avec un délégué de gestion d’événements.
Sinon, lorsque vous inscrivez un délégué, vous pouvez spécifier winrt ::auto_revoke (qui est une valeur de type winrt ::auto_revoke_t) pour demander un révoqueur d’événement (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’elle sort de l’étendue. La fonction revoke 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::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;
};
Voici le bloc de syntaxe extrait de la rubrique de documentation de 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 pour les surcharges register et 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
Dans l’exemple de code ci-dessus, Button::Click_revoker est un alias de type pour winrt::event_revoker<winrt::Microsoft::UI::Xaml::Controls::Primitives::IButtonBase>. Un modèle similaire s’applique à tous les événements C++/WinRT. Chaque événement Windows Runtime a une surcharge de fonction de révocation qui retourne un révoqueur d'événement, et ce type de révoqueur est membre de la source d'événement. Par conséquent, pour prendre un autre exemple, l’événement Window ::SizeChanged a une surcharge de fonction d’inscription qui retourne une valeur de type Window ::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 ne l’enregistrez que s’il n’a pas encore été 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 à cet objet dans votre fonction lambda.
Si votre délégué de révocation automatique ne parvient pas à s’inscrire
Si vous essayez de spécifier winrt ::auto_revoke lors de l’inscription d’un délégué et que le résultat est une exception winrt ::hresult_no_interface , cela signifie généralement que la source d’événement ne prend pas en charge les références faibles. C'est une situation courante dans l'espace de noms Microsoft.UI.Composition, par exemple. Dans ce cas, vous ne pouvez pas utiliser la fonctionnalité de révocation automatique. Vous devrez révoquer manuellement 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 ou sans progression) disposent d’événements d’achèvement et/ou de progression qui prennent 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.
Note
Il n’est pas correct d’implémenter plusieurs gestionnaires d’achèvement pour une action ou une opération asynchrone. Vous pouvez avoir soit un seul délégué pour son événement d’achèvement, soit vous pouvez co_await le faire. 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 eux-mêmes renvoyer 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::Microsoft::UI::Xaml::Controls;
winrt::hstring f(ListView listview)
{
return ListViewPersistenceHelper::GetRelativeScrollPosition(listview, [](IInspectable const& item)
{
return L"key for item goes here";
});
}
Accès sécurisé au pointeur this à l’aide d’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 des exemples de code, consultez Les références fortes et faibles dans C++/WinRT.
API importantes
- winrt ::auto_revoke_t struct de marqueur
- winrt ::implements ::get_weak, fonction
- winrt ::implements ::get_strong, fonction
Rubriques connexes
Windows developer