C++/WinRT でのイベントの作成Author events in C++/WinRT

このトピックでは、その残高が借方に入るときにイベントを発生させる、銀行口座を表すランタイム クラスを含む Windows ランタイム コンポーネントを作成する方法を示します。This topic demonstrates how to author a Windows Runtime Component containing a runtime class representing a bank account, which raises an event when its balance goes into debit. 銀行口座ランタイム クラスを使用し、関数を呼び出して残高を調整して、発生するイベントを処理するコア アプリも示します。It also demonstrates a Core App that consumes the bank account runtime class, calls a function to adjust the balance, and handles any events that result.

注意

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.

重要

C++/WinRT でランタイム クラスを使用および作成する方法についての理解をサポートするために重要な概念と用語については、「C++/WinRT での API の使用」と「C++/WinRT での作成者 API」を参照してください。For essential concepts and terms that support your understanding of how to consume and author runtime classes with C++/WinRT, see Consume APIs with C++/WinRT and Author APIs with C++/WinRT.

Windows ランタイム コンポーネントの作成 (BankAccountWRC)Create a Windows Runtime Component (BankAccountWRC)

まず、Microsoft Visual Studio で、新しいプロジェクトを作成します。Begin by creating a new project in Microsoft Visual Studio. Windows ランタイム コンポーネント (C++/WinRT) プロジェクトを作成し、("銀行口座 Windows ランタイム コンポーネント" 用に) BankAccountWRC と名前を付けます。Create a Windows Runtime Component (C++/WinRT) project, and name it BankAccountWRC (for "bank account Windows Runtime Component"). まだプロジェクトはまだビルドしないでください。Don't built the project yet.

新しく作成したプロジェクトには、Class.idl という名前のファイルが含まれています。The newly-created project contains a file named Class.idl. そのファイル BankAccount.idl の名前を変更します (.idl ファイルの名前を変更すると、依存ファイル .h および .cpp も自動的に名前変更されます)。Rename that file BankAccount.idl (renaming the .idl file automatically renames the dependent .h and .cpp files, too). BankAccount.idl の内容を、以下のリストで置き換えます。Replace the contents of BankAccount.idl with the listing below.

// BankAccountWRC.idl
namespace BankAccountWRC
{
    runtimeclass BankAccount
    {
        BankAccount();
        event Windows.Foundation.EventHandler<Single> AccountIsInDebit;
        void AdjustBalance(Single value);
    };
}

ファイルを保存します。Save the file. 現時点ではプロジェクトは完成するまでビルドされませんが、ここでビルドすることは有用です。それによって、BankAccount ランタイム クラスを実装するためのソース コード ファイルが生成されるからです。The project won't build to completion at the moment, but building now is a useful thing to do because it generates the source code files in which you'll implement the BankAccount runtime class. それでは、今すぐビルドしてみましょう (この段階で表示されることが予想されるビルド エラーは、Class.hClass.g.h が見つからないことに関係しています)。So go ahead and build now (the build errors you can expect to see at this stage have to do with Class.h and Class.g.h not being found).

ビルド プロセス中に、midl.exe ツールが実行されてコンポーネントの Windows ランタイム メタデータ ファイルが作成されます (これは \BankAccountWRC\Debug\BankAccountWRC\BankAccountWRC.winmd です)。During the build process, the midl.exe tool is run to create your component's Windows Runtime metadata file (which is \BankAccountWRC\Debug\BankAccountWRC\BankAccountWRC.winmd). 次に、cppwinrt.exe ツールが (-component オプションで) 実行され、コンポーネントの作成をサポートするソース コード ファイルが生成されます。Then, the cppwinrt.exe tool is run (with the -component option) to generate source code files to support you in authoring your component. これらのファイルには、IDL で宣言した BankAccount ランタイム クラスの実装を開始するためのスタブが含まれています。These files include stubs to get you started implementing the BankAccount runtime class that you declared in your IDL. これらのスタブは \BankAccountWRC\BankAccountWRC\Generated Files\sources\BankAccount.hBankAccount.cpp です。Those stubs are \BankAccountWRC\BankAccountWRC\Generated Files\sources\BankAccount.h and BankAccount.cpp.

プロジェクト ノードを右クリックし、 [エクスプローラーでフォルダーを開く] をクリックします。Right-click the project node and click Open Folder in File Explorer. これにより、エクスプローラーでプロジェクト フォルダーが開きます。This opens the project folder in File Explorer. そこで、スタブ ファイル BankAccount.h および BankAccount.cpp をフォルダー \BankAccountWRC\BankAccountWRC\Generated Files\sources\ から、ご自分のプロジェクト ファイルを含むフォルダー \BankAccountWRC\BankAccountWRC\ にコピーし、コピー先のファイルを置き換えます。There, copy the stub files BankAccount.h and BankAccount.cpp from the folder \BankAccountWRC\BankAccountWRC\Generated Files\sources\ and into the folder that contains your project files, which is \BankAccountWRC\BankAccountWRC\, and replace the files in the destination. ここで、BankAccount.hBankAccount.cpp を開いてランタイム クラスを実装してみましょう。Now, let's open BankAccount.h and BankAccount.cpp and implement our runtime class. BankAccount.h で、BankAccount の実装 (ファクトリの実装ではありません) に 2 つのプライベート メンバーを追加します。In BankAccount.h, add two private members to the implementation (not the factory implementation) of BankAccount.

// BankAccount.h
...
namespace winrt::BankAccountWRC::implementation
{
    struct BankAccount : BankAccountT<BankAccount>
    {
        ...

    private:
        winrt::event<Windows::Foundation::EventHandler<float>> m_accountIsInDebitEvent;
        float m_balance{ 0.f };
    };
}
...

上に示したように、イベントは、特定のデリゲート型によってパラメータ化された winrt :: event 構造体テンプレートについて実装されています。As you can see above, the event is implemented in terms of the winrt::event struct template, parameterized by a particular delegate type.

BankAccount.cpp で、次のコード例に示すように関数を実装します。In BankAccount.cpp, implement the functions as shown in the code example below. C++/WinRT では、IDL で宣言したイベントは過負荷の状態である関数のセットとして実装されます (プロパティが過負荷の状態の Get および Set 関数のペアとして実装される方法と同様です)。In C++/WinRT, an IDL-declared event is implemented as a set of overloaded functions (similar to the way a property is implemented as a pair of overloaded get and set functions). 1 つのオーバーロードが登録するデリゲートを受け取り、トークンを返します。One overload takes a delegate to be registered, and returns a token. 別のオーバーロードはトークンを受け取り、関連付けられているデリゲートの登録を取り消します。The other takes a token, and revokes the registration of the associated delegate.

// BankAccount.cpp
...
namespace winrt::BankAccountWRC::implementation
{
    winrt::event_token BankAccount::AccountIsInDebit(Windows::Foundation::EventHandler<float> const& handler)
    {
        return m_accountIsInDebitEvent.add(handler);
    }

    void BankAccount::AccountIsInDebit(winrt::event_token const& token) noexcept
    {
        m_accountIsInDebitEvent.remove(token);
    }

    void BankAccount::AdjustBalance(float value)
    {
        m_balance += value;
        if (m_balance < 0.f) m_accountIsInDebitEvent(*this, m_balance);
    }
}

注意

自動イベント リボーカーについて詳しくは、「登録済みデリゲートの取り消し」をご覧ください。For details of what an auto event revoker is, see Revoke a registered delegate. イベント用の自動イベント リボーカーの実装は、無料で手に入ります。You get auto event revoker implementation for free for your event. つまり、C++/WinRT プロジェクションによって提供されているイベント リボーカーのオーバーロードを実装する必要はありません。In other words, you don't need to implement the overload for the event revoker—that's provided for you by the C++/WinRT projection.

他のオーバーロード (登録と手動取り消しのオーバーロード) は、プロジェクションには書き込まれて "いません"。The other overloads (the registration and manual revocation overloads) are not baked into the projection. それは、シナリオに最適なように柔軟に実装できるようにするためです。That's to give you the flexibility to implement them optimally for your scenario. これらの実装で示されているような event::add および event::remove の呼び出しは、効率的で同時実行の安全性を確保できるスレッドセーフな既定値です。Calling event::add and event::remove as shown in these implementations is an efficient and concurrency/thread-safe default. ただし、大量のイベントがある場合は、各イベントのイベント フィールドが必要ないことがあり、代わりに、ある種のスパース実装を選択します。But if you have a very large number of events, then you may not want an event field for each, but rather opt for some kind of sparse implementation instead.

また、AdjustBalance 関数の実装によって、残高が負の値になった場合でも AccountIsInDebit イベントが発生することを上で確認できます。You can also see above that the implementation of the AdjustBalance function raises the AccountIsInDebit event if the balance goes negative.

任意の警告によってビルドが妨げられる場合は、該当する警告を解決するか、またはプロジェクトのプロパティ C/C++ > General > Treat Warnings As ErrorsNo (/WX-) に設定して、もう一度プロジェクトをビルドします。If any warnings prevent you from building, then either resolve them or set the project property C/C++ > General > Treat Warnings As Errors to No (/WX-), and build the project again.

コア アプリ (BankAccountCoreApp) を作成して Windows ランタイム コンポーネントをテストします。Create a Core App (BankAccountCoreApp) to test the Windows Runtime Component

ここで (BankAccountWRC ソリューション、または新しいソリューションのいずれかに) 新しいプロジェクトを作成します。Now create a new project (either in your BankAccountWRC solution, or in a new one). コア アプリ (C++/WinRT) プロジェクトを作成し、BankAccountCoreApp と名前を付けます。Create a Core App (C++/WinRT) project, and name it BankAccountCoreApp.

参照を追加し、\BankAccountWRC\Debug\BankAccountWRC\BankAccountWRC.winmd を参照します (または 2 つのプロジェクトが同じソリューションに属している場合は、プロジェクト間の参照を追加します)。Add a reference, and browse to \BankAccountWRC\Debug\BankAccountWRC\BankAccountWRC.winmd (or add a project-to-project reference, if the two projects are in the same solution). [追加] をクリックして [OK] をクリックします。Click Add, and then OK. ここで BankAccountCoreApp をビルドします。Now build BankAccountCoreApp. 万が一、ペイロード ファイル readme.txt が存在しないというエラーが表示される場合、Windows ランタイム コンポーネント プロジェクトからそのファイルを除外し、これを再ビルドしてから、BankAccountCoreApp を再ビルドします。In the unlikely event that you see an error that the payload file readme.txt doesn't exist, exclude that file from the Windows Runtime Component project, rebuild it, then rebuild BankAccountCoreApp.

ビルド プロセス中に、cppwinrt.exe ツールが実行され、参照されている .winmd ファイルを投影型を含むソース コード ファイルに処理して、コンポーネントの使用をサポートします。During the build process, the cppwinrt.exe tool is run to process the referenced .winmd file into source code files containing projected types to support you in consuming your component. コンポーネントのランタイム クラス (BankAccountWRC.h という名前) の投影型のヘッダーは、フォルダー \BankAccountCoreApp\BankAccountCoreApp\Generated Files\winrt\ 内に生成されます。The header for the projected types for your component's runtime classes—named BankAccountWRC.h—is generated into the folder \BankAccountCoreApp\BankAccountCoreApp\Generated Files\winrt\.

そのヘッダーを App.cpp に含めます。Include that header in App.cpp.

#include <winrt/BankAccountWRC.h>

App.cpp でも、(投影型の既定のコンストラクターを使用して) BankAccount のインスタンスを作成するために次のコードを追加し、イベント ハンドラーを登録して、口座が借方に入るようにします。Also in App.cpp, add the following code to instantiate a BankAccount (using the projected type's default constructor), register an event handler, and then cause the account to go into debit.

WINRT_ASSERT はマクロ定義であり、_ASSERTE に展開されます。WINRT_ASSERT is a macro definition, and it expands to _ASSERTE.

struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
    BankAccountWRC::BankAccount m_bankAccount;
    winrt::event_token m_eventToken;
    ...
    
    void Initialize(CoreApplicationView const &)
    {
        m_eventToken = m_bankAccount.AccountIsInDebit([](const auto &, float balance)
        {
            WINRT_ASSERT(balance < 0.f); // Put a breakpoint here.
        });
    }
    ...

    void Uninitialize()
    {
        m_bankAccount.AccountIsInDebit(m_eventToken);
    }
    ...

    void OnPointerPressed(IInspectable const &, PointerEventArgs const & args)
    {
        m_bankAccount.AdjustBalance(-1.f);
        ...
    }
    ...
};

ウィンドウをクリックするたびに、銀行口座の残高から 1 を減算します。Each time you click the window, you subtract 1 from the bank account's balance. 期待どおりにイベントが発生することを実証するには、AccountIsInDebit イベントを処理しているラムダ式内にブレークポイントを置き、アプリを実行して、そのウィンドウ内をクリックします。To demonstrate that the event is being raised as expected, put a breakpoint inside the lambda expression that's handling the AccountIsInDebit event, run the app, and click inside the window.

ABI 間でのパラメータ化されたデリゲートとシンプルなシグナルParameterized delegates, and simple signals, across an ABI

ご利用のイベントがアプリケーション バイナリ インターフェイス (ABI) 間で (コンポーネントと処理を行うそのアプリケーションとの間など) アクセス可能である必要がある場合、そのイベントでは Windows ランタイム デリゲート型が使用される必要があります。If your event must be accessible across an application binary interface (ABI)—such as between a component and its consuming application—then your event must use a Windows Runtime delegate type. 上記の例では、Windows::Foundation::EventHandler<T> Windows ランタイム デリゲート型が使用されています。The example above uses the Windows::Foundation::EventHandler<T> Windows Runtime delegate type. TypedEventHandler<TSender, TResult> は、Windows ランタイム デリゲート型のもう 1 つの例です。TypedEventHandler<TSender, TResult> is another example of a Windows Runtime delegate type.

これら 2 つのデリゲート型用の型パラメーターは ABI をまたがる必要があるため、型パラメーターも Windows ランタイム型にする必要があります。The type parameters for those two delegate types have to cross the ABI, so the type parameters must be Windows Runtime types, too. それには、ファーストパーティとサードパーティのランタイム クラスに加えて、数値や文字列などのプリミティブ型が含まれます。That includes first- and third-party runtime classes, as well as primitive types such as numbers and strings. その制約を忘れた場合でも、コンパイラがエラー "WinRT 型である必要があります" を表示してサポートしてくれます。The compiler helps you with a "must be WinRT type" error if you forget that constraint.

イベントにパラメーターや引数を渡す必要がない場合は、独自のシンプルな Windows ランタイム デリゲート型を定義できます。If you don't need to pass any parameters or arguments with your event, then you can define your own simple Windows Runtime delegate type. 以下の例では、BankAccount ランタイム クラスのよりシンプルなバージョンを示します。The example below shows a simpler version of the BankAccount runtime class. これは、SignalDelegate という名前のデリゲート型を宣言してから、それを使用して、パラメーターを持つイベントではなくシグナル型のイベントを発生させます。It declares a delegate type named SignalDelegate and then it uses that to raise a signal-type event instead of an event with a parameter.

// BankAccountWRC.idl
namespace BankAccountWRC
{
    delegate void SignalDelegate();

    runtimeclass BankAccount
    {
        BankAccount();
        event BankAccountWRC.SignalDelegate SignalAccountIsInDebit;
        void AdjustBalance(Single value);
    };
}
// BankAccount.h
...
namespace winrt::BankAccountWRC::implementation
{
    struct BankAccount : BankAccountT<BankAccount>
    {
        ...

        winrt::event_token SignalAccountIsInDebit(BankAccountWRC::SignalDelegate const& handler);
        void SignalAccountIsInDebit(winrt::event_token const& token);
        void AdjustBalance(float value);

    private:
        winrt::event<BankAccountWRC::SignalDelegate> m_signal;
        float m_balance{ 0.f };
    };
}
// BankAccount.cpp
...
namespace winrt::BankAccountWRC::implementation
{
    winrt::event_token BankAccount::SignalAccountIsInDebit(BankAccountWRC::SignalDelegate const& handler)
    {
        return m_signal.add(handler);
    }

    void BankAccount::SignalAccountIsInDebit(winrt::event_token const& token)
    {
        m_signal.remove(token);
    }

    void BankAccount::AdjustBalance(float value)
    {
        m_balance += value;
        if (m_balance < 0.f)
        {
            m_signal();
        }
    }
}
// App.cpp
struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
    BankAccountWRC::BankAccount m_bankAccount;
    winrt::event_token m_eventToken;
    ...
    
    void Initialize(CoreApplicationView const &)
    {
        m_eventToken = m_bankAccount.SignalAccountIsInDebit([] { /* ... */ });
    }
    ...

    void Uninitialize()
    {
        m_bankAccount.SignalAccountIsInDebit(m_eventToken);
    }
    ...

    void OnPointerPressed(IInspectable const &, PointerEventArgs const & args)
    {
        m_bankAccount.AdjustBalance(-1.f);
        ...
    }
    ...
};

パラメーター化されたデリゲート、シンプルなシグナル、およびプロジェクト内でのコールバックParameterized delegates, simple signals, and callbacks within a project

自分のイベントを自分の C++/WinRT プロジェクトの内部でのみ使用する (バイナリ間ではない) 場合は、やはり winrt::event 構造体テンプレートを使用しますが、それをパラメーター化するには C++/WinRT の非 Windows ランタイム winrt::delegate<...T> 構造体テンプレートを使用します。これは効率的な、参照カウント デリゲートです。If your event is used only internally within your C++/WinRT project (not across binaries), then you still use the winrt::event struct template, but you parameterize it with C++/WinRT's non-Windows-Runtime winrt::delegate<... T> struct template, which is an efficient, reference-counted delegate. 任意の数のパラメーターをサポートし、それらは Windows ランタイム型に限定されません。It supports any number of parameters, and they are not limited to Windows Runtime types.

以下の例では、最初にパラメーターを取らないデリゲート シグネチャ (基本的にシンプルなシグナル) を示し、次に文字列を取るシグネチャを示します。The example below first shows a delegate signature that doesn't take any parameters (essentially a simple signal), and then one that takes a string.

winrt::event<winrt::delegate<>> signal;
signal.add([] { std::wcout << L"Hello, "; });
signal.add([] { std::wcout << L"World!" << std::endl; });
signal();

winrt::event<winrt::delegate<std::wstring>> log;
log.add([](std::wstring const& message) { std::wcout << message.c_str() << std::endl; });
log.add([](std::wstring const& message) { Persist(message); });
log(L"Hello, World!");

イベントには、サブスクライブしているデリゲートを必要な数だけ追加できることに注目してください。Notice how you can add to the event as many subscribing delegates as you wish. ただし、イベントに関連する何らかのオーバーヘッドがあります。However, there is some overhead associated with an event. ご自分で必要としているのがサブスクライブしている単一のデリゲートのみを含むシンプルなコールバックのみである場合は、winrt::delegate<...T> だけを使用できます。If all you need is a simple callback with only a single subscribing delegate, then you can use winrt::delegate<... T> on its own.

winrt::delegate<> signalCallback;
signalCallback = [] { std::wcout << L"Hello, World!" << std::endl; };
signalCallback();

winrt::delegate<std::wstring> logCallback;
logCallback = [](std::wstring const& message) { std::wcout << message.c_str() << std::endl; }f;
logCallback(L"Hello, World!");

イベントとデリゲートがプロジェクトの内部で使用されている C++/CX コードベースからの移植を行う場合、winrt::delegate を使用すると、C++/WinRT でそのパターンをレプリケートできます。If you're porting from a C++/CX codebase where events and delegates are used internally within a project, then winrt::delegate will help you to replicate that pattern in C++/WinRT.

設計ガイドラインDesign guidelines

関数パラメーターとしては、デリゲートではなくイベントを渡すことをお勧めします。We recommend that you pass events, and not delegates, as function parameters. winrt::eventadd 関数は 1 つの例外です。この場合はデリゲートを渡す必要があるからです。The add function of winrt::event is the one exception, because you must pass a delegate in that case. このガイドラインを設けた理由は、デリゲートが (クライアント登録を 1 つしかサポートしていないのか複数サポートしているかの観点から) さまざまな Windows ランタイム言語で異なる形式を取る可能性があるからです。The reason for this guideline is because delegates can take different forms across different Windows Runtime languages (in terms of whether they support one client registration, or multiple). 複数サブスクライバ モデルを使用したイベントでは、より予測可能で一貫性のあるオプションが構成されます。Events, with their multiple subscriber model, constitute a much more predictable and consistent option.

イベント ハンドラー デリゲート用のシグネチャは、次の 2 つのパラメーターで構成される必要があります: sender (IInspectable) と args (何らかのイベント引数型。たとえば、RoutedEventArgs)。The signature for an event handler delegate should consist of two parameters: sender (IInspectable), and args (some event argument type, for example RoutedEventArgs).

内部 API を設計している場合は、これらのガイドラインが必ずしも適用されるわけではないことに注意してください。Note that these guidelines don't necessarily apply if you're designing an internal API. ただし、内部 API は時間の経過に伴い公開されることがよくあります。Although, internal APIs often become public over time.