次の方法で共有


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 ファイルのみが追加されます。

イベントを処理するデリゲートを登録する

簡単な例として、ボタンのクリック イベントの処理があります。 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 の Blank App (C++/WinRT) プロジェクトから取得されます。 このコード myButton() は、生成されたアクセサー関数を呼び出します。この関数は、myButtonと名前付けした Button を返します。 その x:Name 要素の を変更すると、生成されたアクセサー関数の名前も変更されます。

この場合、イベント ソース (イベントを発生させるオブジェクト) は、myButtonという名前の Button です。 また、イベント受信者 (イベントを処理するオブジェクト) は、MainPageのインスタンスです。 イベント ソースとイベント受信者の有効期間の管理については、このトピックの後半で詳しく説明します。

マークアップで宣言的に行う代わりに、イベントを処理するメンバー関数を命令的に登録できます。 以下のコード例からは明らかではないかもしれませんが、ButtonBase::Click 呼び出しの引数は、RoutedEventHandler デリゲートのインスタンスです。 この場合、オブジェクトとメンバー関数へのポインタを受け取る RoutedEventHandler コンストラクターのオーバーロードを使用しています。

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

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

Von Bedeutung

デリゲートを登録する際、上記のコード例では、生の ポインターであるこの ポインターを(現在のオブジェクトを指して)渡します。 現在のオブジェクトへの強参照または弱参照を確立する方法については、「メンバー関数をデリゲートとして使用する場合」を参照してください。

静的メンバー関数を使用する例を次に示します。より単純な構文に注意してください。

// 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 のドキュメント トピックから取得した構文ブロックを次に示します (Web ページの右上隅にある [言語] ドロップダウンから C++/WinRT を選択します)。 さまざまなコンストラクタに注目してください。1つはラムダを受け取り、もう1つはフリー関数を受け取ります。そして、もう1つ(上で使用したもの)はオブジェクトとメンバー関数へのポインタを受け取ります。

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 を送信者として受け取り、もう 1 つの IInspectable (これは EventHandler's 型パラメーターであるため) を引数として受け取ります。

イベント ハンドラーであまり作業を行っていない場合は、メンバー関数の代わりにラムダ関数を使用できます。 ここでも、以下のコード例からは明らかではないかもしれませんが、RoutedEventHandler デリゲートはラムダ関数から構築されています。これは、ここでも、前に説明した関数呼び出し演算子の構文と一致する必要があります。

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 メンバー関数を呼び出すことで、手動で取り消すことができます。ただし、イベント 取り消し子は、スコープ外になったときにその関数自体を自動的に呼び出します。 の取り消し 関数は、イベント ソースがまだ存在するかどうかを確認し、存在する場合はデリゲートを取り消します。 この例では、イベント ソースを格納する必要はなく、デストラクターも必要ありません。

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 イベントのドキュメント トピックから取得した構文ブロックを次に示します。 3 つの異なる登録および取り消し関数を示します。 3 番目のオーバーロードから、正確にどのタイプのイベントリボーカーを宣言する必要があるかを確認できます。 同じ種類のデリゲートを、レジスタ に渡すことと、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_revokerwinrt::event_revoker<winrt::Windows::UI::Xaml::Controls::Primitives::IButtonBase>の型エイリアスです。 同様のパターンは、すべての C++/WinRT イベントに適用されます。 各 Windows ランタイム イベントには、イベント 取り消し子を返す取り消し関数オーバーロードがあり、その取り消し子の型はイベント ソースのメンバーです。 そのため、別の例を見ると、CoreWindow::SizeChanged イベントには、CoreWindow::SizeChanged_revoker型の値 返す登録関数オーバーロードがあります。

ページ ナビゲーション シナリオでは、ハンドラーの取り消しを検討できます。 ページに繰り返し移動してから戻る場合は、ページから離れたときにハンドラーを取り消すことができます。 または、同じページ インスタンスを再利用する場合は、トークンの値を確認し、まだ設定されていない場合にのみ登録します (if (!m_token){ ... })。 3 つ目のオプションは、イベント 取り消し子をデータ メンバーとしてページに格納することです。 また、このトピックで後述するように、4 番目のオプションは、ラムダ関数でこの オブジェクト への強参照または弱参照をキャプチャすることです。

自動取り消しデリゲートが登録に失敗した場合

デリゲートの登録時に winrt::auto_revoke を指定しようとすると、結果が winrt::hresult_no_interface 例外になります。これは通常、イベント ソースが弱い参照をサポートしていないことを意味します。 これは、たとえば、Windows.UI.Composition 名前空間の一般的な状況です。 この状況では、自動取り消し機能を使用することはできません。 イベントハンドラーを手動で取り消す必要があります。

非同期アクションと操作のデリゲート型

上記の例では、RoutedEventHandler デリゲート型を使用していますが、他にも多くのデリゲート型があります。 たとえば、非同期のアクションや操作(進捗あり/なし)は、対応する型のデリゲートを必要とする完了イベントや進捗イベントを持っています。 たとえば、IAsyncOperationWithProgressを実装する進行状況付き非同期操作の進行イベントには、AsyncOperationProgressHandler型のデリゲートが必要です。 ラムダ関数を使用してその型のデリゲートを作成するコード例を次に示します。 この例では、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 することもできます。 両方がある場合、2 つ目は失敗します。

コルーチンではなくデリゲートを使用する場合は、より簡単な構文を選択できます。

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

値を返すデリゲート型

一部のデリゲート型は、それ自体が値を返す必要があります。 たとえば、文字列を返す ListViewItemToKeyHandler があります。 その型のデリゲートを作成する例を次に示します (ラムダ関数は値を返します)。

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

イベント処理デリゲートを使用してこの ポインター に安全にアクセスする

オブジェクトのメンバー関数を使用してイベントを処理する場合、またはオブジェクトのメンバー関数内のラムダ関数内からイベントを処理する場合は、イベント受信者 (イベントを処理するオブジェクト) とイベント ソース (イベントを発生させるオブジェクト) の相対的な有効期間を考慮する必要があります。 詳細およびコード例については、C++/WinRTにおける強い参照と弱い参照の を参照してください。

重要な API