共用方式為


在 C++/WinRT 中使用委派來處理事件

本主題說明如何使用 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) 專案。 程序代碼 會呼叫一個產生的存取子函式,該函式傳回我們命名為的 Button 。 如果您變更 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 事件的文件主題取得的語法區塊。 它會顯示三種不同的註冊與撤銷功能。 您可以從第三個重載中確定您需要宣告的事件撤銷器類型。 而且您可以將相同類型的委派傳遞至 緩存器,以及使用event_revoker多载撤销

// 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 中的強式和弱式參考

重要 API