C++/WinRT でのデリゲートを使用したイベントの処理Handle events by using delegates in C++/WinRT

このトピックでは、C++/WinRT を使用したイベント処理デリゲートの登録方法と取り消し方法について説明します。This topic shows how to register and revoke event-handling delegates using C++/WinRT. 標準的な C++ 関数のようなオブジェクトを使用してイベントを処理できます。You can handle an event using any standard C++ function-like object.

注意

C++/WinRT Visual Studio Extension (VSIX) と NuGet パッケージ (両者が連携してプロジェクト テンプレートとビルドをサポート) のインストールと使用については、Visual Studio での C++/WinRT のサポートに関する記事を参照してください。For info about installing and using the C++/WinRT Visual Studio Extension (VSIX) and the NuGet package (which together provide project template and build support), see Visual Studio support for C++/WinRT.

Visual Studio 2019 を使用してイベント ハンドラーを追加するUsing Visual Studio 2019 to add an event handler

プロジェクトにイベント ハンドラーを追加する便利な方法は、Visual Studio 2019 の XAML デザイナーのユーザー インターフェイス (UI) を使用することです。A convenient way of adding an event handler to your project is by using the XAML Designer user interface (UI) in Visual Studio 2019. XAML デザイナーで XAML ページを開き、イベントを処理するコントロールを選択します。With your XAML page open in the XAML Designer, select the control whose event you want to handle. そのコントロールのプロパティ ページで、稲妻アイコンをクリックして、そのコントロールが発生元のすべてのイベントを一覧表示します。Over in the property page for that control, click on the lightning-bolt icon to list all of the events that are sourced by that control. その後、処理するイベントをダブルクリックします (たとえば OnClicked)。Then, double-click on the event that you want to handle; for example, OnClicked.

XAML デザイナーにより、適切なイベント ハンドラー関数のプロトタイプ (およびスタブの実装) がソース ファイルに追加され、独自の実装に置き換えることができるようになります。The XAML Designer adds the appropriate event handler function prototype (and a stub implementation) to your source files, ready for you to replace with your own implementation.

注意

通常、イベント ハンドラーを Midl ファイル (.idl) で記述する必要はありません。Typically, your event handlers don't need to be described in your Midl file (.idl). そのため、XAML デザイナーでは、Midl ファイルにイベント ハンドラー関数のプロトタイプが追加されることはありません。So, the XAML Designer doesn't add event handler function prototypes to your Midl file. .h ファイルと .cpp ファイルにだけ追加されます。It only adds them your .h and .cpp files.

デリゲートを登録してイベントを処理するRegister a delegate to handle an event

次に、ボタンのクリック イベントの処理例を示します。A simple example is handling a button's click event. 通常、イベントを処理するメンバー関数を登録するには、次のように XAML マークアップを使用します。It's typical to use XAML markup to register a member function to handle the event, like this.

// MainPage.xaml
<Button x:Name="Button" Click="ClickHandler">Click Me</Button>
// MainPage.cpp
void MainPage::ClickHandler(IInspectable const& /* sender */, RoutedEventArgs const& /* args */)
{
    Button().Content(box_value(L"Clicked"));
}

マークアップで宣言する代わりに、イベントを処理するメンバー関数を命令を使って登録することができます。Instead of doing it declaratively in markup, you can imperatively register a member function to handle an event. 以下のコード例からは分かりにくいかもしれませんが、ButtonBase::Click 呼び出しの引数は RoutedEventHandler デリゲートのインスタンスです。It may not be obvious from the code example below, but the argument to the ButtonBase::Click call is an instance of the RoutedEventHandler delegate. この場合、メンバー関数へのオブジェクトとポインターを取得する RoutedEventHandler コンストラクター オーバーロードを使用しています。In this case, we're using the RoutedEventHandler constructor overload that takes an object and a pointer-to-member-function.

// MainPage.cpp
MainPage::MainPage()
{
    InitializeComponent();

    Button().Click({ this, &MainPage::ClickHandler });
}

重要

デリゲートを登録するとき、上のコード例では (現在のオブジェクトを指す) 生の this ポインターが渡されます。When registering the delegate, the code example above passes a raw this pointer (pointing to the current object). 現在のオブジェクトに対する強い参照または弱い参照を確立する方法については、「デリゲートとしてメンバー関数を使用する場合」をご覧ください。To learn how to establish a strong or a weak reference to the current object, see If you use a member function as a delegate.

RoutedEventHandler の作成には他の方法もあります。There are other ways to construct a RoutedEventHandler. 次に、RoutedEventHandler のドキュメント内にある構文ブロックを示します (Web ページの右上の [言語] ドロップダウンから [C++/WinRT] を選択します)。Below is the syntax block taken from the documentation topic for RoutedEventHandler (choose C++/WinRT from the Language drop-down in the upper-right corner of the webpage). さまざまなコンストラクターがあることに注意してください。ラムダ、自由関数、メンバー関数へのオブジェクトとポインター (上記で使用したもの) を受け取ります。Notice the various constructors: one takes a lambda; another a free function; and another (the one we used above) takes an object and a pointer-to-member-function.

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);
    void operator()(winrt::Windows::Foundation::IInspectable const& sender,
        winrt::Windows::UI::Xaml::RoutedEventArgs const& e) const;
};

また、関数呼び出し演算子の構文も確認に役立ちます。The syntax of the function call operator is also helpful to see. 必要なデリケートのパラメーターを通知します。It tells you what your delegate's parameters need to be. ご覧のように、この場合、関数呼び出し演算子の構文は MainPage::ClickHandler のパラメーターと一致します。As you can see, in this case the function call operator syntax matches the parameters of our MainPage::ClickHandler.

注意

特定のイベントについて、そのデリゲートの詳細とそのデリゲートのパラメーターを把握するには、まずイベント自体のドキュメント トピックに進みます。For any given event, to figure out the details of its delegate, and that delegate's parameters, go first to the documentation topic for the event itself. 例として UIElement.KeyDown イベントを見てみましょう。Let's take the UIElement.KeyDown event as an example. そのトピックにアクセスし、 [言語] ドロップ ダウンから [C++ /WinRT] を選択します。Visit that topic, and choose C++/WinRT from the Language drop-down. トピックの冒頭にある構文ブロックで、これを確認します。In the syntax block at the beginning of the topic, you'll see this.

// Register
event_token KeyDown(KeyEventHandler const& handler) const;

その情報から、UIElement.KeyDown イベント (現在のトピック) には KeyEventHandler のデリゲート型があることがわかります。これは、このイベント型にデリゲートを登録するときに渡す型だからです。That info tells us that the UIElement.KeyDown event (the topic we're on) has a delegate type of KeyEventHandler, since that's the type that you pass when you register a delegate with this event type. 次は、このトピックの KeyEventHandler デリゲート型のリンクをたどりましょう。So, now follow the link on the topic to that KeyEventHandler delegate type. この構文ブロックには関数呼び出し演算子が含まれています。Here, the syntax block contains a function call operator. また、前述のように、必要なデリケートのパラメーターを通知します。And, as mentioned above, that tells you what your delegate's parameters need to be.

void operator()(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e) const;

ご覧のとおり、送信者として IInspectable を、引数として KeyRoutedEventArgs クラスのインスタンスを受け取るようにデリゲートを宣言する必要があります。As you can see, the delegate needs to be declared to take an IInspectable as the sender, and an instance of the KeyRoutedEventArgs class as the args.

別の例を見るために、Popup.Closed イベントを見てみましょう。To take another example, let's look at the Popup.Closed event. そのデリゲート型は EventHandler<IInspectable> です。Its delegate type is EventHandler<IInspectable>. そのため、デリゲートは送信者として IInspectable を、引数として IInspectable を受け取ります (これは EventHandler の型パラメーターであるため)。So, your delegate will take an IInspectable as the sender, and another IInspectable (because that's the EventHandler's type parameter ) as the args.

イベント ハンドラーで多くの処理を行っていない場合は、メンバー関数の代わりにラムダ関数を使用できます。If you're not doing much work in your event handler, then you can use a lambda function instead of a member function. 以下のコード例からはわかりにくいかもしれませんが、RoutedEventHandler デリゲートはラムダ関数から作成されているため、前述の関数呼び出し演算子の構文ともう一度一致させる必要があります。Again, it may not be obvious from the code example below, but a RoutedEventHandler delegate is being constructed from a lambda function which, again, needs to match the syntax of the function call operator that we discussed above.

MainPage::MainPage()
{
    InitializeComponent();

    Button().Click([this](IInspectable const& /* sender */, RoutedEventArgs const& /* args */)
    {
        Button().Content(box_value(L"Clicked"));
    });
}

デリゲートを作成する場合にもう少し明示的に指定することができます。You can choose to be a little more explicit when you construct your delegate. たとえば、次のように渡したり、何回も使用したりする場合です。For example, if you want to pass it around, or use it more than once.

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"));
    };
    Button().Click(click_handler);
    AnotherButton().Click(click_handler);
}

登録済みデリゲートの取り消しRevoke a registered delegate

デリゲートを登録すると、通常、トークンがユーザーに返されます。When you register a delegate, typically a token is returned to you. その後、このトークンを使用してデリゲートを取り消すことができます。つまり、このトークンはイベントから登録解除され、イベントが再び発生しても呼び出されることはありません。You can subsequently use that token to revoke your delegate; meaning that the delegate is unregistered from the event, and won't be called should the event be raised again. 説明を簡単にするために、上記の例にはその方法を示すコードは含まれていません。For the sake of simplicity, none of the code examples above showed how to do that. ただし、次のコード例では、トークンを構造体のプライベート データ メンバーに格納し、デストラクターの該当するハンドラーを取り消しています。But this next code example stores the token in the struct's private data member, and revokes its handler in the destructor.

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 の強参照と弱参照」を参照してください)。Instead of a strong reference, as in the example above, you can store a weak reference to the button (see Strong and weak references in C++/WinRT).

または、デリゲートを登録する場合、winrt::auto_revoke (型 winrt::auto_revoke_t の値) を指定して (winrt::event_revoker 型の) イベント リボーカーを要求できます。Alternatively, when you register a delegate, you can specify winrt::auto_revoke (which is a value of type winrt::auto_revoke_t) to request an event revoker (of type winrt::event_revoker). イベント リボーカーにより、イベント ソース (イベントを発生させるオブジェクト) への弱参照が保持されます。The event revoker holds a weak reference to the event source (the object that raises the event) for you. event_revoker::revoke メンバー関数を呼び出して手動で取り消すことができますが、イベント リボーカーは参照が範囲外になったときに自動的にその関数自体を呼び出します。You can manually revoke by calling the event_revoker::revoke member function; but the event revoker calls that function itself automatically when it goes out of scope. revoke 関数は、イベント ソースがまだ存在するかどうかを確認し、存在する場合は、デリケートを取り消します。The revoke function checks whether the event source still exists and, if so, revokes your delegate. 次の例では、イベント ソースを格納する必要がないため、デストラクターは必要ありません。In this example, there's no need to store the event source, and no need for a destructor.

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 イベントのドキュメント トピックからの抜粋です。Below is the syntax block taken from the documentation topic for the ButtonBase::Click event. 3 つの異なる登録と取り消し関数を示しています。It shows the three different registration and revoking functions. 3 番目のオーバーロードから宣言する必要があるイベント リボーカーの型を正確に確認できます。You can see exactly what type of event revoker you need to declare from the third overload.

// 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_revokerwinrt::event_revoker<winrt::Windows::UI::Xaml::Controls::Primitives::IButtonBase> の型エイリアスです。In the code example above, Button::Click_revoker is a type alias for winrt::event_revoker<winrt::Windows::UI::Xaml::Controls::Primitives::IButtonBase>. 同じようなパターンがすべての C++/WinRT イベントに適用されます。A similar pattern applies to all C++/WinRT events. 各 Windows ランタイム イベントには、イベント リボーカーを返す失効関数オーバーロードがあり、そのリボーカーの型はイベント ソースのメンバーです。Each Windows Runtime event has a revoke function overload that returns an event revoker, and that revoker's type is a member of the event source. そのため、別の例を挙げると、CoreWindow:: SizeChanged イベントには、型 CoreWindow::SizeChanged_revoker の値を返す登録関数オーバーロードがあります。So, to take another example, the CoreWindow::SizeChanged event has a registration function overload that returns a value of type CoreWindow::SizeChanged_revoker.

ページのナビゲーションのシナリオでハンドラーの取り消しを検討します。You might consider revoking handlers in a page-navigation scenario. あるページへの移動を繰り返す場合、そのページから移動する際にハンドラーを取り消すことができます。If you're repeatedly navigating into a page and then back out, then you could revoke any handlers when you navigate away from the page. または、同じページ インスタンスを再使用しているときは、トークンの値を確認し、(if (!m_token){ ... }) がまだ設定されていない場合のみ登録します。Alternatively, if you're re-using the same page instance, then check the value of your token and only register if it's not yet been set (if (!m_token){ ... }). 3 番目のオプションでは、ページにイベント リボーカーをデータ メンバーとして格納します。A third option is to store an event revoker in the page as a data member. このトピック後半で紹介する 4 番目のオプションでは、ラムダ関数内のこのオブジェクトの強参照または弱参照をキャプチャします。And a fourth option, as described later in this topic, is to capture a strong or a weak reference to the this object in your lambda function.

自動取り消しのデリゲートの登録が失敗する場合If your auto-revoke delegate fails to register

デリゲートを登録するときに winrt::auto_revoke を指定しようとして、結果が winrt::hresult_no_interface 例外である場合、通常それはイベント ソースで弱い参照がサポートされていないことを意味します。If you try to specify winrt::auto_revoke when registering a delegate, and the result is a winrt::hresult_no_interface exception, then that usually means that the event source doesn't support weak references. たとえば、Windows.UI.Composition 名前空間ではよくあることです。That's a common situation in the Windows.UI.Composition namespace, for example. このような場合は、自動取り消し機能を使用できません。In this situation, you can't use the auto-revoke feature. イベント ハンドラーの手動取り消しにフォールバックする必要があります。You'll have to fall back to manually revoking your event handlers.

非同期アクションと非同期操作のデリゲート型Delegate types for asynchronous actions and operations

上記の例では、RoutedEventHandler デリゲート型を使用していますが、他にも多くのデリゲート型があります。The examples above use the RoutedEventHandler delegate type, but there are of course many other delegate types. たとえば、非同期アクションと非同期操作 (進行状況ありとなし) には、対応するデリゲート型を必要とする完了イベントと進行状況イベントがあります。For example, asynchronous actions and operations (with and without progress) have completed and/or progress events that expect delegates of the corresponding type. たとえば、進行状況ありの非同期操作の進行状況イベント (IAsyncOperationWithProgress を実装する任意のイベント) には、AsyncOperationProgressHandler のデリゲート型が必要です。For example, the progress event of an asynchronous operation with progress (which is anything that implements IAsyncOperationWithProgress) requires a delegate of type AsyncOperationProgressHandler. 次に、ラムダ関数を使用してこの型のデリゲートを作成するコード例を示します。Here's a code example of authoring a delegate of that type using a lambda function. この例は、AsyncOperationWithProgressCompletedHandler デリゲートの作成方法も示しています。The example also shows how to author an AsyncOperationWithProgressCompletedHandler delegate.

#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 };
}

上記の "コルーチン" のコメントからも分かるように、非同期アクションと非同期操作の完了イベントでデリゲートを使用するよりも、コルーチンを使用するほうがより自然です。As the "coroutine" comment above suggests, instead of using a delegate with the completed events of asynchronous actions and operations, you'll probably find it more natural to use coroutines. 詳細とコード例については、「C++/WinRT を使用した同時実行操作と非同期操作」を参照してください。For details, and code examples, see Concurrency and asynchronous operations with C++/WinRT.

注意

非同期アクションまたは操作に、複数の完了ハンドラーを実装するのは誤りです。It's not correct to implement more than one completion handler for an asynchronous action or operation. 完了したイベントに対して 1 つのデリゲートを持つか、それを co_await することができます。You can have either a single delegate for its completed event, or you can co_await it. 両方ある場合、2 つ目は失敗します。If you have both, then the second will fail.

コルーチンではなくデリゲートにこだわる場合は、もっと簡単な構文を選択できます。If you stick with delegates instead of a coroutine, then you can opt for a simpler syntax.

async_op_with_progress.Completed(
    [](auto&& /*sender*/, AsyncStatus const /* args */)
{
    // ...
});

値を返すデリゲート型Delegate types that return a value

一部のデリゲート型では自身に値を戻す必要があります。Some delegate types must themselves return a value. たとえば、ListViewItemToKeyHandler は文字列を返します。An example is ListViewItemToKeyHandler, which returns a string. 次に、このような型のデリゲートの作成例を示します (ラムダ関数が値を返します)。Here's an example of authoring a delegate of that type (note that the lambda function returns a value).

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";
    });
}

イベント処理デリゲートで this ポインターに安全にアクセスするSafely accessing the this pointer with an event-handling delegate

オブジェクトのメンバー関数でイベントを処理する場合、あるいはオブジェクトのメンバー関数内にあるラムダ関数内からイベントを処理する場合、イベント受信側 (イベントを処理するオブジェクト) とイベント ソース (イベントを発生させるオブジェクト) の相対的な有効期間を考慮する必要があります。If you handle an event with an object's member function, or from within a lambda function inside an object's member function, then you need to think about the relative lifetimes of the event recipient (the object handling the event) and the event source (the object raising the event). 詳細とコード例については、「C++/WinRT の強参照と弱参照」を参照してください。For more info, and code examples, see Strong and weak references in C++/WinRT.

重要な APIImportant APIs