イベント (C++/CX)
Windows ランタイム型は、イベントを宣言 (つまり、発行) できます。そして、同じコンポーネント内または他のコンポーネント内のクライアント コードは、イベント ハンドラーと呼ばれるメソッドをイベントに関連付けることにより、これらのイベントにサブスクライブすることができます。 複数のイベント ハンドラーを単一のイベントに関連付けることもできます。 発行オブジェクトがイベントを発生させた時点で、すべてのイベント ハンドラーが呼び出される結果になります。 この方法で、発行側がイベントを発生させたときに、サブスクライブ側クラスはあらゆる適切なカスタム アクションを実行できます。 各イベントには、そのイベントにサブスクライブしようとするすべてのイベント ハンドラーが所有している必要があるシグネチャを指定するデリゲート型があります。
Windows コンポーネントでのイベントの利用
Windows ランタイムの多くのコンポーネントは、イベントを公開します。 たとえば、LightSensor オブジェクトは、センサーが新しい発光値を報告すると ReadingChanged イベントを発生させます。 プログラム内で LightSensor オブジェクトを使用する場合、ReadingChanged イベントが発生したときに呼び出されるメソッドを定義できます。 メソッドは、任意の処理を実行できます。唯一の要件は、その署名が呼び出されたデリゲートのシグネチャと一致する必要があることです。 デリゲート イベント ハンドラーを作成し、イベントをサブスクライブする方法の詳細については、「デリゲート」を参照してください。
カスタム イベントの作成
宣言
ref クラスまたはインターフェイスでイベントを宣言できます。イベントは、public、internal (public/private)、public protected、protected、private protected、または private のアクセシビリティを持つことができます。 イベントを宣言する場合、コンパイラは内部で、add および remove という 2 つのアクセサー メソッドを公開するオブジェクトを作成します。 サブスクライブ側のオブジェクトがイベント ハンドラーを登録した時点で、イベント オブジェクトはそれらのイベント ハンドラーをコレクションに格納します。 イベントが発生すると、イベント オブジェクトはリスト内のすべてのハンドラーを順番に呼び出します。 次の例のような単純なイベントには、暗黙のバッキング ストアと共に暗黙の add
および remove
アクセサー メソッドがあります。 また、プロパティにカスタムの get
アクセサーと set
アクセサーを指定するのと同じ方法で、独自のアクセサーを指定することもできます。 実装するクラスは、単純なイベントのイベント サブスクライバーの一覧内を手動で循環することはできません。
次の例に、イベントを宣言して発生させる方法を示します。 イベントはデリゲート型を持ち、"^" シンボルを使用して宣言されることに注意してください。
namespace EventTest
{
ref class Class1;
public delegate void SomethingHappenedEventHandler(Class1^ sender, Platform::String^ s);
public ref class Class1 sealed
{
public:
Class1(){}
event SomethingHappenedEventHandler^ SomethingHappened;
void DoSomething()
{
//Do something....
// ...then fire the event:
SomethingHappened(this, L"Something happened.");
}
};
}
使用方法
次の例では、サブスクライブ側のクラスが +=
演算子を使用してイベントにサブスクライブし、イベントが発生したときに呼び出されるイベント ハンドラーを指定する方法を示します。 指定された関数が、 EventTest
名前空間でパブリッシャー側で定義されたデリゲートの署名と一致していることに注目してください。
namespace EventClient
{
using namespace EventTest;
namespace PC = Platform::Collections; //#include <collection.h>
public ref class Subscriber sealed
{
public:
Subscriber() : eventCount(0)
{
// Instantiate the class that publishes the event.
publisher= ref new EventTest::Class1();
// Subscribe to the event and provide a handler function.
publisher->SomethingHappened +=
ref new EventTest::SomethingHappenedEventHandler(
this,
&Subscriber::MyEventHandler);
eventLog = ref new PC::Map<int, Platform::String^>();
}
void SomeMethod()
{
publisher->DoSomething();
}
void MyEventHandler(EventTest::Class1^ mc, Platform::String^ msg)
{
// Our custom action: log the event.
eventLog->Insert(eventCount, msg);
eventCount++;
}
private:
PC::Map<int, Platform::String^>^ eventLog;
int eventCount;
EventTest::Class1^ publisher;
};
}
警告
細心の注意を払って循環参照を回避する場合を除き、一般にイベント ハンドラーにはラムダではなく名前付き関数を使用する方が適しています。 名前付き関数では弱い参照によって "this" ポインターをキャプチャするのに対し、ラムダでは強い参照によってこのポインターをキャプチャし、循環参照を作成します。 詳細については、「 Weak references and breaking cycles (C++/CX) (弱参照と循環の解除 (C++/CX))」を参照してください。
カスタムの add メソッドと remove メソッド
内部的には、イベントには add メソッド、remove メソッド、および raise メソッドがあります。 クライアント コードがイベントにサブスクライブすると、add メソッドが呼び出され、渡されたデリゲートが、イベントの呼び出しリストに追加されます。 パブリッシャー クラスがイベントを呼び出します。すると、raise() メソッドが呼び出され、リスト内の各デリゲートが順番に呼び出されます。 サブスクライバーは、デリゲートの一覧から自身を削除できます。これにより、イベントの remove メソッドが呼び出されます。 ユーザーがコードでこれらのメソッドを定義していない場合に備えて、コンパイラには、既定バージョンのメソッドが用意されています。これらは単純なイベントと呼ばれます。 多くの場合、単純なイベント以外に必要とされることはありません。
サブスクライバーの追加または削除に応じてカスタム ロジックを実行する必要がある場合に、イベントのカスタム add、remove、および raise メソッドを指定できます。 たとえば、イベントのレポートにのみ必要な負荷の高いオブジェクトがある場合、クライアントがイベントを実際にサブスクライブするまでオブジェクトの作成を延期できます。
次の例は、カスタムの add、remove、および raise メソッドをイベントに追加する方法を示しています。
namespace EventTest2
{
ref class Class1;
public delegate void SomethingHappenedEventHandler(Class1^ sender, Platform::String^ msg);
public ref class Class1 sealed
{
public:
Class1(){}
event SomethingHappenedEventHandler^ SomethingHappened;
void DoSomething(){/*...*/}
void MethodThatFires()
{
// Fire before doing something...
BeforeSomethingHappens(this, "Something's going to happen.");
DoSomething();
// ...then fire after doing something...
SomethingHappened(this, L"Something happened.");
}
event SomethingHappenedEventHandler^ _InternalHandler;
event SomethingHappenedEventHandler^ BeforeSomethingHappens
{
Windows::Foundation::EventRegistrationToken add(SomethingHappenedEventHandler^ handler)
{
// Add custom logic here:
//....
return _InternalHandler += handler;
}
void remove(Windows::Foundation::EventRegistrationToken token)
{
// Add custom logic here:
//....
_InternalHandler -= token;
}
void raise(Class1^ sender, Platform::String^ str)
{
// Add custom logic here:
//....
return _InternalHandler(sender, str);
}
}
};
}
サブスクライバー側からのイベント ハンドラーの削除
まれに、前にサブスクライブしたイベントのイベント ハンドラーを削除する必要が生じることがあります。 たとえば、他のイベント ハンドラーに置き換える場合や、ハンドラーが保持するリソースを削除する場合です。 ハンドラーを削除するには、 +=
演算から返される EventRegistrationToken を保存しておく必要があります。 その後、トークンの -=
演算子を使用してイベント ハンドラーを削除します。 ただし、元のハンドラーは削除した後でも呼び出すことができます。 たとえば、イベント ソースがハンドラーのリストを取得して呼び出しを開始したときに、競合状態が発生する可能性があります。 このエラーが発生したときにイベント ハンドラーが削除された場合、リストは最新ではなくなります。 そのため、イベント ハンドラーを削除する場合は、メンバー フラグを作成します。 イベントが削除された場合はそれを設定し、イベント ハンドラーでフラグをチェックして、設定されている場合はすぐに制御を戻します。 次の例に、基本的なパターンを示します。
namespace EventClient2
{
using namespace EventTest2;
ref class Subscriber2 sealed
{
private:
bool handlerIsActive;
Platform::String^ lastMessage;
void TestMethod()
{
Class1^ c1 = ref new Class1();
handlerIsActive = true;
Windows::Foundation::EventRegistrationToken cookie =
c1->SomethingHappened +=
ref new EventTest2::SomethingHappenedEventHandler(this, &Subscriber2::MyEventHandler);
c1->DoSomething();
// Do some other work�..then remove the event handler and set the flag.
handlerIsActive = false;
c1->SomethingHappened -= cookie;
}
void MyEventHandler(Class1^ mc, Platform::String^ msg)
{
if (!handlerIsActive)
return;
lastMessage = msg;
}
};
}
解説
複数のハンドラーが、同じイベントに関連付けられている場合があります。 イベント ソースは、同じスレッドからすべてのイベント ハンドラーを順番に呼び出します。 イベント ハンドラー メソッド内でイベント レシーバーがブロックする場合、このイベントに対する他のイベント ハンドラーの呼び出しからイベント ソースをブロックします。
イベント ソースがイベント レシーバーのイベント ハンドラーを呼び出す順序は保証されず、呼び出しごとに異なる可能性があります。