Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Important
Entwickeln mit dem Windows App SDK? Der Code dieses Artikels verwendet UWP(Windows.UI.Xaml)-Namespaces. Wenn Ihr Projekt auf WinUI 3 (Windows App SDK) abzielt, ersetzen Sie durchgängig Microsoft.UI.Xaml sowie die zugehörigen Microsoft.UI.*-Namespaces. Eine vollständige Zuordnung finden Sie unter Zuordnen von UWP-APIs dem Windows App SDK und weitere Details im Leitfaden zur UI-Migration.
In diesem Thema wird gezeigt, wie Sie Ereignisbehandlungsdelegaten mit C++/WinRT registrieren und aufheben. Sie können ein Ereignis mithilfe eines beliebigen standardmäßigen C++-Funktionsobjekts behandeln.
Note
Informationen zum Installieren und Verwenden der C++/WinRT Visual Studio Extension (VSIX) und des NuGet-Pakets (die zusammen Projektvorlage und Buildunterstützung bereitstellen), finden Sie unter Visual Studio Unterstützung für C++/WinRT.
Verwenden von Visual Studio zum Hinzufügen eines Ereignishandlers
Eine bequeme Möglichkeit zum Hinzufügen eines Ereignishandlers zu Ihrem Projekt ist die Verwendung der XAML-Designer-Benutzeroberfläche (UI) in Visual Studio. Wählen Sie bei geöffneter XAML-Seite im XAML-Designer das Steuerelement aus, dessen Ereignis Sie behandeln möchten. Klicken Sie auf der Eigenschaftenseite für dieses Steuerelement auf das Blitzsymbol, um alle Ereignisse auflisten zu können, die von diesem Steuerelement stammen. Doppelklicken Sie dann auf das Ereignis, das Sie behandeln möchten. Beispiel: OnClicked.
Der XAML-Designer fügt ihren Quelldateien den entsprechenden Prototyp der Ereignishandlerfunktion (und eine Stubimplementierung) hinzu, die Sie durch Ihre eigene Implementierung ersetzen können.
Note
In der Regel müssen Ihre Ereignishandler nicht in der Midl-Datei (.idl) beschrieben werden. Daher fügt der XAML-Designer ihrer Midl-Datei keine Prototypen der Ereignishandlerfunktion hinzu. Es fügt sie nur Ihren .h- und .cpp-Dateien hinzu.
Einen Delegaten zur Behandlung eines Ereignisses registrieren
Ein einfaches Beispiel ist die Behandlung des Klickereignisses einer Schaltfläche. In der Regel wird XAML-Markup verwendet, um eine Memberfunktion für die Behandlung des Ereignisses zu registrieren:
// 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"));
}
Der obige Code stammt aus dem Projekt "Leere App, Verpackt" (WinUI 3 in Desktop) in Visual Studio. Der Code myButton() ruft eine generierte Accessorfunktion auf, die die Schaltfläche zurückgibt, die wir myButton genannt haben. Wenn Sie das x:Name Element " Button" ändern, ändert sich auch der Name der generierten Accessorfunktion.
Note
In diesem Fall ist die Ereignisquelle (das Objekt, das das Ereignis auslöst) die Schaltfläche mit dem Namen myButton. Und der Ereignisempfänger (das Objekt, das das Ereignis behandelt) ist eine Instanz von MainPage. Weitere Informationen finden Sie weiter unten in diesem Thema zum Verwalten der Lebensdauer von Ereignisquellen und Ereignisempfängern.
Alternativ kannst du auch imperativ eine Memberfunktion für die Behandlung eines Ereignisses registrieren, anstatt eine Deklaration im Markup zu verwenden. Es ist möglicherweise nicht offensichtlich aus dem folgenden Codebeispiel, aber das Argument für den ButtonBase::Click-Aufruf ist eine Instanz des RoutedEventHandler-Delegaten . In diesem Fall verwenden wir die Konstruktorüberladung RoutedEventHandler, die ein Objekt und eine Pointer-to-Member-Funktion akzeptiert.
// MainPage.cpp
MainPage::MainPage()
{
InitializeComponent();
myButton().Click({ this, &MainPage::ClickHandler });
}
Important
Beim Registrieren des Delegaten übergibt das obige Codebeispiel einen rohen this-Zeiger (der auf das aktuelle Objekt zeigt). Informationen dazu, wie Sie eine starke oder schwache Referenz auf das aktuelle Objekt erstellen, finden Sie unter Wenn Sie eine Mitgliedsfunktion als Delegat verwenden.
Hier ist ein Beispiel, das eine statische Memberfunktion verwendet. Beachten Sie die einfachere Syntax.
// 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 */) { ... }
Es gibt andere Möglichkeiten zum Erstellen eines RoutedEventHandlers. Unten sehen Sie den Syntaxblock aus dem Dokumentationsthema für RoutedEventHandler (wählen Sie C++/WinRT aus der Dropdownliste "Sprache " in der oberen rechten Ecke der Webseite aus). Beachte die verschiedenen Konstruktoren: Einer akzeptiert eine Lambda-Funktion, ein anderer eine freie Funktion und ein weiterer (der oben verwendete) ein Objekt sowie eine Pointer-to-Member-Funktion.
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;
};
Es ist auch hilfreich, sich die Syntax des Funktionsaufrufoperators anzusehen. Sie erfahren, was die Parameter Ihrer Stellvertretung sein müssen. Wie Sie sehen können, entspricht die Syntax des Funktionsaufrufoperators den Parametern unserer MainPage::ClickHandler.
Note
Für ein bestimmtes Ereignis finden Sie die Details zu seinem Delegaten und dessen Parametern, indem Sie zunächst den Dokumentationsabschnitt zu dem Ereignis selbst aufrufen. Nehmen wir uns das UIElement.KeyDown-Ereignis als Beispiel an. Besuchen Sie dieses Thema, und wählen Sie in der Dropdownliste "Sprache" C++/WinRT aus. Im Syntaxblock am Anfang des Themas sehen Sie dies.
// Register
event_token KeyDown(KeyEventHandler const& handler) const;
Diese Informationen sagen uns, dass das UIElement.KeyDown-Ereignis (das Thema, mit dem wir uns gerade befassen) den Delegattyp KeyEventHandler hat, da dies der Typ ist, den Sie übergeben, wenn Sie einen Delegaten für dieses Ereignis registrieren. Klicke in dem Thema auf den Link zu diesem Delegattyp (KeyEventHandler). Hier enthält der Syntaxblock einen Funktionsaufrufoperator. Und wie oben erwähnt, erfahren Sie, was die Parameter Ihrer Stellvertretung sein müssen.
void operator()(
winrt::Windows::Foundation::IInspectable const& sender,
winrt::Microsoft::UI::Xaml::Input::KeyRoutedEventArgs const& e) const;
Wie du siehst, muss der Delegat deklariert werden, um ein Element vom Typ IInspectable als Absender und eine Instanz der Klasse „KeyRoutedEventArgs“ als Argumente zu akzeptieren.
Um ein weiteres Beispiel zu betrachten, sehen wir uns das Popup.Closed-Ereignis an. Der Delegattyp ist "EventHandler<IInspectable>". Dein Delegat akzeptiert also ein Element vom Typ IInspectable als Absender und ein weiteres Element vom Typ IInspectable als Argumente (da es sich dabei um den Typparameter von EventHandler handelt).
Wenn Sie nicht viel Arbeit in Ihrem Ereignishandler ausführen, können Sie anstelle einer Memberfunktion eine Lambda-Funktion verwenden. Auch hier ist es möglicherweise nicht offensichtlich aus dem folgenden Codebeispiel, aber ein RoutedEventHandler-Delegat wird aus einer Lambda-Funktion erstellt, die wiederum mit der Syntax des oben beschriebenen Funktionsaufrufoperators übereinstimmen muss.
MainPage::MainPage()
{
InitializeComponent();
myButton().Click([this](IInspectable const& /* sender */, RoutedEventArgs const& /* args */)
{
myButton().Content(box_value(L"Clicked"));
});
}
Sie können beim Konstruieren Ihres Delegaten etwas expliziter vorgehen. Wenn Sie es beispielsweise mehrmals übergeben oder verwenden möchten.
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);
}
Widerrufen eines registrierten Delegaten
Wenn Sie eine Stellvertretung registrieren, wird normalerweise ein Token an Sie zurückgegeben. Anschließend können Sie dieses Token verwenden, um die Registrierung Ihres Delegaten aufzuheben; das bedeutet, dass der Delegat vom Ereignis abgemeldet wird und nicht aufgerufen wird, sollte das Ereignis erneut ausgelöst werden.
Aus Gründen der Einfachheit zeigte keiner der obigen Codebeispiele, wie dies zu tun ist. Im nächsten Codebeispiel wird das Token allerdings im privaten Datenmember der Struktur gespeichert und der zugehörige Handler im Destruktor widerrufen.
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;
};
Anstelle eines starken Verweises können Sie wie im obigen Beispiel einen schwachen Verweis auf die Schaltfläche speichern (siehe Starke und schwache Verweise in C++/WinRT).
Note
Wenn eine Ereignisquelle die Ereignisse synchron auslöst, kannst du den Handler widerrufen und sicher sein, dass keine weiteren Ereignisse empfangen werden. Bei asynchronen Ereignissen kann jedoch auch nach dem Widerrufen (insbesondere bei einem Widerruf innerhalb des Destruktors) ein In-Flight-Ereignis das Objekt erreichen, nachdem mit der Zerstörung begonnen wurde. Eine Möglichkeit, das Problem zu entschärfen, besteht darin, vor der Zerstörung eine Stelle zum Abmelden zu finden; eine robuste Lösung finden Sie unter Sicherer Zugriff auf den this-Zeiger mit einem Ereignisbehandlungsdelegaten.
Alternativ können Sie beim Registrieren einer Stellvertretung winrt::auto_revoke (ein Wert vom Typ winrt::auto_revoke_t) angeben, um einen Ereignis-Revoker (vom Typ winrt::event_revoker) anzufordern. Der Ereignis-Revoker enthält einen schwachen Verweis auf die Ereignisquelle (das Objekt, das das Ereignis auslöst). Zum manuellen Widerrufen kannst du die Memberfunktion event_revoker::revoke aufrufen. Der Ereignis-Revoker ruft diese Funktion bei Verlassen des Gültigkeitsbereichs aber automatisch selbst auf. Die Funktion revoke überprüft, ob die Ereignisquelle noch existiert, und hebt gegebenenfalls Ihre Delegierung auf. In diesem Beispiel ist es nicht erforderlich, die Ereignisquelle zu speichern und keinen Destruktor zu benötigen.
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;
};
Nachfolgend finden Sie den Syntaxblock aus dem Dokumentationsabschnitt für das ButtonBase::Click-Ereignis. Es zeigt die drei verschiedenen Registrierungs- und Widerrufsfunktionen. Anhand der dritten Überladung siehst du ganz genau, welche Art von Ereignis-Revoker du deklarieren musst. Und Sie können die gleichen Arten von Delegaten sowohl an die Registrieren- als auch an die Widerrufen mit event_revoker-Überladungen übergeben.
// 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
Im obigen Codebeispiel ist Button::Click_revoker ein Typalias für winrt::event_revoker<winrt::Microsoft::UI::Xaml::Controls::Primitives::IButtonBase>. Ein ähnliches Muster gilt für alle C++/WinRT-Ereignisse. Jedes Windows-Runtime-Ereignis verfügt über eine Widerrufsfunktionsüberladung, die einen Ereignis-Revoker zurückgibt, und dieser Revokertyp gehört zur Ereignisquelle. Für ein weiteres Beispiel verfügt das Window::SizeChanged-Ereignis also über eine Registrierungsfunktionsüberladung, die einen Wert vom Typ "Window::SizeChanged_revoker" zurückgibt.
Sie könnten in Betracht ziehen, Handler im Fall einer Seitennavigation zu deaktivieren. Wenn du wiederholt zu einer Seite navigierst und sie wieder verlässt, kannst du beim Verlassen der Seite alle Handler widerrufen. Alternativ: Wenn Sie dieselbe Seiteninstanz wiederverwenden, prüfen Sie den Wert Ihres Tokens und registrieren Sie es nur, wenn es noch nicht gesetzt ist (if (!m_token){ ... }). Eine dritte Möglichkeit besteht darin, einen Ereignis-Revoker auf der Seite als Datenmember zu speichern. Und eine vierte Option, wie weiter unten in diesem Thema beschrieben, besteht darin, einen starken oder schwachen Verweis auf dieses Objekt in Ihrer Lambda-Funktion zu erfassen.
Wenn Ihr Delegierter für automatischen Widerruf nicht registriert werden kann
Wenn Sie beim Registrieren eines Delegaten winrt::auto_revoke angeben und das Ergebnis eine winrt::hresult_no_interface-Ausnahme ist, bedeutet dies in der Regel, dass die Ereignisquelle schwache Verweise nicht unterstützt. Das ist beispielsweise eine häufige Situation im Microsoft.UI.Composition-Namensraum. In diesem Fall können Sie das Feature zum automatischen Widerrufen nicht verwenden. Sie müssen die Ereignishandler manuell widerrufen.
Delegattypen für asynchrone Aktionen und Vorgänge
In den obigen Beispielen wird der RoutedEventHandler-Delegattyp verwendet, aber natürlich gibt es viele andere Delegattypen. Asynchrone Aktionen und Vorgänge (mit und ohne Fortschritt) verfügen beispielsweise über Abschluss- und/oder Fortschrittsereignisse, die Delegaten des entsprechenden Typs erwarten. Beispielsweise erfordert das Statusereignis eines asynchronen Vorgangs mit Fortschritt (d. h. alles, was IAsyncOperationWithProgress implementiert) einen Delegaten vom Typ "AsyncOperationProgressHandler". Hier ist ein Codebeispiel für die Definition eines Delegaten dieses Typs mit einer Lambda-Funktion. Das Beispiel zeigt auch, wie ein AsyncOperationWithProgressCompletedHandler-Delegat erstellt wird.
#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 };
}
Wie im obigen Kommentar zu Coroutinen bereits angedeutet, ist die Verwendung von Coroutinen für dich wahrscheinlich naheliegender als die Verwendung eines Delegaten mit den Abschlussereignissen asynchroner Aktionen und Vorgänge. Ausführliche Informationen und Codebeispiele finden Sie unter Parallelität und asynchrone Vorgänge mit C++/WinRT.
Note
Es ist nicht richtig, mehr als einen Abschlusshandler für eine asynchrone Aktion oder einen Vorgang zu implementieren. Du kannst entweder einen einzelnen Delegaten für das Abschlussereignis verwenden oder co_await dafür ausführen. Wenn Sie beides haben, schlägt die zweite fehl.
Wenn du dich für Delegaten und gegen eine Coroutine entscheidest, kannst du eine einfachere Syntax verwenden.
async_op_with_progress.Completed(
[](auto&& /*sender*/, AsyncStatus const /* args */)
{
// ...
});
Delegattypen, die einen Wert zurückgeben
Einige Delegattypen müssen selbst einen Wert zurückgeben. Ein Beispiel ist ListViewItemToKeyHandler, der eine Zeichenfolge zurückgibt. Hier ist ein Beispiel für die Erstellung eines Delegaten dieses Typs (beachten Sie, dass die Lambda-Funktion einen Wert zurückgibt).
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";
});
}
Sicherer Zugriff auf diesen Zeiger mit einem Ereignisbehandlungsdelegat
Wenn Sie ein Ereignis mit der Memberfunktion eines Objekts oder innerhalb einer Lambda-Funktion innerhalb der Memberfunktion eines Objekts behandeln, müssen Sie über die relative Lebensdauer des Ereignisempfängers (das Objekt, das das Ereignis verarbeitet) und die Ereignisquelle (das Objekt, das das Ereignis auslöst) nachdenken. Weitere Informationen und Codebeispiele finden Sie unter Starke und schwache Verweise in C++/WinRT.
Wichtige APIs
- winrt::auto_revoke_t-Markierungsstruktur
- winrt::implements::get_weak-Funktion
- winrt::implements::get_strong-Funktion
Zugehörige Themen
Windows developer