Erstellen von Ereignissen in C++/WinRTAuthor events in C++/WinRT

Dieses Thema baut auf der Windows-Runtime-Komponente sowie der nutzenden Anwendung auf, deren Erstellung im Thema Windows-Runtime-Komponenten mit C++/WinRT beschrieben wird.This topic builds on the Windows Runtime component, and the consuming application, that the Windows Runtime components with C++/WinRT topic shows you how to build.

Hier finden Sie die neuen Funktionen, die in diesem Thema hinzugefügt wurden.Here are the new features that this topic adds.

  • Aktualisierung der Thermometerlaufzeitklasse, um ein Ereignis auszulösen, wenn die Temperatur unter den Gefrierpunkt sinkt.Update the thermometer runtime class to raise an event when its temperature goes below freezing.
  • Aktualisierung der Core-App, die die Thermometerlaufzeitklasse nutzt, sodass sie dieses Ereignis verarbeiten kann.Update the Core App that consumes the thermometer runtime class so that it handles that event.

Hinweis

Informationen zum Installieren und Verwenden der Visual Studio-Erweiterung (VSIX) C++/WinRT und des NuGet-Pakets (die zusammen die Projektvorlage und Buildunterstützung bereitstellen) findest du unter Visual Studio support for C++/WinRT, XAML, the VSIX extension, and the NuGet package (Visual Studio-Unterstützung für C++/WinRT, XAML, die VSIX-Erweiterung und das NuGet-Paket).For info about installing and using the C++/WinRT Visual Studio Extension (VSIX) and the NuGet package (which together provide project template and build support), see Visual Studio support for C++/WinRT.

Wichtig

Wichtige Konzepte und Begriffe im Zusammenhang mit der Nutzung und Erstellung von Laufzeitklassen mit C++/WinRT findest du unter Verwenden von APIs mit C++/WinRT sowie unter Erstellen von APIs mit C++/WinRT.For essential concepts and terms that support your understanding of how to consume and author runtime classes with C++/WinRT, see Consume APIs with C++/WinRT and Author APIs with C++/WinRT.

Erstellen von ThermometerWRC- und ThermometerCoreAppCreate ThermometerWRC and ThermometerCoreApp

Wenn Sie die in diesem Thema gezeigten Updates parallel durchführen möchten, damit Sie den Code erstellen und ausführen können, besteht Ihr erster Schritt darin, die exemplarische Vorgehensweise im Thema Windows-Runtime-Komponenten mit C++/WinRT durchzuführen.If you want to follow along with the updates shown in this topic, so that you can build and run the code, then the first step is to follow the walkthrough in the Windows Runtime components with C++/WinRT topic. Hierdurch erhalten Sie die Windows-Runtime-Komponente ThermometerWRC sowie die Core-App ThermometerCoreApp- , die diese nutzt.By doing so, you'll have the ThermometerWRC Windows Runtime component, and the ThermometerCoreApp Core App that consumes it.

Aktualisieren von ThermometerWRC, damit sie ein Ereignis auslöstUpdate ThermometerWRC to raise an event

Aktualisieren Sie Thermometer.idl so, dass sie wie das folgende Listung aussieht.Update Thermometer.idl to look like the listing below. Auf diese Weise deklarieren Sie ein Ereignis, dessen Delegattyp EventHandler mit einem Argument einer Gleitkommazahl mit einfacher Genauigkeit ist.This is how to declare an event whose delegate type is EventHandler with an argument of a single-precision floating-point number.

// Thermometer.idl
namespace ThermometerWRC
{
    runtimeclass Thermometer
    {
        Thermometer();
        void AdjustTemperature(Single deltaFahrenheit);
        event Windows.Foundation.EventHandler<Single> TemperatureIsBelowFreezing;
    };
}

Speichern Sie die Datei.Save the file. Das Projekt wird in seinem aktuellen Zustand nicht bis zum Abschluss erstellt, aber führen Sie jetzt in jedem Fall eine Erstellung aus, um aktualisierte Versionen von \ThermometerWRC\ThermometerWRC\Generated Files\sources\Thermometer.h und den Thermometer.cpp-Stubdateien zu generieren.The project won't build to completion in its current state, but perform a build now in any case to generate updated versions of the \ThermometerWRC\ThermometerWRC\Generated Files\sources\Thermometer.h and Thermometer.cpp stub files. In diesen Dateien können Sie jetzt Stubimplementierungen des TemperatureIsBelowFreezing-Ereignisses sehen.Inside those files you can now see stub implementations of the TemperatureIsBelowFreezing event. In C++/WinRT wird ein IDL-deklariertes Ereignis als Gruppe überladener Funktionen implementiert (ähnlich wie eine Eigenschaft, die als ein Paar überladener Get- und Set-Funktionen implementiert wird).In C++/WinRT, an IDL-declared event is implemented as a set of overloaded functions (similar to the way a property is implemented as a pair of overloaded get and set functions). Eine Überladung akzeptiert einen zu registrierenden Delegaten und gibt ein Token zurück (ein winrt::event_token).One overload takes a delegate to be registered, and returns a token (a winrt::event_token). Die andere akzeptiert ein Token und widerruft die Registrierung des zugeordneten Delegaten.The other takes a token, and revokes the registration of the associated delegate.

Öffnen Sie nun Thermometer.h und Thermometer.cpp, und aktualisieren Sie die Implementierung der Thermometer-Laufzeitklasse.Now open Thermometer.h and Thermometer.cpp, and update the implementation of the Thermometer runtime class. Fügen Sie in Thermometer.h die beiden überladenen TemperatureIsBelowFreezing-Funktionen sowie einen privaten Ereignisdatenmember hinzu, der in der Implementierung dieser Funktionen verwendet werden soll.In Thermometer.h, add the two overloaded TemperatureIsBelowFreezing functions, as well as a private event data member to use in the implementation of those functions.

// Thermometer.h
...
namespace winrt::ThermometerWRC::implementation
{
    struct Thermometer : ThermometerT<Thermometer>
    {
        ...
        winrt::event_token TemperatureIsBelowFreezing(Windows::Foundation::EventHandler<float> const& handler);
        void TemperatureIsBelowFreezing(winrt::event_token const& token) noexcept;

    private:
        winrt::event<Windows::Foundation::EventHandler<float>> m_temperatureIsBelowFreezingEvent;
        ...
    };
}
...

Wie oben zu sehen, wird ein Ereignis von der Strukturvorlage winrt::event dargestellt und durch einen bestimmten Delegattyp parametrisiert (der wiederum selbst durch einen Argumenttyp (args) parametrisiert werden kann).As you can see above, an event is represented by the winrt::event struct template, parameterized by a particular delegate type (which itself can be parameterized by an args type).

Implementieren Sie in Thermometer.cpp die beiden überladenen TemperatureIsBelowFreezing-Funktionen.In Thermometer.cpp, implement the two overloaded TemperatureIsBelowFreezing functions.

// Thermometer.cpp
...
namespace winrt::ThermometerWRC::implementation
{
    winrt::event_token Thermometer::TemperatureIsBelowFreezing(Windows::Foundation::EventHandler<float> const& handler)
    {
        return m_temperatureIsBelowFreezingEvent.add(handler);
    }

    void Thermometer::TemperatureIsBelowFreezing(winrt::event_token const& token) noexcept
    {
        m_temperatureIsBelowFreezingEvent.remove(token);
    }

    void Thermometer::AdjustTemperature(float deltaFahrenheit)
    {
        m_temperatureFahrenheit += deltaFahrenheit;
        if (m_temperatureFahrenheit < 32.f) m_temperatureIsBelowFreezingEvent(*this, m_temperatureFahrenheit);
    }
}

Hinweis

Informationen über automatische Ereignis-Revoker finden Sie unter Einen registrierten Delegaten widerrufen.For details of what an auto event revoker is, see Revoke a registered delegate. Sie erhalten eine kostenlose Implementierung des automatischen Ereignis-Revokers für das Ereignis.You get auto event revoker implementation for free for your event. Das heißt, Sie müssen nicht die Überladung für den Ereignis-Revoker implementieren – er wird von der C++/WinRT-Projektion für Sie bereitgestellt.In other words, you don't need to implement the overload for the event revoker—that's provided for you by the C++/WinRT projection.

Die anderen Überladungen (die Überladungen für Registrierung und manuellen Rückruf) sind nicht in die Projektion integriert.The other overloads (the registration and manual revocation overloads) are not baked into the projection. Dies bietet Ihnen die Flexibilität, sie optimal für Ihr Szenario zu implementieren.That's to give you the flexibility to implement them optimally for your scenario. Der in diesen Implementierungen gezeigte Aufruf von event::add und event::remove ist eine effiziente und parallele/threadsichere Standardmethode.Calling event::add and event::remove as shown in these implementations is an efficient and concurrency/thread-safe default. Bei einer großen Anzahl von Ereignissen empfiehlt sich jedoch unter Umständen eine Implementierung mit geringer Dichte anstatt einer Implementierung, in der jedes Ereignis ein eigenes Ereignisfeld besitzt.But if you have a very large number of events, then you may not want an event field for each, but rather opt for some kind of sparse implementation instead.

Oben ist außerdem zu sehen, dass die Implementierung der Funktion AdjustTemperature so aktualisiert wurde, dass sie nun das Ereignis TemperatureIsBelowFreezing auslöst, wenn die Temperatur unter den Gefrierpunkt fällt.You can also see above that the implementation of the AdjustTemperature function has been updated to raise the TemperatureIsBelowFreezing event if the temperature goes below freezing.

Aktualisieren von ThermometerCoreApp, damit sie das Ereignis behandeltUpdate ThermometerCoreApp to handle the event

Nehmen Sie im ThermometerCoreApp-Projekt in App.cpp die folgenden Änderungen am Code zum Registrieren eines Ereignishandlers vor, und sorgen Sie dann dafür, dass die Temperatur unter den Gefrierpunkt sinkt.In the ThermometerCoreApp project, in App.cpp, make the following changes to the code to register an event handler, and then cause the temperature to go below freezing.

WINRT_ASSERT ist eine Makrodefinition, die auf _ASSERTE erweitert wird.WINRT_ASSERT is a macro definition, and it expands to _ASSERTE.

struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
    winrt::event_token m_eventToken;
    ...
    
    void Initialize(CoreApplicationView const &)
    {
        m_eventToken = m_thermometer.TemperatureIsBelowFreezing([](const auto &, float temperatureFahrenheit)
        {
            WINRT_ASSERT(temperatureFahrenheit < 32.f); // Put a breakpoint here.
        });
    }
    ...

    void Uninitialize()
    {
        m_thermometer.TemperatureIsBelowFreezing(m_eventToken);
    }
    ...
    
    void OnPointerPressed(IInspectable const &, PointerEventArgs const & args)
    {
        m_thermometer.AdjustTemperature(-1.f);
        ...
    }
    ...
};

Beachten Sie die Änderung an der OnPointerPressed-Methode.Be aware of the change to the OnPointerPressed method. Jetzt wird bei jedem Klick auf das Fenster 1 Grad Fahrenheit von der Temperatur des Thermometers abgezogen.Now, each time you click the window, you subtract 1 degree Fahrenheit from the thermometer's temperature. Und jetzt verarbeitet die App das Ereignis, das ausgelöst wird, wenn die Temperatur unter den Gefrierpunkt sinkt.And now, the app is handling the event that's raised when the temperature goes below freezing. Um zu sehen, dass das Ereignis wie erwartet ausgelöst wird, platziere einen Haltepunkt im Lambda-Ausdruck, der das Ereignis TemperatureIsBelowFreezing behandelt, führe die App aus, und klicke innerhalb des Fensters.To demonstrate that the event is being raised as expected, put a breakpoint inside the lambda expression that's handling the TemperatureIsBelowFreezing event, run the app, and click inside the window.

Parametrisierte Delegaten innerhalb einer ABIParameterized delegates across an ABI

Wenn dein Ereignis innerhalb einer binären Anwendungsschnittstelle (Application Binary Interface, ABI) zugänglich sein muss (etwa zwischen einer Komponente und der zugehörigen verwendenden Anwendung), muss dein Ereignis einen Windows-Runtime-Delegattyp verwenden.If your event must be accessible across an application binary interface (ABI)—such as between a component and its consuming application—then your event must use a Windows Runtime delegate type. Im obigen Beispiel wird der Windows-Runtime-Delegattyp Windows::Foundation::EventHandler<T> verwendet.The example above uses the Windows::Foundation::EventHandler<T> Windows Runtime delegate type. TypedEventHandler<TSender, TResult> ist ein weiteres Beispiel für einen Windows-Runtime-Delegattyp.TypedEventHandler<TSender, TResult> is another example of a Windows Runtime delegate type.

Da die Typparameter für diese beiden Delegattypen die ABI durchlaufen müssen, müssen die Typparameter ebenfalls Windows-Runtime-Typen sein.The type parameters for those two delegate types have to cross the ABI, so the type parameters must be Windows Runtime types, too. Dies schließt Laufzeitklassen von Windows und Drittanbietern sowie primitive Typen wie Zahlen und Zeichenfolgen mit ein.That includes Windows runtime classes, third-party runtime classes, and primitive types such as numbers and strings. Sollten Sie diese Einschränkung vergessen, tritt ein Compilerfehler mit dem Hinweis auf, dass T ein WinRT-Typ sein muss.The compiler helps you with a "T must be WinRT type" error if you forget that constraint.

Im Folgenden finden Sie ein Beispiel in Form von Codeauflistungen.Below is an example in the form of code listings. Beginnen Sie mit den Projekten ThermometerWRC und ThermometerCoreApp, die Sie zuvor in diesem Thema erstellt haben, und bearbeiten Sie den Code in diesen Projekten so, dass er dem Code in diesen Auflistungen entspricht.Begin with the ThermometerWRC and ThermometerCoreApp projects that you created earlier in this topic, and edit the code in those projects to look like the code in these listings.

Diese erste Liste ist für das Projekt ThermometerWRC bestimmt.This first listing is for the ThermometerWRC project. Nachdem Sie ThermometerWRC.idl wie unten dargestellt bearbeitet haben, erstellen Sie das Projekt, und kopieren Sie MyEventArgs.h und .cpp in das Projekt (aus dem Ordner Generated Files), wie Sie es zuvor mit Thermometer.h und .cpp getan haben.After editing ThermometerWRC.idl as shown below, build the project, and then copy MyEventArgs.h and .cpp into the project (from the Generated Files folder) just like you did earlier with Thermometer.h and .cpp.

// ThermometerWRC.idl
namespace ThermometerWRC
{
    [default_interface]
    runtimeclass MyEventArgs
    {
        Single TemperatureFahrenheit{ get; };
    }

    [default_interface]
    runtimeclass Thermometer
    {
        ...
        event Windows.Foundation.EventHandler<ThermometerWRC.MyEventArgs> TemperatureIsBelowFreezing;
        ...
    };
}

// MyEventArgs.h
#pragma once
#include "MyEventArgs.g.h"

namespace winrt::ThermometerWRC::implementation
{
    struct MyEventArgs : MyEventArgsT<MyEventArgs>
    {
        MyEventArgs() = default;
        MyEventArgs(float temperatureFahrenheit);
        float TemperatureFahrenheit();

    private:
        float m_temperatureFahrenheit{ 0.f };
    };
}

// MyEventArgs.cpp
#include "pch.h"
#include "MyEventArgs.h"
#include "MyEventArgs.g.cpp"

namespace winrt::ThermometerWRC::implementation
{
    MyEventArgs::MyEventArgs(float temperatureFahrenheit) : m_temperatureFahrenheit(temperatureFahrenheit)
    {
    }

    float MyEventArgs::TemperatureFahrenheit()
    {
        return m_temperatureFahrenheit;
    }
}

// Thermometer.h
...
struct Thermometer : ThermometerT<Thermometer>
{
...
    winrt::event_token TemperatureIsBelowFreezing(Windows::Foundation::EventHandler<ThermometerWRC::MyEventArgs> const& handler);
...
private:
    winrt::event<Windows::Foundation::EventHandler<ThermometerWRC::MyEventArgs>> m_temperatureIsBelowFreezingEvent;
...
}
...

// Thermometer.cpp
#include "MyEventArgs.h"
...
winrt::event_token Thermometer::TemperatureIsBelowFreezing(Windows::Foundation::EventHandler<ThermometerWRC::MyEventArgs> const& handler) { ... }
...
void Thermometer::AdjustTemperature(float deltaFahrenheit)
{
    m_temperatureFahrenheit += deltaFahrenheit;

    if (m_temperatureFahrenheit < 32.f)
    {
        auto args = winrt::make_self<winrt::ThermometerWRC::implementation::MyEventArgs>(m_temperatureFahrenheit);
        m_temperatureIsBelowFreezingEvent(*this, *args);
    }
}
...

Diese Liste ist für das Projekt ThermometerCoreApp bestimmt.This listing is for the ThermometerCoreApp project.

// App.cpp
...
void Initialize(CoreApplicationView const&)
{
    m_eventToken = m_thermometer.TemperatureIsBelowFreezing([](const auto&, ThermometerWRC::MyEventArgs args)
    {
        float degrees = args.TemperatureFahrenheit();
        WINRT_ASSERT(degrees < 32.f); // Put a breakpoint here.
    });
}
...

Einfache Signale innerhalb einer ABISimple signals across an ABI

Wenn du mit deinem Ereignis keine Parameter oder Argumente übergeben musst, kannst du einen eigenen einfachen Windows-Runtime-Delegattyp definieren.If you don't need to pass any parameters or arguments with your event, then you can define your own simple Windows Runtime delegate type. Das folgende Beispiel zeigt eine einfachere Version der Laufzeitklasse Thermometer.The example below shows a simpler version of the Thermometer runtime class. Darin wird ein Delegattyp namens SignalDelegate deklariert und anschließend verwendet, um ein Signaltypereignis auszulösen (anstatt eines Ereignisses mit einem Parameter).It declares a delegate type named SignalDelegate and then it uses that to raise a signal-type event instead of an event with a parameter.

// ThermometerWRC.idl
namespace ThermometerWRC
{
    delegate void SignalDelegate();

    runtimeclass Thermometer
    {
        Thermometer();
        event ThermometerWRC.SignalDelegate SignalTemperatureIsBelowFreezing;
        void AdjustTemperature(Single value);
    };
}
// Thermometer.h
...
namespace winrt::ThermometerWRC::implementation
{
    struct Thermometer : ThermometerT<Thermometer>
    {
        ...

        winrt::event_token SignalTemperatureIsBelowFreezing(ThermometerWRC::SignalDelegate const& handler);
        void SignalTemperatureIsBelowFreezing(winrt::event_token const& token);
        void AdjustTemperature(float deltaFahrenheit);

    private:
        winrt::event<ThermometerWRC::SignalDelegate> m_signal;
        float m_temperatureFahrenheit{ 0.f };
    };
}
// Thermometer.cpp
...
namespace winrt::ThermometerWRC::implementation
{
    winrt::event_token Thermometer::SignalTemperatureIsBelowFreezing(ThermometerWRC::SignalDelegate const& handler)
    {
        return m_signal.add(handler);
    }

    void Thermometer::SignalTemperatureIsBelowFreezing(winrt::event_token const& token)
    {
        m_signal.remove(token);
    }

    void Thermometer::AdjustTemperature(float deltaFahrenheit)
    {
        m_temperatureFahrenheit += deltaFahrenheit;
        if (m_temperatureFahrenheit < 32.f)
        {
            m_signal();
        }
    }
}
// App.cpp
struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
    ThermometerWRC::Thermometer m_thermometer;
    winrt::event_token m_eventToken;
    ...
    
    void Initialize(CoreApplicationView const &)
    {
        m_eventToken = m_thermometer.SignalTemperatureIsBelowFreezing([] { /* ... */ });
    }
    ...

    void Uninitialize()
    {
        m_thermometer.SignalTemperatureIsBelowFreezing(m_eventToken);
    }
    ...

    void OnPointerPressed(IInspectable const &, PointerEventArgs const & args)
    {
        m_thermometer.AdjustTemperature(-1.f);
        ...
    }
    ...
};

Parametrisierte Delegaten, einfache Signale und Rückrufe in einem ProjektParameterized delegates, simple signals, and callbacks within a project

Wenn Sie Ereignisse benötigen, die im Rahmen Ihres Visual Studio-Projekts intern sind (nicht Binary-übergreifend) und nicht auf Windows-Runtime-Typen eingeschränkt, können Sie trotzdem die Klassenvorlage winrt::event<Delegate> verwenden.If you need events that are internal to your Visual Studio project (not across binaries), where those events are not limited to Windows Runtime types, then you can still use the winrt::event<Delegate> class template. Verwenden Sie einfach winrt::delegate anstelle eines tatsächlichen Windows Runtime-Delegattyps, da winrt::delegate auch Parameter unterstützt, die nicht aus Windows Runtime stammen.Simply use winrt::delegate instead of an actual Windows Runtime delegate type, since winrt::delegate also supports non Windows Runtime parameters.

Das folgende Beispiel zeigt zuerst eine Delegatsignatur, die keine Parameter akzeptiert (also im Prinzip ein einfaches Signal), und anschließend eine, die eine Zeichenfolge akzeptiert.The example below first shows a delegate signature that doesn't take any parameters (essentially a simple signal), and then one that takes a string.

winrt::event<winrt::delegate<>> signal;
signal.add([] { std::wcout << L"Hello, "; });
signal.add([] { std::wcout << L"World!" << std::endl; });
signal();

winrt::event<winrt::delegate<std::wstring>> log;
log.add([](std::wstring const& message) { std::wcout << message.c_str() << std::endl; });
log.add([](std::wstring const& message) { Persist(message); });
log(L"Hello, World!");

Beachte, dass du dem Ereignis beliebig viele abonnierende Delegaten hinzufügen kannst.Notice how you can add to the event as many subscribing delegates as you wish. Für ein Ereignis ist jedoch etwas mehr Aufwand erforderlich.However, there is some overhead associated with an event. Wenn du lediglich einen einfachen Rückruf mit nur einem einzelnen abonnierenden Delegaten benötigst, kannst du winrt::delegate<... T> für sich allein verwenden.If all you need is a simple callback with only a single subscribing delegate, then you can use winrt::delegate<... T> on its own.

winrt::delegate<> signalCallback;
signalCallback = [] { std::wcout << L"Hello, World!" << std::endl; };
signalCallback();

winrt::delegate<std::wstring> logCallback;
logCallback = [](std::wstring const& message) { std::wcout << message.c_str() << std::endl; }f;
logCallback(L"Hello, World!");

Wenn du eine C++/CX-Codebasis portierst, bei der Ereignisse und Delegaten intern innerhalb eines Projekts verwendet werden, hilft dir winrt::delegate dabei, dieses Muster in C++/WinRT zu replizieren.If you're porting from a C++/CX codebase where events and delegates are used internally within a project, then winrt::delegate will help you to replicate that pattern in C++/WinRT.

EntwurfsrichtlinienDesign guidelines

Es wird empfohlen, als Funktionsparameter keine Delegaten, sondern Ereignisse zu übergeben.We recommend that you pass events, and not delegates, as function parameters. Einzige Ausnahme ist die Funktion add von winrt::event, da in diesem Fall ein Delegat übergeben werden muss.The add function of winrt::event is the one exception, because you must pass a delegate in that case. Der Grund für diese Richtlinie: Delegaten können in unterschiedlichen Windows-Runtime-Sprachen unterschiedliche Formen haben. (Manche unterstützen nur eine einzelne Clientregistrierung, andere dagegen mehrere.)The reason for this guideline is because delegates can take different forms across different Windows Runtime languages (in terms of whether they support one client registration, or multiple). Ereignisse sind dank ihres Modells mit mehreren Abonnenten berechenbarer und konsistenter.Events, with their multiple subscriber model, constitute a much more predictable and consistent option.

Die Signatur für einen Ereignishandlerdelegaten muss aus zwei Parametern bestehen: sender (IInspectable) und args (ein Argumenttyp, etwa RoutedEventArgs).The signature for an event handler delegate should consist of two parameters: sender (IInspectable), and args (some event argument type, for example RoutedEventArgs).

Diese Richtlinien gelten möglicherweise nicht, wenn du eine interne API entwirfst.Note that these guidelines don't necessarily apply if you're designing an internal API. Interne APIs werden Laufe der Zeit allerdings häufig zu öffentlichen APIs.Although, internal APIs often become public over time.