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.
Dieses Thema baut auf den Windows-Runtime-Komponenten und der Anwendung auf, die diese nutzt und deren Erstellung im Thema Windows-Runtime-Komponenten mit C++/WinRT erläutert wird.
Hier sind die neuen Features, die in diesem Thema hinzugefügt werden.
- Aktualisieren Sie die Thermometerlaufzeitklasse, um ein Ereignis auszuheben, wenn die Temperatur unter dem Einfrieren liegt.
- Aktualisieren Sie die Core-App, die die Thermometerlaufzeitklasse verwendet, damit es dieses Ereignis behandelt.
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.
Important
Wichtige Konzepte und Begriffe, die Ihr Verständnis für die Nutzung und Erstellung von Laufzeitklassen mit C++/WinRT unterstützen, finden Sie unter Verwenden von APIs mit C++/WinRT und Erstellen von APIs mit C++/WinRT.
Erstellen von ThermometerWRC und ThermometerCoreApp
Wenn Sie die in diesem Thema gezeigten Updates befolgen möchten, damit Sie den Code erstellen und ausführen können, besteht der erste Schritt darin, die exemplarische Vorgehensweise im Thema Windows-Runtime Komponenten mit C++/WinRT zu befolgen. Auf diese Weise verfügen Sie über die ThermometerWRC-Windows-Runtime-Komponente und die ThermometerCoreApp Core-App, die sie verbraucht.
Aktualisieren Sie ThermometerWRC, um ein Ereignis auszulösen
Aktualisieren Sie Thermometer.idl so, dass es der unten stehenden Liste entspricht. So deklarieren Sie ein Ereignis, dessen Delegattyp "EventHandler " mit einem Argument einer Gleitkommazahl mit einfacher Genauigkeit ist.
// Thermometer.idl
namespace ThermometerWRC
{
runtimeclass Thermometer
{
Thermometer();
void AdjustTemperature(Single deltaFahrenheit);
event Windows.Foundation.EventHandler<Single> TemperatureIsBelowFreezing;
};
}
Speichern Sie die Datei. Das Projekt lässt sich in seinem aktuellen Zustand nicht vollständig erstellen, aber führen Sie jetzt trotzdem einen Build aus, um aktualisierte Versionen der \ThermometerWRC\ThermometerWRC\Generated Files\sources\Thermometer.h- und Thermometer.cpp-Stubdateien zu generieren. In diesen Dateien können Sie jetzt Stubimplementierungen des TemperatureIsBelowFreezing-Ereignisses sehen. In C++/WinRT wird ein IDL-deklariertes Ereignis als eine Reihe überladener Funktionen implementiert (ähnlich wie eine Eigenschaft als Paar überladener Get- und Set-Funktionen implementiert wird). Eine Überladung benötigt einen Delegaten, um registriert zu werden, und gibt ein Token zurück ( winrt::event_token). Die andere nimmt ein Token entgegen und hebt die Registrierung des zugehörigen Delegaten auf.
Öffnen Sie nun Thermometer.h und Thermometer.cpp, und aktualisieren Sie die Implementierung der Thermometer-Laufzeitklasse.
Thermometer.hFügen Sie in der Datei die beiden überladenen TemperatureIsBelowFreezing-Funktionen sowie ein privates Ereignisdatenelement hinzu, das bei der Implementierung dieser Funktionen verwendet werden soll.
// 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 Sie oben sehen können, wird ein Ereignis durch die Winrt::event-Strukturvorlage dargestellt, parametrisiert durch einen bestimmten Delegattyp (der selbst durch einen Argstyp parametrisiert werden kann).
Implementieren Sie in Thermometer.cpp die beiden überladenen Funktionen TemperatureIsBelowFreezing.
// 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
Weitere Informationen dazu, was ein Auto-Event-Revoker ist, finden Sie unter Einen registrierten Delegaten widerrufen. Sie erhalten die Auto-Event-Revoker-Implementierung für Ihr Event kostenlos. Mit anderen Worten: Sie müssen die Überladung für den Event-Revoker nicht implementieren, denn diese wird Ihnen von der C++/WinRT-Projektion bereitgestellt.
Die anderen Überladungen (die Überladungen für die Registrierung und den manuellen Widerruf) sind nicht fest in die Projektion integriert. Dadurch erhalten Sie die Flexibilität, sie optimal für Ihr Szenario zu implementieren. Das Aufrufen von event::add und event::remove, wie in diesen Implementierungen gezeigt, ist ein effizienter und nebenläufigkeits-/threadsicherer Standard. Wenn Sie jedoch über eine sehr große Anzahl von Ereignissen verfügen, möchten Sie möglicherweise kein Ereignisfeld für jedes Ereignisfeld, sondern stattdessen eine Art sparsamer Implementierung verwenden.
Darüber hinaus können Sie sehen, dass die Implementierung der AdjustTemperature-Funktion aktualisiert wurde, um das TemperatureIsBelowFreezing-Ereignis auszuheben, wenn die Temperatur unter dem Einfrieren liegt.
Aktualisieren Sie ThermometerCoreApp, um das Ereignis zu verarbeiten.
Nehmen Sie im Projekt ThermometerCoreApp in App.cppden folgenden Änderungen am Code vor, um einen Ereignishandler zu registrieren, und führen Sie dann dazu, dass die Temperatur unter dem Einfrieren liegt.
WINRT_ASSERT ist eine Makrodefinition und wird auf _ASSERTE erweitert.
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 der OnPointerPressed-Methode . Jedes Mal, wenn Sie auf das Fenster klicken, subtrahieren Sie 1 Grad Fahrenheit von der Temperatur des Thermometers. Und jetzt behandelt die App das Ereignis, das ausgelöst wird, wenn die Temperatur unter den Gefrierpunkt fällt. Um zu veranschaulichen, dass das Ereignis wie erwartet ausgelöst wird, setzen Sie einen Haltepunkt im Lambda-Ausdruck, der das Ereignis TemperatureIsBelowFreezing behandelt, führen Sie die App aus und klicken Sie in das Fenster.
Parametrisierte Delegaten über ABI-Grenzen hinweg
Wenn Ihr Ereignis über eine Anwendungsbinärschnittstelle (ABI) hinweg zugänglich sein muss – etwa zwischen einer Komponente und der Anwendung, die sie nutzt –, dann muss Ihr Ereignis einen Windows-Runtime-Delegattyp verwenden. Im obigen Beispiel wird der Windows::Foundation::EventHandler<T>-Windows-Runtime-Delegattyp verwendet. TypedEventHandler<TSender, TResult> ist ein weiteres Beispiel für einen Delegattyp der Windows-Runtime.
Die Typparameter für diese beiden Delegattypen müssen die ABI-Grenze überschreiten, daher müssen die Typparameter ebenfalls Windows-Runtime-Typen sein. Dazu gehören Windows Laufzeitklassen, Laufzeitklassen von Drittanbietern und Grundtypen wie Zahlen und Zeichenfolgen. Der Compiler hilft Ihnen bei einem Fehler "T muss WinRT-Typ sein", wenn Sie diese Einschränkung vergessen.
Nachfolgend finden Sie ein Beispiel in Form von Codeauflistungen. 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 wie der Code in diesen Auflistungen aussieht.
Diese erste Auflistung ist für das ThermometerWRC-Projekt . Nachdem Sie ThermometerWRC.idl wie unten gezeigt bearbeitet haben, kompilieren Sie das Projekt und kopieren dann MyEventArgs.h und .cpp in das Projekt (aus dem Ordner Generated Files), so wie Sie zuvor bereits Thermometer.h und .cpp kopiert haben. Denken Sie daran, das static_assert aus beiden Dateien zu löschen.
// 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 Auflistung gilt für das ThermometerCoreApp-Projekt .
// 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 über eine ABI
Wenn Sie für Ihr Ereignis keine Parameter oder Argumente übergeben müssen, können Sie Ihren eigenen einfachen Delegattyp für die Windows-Runtime definieren. Das folgende Beispiel zeigt eine einfachere Version der Thermometer-Laufzeitklasse . Er deklariert einen Delegattyp mit dem Namen SignalDelegate und verwendet ihn dann, um ein Signaltypereignis anstelle eines Ereignisses mit einem Parameter auszuheben.
// 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 Stellvertretungen, einfache Signale und Rückrufe innerhalb eines Projekts
Wenn Sie Ereignisse benötigen, die für Ihr Visual Studio Projekt intern sind (nicht über Binärdateien hinweg), bei denen diese Ereignisse nicht auf Windows-Runtime Typen beschränkt sind, können Sie die Klassenvorlage "winrt::event<Delegate>" weiterhin verwenden. Verwenden Sie einfach winrt::delegate anstelle eines tatsächlichen Windows-Runtime-Delegattyps, da winrt::delegate auch Nicht-Windows-Runtime-Parameter unterstützt.
Das folgende Beispiel zeigt zunächst eine Stellvertretungssignatur, die keine Parameter verwendet (im Wesentlichen ein einfaches Signal), und dann eine, die eine Zeichenfolge akzeptiert.
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!");
Beachten Sie, dass Sie dem Ereignis beliebig viele abonnierende Delegaten hinzufügen können. Es gibt jedoch einen zusätzlichen Aufwand, der einem Ereignis zugeordnet ist. Wenn Sie nur einen einfachen Rückruf mit nur einem einzigen registrierten Delegate benötigen, können Sie winrt::delegate<... T> eigenständig verwenden.
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 Sie von einer C++/CX-Codebasis portieren, in der Ereignisse und Delegates intern in einem Projekt verwendet werden, hilft Ihnen winrt::delegate dabei, dieses Muster in C++/WinRT nachzubilden.
Aufschiebbare Ereignisse
Ein gängiges Muster in der Windows-Runtime ist das zurückstellbare Ereignis. Ein Ereignishandler fordert ein Deferral an, indem er die GetDeferral-Methode des Ereignisarguments aufruft. Dadurch wird der Ereignisquelle signalisiert, dass Aktivitäten nach dem Ereignis verschoben werden sollen, bis der Aufschub abgeschlossen ist. Dadurch kann ein Ereignishandler asynchrone Aktionen als Reaktion auf ein Ereignis ausführen.
Die Strukturvorlage winrt::deferrable_event_args ist eine Hilfsklasse zur Implementierung (Bereitstellung) des Windows-Runtime-Deferralmusters. Hier finden Sie ein Beispiel dafür.
// Widget.idl
namespace Sample
{
runtimeclass WidgetStartingEventArgs
{
Windows.Foundation.Deferral GetDeferral();
Boolean Cancel;
};
runtimeclass Widget
{
event Windows.Foundation.TypedEventHandler<
Widget, WidgetStartingEventArgs> Starting;
};
}
// Widget.h
namespace winrt::Sample::implementation
{
struct Widget : WidgetT<Widget>
{
Widget() = default;
event_token Starting(Windows::Foundation::TypedEventHandler<
Sample::Widget, Sample::WidgetStartingEventArgs> const& handler)
{
return m_starting.add(handler);
}
void Starting(event_token const& token) noexcept
{
m_starting.remove(token);
}
private:
event<Windows::Foundation::TypedEventHandler<
Sample::Widget, Sample::WidgetStartingEventArgs>> m_starting;
};
struct WidgetStartingEventArgs : WidgetStartingEventArgsT<WidgetStartingEventArgs>,
deferrable_event_args<WidgetStartingEventArgs>
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
{
bool Cancel() const noexcept { return m_cancel; }
void Cancel(bool value) noexcept { m_cancel = value; }
bool m_cancel = false;
};
}
Hier erfahren Sie, wie der Ereignisempfänger das aufschiebbare Ereignismuster nutzt.
// EventRecipient.h
widget.Starting([](auto sender, auto args) -> fire_and_forget
{
auto deferral = args.GetDeferral();
if (!co_await CanWidgetStartAsync(sender))
{
// Do not allow the widget to start.
args.Cancel(true);
}
deferral.Complete();
});
Als Implementierer (Produzent) der Ereignisquelle leiten Sie Ihre Klasse für die Ereignisargumente von winrt::deferrable_event_args ab. < deferrable_event_argsT> implementiert T::GetDeferral für Sie. Außerdem stellt es eine neue Hilfsmethode deferrable_event_args::wait_for_deferrals bereit, die dann abgeschlossen wird, wenn alle ausstehenden Aufschübe abgeschlossen wurden (wenn keine Aufschübe angefordert wurden, wird sie sofort abgeschlossen).
// Widget.h
IAsyncOperation<bool> TryStartWidget(Widget const& widget)
{
auto args = make_self<WidgetStartingEventArgs>();
// Raise the event to let people know that the widget is starting
// and give them a chance to prevent it.
m_starting(widget, *args);
// Wait for deferrals to complete.
co_await args->wait_for_deferrals();
// Use the results.
bool started = false;
if (!args->Cancel())
{
widget.InsertBattery();
widget.FlipPowerSwitch();
started = true;
}
co_return started;
}
Entwurfsrichtlinien
Es wird empfohlen, Ereignisse und keine Stellvertretungen als Funktionsparameter zu übergeben. Die add-Funktion von winrt::event ist die einzige Ausnahme, da Sie in diesem Fall einen Delegaten übergeben müssen. Der Grund für diese Richtlinie ist, dass Delegaten in verschiedenen Windows-Runtime-Sprachen unterschiedliche Formen annehmen können, je nachdem, ob sie eine einzelne oder mehrere Clientregistrierungen unterstützen. Ereignisse mit ihrem Modell mit mehreren Abonnenten stellen eine viel vorhersehbarere und konsistentere Option dar.
Die Signatur für einen Ereignishandlerdelegat sollte aus zwei Parametern bestehen: Sender (IInspectable) und Args (einiger Ereignisargumenttyp, z. B. RoutedEventArgs).
Beachten Sie, dass diese Richtlinien nicht unbedingt gelten, wenn Sie eine interne API entwerfen. Obwohl interne APIs im Laufe der Zeit häufig öffentlich werden.
Zugehörige Themen
Windows developer