Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Toto téma vychází z komponenty Prostředí Windows Runtime a aplikace, kterou součásti prostředí Windows Runtime pomocí tématu C++/WinRT ukazuje, jak sestavit.
Tady jsou nové funkce, které toto téma přidává.
- Aktualizujte běhovou třídu teploměru, aby vyvolala událost, když teplota klesne pod bod mrazu.
- Aktualizujte jádrovou aplikaci, která používá runtime třídu teploměru, aby zvládala tuto událost.
Poznámka:
Informace o instalaci a používání C++/WinRT rozšíření sady Visual Studio (VSIX) a balíčku NuGet (které společně poskytují podporu sestavení a šablony projektu) najdete v tématu podpora sady Visual Studio pro C++/WinRT.
Důležité
Základní koncepty a termíny, které podporují pochopení toho, jak využívat a vytvářet třídy běhového prostředí pomocí C++/WinRT, najdete v tématu Využití rozhraní API pomocí C++/WinRT a Vytváření rozhraní API s C++/WinRT.
Vytvořit ThermometerWRC a ThermometerCoreApp
Pokud chcete postupovat podle aktualizací uvedených v tomto tématu, abyste mohli sestavit a spustit kód, pak prvním krokem je postupovat podle návodu v komponentách prostředí Windows Runtime pomocí tématu C++/WinRT. Tímto způsobem budete mít komponentu TeploměrWRC Windows Runtime a jádrovou aplikaci ThermometerCoreApp, která ji využívá.
Aktualizace ThermometerWRC k vyvolání události
Aktualizujte Thermometer.idl tak, aby vypadalo jako v následujícím seznamu. Toto je způsob, jak deklarovat událost, jejíž typ delegáta je EventHandler s argumentem čísla s jednoduchou přesností v plovoucí desetinné čárce.
// Thermometer.idl
namespace ThermometerWRC
{
runtimeclass Thermometer
{
Thermometer();
void AdjustTemperature(Single deltaFahrenheit);
event Windows.Foundation.EventHandler<Single> TemperatureIsBelowFreezing;
};
}
Uložte soubor. Projekt nelze dokončit v jeho aktuálním stavu, ale i tak proveďte sestavení, abyste vygenerovali aktualizované verze zástupných souborů \ThermometerWRC\ThermometerWRC\Generated Files\sources\Thermometer.h a Thermometer.cpp. Uvnitř těchto souborů nyní můžete vidět zástupné implementace události TemperatureIsBelowFreezing. V jazyce C++/WinRT se deklarovaná událost IDL implementuje jako sada přetížených funkcí (podobně jako vlastnost je implementována jako dvojice přetížených funkcí get a set). Jeden overload přijímá delegáta, který má být zaregistrován, a vrací token (winrt::event_token). Druhý vezme token a zruší registraci přidruženého delegáta.
Nyní otevřete Thermometer.h a Thermometer.cppa aktualizujte implementaci třídy teploměru za běhu. V Thermometer.hpřidejte dvě přetížené funkce TemperatureIsBelowFreezing, stejně jako privátní datový člen události, který se má použít při implementaci těchto funkcí.
// 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;
...
};
}
...
Jak vidíte výše, událost je reprezentována winrt::event šablonou struktury, parametrizovanou konkrétním typem delegáta (který sám může být parametrizován typem parametrů args).
V Thermometer.cppimplementujte dvě přetížené TemperatureIsBelowFreezing funkce.
// 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);
}
}
Poznámka:
Podrobnosti o tom, co je automatický odvolávač událostí, naleznete v tématu Odvolání registrovaného delegáta. Implementaci automatického odvolávání událostí získáte zdarma pro vaši událost. Jinými slovy, nemusíte implementovat přetížení pro odvolávání událostí — o to se postará projekce C++/WinRT.
Ostatní přetížení (registrace a ruční odvolání) nejsou , zakomponovány do projekce. To vám poskytne flexibilitu při optimální implementaci pro váš scénář. Volání event::add a event::remove, jak je znázorněno v těchto implementacích, je efektivní a implicitně bezpečný pro souběžnost i vlákna. Pokud ale máte velmi velký počet událostí, možná nebudete chtít pole události pro každou z nich, ale raději zvolit nějaký druh řídké implementace.
Můžete také vidět výše, že implementace funkce AdjustTemperature byla aktualizována tak, aby vyvolala událost TemperatureIsBelowFreezing, pokud teplota klesne pod bod mrazu.
Aktualizujte ThermometerCoreApp pro zpracování události
V projektu ThermometerCoreApp proveďte v App.cppnásledující změny kódu, abyste zaregistrovali obslužnou rutinu události, a pak způsobte, aby teplota klesla pod bod mrazu.
WINRT_ASSERT je makro definice a rozbalí se na _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);
...
}
...
};
Uvědomte si změnu metody OnPointerPressed. Pokaždé, když kliknete na okno, odečtete 1 stupeň Fahrenheita z teploty teploměru. Aplikace teď zpracovává událost, která je vyvolána, když teplota klesne pod mráz. Chcete-li předvést, že událost je vyvolána podle očekávání, vložte bod přerušení do lambda výrazu, který zpracovává událost TemperatureIsBelowFreezing, spusťte aplikaci a klikněte na okno.
Parametrizované delegáty napříč rozhraním ABI
Pokud musí být událost přístupná v binárním rozhraní aplikace (ABI), například mezi komponentou a její aplikací, musí vaše událost používat typ delegáta prostředí Windows Runtime. Výše uvedený příklad používá typu delegáta prostředí Windows Runtime::Foundation::EventHandler<T> prostředí Windows Runtime. TypedEventHandler<TSender, TResult> je dalším příkladem typu delegáta prostředí Windows Runtime.
Parametry typu pro tyto dva typy delegátů musí procházet přes ABI, takže parametry typu musí být také typu Windows Runtime. To zahrnuje třídy modulu runtime systému Windows, třídy modulu runtime třetích stran a primitivní typy, jako jsou čísla a řetězce. Kompilátor vám pomůže s chybou typu "T musí být typ WinRT", pokud toto omezení zapomenete.
Níže je příklad ve formě výpisů kódu. Začněte s projekty TeploměrWRC a TeploměrCoreApp, které jste vytvořili dříve v tomto tématu, a upravte kód v těchto projektech tak, aby vypadal jako uvedený kód v uvedených výpisech.
Tento první výpis je pro projekt TeploměrWRC. Po úpravě ThermometerWRC.idl, jak je znázorněno níže, sestavte projekt a potom zkopírujte MyEventArgs.h a .cpp do projektu (ze složky Generated Files) stejně jako předtím s Thermometer.h a .cpp. Nezapomeňte odstranit static_assert z obou souborů.
// 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);
}
}
...
Tento seznam je určený pro projekt TeploměrCoreApp.
// 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.
});
}
...
Jednoduché signály napříč ABI
Pokud ve své události nepotřebujete předávat žádné parametry ani argumenty, můžete definovat vlastní jednoduchý typ delegáta prostředí Windows Runtime. Následující příklad ukazuje jednodušší verzi běhové třídy Teploměr. Deklaruje typ delegáta s názvem SignalDelegate a pak používá k vyvolání události typu signálu místo události s parametrem.
// 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);
...
}
...
};
Parametrizované delegáty, jednoduché signály a zpětná volání v rámci projektu
Pokud potřebujete události, které jsou interní pro váš projekt sady Visual Studio (ne napříč binárními soubory), kde tyto události nejsou omezené na typy prostředí Windows Runtime, můžete stále použít šablonu třídy winrt::event<> Delegate. Jednoduše použijte winrt::d elegate místo skutečného typu delegáta prostředí Windows Runtime, protože winrt::d elegate podporuje také parametry jiného prostředí než Windows Runtime.
Následující příklad nejprve ukazuje podpis delegáta, který nepřijímá žádné parametry (v podstatě jednoduchý signál) a pak ten, který přebírá řetězec.
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!");
Všimněte si, jak můžete přidat do události tolik účastníků přihlášení, kolik chcete. S událostí však souvisí určité režijní náklady. Pokud potřebujete jenom jednoduché zpětné volání s jediným delegátem pro přihlášení k odběru, můžete použít winrt::delegate<... T> samostatně.
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!");
Pokud přenášíte z základu kódu C++/CX, kde se události a delegáty používají interně v rámci projektu, winrt::d elegate vám pomůže tento vzor replikovat v jazyce C++/WinRT.
Odložené události
Běžným vzorem v prostředí Windows Runtime je odložená událost. Obslužná rutina události
Šablona struktury winrt::deferrable_event_args je pomocná třída pro implementaci (generování) modelu odložení prostředí Windows Runtime. Tady je příklad.
// 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;
};
}
Tady je postup, jak příjemce události využívá odložitelný vzor události.
// 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();
});
Jako implementátor (producent) zdroje událostí odvodíte třídu argumentů události z winrt::deferrable_event_args. deferrable_event_args<T> za vás implementuje T::GetDeferral. Také zavádí novou pomocnou metodu deferrable_event_args::wait_for_deferrals, která se dokončí, když se dokončí všechny zbývající odklady (pokud nebyly přijaty žádné odklady, dokončí se okamžitě).
// 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;
}
Pokyny k návrhu
Jako parametry funkce doporučujeme předávat události, nikoli delegáty. Funkce přidat u winrt::event je jedinou výjimkou, protože musíte v tomto případě předat delegáta. Důvodem tohoto pokynu je, že delegáti mohou mít různé formy v různých jazycích prostředí Windows Runtime (pokud jde o to, jestli podporují jednu registraci klienta nebo více). Události s modelem více odběratelů představují mnohem předvídatelnější a konzistentnější možnost.
Podpis delegáta pro obslužnou rutinu události by měl sestávat ze dvou parametrů: odesílatel (IInspectable) a argument (nějaký typ argumentu události, například RoutedEventArgs).
Upozorňujeme, že tyto pokyny se nemusí nutně vztahovat, pokud navrhujete interní rozhraní API. Interní rozhraní API se ale v průběhu času často stávají veřejnými.
Související témata
- Tvorba rozhraní API pomocí C++/WinRT
- Práce s rozhraními API v C++/WinRT
- Obsluha událostí pomocí delegátů v C++/WinRT
- Komponenty prostředí Windows Runtime s C++/WinRT