本主題說明如何使用 C++/WinRT 註冊和撤銷事件處理委派。 您可以使用任何標準C++類似函式的對象來處理事件。
備註
如需安裝和使用 C++/WinRT Visual Studio 延伸模組 (VSIX) 和 NuGet 套件的資訊(一起提供專案範本和建置支援),請參閱 Visual Studio 支援 C++/WinRT。
使用 Visual Studio 新增事件處理程式
在 Visual Studio 中使用 XAML 設計工具使用者介面 (UI),即可將事件處理程式新增至專案。 在 XAML 設計工具中開啟 XAML 頁面時,選取您要處理其事件的控件。 在該控件的屬性頁中,按兩下閃電圖示以列出該控件所來源的所有事件。 然後,雙擊您想要處理的事件,例如,OnClicked。
XAML 設計工具會將適當的事件處理程式函式原型(以及存根實作)新增至您的原始程式檔,讓您準備好將 取代為您自己的實作。
備註
一般而言,您的事件處理程式不需要在 Midl 檔案 (.idl
) 中描述。 因此,XAML 設計工具不會將事件處理程式函式原型新增至 Midl 檔案。 它只會新增您的 .h
和 .cpp
檔案。
註冊委派以處理事件
簡單的範例是處理按鈕的 Click 事件。 通常使用 XAML 標記來註冊成員函式來處理事件,如下所示。
// MainPage.xaml
<Button x:Name="myButton" Click="ClickHandler">Click Me</Button>
// MainPage.h
void ClickHandler(
winrt::Windows::Foundation::IInspectable const& sender,
winrt::Windows::UI::Xaml::RoutedEventArgs const& args);
// MainPage.cpp
void MainPage::ClickHandler(
IInspectable const& /* sender */,
RoutedEventArgs const& /* args */)
{
myButton().Content(box_value(L"Clicked"));
}
上述程式代碼取自 Visual Studio 中的 空白應用程式 (C++/WinRT) 專案。 程序代碼 x:Name
元素中的 ,則所產生的存取子函式名稱也會隨之變更。
備註
在此情況下,事件來源(引發事件的 物件)是名為 myButtonButton。 而事件收件者(處理事件的物件)是 MainPage的實例。 本主題稍後有關於管理事件來源和事件收件者存留期的詳細資訊。
您可以命令性地註冊成員函式來處理事件,而不是以宣告方式在標記中執行。 下列程式碼範例中可能不明顯,但在 ButtonBase::Click 的呼叫中,所傳遞的引數是 RoutedEventHandler 委派的實例。 在此情況下,我們會使用 RoutedEventHandler 的建構函式重載,它接受一個物件和指向成員函數的指標。
// MainPage.cpp
MainPage::MainPage()
{
InitializeComponent();
myButton().Click({ this, &MainPage::ClickHandler });
}
這很重要
註冊委派時,上述程式代碼範例會傳遞原始 這個 指標(指向目前的物件)。 若要瞭解如何建立目前物件的強式或弱式參考,請參閱 。若您使用成員函式作為委派,請參閱。
以下是使用靜態成員函式的範例;請注意更簡單的語法。
// MainPage.h
static void ClickHandler(
winrt::Windows::Foundation::IInspectable const& sender,
winrt::Windows::UI::Xaml::RoutedEventArgs const& args);
// MainPage.cpp
MainPage::MainPage()
{
InitializeComponent();
myButton().Click( MainPage::ClickHandler );
}
void MainPage::ClickHandler(
IInspectable const& /* sender */,
RoutedEventArgs const& /* args */) { ... }
還有其他方法可以建構 RoutedEventHandler。 以下是從 RoutedEventHandler 文件主題中取出的語法區塊(從網頁右上角的 [語言] 下拉式清單中選擇 [C++/WinRT])。 請注意各種建構函式:一個採用 lambda 表達式,另一個採用自由函式,而(我們上述使用的)另一個則採用物件和指向成員函式的指標。
struct RoutedEventHandler : winrt::Windows::Foundation::IUnknown
{
RoutedEventHandler(std::nullptr_t = nullptr) noexcept;
template <typename L> RoutedEventHandler(L lambda);
template <typename F> RoutedEventHandler(F* function);
template <typename O, typename M> RoutedEventHandler(O* object, M method);
/* ... other constructors ... */
void operator()(winrt::Windows::Foundation::IInspectable const& sender,
winrt::Windows::UI::Xaml::RoutedEventArgs const& e) const;
};
函數調用運算子的語法也有助於理解。 它會告訴您委派的參數為何。 如您所見,在此案例中,函數調用運算符語法符合 MainPage::ClickHandler 的參數。
備註
針對任何特定事件,若要弄清楚其委派物件的詳細資訊,以及該委派物件的參數,請先檢視事件本身的文件主題。 讓我們以 UIElement.KeyDown 事件為例。 請瀏覽該主題,然後從 [語言] 下拉式清單中選擇 [C++/WinRT]。 在主題開頭的語法區塊中,您會看到這一點。
// Register
event_token KeyDown(KeyEventHandler const& handler) const;
該資訊告訴我們,UIElement.KeyDown 事件(我們正在討論的主題)具有 KeyEventHandler的委派類型,因為這是您在註冊此事件類型的委派時所傳遞的類型。 現在,請遵循主題的連結,轉到那個 KeyEventHandler 委托處理程序 類型。 在這裡,語法區塊包含函數調用運算符。 如上所述,這會告訴您您的委託的參數需要是什麼。
void operator()(
winrt::Windows::Foundation::IInspectable const& sender,
winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e) const;
如您所見,委派需要宣告以接受 IInspectable 作為發送者,並將 KeyRoutedEventArgs 類別的實例 作為參數。
以另一個例子來說明,讓我們看看 Popup.Closed 事件。 其委派類型是 EventHandler<IInspectable>。 因此,您的委派會將 IInspectable 作為發件者,而另一個 IInspectable (因為這是 EventHandler的類型參數)作為自變數。
如果您在事件處理程式中沒有執行太多工作,則可以使用 Lambda 函式,而不是成員函式。 同樣地,從下列程式碼範例中可能並不明顯地看出來,但是一個 RoutedEventHandler 委派正從 Lambda 函式構建,並且該 Lambda 函式必須再度符合我們上面討論過的函式呼叫運算子的語法。
MainPage::MainPage()
{
InitializeComponent();
myButton().Click([this](IInspectable const& /* sender */, RoutedEventArgs const& /* args */)
{
myButton().Content(box_value(L"Clicked"));
});
}
當您建構委派時,您可以選擇更明確一點。 例如,如果您想要分享這個,或多次使用它。
MainPage::MainPage()
{
InitializeComponent();
auto click_handler = [](IInspectable const& sender, RoutedEventArgs const& /* args */)
{
sender.as<winrt::Windows::UI::Xaml::Controls::Button>().Content(box_value(L"Clicked"));
};
myButton().Click(click_handler);
AnotherButton().Click(click_handler);
}
撤銷已註冊的代理人
當您註冊委派時,通常會將令牌傳回給您。 您接著可以使用該令牌來撤銷您的委派,這意味著該委派會從事件中取消註冊,並且不會被再次呼叫。
為了簡單起見,上述程式代碼範例都未示範如何執行此動作。 但下一個程式代碼範例會將令牌儲存在結構的私人數據成員中,並在解構函式中撤銷其處理程式。
struct Example : ExampleT<Example>
{
Example(winrt::Windows::UI::Xaml::Controls::Button const& button) : m_button(button)
{
m_token = m_button.Click([this](IInspectable const&, RoutedEventArgs const&)
{
// ...
});
}
~Example()
{
m_button.Click(m_token);
}
private:
winrt::Windows::UI::Xaml::Controls::Button m_button;
winrt::event_token m_token;
};
除了強式參考,如上述範例所示,您可以儲存按鈕的弱式參考(請參閱 C++/WinRT 中的強式和弱式參考)。
備註
當事件來源同步觸發事件時,您可以移除事件處理程式,並確信您不會再收到任何事件。 但是對於異步事件,即使在撤銷之後(特別是在解構函式內撤銷時),執行中的事件可能會在開始解構之後到達您的物件。 在解構前尋找取消訂閱的位置可能會減輕問題,或針對強固的解決方案,請參閱
或者,當您註冊委派時,可以指定 winrt::auto_revoke(其類型為 winrt::auto_revoke_t的值),以要求一個事件撤銷者(其類型為 winrt::event_revoker)。 事件撤銷程式會為您保留事件來源的弱式參考(引發事件的 物件)。 您可以呼叫 event_revoker::revoke 成員函式來手動撤銷,但事件撤銷器會在超出範圍時自動呼叫該函式本身。 revoke 函式會檢查事件來源是否仍然存在,如果是,則會撤銷您的委派。 在此範例中,不需要儲存事件來源,也不需要解構子。
struct Example : ExampleT<Example>
{
Example(winrt::Windows::UI::Xaml::Controls::Button button)
{
m_event_revoker = button.Click(
winrt::auto_revoke,
[this](IInspectable const& /* sender */,
RoutedEventArgs const& /* args */)
{
// ...
});
}
private:
winrt::Windows::UI::Xaml::Controls::Button::Click_revoker m_event_revoker;
};
以下是從 ButtonBase::Click 事件的文件主題取得的語法區塊。 它會顯示三種不同的註冊與撤銷功能。 您可以從第三個重載中確定您需要宣告的事件撤銷器類型。 而且您可以將相同類型的委派傳遞至
// Register
winrt::event_token Click(winrt::Windows::UI::Xaml::RoutedEventHandler const& handler) const;
// Revoke with event_token
void Click(winrt::event_token const& token) const;
// Revoke with event_revoker
Button::Click_revoker Click(winrt::auto_revoke_t,
winrt::Windows::UI::Xaml::RoutedEventHandler const& handler) const;
備註
在上述程式代碼範例中, Button::Click_revoker
是的 winrt::event_revoker<winrt::Windows::UI::Xaml::Controls::Primitives::IButtonBase>
型別別名。 類似的模式適用於所有C++/WinRT 事件。 每個 Windows 執行階段事件都有一個擁有撤銷功能的多載函數,該函數會傳回一個事件撤銷器,而該撤銷器的型別是事件來源的成員。 因此,若要舉另一個範例,CoreWindow::SizeChanged 事件具有註冊函數的多載,會傳回 CoreWindow::SizeChanged_revoker類型的值。
您可能會考慮在頁面導覽案例中撤銷處理程式。 如果您重複流覽至頁面,然後返回頁面,則可以在離開頁面時撤銷任何處理程式。 或者,如果您要重複使用相同的頁面實例,請檢查令牌的值,而且只有在尚未設定令牌時才註冊 (if (!m_token){ ... }
)。 第三個選項是將事件撤銷程式儲存在頁面中作為資料成員。 如本主題稍後所述,第四個選項是擷取 lambda 函式中這個 物件的強式或弱式參考。
如果您的自動撤銷委派無法註冊
如果您在註冊委派時嘗試指定 winrt::auto_revoke,而結果為 winrt::hresult_no_interface 例外狀況,這通常表示事件來源不支援弱式參考。 例如,這是 Windows.UI.Composition 命名空間中的常見情況。 在此情況下,您無法使用自動撤銷功能。 您必須退回手動撤銷您的事件處理程式。
異步動作和作業的委派類型
上述範例使用 RoutedEventHandler 委派類型,但當然還有其他許多委派類型。 例如,異步動作和作業(具有和沒有進度)已完成和/或預期對應型別委派的進度事件。 例如,具有進度的異步操作,其進度事件(實作 IAsyncOperationWithProgress的任何項目)需要類型為 AsyncOperationProgressHandler的委派。 以下是使用 Lambda 函式撰寫該類型委派的程式代碼範例。 此範例也顯示如何撰寫 AsyncOperationWithProgressCompletedHandler 委派。
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Web.Syndication.h>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;
void ProcessFeedAsync()
{
Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
SyndicationClient syndicationClient;
auto async_op_with_progress = syndicationClient.RetrieveFeedAsync(rssFeedUri);
async_op_with_progress.Progress(
[](
IAsyncOperationWithProgress<SyndicationFeed,
RetrievalProgress> const& /* sender */,
RetrievalProgress const& args)
{
uint32_t bytes_retrieved = args.BytesRetrieved;
// use bytes_retrieved;
});
async_op_with_progress.Completed(
[](
IAsyncOperationWithProgress<SyndicationFeed,
RetrievalProgress> const& sender,
AsyncStatus const /* asyncStatus */)
{
SyndicationFeed syndicationFeed = sender.GetResults();
// use syndicationFeed;
});
// or (but this function must then be a coroutine, and return IAsyncAction)
// SyndicationFeed syndicationFeed{ co_await async_op_with_progress };
}
如上述「協同程式」批註所建議,與其在異步操作和任務完成時使用委託,您可能會發現使用協同程式更自然。 如需詳細資料和程式碼範例,請參閱 使用 C++/WinRT 進行並行和異步操作。
備註
異步動作或作業不應實作多於一個 完成處理程序。 您可以為完成的事件指定單一代理,或者對其執行 co_await
操作。 如果您有這兩者,則第二個將會失敗。
如果您選擇使用委派而不是協程,則可以選擇更簡單的語法。
async_op_with_progress.Completed(
[](auto&& /*sender*/, AsyncStatus const /* args */)
{
// ...
});
傳回值的委派型別
某些委派類型本身必須傳回值。 例如 ListViewItemToKeyHandler,它會傳回字串。 以下是撰寫該類型委派的範例(請注意 Lambda 函式會傳回值)。
using namespace winrt::Windows::UI::Xaml::Controls;
winrt::hstring f(ListView listview)
{
return ListViewPersistenceHelper::GetRelativeScrollPosition(listview, [](IInspectable const& item)
{
return L"key for item goes here";
});
}
使用事件處理委派安全地存取 此 指標
如果您使用對象的成員函式處理事件,或從對象成員函式內的 Lambda 函式內處理事件,則必須考慮事件收件者的相對存留期(處理事件的物件)和事件來源(引發事件的物件)。 如需詳細資訊和程式碼範例,請參閱 C++/WinRT 中的強式和弱式參考。