Freigeben über


Ereignisse mit Delegaten in C++/WinRT behandeln

In diesem Thema wird gezeigt, wie Sie Ereignisbehandlungsdelegaten mithilfe C++/WinRT-registrieren und widerrufen. Sie können ein Ereignis mithilfe eines beliebigen standardmäßigen C++-Funktionsobjekts behandeln.

Hinweis

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; zum Beispiel: OnClicked.

Der XAML-Designer fügt den entsprechenden Prototyp der Ereignishandlerfunktion (und eine Stubimplementierung) zu den Quellendateien hinzu, damit Sie diese durch Ihre eigene Implementierung ersetzen können.

Hinweis

In der Regel müssen Ihre Ereignishandler nicht in Ihrer Midl-Datei (.idl) beschrieben werden. Daher fügt der XAML-Designer ihrer Midl-Datei keine Prototypen der Ereignishandlerfunktion hinzu. Sie fügt nur Ihre .h und .cpp Dateien hinzu.

Einen Delegierten registrieren, um ein Ereignis zu behandeln

Ein einfaches Beispiel ist die Behandlung des Klickereignisses einer Schaltfläche. Es ist typisch, XAML-Markup zu verwenden, um eine Memberfunktion für die Behandlung des Ereignisses zu registrieren, wie in diesem Beispiel.

// 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"));
}

Der obige Code stammt aus dem Leere App (C++/WinRT) Projekt in Visual Studio. Der Code myButton() ruft eine generierte Zugriffsfunktion auf, die den Button zurückgibt, den wir myButtongenannt haben. Wenn Sie die x:Name dieses -Button--Element ändern, ändert sich auch der Name der generierten Accessor-Funktion.

Hinweis

In diesem Fall ist die Ereignisquelle (das Objekt, das das Ereignis auslöst) der Button mit dem Namen myButton. Und der Ereignisempfänger (das Objekt, das das Ereignis behandelt) ist eine Instanz von MainPage. Weitere Informationen zur Verwaltung der Lebensdauer von Ereignisquellen und Ereignisempfängern finden Sie später in diesem Thema.

Anstatt sie deklarativ im Markup auszuführen, können Sie eine Memberfunktion zwingend registrieren, um ein Ereignis zu behandeln. 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 RoutedEventHandler Konstruktorüberladung, die ein Objekt und eine Zeiger-zu-Member-Funktion verwendet.

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

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

Von Bedeutung

Beim Registrieren des Delegaten übergibt das obige Codebeispiel einen unformatierten diesen Zeiger (zeigt auf das aktuelle Objekt). Um zu lernen, wie man einen starken oder schwachen Verweis auf das aktuelle Objekt einrichtet, lesen Sie . Wenn Sie eine Memberfunktion als Delegat verwenden, siehe.

Hier ist ein Beispiel, das eine statische Memberfunktion verwendet; beachte die einfachere Syntax.

// 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 */) { ... }

Es gibt weitere Möglichkeiten zum Erstellen eines RoutedEventHandler-. 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). Beachten Sie die verschiedenen Konstruktoren: eine nimmt eine Lambda-Funktion; eine weitere freie Funktion; und eine andere (die oben verwendete) übernimmt ein Objekt und eine Zeiger-zu-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::Windows::UI::Xaml::RoutedEventArgs const& e) const;
};

Die Syntax des Funktionsaufrufoperators ist ebenfalls hilfreich zu betrachten. Es wird Ihnen mitgeteilt, welche Parameter Ihr Delegat haben muss. Wie Sie sehen können, entspricht die Syntax des Funktionsaufrufoperators den Parametern unseres MainPage::ClickHandler.

Hinweis

Um die Details des Delegaten zu ermitteln und die Parameter dieses Delegaten zu ermitteln, gehen Sie zunächst zum Dokumentationsthema für das Ereignis selbst. Als Beispiel nehmen wir das UIElement.KeyDown-Ereignis. Besuchen Sie dieses Thema, und wählen Sie C++/WinRT aus der Dropdownliste Sprache aus. Im Syntaxblock am Anfang des Themas sehen Sie dies.

// Register
event_token KeyDown(KeyEventHandler const& handler) const;

Diese Informationen geben uns an, dass das UIElement.KeyDown-Ereignis (das Thema, auf dem wir arbeiten) über einen Delegattyp KeyEventHandlerverfügt, da dies der Typ ist, den Sie beim Registrieren eines Delegaten bei diesem Ereignistyp übergeben. Folgen Sie nun dem Link zu diesem KeyEventHandler-Delegaten Typ. Hier enthält der Syntaxblock einen Funktionsaufrufoperator. Und wie oben erwähnt, zeigt Ihnen das, was die Parameter Ihres Delegierten sein müssen.

void operator()(
  winrt::Windows::Foundation::IInspectable const& sender,
  winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e) const;

Wie Sie sehen können, muss die Stellvertretung deklariert werden, um eine IInspectable- als Absender zu übernehmen, und eine Instanz der KeyRoutedEventArgs-Klasse als Argumente.

Um ein weiteres Beispiel zu betrachten, sehen wir uns das Popup.Closed-Ereignisan. Der Delegattyp ist EventHandler<IInspectable>. Ihr Delegat nimmt also eine IInspectable als Absender und eine andere IInspectable (da dies der Typparameter des EventHandlersist) als Argument.

Wenn Sie nicht viel Arbeit in Ihrem Ereignishandler ausführen, können Sie anstelle einer Memberfunktion eine Lambda-Funktion verwenden. Erneut, es ist möglicherweise im folgenden Codebeispiel nicht sofort erkennbar, 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 sich entscheiden, etwas expliziter zu sein, wenn Sie Ihre Stellvertretung erstellen. Wenn Sie es beispielsweise weitergeben oder mehrmals verwenden möchten.

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);
}

Widerrufen einer registrierten Stellvertretung

Wenn Sie einen Delegierten registrieren, wird normalerweise ein Token an Sie zurückgegeben. Anschließend können Sie dieses Token verwenden, um Ihren Delegaten zu widerrufen, d. h., dass er vom Ereignis abgemeldet wird und beim nächsten Ereignis nicht wieder aufgerufen wird.

Aus Gründen der Einfachheit zeigte keiner der obigen Codebeispiele, wie dies zu tun ist. In diesem nächsten Codebeispiel wird das Token in einem privaten Datenmember der Struktur gespeichert und der Handler im Destruktor zurückgenommen.

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;
};

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).

Hinweis

Wenn eine Ereignisquelle ihre Ereignisse synchron auslöst, können Sie den Ereignishandler widerrufen und sicher sein, dass Sie keine weiteren Ereignisse erhalten. Bei asynchronen Ereignissen kann ein In-Flight-Ereignis jedoch auch nach dem Widerrufen (und insbesondere beim Widerrufen innerhalb des Destruktors) ihr Objekt erreichen, nachdem es mit der Destruktierung begonnen hat. Das Auffinden eines Orts zum Kündigen vor der Vernichtung kann das Problem mindern oder für eine robuste Lösung sicheren Zugriff auf die dieses Zeigers mit einem Ereignisbehandlungsdelegat.

Alternativ können Sie beim Registrieren eines Delegats winrt::auto_revoke (ein Wert vom Typ winrt::auto_revoke_t) angeben, um einen Ereignis-Revoke-Handler (vom Typ winrt::event_revoker) anforderbar zu machen. Der Ereigniswiderrufer enthält einen schwachen Verweis auf die Ereignisquelle (das Objekt, das das Ereignis auslöst). Sie können die event_revoker::revoke Memberfunktion manuell widerrufen; aber der Ereignis-Revoker ruft diese Funktion automatisch auf, wenn sie außerhalb des Gültigkeitsbereichs ist. Die Rückgängig machen-Funktion überprüft, ob die Ereignisquelle noch vorhanden ist und widerruft, wenn dies der Fall ist, Ihre Delegierung. In diesem Beispiel ist es nicht erforderlich, die Ereignisquelle zu speichern, und es gibt keine Notwendigkeit für einen Destruktor.

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;
};

Nachfolgend sehen Sie den Syntaxabschnitt aus dem Dokumentationsthema für das ButtonBase::Click-Ereignis. Es zeigt die drei verschiedenen Registrierungs- und Widerrufsfunktionen. Sie können genau sehen, welche Art von Ereignisrevoker Sie aus dem dritten Überladungspunkt deklarieren müssen. Und Sie können die gleichen Arten von Stellvertretungen sowohl an das registrieren, als auch das mit event_revoker Überladungen widerrufen.

// 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;

Hinweis

Im obigen Codebeispiel ist Button::Click_revoker ein Typalias für winrt::event_revoker<winrt::Windows::UI::Xaml::Controls::Primitives::IButtonBase>. Ein ähnliches Muster gilt für alle C++/WinRT-Ereignisse. Jedes Windows-Runtime-Ereignis verfügt über eine Revoke-Funktionsüberladung, die einen Ereignis-Revoker zurückgibt, und dessen Typ ein Mitglied der Ereignisquelle ist. Um ein weiteres Beispiel anzuführen: Das Ereignis CoreWindow::SizeChanged verfügt über eine Überladung der Registrierungsfunktion, die einen Wert vom Typ CoreWindow::SizeChanged_revokerzurückgibt.

Möglicherweise könnten Sie Handler in einem Seitennavigationsszenario widerrufen. Wenn Sie wiederholt zu einer Seite navigieren und dann wieder zurückkehren, können Sie alle Handler entfernen, wenn Sie die Seite verlassen. Alternativ, wenn Sie dieselbe Seiteninstanz verwenden, überprüfen Sie den Wert Ihres Tokens und registrieren Sie sich nur dann, wenn es noch nicht festgelegt wurde (if (!m_token){ ... }). Eine dritte Option besteht darin, einen Ereignis-Widerrufer innerhalb der Seite als ein Datenmitglied zu speichern. Und eine vierte Option, wie weiter unten in diesem Thema beschrieben, besteht darin, einen starken oder schwachen Verweis auf die dieses-Objekts in Ihrer Lambda-Funktion zu erfassen.

Wenn Ihr Auto-Widerruf-Delegierter nicht registriert werden kann

Wenn Sie versuchen, winrt::auto_revoke beim Registrieren eines Delegaten anzugeben, und das Ergebnis ist eine winrt::hresult_no_interface Ausnahme, dann bedeutet das normalerweise, dass die Ereignisquelle keine schwachen Verweise unterstützt. Dies ist beispielsweise eine häufige Situation im Windows.UI.Composition Namespace. In diesem Fall können Sie das Feature zum automatischen Widerrufen nicht verwenden. Sie müssen auf das manuelle Widerrufen der Ereignishandler zurückgreifen.

Delegattypen für asynchrone Aktionen und Vorgänge

In den obigen Beispielen wird der Delegattyp RoutedEventHandler verwendet, aber natürlich gibt es viele andere Delegattypen. Beispielsweise haben asynchrone Aktionen und Vorgänge (mit und ohne Fortschritt) Abschluss- und/oder Fortschrittsevents, die Delegaten des entsprechenden Typs erwarten. Beispielsweise erfordert das Statusereignis eines asynchronen Vorgangs mit Fortschritt (d. h. alles, was IAsyncOperationWithProgressimplementiert) einen Delegat vom Typ AsyncOperationProgressHandler. Hier ist ein Codebeispiel für die Erstellung eines Delegaten dieses Typs mithilfe einer Lambda-Funktion. Das Beispiel zeigt auch, wie ein AsyncOperationWithProgressCompletedHandler Delegate 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 der oben stehende Kommentar "coroutine" andeutet, ist es wahrscheinlich natürlicher, Coroutines zu verwenden, anstatt einen Delegaten für die abgeschlossenen Ereignisse asynchroner Aktionen und Operationen zu nutzen. Ausführliche Informationen und Codebeispiele finden Sie unter Nebenläufigkeit und asynchrone Vorgänge mit C++/WinRT.

Hinweis

Es ist nicht korrekt, mehr als einen Abschlusshandler für eine asynchrone Aktion oder für einen asynchronen Vorgang zu implementieren. Sie können entweder einen einzigen Delegaten für dessen abgeschlossenes Ereignis haben oder Sie können es co_await. Wenn Sie beides haben, schlägt die zweite fehl.

Wenn Sie mit Delegaten anstelle einer Coroutine arbeiten, können Sie sich für eine einfachere Syntax entscheiden.

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 das Definieren eines Delegaten dieses Typs (beachten Sie, dass die Lambda-Funktion einen Wert zurückgibt).

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";
    });
}

Sicherer Zugriff auf den --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 relativen Lebenszyklen des Ereignisempfängers (das Objekt, das das Ereignis verarbeitet) und der Ereignisquelle (das Objekt, das das Ereignis auslöst) nachdenken. Weitere Informationen und Codebeispiele finden Sie in Starke und schwache Referenzen in C++/WinRT.

Wichtige APIs