このトピックでは、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という名前の
マークアップで宣言的に行う代わりに、イベントを処理するメンバー関数を命令的に登録できます。 以下のコード例からは明らかではないかもしれませんが、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型
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 番目のオーバーロードから、正確にどのタイプのイベントリボーカーを宣言する必要があるかを確認できます。 同じ種類のデリゲートを、
// 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 ランタイム イベントには、イベント 取り消し子を返す取り消し関数オーバーロードがあり、その取り消し子の型はイベント ソースのメンバーです。 そのため、別の例を見ると、
ページ ナビゲーション シナリオでは、ハンドラーの取り消しを検討できます。 ページに繰り返し移動してから戻る場合は、ページから離れたときにハンドラーを取り消すことができます。 または、同じページ インスタンスを再利用する場合は、トークンの値を確認し、まだ設定されていない場合にのみ登録します (if (!m_token){ ... }
)。 3 つ目のオプションは、イベント 取り消し子をデータ メンバーとしてページに格納することです。 また、このトピックで後述するように、4 番目のオプションは、ラムダ関数でこの オブジェクト
自動取り消しデリゲートが登録に失敗した場合
デリゲートの登録時に winrt::auto_revoke
非同期アクションと操作のデリゲート型
上記の例では、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における強い参照と弱い参照の を参照してください。