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

このトピックでは、「C++/WinRT を使用した Windows ランタイム コンポーネント」トピックで作成方法が示されている Windows ランタイム コンポーネントとそれを使用するアプリケーションが基になっています。This topic builds on the Windows Runtime component, and the consuming application, that the Windows Runtime components with C++/WinRT topic shows you how to build.

このトピックで追加される新機能を次に示します。Here are the new features that this topic adds.

  • 気温が氷点下を下回る際に、温度計ランタイム クラスを更新してイベントを発生させます。Update the thermometer runtime class to raise an event when its temperature goes below freezing.
  • そのイベントを処理するように、温度計ランタイム クラスを使用する Core アプリを更新しします。Update the Core App that consumes the thermometer runtime class so that it handles that event.

注意

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.

ThermometerWRCThermometerCoreApp を作成するCreate ThermometerWRC and ThermometerCoreApp

このトピックで示されている更新に従って、コードをビルドして実行できるようにする場合は、最初に「C++/WinRT を使用した Windows ランタイム コンポーネント」トピックのチュートリアルに従ってください。If you want to follow along with the updates shown in this topic, so that you can build and run the code, then the first step is to follow the walkthrough in the Windows Runtime components with C++/WinRT topic. これにより、ThermometerWRC Windows ランタイム コンポーネントと、それを使用する ThermometerCoreApp Core アプリが作成されます。By doing so, you'll have the ThermometerWRC Windows Runtime component, and the ThermometerCoreApp Core App that consumes it.

イベントが発生されるように ThermometerWRC を更新するUpdate ThermometerWRC to raise an event

次のリストのように Thermometer.idl を更新します。Update Thermometer.idl to look like the listing below. これは、デリゲート型が EventHandler で引数が単精度浮動小数点数であるイベントを宣言する方法です。This is how to declare an event whose delegate type is EventHandler with an argument of a single-precision floating-point number.

// Thermometer.idl
namespace ThermometerWRC
{
    runtimeclass Thermometer
    {
        Thermometer();
        void AdjustTemperature(Single deltaFahrenheit);
        event Windows.Foundation.EventHandler<Single> TemperatureIsBelowFreezing;
    };
}

ファイルを保存します。Save the file. 現在の状態では、プロジェクトは完全にはビルドされませんが、それでもここでビルドを実行して、\ThermometerWRC\ThermometerWRC\Generated Files\sources\Thermometer.h および Thermometer.cpp スタブ ファイルの更新バージョンを生成します。The project won't build to completion in its current state, but perform a build now in any case to generate updated versions of the \ThermometerWRC\ThermometerWRC\Generated Files\sources\Thermometer.h and Thermometer.cpp stub files. それらのファイルの内容で、TemperatureIsBelowFreezing イベントのスタブの実装を確認できるようになります。Inside those files you can now see stub implementations of the TemperatureIsBelowFreezing event. 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 つのオーバーロードが登録するデリゲートを受け取り、トークン (winrt::event_token) を返します。One overload takes a delegate to be registered, and returns a token (a winrt::event_token). 別のオーバーロードはトークンを受け取り、関連付けられているデリゲートの登録を取り消します。The other takes a token, and revokes the registration of the associated delegate.

次に、Thermometer.hThermometer.cpp を開き、Thermometer ランタイム クラスの実装を更新します。Now open Thermometer.h and Thermometer.cpp, and update the implementation of the Thermometer runtime class. Thermometer.h で、2 つのオーバーロードされた TemperatureIsBelowFreezing 関数と、それらの関数の実装で使用するためのプライベート イベント データ メンバーを追加します。In Thermometer.h, add the two overloaded TemperatureIsBelowFreezing functions, as well as a private event data member to use in the implementation of those functions.

// Thermometer.h
...
namespace winrt::ThermometerWRC::implementation
{
    struct Thermometer : ThermometerT<Thermometer>
    {
        ...
        winrt::event_token TemperatureIsBelowFreezing(Windows::Foundation::EventHandler<float> const& handler);
        void TemperatureIsBelowFreezing(winrt::event_token const& token) noexcept;

    private:
        winrt::event<Windows::Foundation::EventHandler<float>> m_temperatureIsBelowFreezingEvent;
        ...
    };
}
...

前述のように、イベントは winrt::event 構造体テンプレートによって表され、(それ自体が args 型によってパラメーター化できる) 特定のデリゲート型によってパラメーター化されます。As you can see above, an event is represented by the winrt::event struct template, parameterized by a particular delegate type (which itself can be parameterized by an args type).

Thermometer.cpp で、2 つのオーバーロードされた TemperatureIsBelowFreezing 関数を実装します。In Thermometer.cpp, implement the two overloaded TemperatureIsBelowFreezing functions.

// Thermometer.cpp
...
namespace winrt::ThermometerWRC::implementation
{
    winrt::event_token Thermometer::TemperatureIsBelowFreezing(Windows::Foundation::EventHandler<float> const& handler)
    {
        return m_temperatureIsBelowFreezingEvent.add(handler);
    }

    void Thermometer::TemperatureIsBelowFreezing(winrt::event_token const& token) noexcept
    {
        m_temperatureIsBelowFreezingEvent.remove(token);
    }

    void Thermometer::AdjustTemperature(float deltaFahrenheit)
    {
        m_temperatureFahrenheit += deltaFahrenheit;
        if (m_temperatureFahrenheit < 32.f) m_temperatureIsBelowFreezingEvent(*this, m_temperatureFahrenheit);
    }
}

注意

自動イベント リボーカーについて詳しくは、「登録済みデリゲートの取り消し」をご覧ください。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.

また、上の例では、気温が氷点下を下回る場合でも TemperatureIsBelowFreezing イベントが発生するように AdjustTemperature 関数の実装が更新されていることも確認できます。You can also see above that the implementation of the AdjustTemperature function has been updated to raise the TemperatureIsBelowFreezing event if the temperature goes below freezing.

イベントを処理するように ThermometerCoreApp を更新するUpdate ThermometerCoreApp to handle the event

ThermometerCoreApp プロジェクトの App.cpp で、イベント ハンドラーを登録してから気温が氷点下を下回るよう、コードを次のように変更します。In the ThermometerCoreApp project, in App.cpp, make the following changes to the code to register an event handler, and then cause the temperature to go below freezing.

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

struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
    winrt::event_token m_eventToken;
    ...
    
    void Initialize(CoreApplicationView const &)
    {
        m_eventToken = m_thermometer.TemperatureIsBelowFreezing([](const auto &, float temperatureFahrenheit)
        {
            WINRT_ASSERT(temperatureFahrenheit < 32.f); // Put a breakpoint here.
        });
    }
    ...

    void Uninitialize()
    {
        m_thermometer.TemperatureIsBelowFreezing(m_eventToken);
    }
    ...
    
    void OnPointerPressed(IInspectable const &, PointerEventArgs const & args)
    {
        m_thermometer.AdjustTemperature(-1.f);
        ...
    }
    ...
};

OnPointerPressed メソッドの変更に注意してください。Be aware of the change to the OnPointerPressed method. これで、ウィンドウをクリックするたびに、温度計の気温から華氏 1 度差し引かれますます。Now, each time you click the window, you subtract 1 degree Fahrenheit from the thermometer's temperature. また、アプリでは、気温が氷点下を下回ると発生するイベントが処理されています。And now, the app is handling the event that's raised when the temperature goes below freezing. 期待どおりにイベントが発生することを実証するには、TemperatureIsBelowFreezing イベントを処理しているラムダ式内にブレークポイントを置き、アプリを実行して、そのウィンドウ内をクリックします。To demonstrate that the event is being raised as expected, put a breakpoint inside the lambda expression that's handling the TemperatureIsBelowFreezing event, run the app, and click inside the window.

ABI 間でのパラメーター化されたデリゲートParameterized delegates 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. これには、Windows ランタイム クラス、サードパーティ製のランタイム クラス、数値や文字列などのプリミティブ型が含まれます。That includes Windows runtime classes, third-party runtime classes, and primitive types such as numbers and strings. その制約を忘れた場合でも、コンパイラからのサポートとして "T は WinRT 型である必要があります" というエラーが表示されます。The compiler helps you with a "T must be WinRT type" error if you forget that constraint.

次に、コード リスティングの形式での例を示します。Below is an example in the form of code listings. このトピックの前半で作成した ThermometerWRC および ThermometerCoreApp プロジェクトから開始し、これらのプロジェクト内でコードを編集して、リスティングのコードのように見えるようにします。Begin with the ThermometerWRC and ThermometerCoreApp projects that you created earlier in this topic, and edit the code in those projects to look like the code in these listings.

最初に示すこのリスティングは、ThermometerWRC プロジェクトのためのものです。This first listing is for the ThermometerWRC project. 次に示すように ThermometerWRC.idl を編集してから、プロジェクトをビルドし、前の手順で Thermometer.h および .cpp について行ったのと同じようにして、MyEventArgs.h および .cpp をプロジェクトにコピーします (Generated Files フォルダーから)。After editing ThermometerWRC.idl as shown below, build the project, and then copy MyEventArgs.h and .cpp into the project (from the Generated Files folder) just like you did earlier with Thermometer.h and .cpp.

// ThermometerWRC.idl
namespace ThermometerWRC
{
    [default_interface]
    runtimeclass MyEventArgs
    {
        Single TemperatureFahrenheit{ get; };
    }

    [default_interface]
    runtimeclass Thermometer
    {
        ...
        event Windows.Foundation.EventHandler<ThermometerWRC.MyEventArgs> TemperatureIsBelowFreezing;
        ...
    };
}

// MyEventArgs.h
#pragma once
#include "MyEventArgs.g.h"

namespace winrt::ThermometerWRC::implementation
{
    struct MyEventArgs : MyEventArgsT<MyEventArgs>
    {
        MyEventArgs() = default;
        MyEventArgs(float temperatureFahrenheit);
        float TemperatureFahrenheit();

    private:
        float m_temperatureFahrenheit{ 0.f };
    };
}

// MyEventArgs.cpp
#include "pch.h"
#include "MyEventArgs.h"
#include "MyEventArgs.g.cpp"

namespace winrt::ThermometerWRC::implementation
{
    MyEventArgs::MyEventArgs(float temperatureFahrenheit) : m_temperatureFahrenheit(temperatureFahrenheit)
    {
    }

    float MyEventArgs::TemperatureFahrenheit()
    {
        return m_temperatureFahrenheit;
    }
}

// Thermometer.h
...
struct Thermometer : ThermometerT<Thermometer>
{
...
    winrt::event_token TemperatureIsBelowFreezing(Windows::Foundation::EventHandler<ThermometerWRC::MyEventArgs> const& handler);
...
private:
    winrt::event<Windows::Foundation::EventHandler<ThermometerWRC::MyEventArgs>> m_temperatureIsBelowFreezingEvent;
...
}
...

// Thermometer.cpp
#include "MyEventArgs.h"
...
winrt::event_token Thermometer::TemperatureIsBelowFreezing(Windows::Foundation::EventHandler<ThermometerWRC::MyEventArgs> const& handler) { ... }
...
void Thermometer::AdjustTemperature(float deltaFahrenheit)
{
    m_temperatureFahrenheit += deltaFahrenheit;

    if (m_temperatureFahrenheit < 32.f)
    {
        auto args = winrt::make_self<winrt::ThermometerWRC::implementation::MyEventArgs>(m_temperatureFahrenheit);
        m_temperatureIsBelowFreezingEvent(*this, *args);
    }
}
...

このリスティングは、ThermometerCoreApp プロジェクトのためのものです。This listing is for the ThermometerCoreApp project.

// App.cpp
...
void Initialize(CoreApplicationView const&)
{
    m_eventToken = m_thermometer.TemperatureIsBelowFreezing([](const auto&, ThermometerWRC::MyEventArgs args)
    {
        float degrees = args.TemperatureFahrenheit();
        WINRT_ASSERT(degrees < 32.f); // Put a breakpoint here.
    });
}
...

ABI 間のシンプルなシグナルSimple signals across an ABI

イベントにパラメーターや引数を渡す必要がない場合は、独自のシンプルな 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. 以下の例では、Thermometer ランタイム クラスのよりシンプルなバージョンを示します。The example below shows a simpler version of the Thermometer 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.

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

    runtimeclass Thermometer
    {
        Thermometer();
        event ThermometerWRC.SignalDelegate SignalTemperatureIsBelowFreezing;
        void AdjustTemperature(Single value);
    };
}
// Thermometer.h
...
namespace winrt::ThermometerWRC::implementation
{
    struct Thermometer : ThermometerT<Thermometer>
    {
        ...

        winrt::event_token SignalTemperatureIsBelowFreezing(ThermometerWRC::SignalDelegate const& handler);
        void SignalTemperatureIsBelowFreezing(winrt::event_token const& token);
        void AdjustTemperature(float deltaFahrenheit);

    private:
        winrt::event<ThermometerWRC::SignalDelegate> m_signal;
        float m_temperatureFahrenheit{ 0.f };
    };
}
// Thermometer.cpp
...
namespace winrt::ThermometerWRC::implementation
{
    winrt::event_token Thermometer::SignalTemperatureIsBelowFreezing(ThermometerWRC::SignalDelegate const& handler)
    {
        return m_signal.add(handler);
    }

    void Thermometer::SignalTemperatureIsBelowFreezing(winrt::event_token const& token)
    {
        m_signal.remove(token);
    }

    void Thermometer::AdjustTemperature(float deltaFahrenheit)
    {
        m_temperatureFahrenheit += deltaFahrenheit;
        if (m_temperatureFahrenheit < 32.f)
        {
            m_signal();
        }
    }
}
// App.cpp
struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
    ThermometerWRC::Thermometer m_thermometer;
    winrt::event_token m_eventToken;
    ...
    
    void Initialize(CoreApplicationView const &)
    {
        m_eventToken = m_thermometer.SignalTemperatureIsBelowFreezing([] { /* ... */ });
    }
    ...

    void Uninitialize()
    {
        m_thermometer.SignalTemperatureIsBelowFreezing(m_eventToken);
    }
    ...

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

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

Visual Studio プロジェクトの内部の (複数のバイナリにまたがらない) イベントが必要であり、それらのイベントが Windows ランタイム型に限定されない場合でも、winrt::event<Delegate> クラス テンプレートを使用できます。If you need events that are internal to your Visual Studio project (not across binaries), where those events are not limited to Windows Runtime types, then you can still use the winrt::event<Delegate> class template. winrt::delegate は Windows ランタイム以外のパラメーターもサポートするため、実際の Windows ランタイム デリゲート型の代わりに winrt::delegate を使用するだけです。Simply use winrt::delegate instead of an actual Windows Runtime delegate type, since winrt::delegate also supports non Windows Runtime parameters.

以下の例では、最初にパラメーターを取らないデリゲート シグネチャ (基本的にシンプルなシグナル) を示し、次に文字列を取るシグネチャを示します。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.