此主題建立於 Windows 執行階段元件和消費應用程式之上,而 "以 C++/WinRT 建立的 Windows 執行階段元件" 主題展示了如何建置這些元件。
以下是本主題新增的新功能。
- 更新溫度計運行時間類別,以在溫度低於凍結時引發事件。
- 更新取用溫度計運行時間類別的核心應用程式,以便處理該事件。
備註
如需安裝和使用 C++/WinRT Visual Studio 延伸模組 (VSIX) 和 NuGet 套件的資訊(一起提供專案範本和建置支援),請參閱 Visual Studio 對 C++/WinRT 的支援。
這很重要
如需基本概念和術語,以幫助您理解如何使用 C++/WinRT 取用和撰寫執行階段類別,請參閱 使用 C++/WinRT 取用 API,以及 使用 C++/WinRT 撰寫 API。
建立 溫度計 WRC 和 溫度計 核心應用程式
如果您想要跟進本主題所示的更新,以便建置並執行程式碼,第一個步驟是按照 Windows 執行階段元件與 C++/WinRT 主題中的逐步指南。 如此一來,您將擁有 ThermometerWRC Windows 運行時間元件,以及取用它的 ThermometerCoreApp 核心應用程式。
更新 溫度計WRC 以觸發事件
更新 Thermometer.idl
,如下所示。 這是宣告一個事件的方法,其委派類型為 EventHandler,且接受單精度浮點數作為參數。
// Thermometer.idl
namespace ThermometerWRC
{
runtimeclass Thermometer
{
Thermometer();
void AdjustTemperature(Single deltaFahrenheit);
event Windows.Foundation.EventHandler<Single> TemperatureIsBelowFreezing;
};
}
儲存檔案。 專案在目前狀態無法完成建置,但不論如何,現在仍應進行建置,以產生更新版本的 \ThermometerWRC\ThermometerWRC\Generated Files\sources\Thermometer.h
和 Thermometer.cpp
存根檔案。 在這些檔案中,您現在可以看到 TemperatureIsBelowFreezing 事件的存根實作。 在 C++/WinRT 中,IDL 宣告的事件會實作為一組多載函式(類似於屬性實作為一對多載 get 和 set 函式的方式)。 一個重載接受要註冊的委派,並回傳一個令牌(winrt::event_token)。 另一方會取得一個令牌,並撤銷與其相關的委派註冊。
現在開啟 Thermometer.h
和 Thermometer.cpp
,並更新 溫度計 的執行階段類別實作。 在 Thermometer.h
中,新增兩個重載的 TemperatureIsBelowFreezing 函式,以及一個供這些函式實作使用的私人事件數據成員。
// 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;
...
};
}
...
如上所示,事件是由 winrt::event 結構範本所代表,由特定委派類型參數化(其本身可由 args 類型參數化)。
在 Thermometer.cpp
中,實作兩個多載的 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);
}
}
備註
如需自動事件撤銷程式的詳細資料,請參閱 撤銷已註冊的委派。 您可以免費獲得為您的事件提供的自動事件撤銷功能實作。 換句話說,您不需要實作事件撤銷程式的多載,這是由 C++/WinRT 投影提供。
其他多載(註冊和手動撤銷多載)則 不會 內建於投影中。 這可讓您彈性地針對您的案例以最佳方式實作。 呼叫 event::add 和 event::remove,如這些實作所示,是有效率且安全線程的預設值。 但是,如果您有非常大量的事件,您可能不希望為每個事件建立一個事件欄位,而是選擇某種稀疏實作。
您也可以看到,如果溫度低於冰點,已更新 AdjustTemperature 函式的實作,以引發 TemperatureIsBelowFreezing 事件。
更新 ThermometerCoreApp 以便處理事件
在 ThermometerCoreApp 專案中,在 App.cpp
中,對程式碼進行下列變更以註冊事件處理程式,然後使溫度低於冰點。
WINRT_ASSERT
是一個宏定義,它展開為 _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);
...
}
...
};
請注意 OnPointerPressed 方法的變更。 現在,每次按兩下視窗時,您 從溫度計的溫度減去 1 度華氏度。 現在,應用程式正在處理溫度低於冰點時引發的事件。 為了證明事件正確被引發,請在處理 TemperatureIsBelowFreezing 事件的 Lambda 表達式中設置斷點,執行應用程式,然後在視窗內點擊。
跨 ABI 的參數化代理
如果您的事件必須在應用程式二進位介面 (ABI) 之間存取,例如元件與其取用應用程式之間,則您的事件必須使用 Windows 運行時間委派類型。 上述範例使用 Windows::Foundation::EventHandler<T> Windows 運行時間委派類型。 TypedEventHandler<TSender,TResult> 是 Windows 運行時間委派類型的另一個範例。
這兩個委派類型的類型參數必須跨越 ABI,因此類型參數也必須是 Windows 執行時間類型。 這包括 Windows 執行時間類別、第三方運行時間類別,以及數位和字串等基本類型。 如果您忘記該條件約束,編譯程式會提示您「T 必須是 WinRT 型別」錯誤。
以下是程式代碼清單形式的範例。 從在本主題中稍早建立的 溫度計WRC 和 ThermometerCoreApp 專案開始,並編輯這些專案中的程式代碼,使之與這些範例中的程式碼相符。
第一個清單適用於 溫度計WRC 專案。 編輯 ThermometerWRC.idl
之後,請建置專案,然後將 MyEventArgs.h
和 .cpp
從 Generated Files
資料夾中複製到專案中,就像您先前將 Thermometer.h
和 .cpp
複製到專案時所做的一樣。 請記得從這兩個檔案中移除 static_assert
。
// 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);
}
}
...
此清單適用於 ThermometerCoreApp 專案。
// 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.
});
}
...
ABI 上的簡單訊號
如果您不需要使用事件傳遞任何參數或自變數,則可以定義自己的簡單 Windows 運行時間委派類型。 下列範例顯示較簡單的 溫度計 執行階段類別版本。 它會宣告一個名為 SignalDelegate 的委派類型,然後使用它來引發訊號類型事件,而非具有參數的事件。
// 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);
...
}
...
};
專案內的參數化委派、簡單訊號和回呼
如果您需要 Visual Studio 項目內部的事件(而非跨二進位檔),其中這些事件不限於 Windows 運行時間類型,您仍然可以使用 winrt::event<Delegate> 類別範本。 只要使用 winrt::d elegate 而不是實際的 Windows 運行時間委派類型,因為 winrt::d elegate 也支援非 Windows 運行時間參數。
下列範例首先顯示一個不採用任何參數的委派的簽章(基本上是簡單的訊號),接著顯示一個接受字串的簽章。
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!");
請注意,您可以隨意新增任意多的訂閱委派至事件中。 不過,事件會產生一些額外開銷。 如果您需要的是只有單一訂閱委派的簡單回呼,您可以使用 winrt::d elegate<...T> 本身。
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!");
如果您要在專案中將使用事件和委派的 C++/CX 程式碼基底進行移植,那麼 winrt::delegate 能幫助您在 C++/WinRT 中複製該模式。
可延遲的事件
Windows 運行時間中的常見模式是可延遲的事件。 事件處理程式會藉由呼叫事件自變數的 GetDeferral 方法來接受延遲。 這樣做會向事件來源指出事件後活動應該延後到延遲完成為止。 這可讓事件處理程式執行異步動作,以回應事件。
winrt::deferrable_event_args 結構範本是實作 Windows 執行階段延期模式的協助程序類別。 以下是範例。
// 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;
};
}
以下是事件接收者如何使用可延遲的事件模式。
// 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();
});
身為事件來源的實作者(產生者),您可以從 winrt::deferrable_event_args 衍生您的事件引數類別。 deferrable_event_args<T> 會為您實作 T::GetDeferral。 它還公開了一個新的輔助方法 deferrable_event_args::wait_for_deferrals,該方法在所有未完成的延遲完成後才算完成(如果沒有延遲,則會立即完成)。
// 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;
}
設計指導方針
建議您傳遞事件,而不是委派作為函式參數。
事件處理程式委派的簽名應該包含兩個參數:傳送者(IInspectable),以及 參數 (某種事件參數類型,例如 RoutedEventArgs)。
請注意,如果您要設計內部 API,這些指導方針不一定適用。 雖然內部 API 通常會隨著時間而公開。