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. 作成、 Visual C > Windows ユニバーサル > Windows ランタイム コンポーネント (C +/cli WinRT) プロジェクト、および名前を付けますBankAccountWRC (の「銀行口座 Windows ランタイム コンポーネント」)。Create a Visual C++ > Windows Universal > Windows Runtime Component (C++/WinRT) project, and name it BankAccountWRC (for "bank account Windows Runtime Component").

新しく作成したプロジェクトには、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. これらのファイルは、スタブ実装を開始するため、 BankAccount IDL 内で宣言されているランタイム クラスです。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.hBankAccount.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)
    {
        m_accountIsInDebitEvent.remove(token);
    }

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

イベント revoker のオーバー ロードを実装する必要はありません (詳細については、次を参照してください。登録されたデリゲートを取り消す)—をが隠メ諶の c++/cli WinRT 投影します。You don't need to implement the overload for the event revoker (for details, see Revoke a registered delegate)—that's taken care of for you by the C++/WinRT projection. 別のオーバーロードは、開発者がシナリオに最適な方法で柔軟に実装できるようにするため、プロジェクションに書き込まれません。The other overloads are not baked into the projection, in order to give you the flexibility to implement them optimally for your scenario. このような event::add および event::remove の呼び出しは、効率的で同時実行の安全性を確保できるスレッド セーフな既定値です。Calling event::add and event::remove like this 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++ > 全般 > 警告をエラーとして扱ういいえ (/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). 作成、 Visual C > Windows ユニバーサル > Core アプリ (C +/cli WinRT) プロジェクト、および名前を付けますBankAccountCoreApp.Create a Visual C++ > Windows Universal > 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.

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

    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 ランタイムのデリゲート型の別の例を示します。TypedEventHandler<TSender, TResult> is another example of a Windows Runtime delegate type.

これら 2 つのデリゲート型の型パラメーターは、型パラメーターでは、Windows ランタイムの型をもする必要がありますので、ABI を通過する必要があります。The type parameters for those two delegate types have to cross the ABI, so the type parameters must be Windows Runtime types, too. 1 つ目とサード パーティのランタイム クラスの数値や文字列などのプリミティブ型も含まれます。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 + 内で/cli WinRT は、使用することも、(バイナリ)、全体ではなくプロジェクト、 winrt::event 構造体のテンプレートがパラメーター C +/cli 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.

(基本的には単純な信号を)、パラメーターがない署名とは文字列を受け取り、1、次の例は最初にデリゲートを示します。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++ から移植している場合/cli CX コードベースでイベントとデリゲートを内部的に使用されます、プロジェクト内で、 winrt::delegate C + では、そのパターンをレプリケートする際に役立つ/cli 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::event 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 つのパラメーターで構成されます:送信者(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.