Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Det här avsnittet bygger på Windows Runtime-komponenten och den konsumerande applikationen som avsnittet Windows Runtime-komponenter med C++/WinRT visar hur du skapar.
Här är de nya funktionerna som det här avsnittet lägger till.
- Uppdatera termometerns runtime-klass för att utlösa en händelse när temperaturen sjunker under fryspunkten.
- Uppdatera core-appen som använder termometerns körningsklass så att den hanterar händelsen.
Anmärkning
Information om hur du installerar och använder C++/WinRT Visual Studio-tillägget (VSIX) och NuGet-paketet (som tillsammans tillhandahåller projektmall och byggstöd) finns i Visual Studio-stöd för C++/WinRT-.
Viktigt!
Viktiga begrepp och termer som stöder din förståelse av hur du använder och författar körningsklasser med C++/WinRT kan du läsa om i Använda API:er med C++/WinRT och Skapa API:er med C++/WinRT.
Skapa ThermometerWRC och ThermometerCoreApp
Om du vill följa med i de uppdateringar som visas i det här avsnittet, så att du kan skapa och köra koden, är det första steget att följa genomgången i Avsnittet Windows Runtime-komponenter med C++/WinRT . På så sätt har du komponenten ThermometerWRC Windows Runtime och ThermometerCoreApp Core App som använder den.
Uppdatera ThermometerWRC för att utlösa en händelse
Uppdatera Thermometer.idl
för att se ut som listan nedan. Så här deklarerar du en händelse vars delegattyp är EventHandler med ett argument som är ett flyttal med enkel precision.
// Thermometer.idl
namespace ThermometerWRC
{
runtimeclass Thermometer
{
Thermometer();
void AdjustTemperature(Single deltaFahrenheit);
event Windows.Foundation.EventHandler<Single> TemperatureIsBelowFreezing;
};
}
Spara filen. Projektet kommer inte att kunna byggas klart i sitt nuvarande tillstånd, men bygg ändå nu för att generera uppdaterade versioner av \ThermometerWRC\ThermometerWRC\Generated Files\sources\Thermometer.h
och Thermometer.cpp
stubfiler. I dessa filer kan du nu se stub-implementeringar av händelserna TemperatureIsBelowFreezing. I C++/WinRT implementeras en IDL-deklarerad händelse som en uppsättning överlagrade funktioner (ungefär som hur en egenskap implementeras som ett par överlagrade get- och set-funktioner). En överlagring tar en delegerad som ska registreras och returnerar ett token (en winrt::event_token). Den andra tar en token och återkallar registreringen av det associerade ombudet.
Öppna nu Thermometer.h
och Thermometer.cpp
och uppdatera implementeringen av Termometer-körningsklassen. I Thermometer.h
lägger du till de två överlagrade TemperatureIsBelowFreezing- funktionerna och även en privat händelsedatamedlem för att användas i implementeringen av dessa funktioner.
// 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;
...
};
}
...
Som du ser ovan representeras en händelse av mallen winrt::event struct, som parametriseras av en viss delegattyp (som i sig kan parametriseras av en args-typ).
I Thermometer.cpp
implementerar du de två överlagrade TemperatureIsBelowFreezing-funktionerna .
// 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);
}
}
Anmärkning
Mer information om vad en automatisk händelse-återkallare är finns i Återkalla ett registrerat ombud. Du får automatisk implementering av återkallande av händelser kostnadsfritt för ditt evenemang. Med andra ord behöver du inte implementera överlagringen för händelseåterkallaren – det tillhandahålls för dig av C++/WinRT-projektionen.
De andra överbelastningarna (registrering och manuella återkallelses överbelastningar) är inte inbakade i projektionen. Det är för att ge dig flexibiliteten att implementera dem optimalt för ditt scenario. Anropa händelse::lägga till och händelse::ta bort enligt dessa implementeringar är en effektiv och samtidighets-/trådsäker standard. Men om du har ett mycket stort antal händelser kanske du inte vill ha ett händelsefält för var och en, utan snarare väljer någon form av gles implementering i stället.
Du kan också se ovan att implementeringen av funktionen AdjustTemperature har uppdaterats för att utlösa TemperatureIsBelowFreezing-händelsen om temperaturen går under fryspunkten.
Uppdatera ThermometerCoreApp för att hantera händelsen
I projektet ThermometerCoreApp i gör du följande ändringar i App.cpp
koden för att registrera en händelsehanterare och gör sedan att temperaturen hamnar under fryspunkten.
WINRT_ASSERT
är en makrodefinition och expanderas till _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);
...
}
...
};
Var medveten om ändringen av metoden OnPointerPressed. Varje gång du klickar på fönstret du subtrahera 1 grad Fahrenheit från termometerns temperatur. Och nu hanterar appen händelsen som utlöses när temperaturen går under fryspunkten. För att visa att händelsen genereras som förväntat, placera en brytpunkt i lambda-uttrycket som hanterar TemperatureIsBelowFreezing-händelsen, kör appen och klicka i fönstret.
Parametriserade delegeringar över en ABI
Om din händelse måste vara tillgänglig i ett program-binärt gränssnitt (ABI) - till exempel mellan en komponent och dess förbrukande program - måste händelsen använda en Windows Runtime-delegattyp. Exemplet ovan använder den delegate-typen Windows::Foundation::EventHandler<T> Windows Runtime. TypedEventHandler<TSender, TResult> är ett annat exempel på en delegerad Windows Runtime-typ.
Typparametrarna för dessa två delegattyper behöver gå över ABI, så typparametrarna måste även vara Windows Runtime-typer. Det inkluderar Windows-körningsklasser, körningsklasser från tredje part och primitiva typer som tal och strängar. Kompilatorn hjälper dig med felet "T måste vara WinRT-typ" om du glömmer den restriktionen.
Nedan visas ett exempel i form av kodlistor. Börja med termometerWRC - och ThermometerCoreApp-projekten som du skapade tidigare i det här avsnittet och redigera koden i dessa projekt för att se ut som koden i dessa listor.
Den här första listan är avsedd för ThermometerWRC-projektet . När du har redigerat ThermometerWRC.idl
enligt nedan skapar du projektet och kopierar MyEventArgs.h
sedan och .cpp
till projektet (från Generated Files
mappen) precis som du gjorde tidigare med Thermometer.h
och .cpp
. Kom ihåg att ta bort static_assert
från båda filerna.
// 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);
}
}
...
Den här listan är avsedd för ThermometerCoreApp-projektet .
// 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.
});
}
...
Enkla signaler över en ABI
Om du inte behöver skicka några parametrar eller argument med din händelse kan du definiera din egen enkla ombudstyp för Windows Runtime. Exemplet nedan visar en enklare version av Termometer-körningsklass. Den deklarerar en delegattyp med namnet SignalDelegate och använder sedan den för att generera en signaltyphändelse i stället för en händelse med en 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);
...
}
...
};
Parametriserade ombud, enkla signaler och återanrop i ett projekt
Om du behöver händelser som är interna för ditt Visual Studio-projekt (inte mellan binärfiler), där dessa händelser inte är begränsade till Windows Runtime-typer, kan du fortfarande använda klassmallen winrt::event<Delegate> . Använd bara winrt::d elegate i stället för en faktisk Windows Runtime-delegattyp, eftersom winrt::d elegate även stöder parametrar som inte är windows-körningsparametrar.
Exemplet nedan visar först en ombudssignatur som inte tar några parametrar (i huvudsak en enkel signal) och sedan en som tar en sträng.
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!");
Observera hur du kan lägga till i händelsen så många prenumererande ombud som du vill. Det finns dock vissa omkostnader som är associerade med en händelse. Om allt du behöver är ett enkelt återanrop med endast en prenumererande delegat, kan du använda winrt::delegate<... T> själv.
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!");
Om du porterar från en C++/CX-kodbas där händelser och ombud används internt i ett projekt hjälper winrt::d elegate dig att replikera det mönstret i C++/WinRT.
Uppskjutbara händelser
Ett vanligt mönster i Windows Runtime är den uppskjutbara händelsen. En händelsehanterare tar ett uppskov genom att anropa metoden på händelseargumentet GetDeferral. Detta indikerar för händelsekällan att aktiviteter efter händelsen ska skjutas upp tills uppskjutningen har slutförts. Detta gör att en händelsehanterare kan utföra asynkrona åtgärder som svar på en händelse.
Mallen winrt::deferrable_event_args struct är en hjälpklass för att implementera (producera) Windows Runtime-uppskjutningsmönstret. Här är ett exempel.
// 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;
};
}
Så här använder händelsemottagaren det uppskjutbara händelsemönstret.
// 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();
});
Som implementerare (producent) för händelsekällan härleder du klassen event args från winrt::deferrable_event_args. deferrable_event_args<T> implementerar T::GetDeferral åt dig. Den exponerar också en ny hjälpmetod deferrable_event_args::wait_for_deferrals, som slutförs när alla pågående uppskjutningar har slutförts (om inga uppskjutningar har gjorts slutförs den omedelbart).
// 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;
}
Designriktlinjer
Vi rekommenderar att du skickar händelser och inte delegater som funktionsparametrar. Det lägga till funktion för winrt::event är det enda undantaget, eftersom du måste skicka ett ombud i så fall. Anledningen till den här riktlinjen är att ombud kan ha olika former på olika Windows Runtime-språk (oavsett om de stöder en klientregistrering eller flera). Händelser, med sin modell för flera prenumeranter, utgör ett mycket mer förutsägbart och konsekvent alternativ.
Signaturen för ett händelsehanterardelegat bör bestå av två parametrar: avsändaren (IInspectable) och args (någon typ av händelseargument, till exempel RoutedEventArgs).
Observera att dessa riktlinjer inte nödvändigtvis gäller om du utformar ett internt API. Även om interna API:er ofta blir offentliga över tid.