添付イベントの概要
Extensible Application Markup Language (XAML) は、言語コンポーネントと、添付イベントと呼ばれる種類のイベントも定義します。 添付イベントの概念によって、特定のイベントのハンドラーを、実際にそのイベントを定義または継承する要素ではなく任意の要素に追加することができます。 この場合、イベントを発生させる可能性のあるオブジェクトでも、インスタンスを処理する添付先でも、そのイベントを定義したり "所有" したりしません。
このトピックは、次のセクションで構成されています。
- 必要条件
- 添付イベントの構文
- WPF の添付イベント実装方法
- 添付イベントのシナリオ
- WPF の添付イベント処理
- 独自の添付イベントをルーティング イベントとして定義する
- WPF 添付イベントの発生
- 関連トピック
必要条件
このトピックは、「ルーティング イベントの概要」および「XAML の概要 (WPF)」を既に通読していることを前提としています。
添付イベントの構文
添付イベントには XAML 構文を使用します。また、添付イベントの使用に対応できるように、関連するコードでは特定のコーディング パターンを使用する必要があります。
XAML 構文では、添付イベントはイベント名だけでなく、所有している型とイベント名をドット (.) で区切って指定します。 イベント名にそれが所有する型が付加されるため、添付イベントの構文では、インスタンス化できる任意の要素に添付イベントをアタッチすることができます。
たとえば、ハンドラーをカスタムの NeedsCleaning 添付イベントにアタッチする XAML 構文の例を次に示します。
<aqua:Aquarium Name="theAquarium" Height="600" Width="800" aqua:AquariumFilter.NeedsCleaning="WashMe"/>
aqua: プレフィクスがあることに注意してください。この例の添付イベントは独自にマップされた xmlns によるカスタム イベントであるため、このプレフィクスが必要です。
WPF の添付イベント実装方法
WPF では、添付イベントは RoutedEvent フィールドによってサポートされ、発生後はツリーを通じてルーティングされます。 通常、添付イベントの発生元 (イベントを発生させたオブジェクト) はシステムまたはサービスのオブジェクトです。したがって、イベントを発生させるコードを実行するオブジェクトは要素ツリーの直接の構成部分ではありません。
添付イベントのシナリオ
WPF で添付イベントがよく使用されるのは、サービス レベルでの抽象化が含まれる機能で、これは静的 Mouse クラスまたは Validation クラスによって有効にされるイベントなどのためです。 サービスと対話したりサービスを使用したりするクラスでは、イベントを添付イベント構文で使用するか、添付イベントをルーティング イベントとしてクラスによるサービス機能統合の一部とすることができます。
WPF には多くの添付イベントが定義されていますが、添付イベントを直接使用または処理するシナリオは限られます。 一般に添付イベントは、アーキテクチャ上の目的を満たしてから、非添付の (CLR イベント "ラッパー" によってサポートされる) ルーティング イベントに転送されます。
たとえば、基になる添付イベント Mouse.MouseDown を特定の UIElement でより簡単に処理するには、添付イベント構文を XAML またはコードで使用するのではなく、その UIElement で MouseDown を使用します。 添付イベントは、入力デバイスを将来的に拡張することを可能にすることから、アーキテクチャ上の目的を満たします。 この架空のデバイスは、マウスからの入力をシミュレートする目的で Mouse.MouseDown を発生させるためだけに必要であり、それを行うために Mouse から派生する必要はありません。 ただし、このシナリオでは、イベントを処理するコードが必要であり、添付イベントを XAML で処理することは不適切です。
WPF の添付イベント処理
添付イベントを処理するプロセスと作成するハンドラー コードは、基本的にはルーティング イベントの場合と同じです。
一般に WPF の添付イベントは、WPF のルーティング イベントと大差ありません。 違いは、イベントが発生する場所とクラスからメンバーとして公開される方法 (これは XAML ハンドラー構文にも影響する) です。
ただし、既に触れたとおり、既存の WPF 添付イベントは、WPF で処理されることを特に意図していません。 多くの場合、イベントの目的は、複合要素から複合の親要素に状態を報告できるようにすることです。このとき、一般にイベントはコードで発生し、関連する親クラスのクラス処理にも依存します。 たとえば、Selector 内の項目は添付 Selected イベントを生成すると見なされます。このイベントは Selector クラスによってクラス処理されてから、おそらくは Selector クラスによって別のルーティング イベント SelectionChanged に変換されます。 ルーティング イベントとクラス処理の詳細については、「ルーティング イベントの処理済みとしてのマーキング、およびクラス処理」を参照してください。
独自の添付イベントをルーティング イベントとして定義する
共通の WPF 基本クラスから派生する場合、特定のパターンのメソッドをクラスに含め、基本クラスに既に提供されているユーティリティ メソッドを使用することにより、独自の添付イベントを実装することができます。
パターンは次のとおりです。
2 つのパラメーターを指定した Add*Handler メソッド。 1 番目のパラメーターにはイベントを識別する情報を指定します。イベントの名前は、メソッド名の "*" 部分に一致する必要があります。 2 番目のパラメーターは、追加するハンドラーです。 メソッドは、戻り値のないパブリックで静的なメソッドである必要があります。
2 つのパラメーターを指定した Remove*Handler メソッド。 1 番目のパラメーターにはイベントを識別する情報を指定します。イベントの名前は、メソッド名の "*" 部分に一致する必要があります。 2 番目のパラメーターは、削除するハンドラーです。 メソッドは、戻り値のないパブリックで静的なメソッドである必要があります。
Add*Handler アクセサー メソッドは、添付イベント ハンドラーの属性が要素で宣言されているときに XAML の処理を手助けします。 Add*Handler メソッドと Remove*Handler メソッドも、添付イベントのイベント ハンドラー ストアにアクセスする手段となります。
この一般的なパターンは、まだフレームワークとして実装するほど厳密なものではありません。というのも、XAML リーダー実装では、基になるイベントを識別するためにサポート言語とアーキテクチャによって異なる方式が使用される場合があるためです。 これは、WPF が添付イベントをルーティング イベントとして実装する理由の 1 つです。イベント (RoutedEvent) に使用する識別子は、既に WPF イベント システムに定義されています。 また、ルーティング イベントは、添付イベントの XAML 言語レベルの概念においては実装の自然な拡張です。
WPF 添付イベントの Add*Handler 実装は、ルーティング イベントとハンドラーを引数として指定する AddHandler 呼び出しで構成されます。
この実装方式とルーティング イベント システム全般では、添付イベントを処理できるのは UIElement 派生クラスまたは ContentElement 派生クラスに限定されます。これらのクラスのみが AddHandler を持つことがその理由です。
たとえば、次のコードでは、添付イベントをルーティング イベントとして宣言する WPF 添付イベント方式を使用して、NeedsCleaning 添付イベントを所有者クラス Aquarium に定義しています。
Public Shared ReadOnly NeedsCleaningEvent As RoutedEvent = EventManager.RegisterRoutedEvent("NeedsCleaning", RoutingStrategy.Bubble, GetType(RoutedEventHandler), GetType(AquariumFilter))
Public Shared Sub AddNeedsCleaningHandler(ByVal d As DependencyObject, ByVal handler As RoutedEventHandler)
Dim uie As UIElement = TryCast(d, UIElement)
If uie IsNot Nothing Then
uie.AddHandler(AquariumFilter.NeedsCleaningEvent, handler)
End If
End Sub
Public Shared Sub RemoveNeedsCleaningHandler(ByVal d As DependencyObject, ByVal handler As RoutedEventHandler)
Dim uie As UIElement = TryCast(d, UIElement)
If uie IsNot Nothing Then
uie.RemoveHandler(AquariumFilter.NeedsCleaningEvent, handler)
End If
End Sub
public static readonly RoutedEvent NeedsCleaningEvent = EventManager.RegisterRoutedEvent("NeedsCleaning", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(AquariumFilter));
public static void AddNeedsCleaningHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement uie = d as UIElement;
if (uie != null)
{
uie.AddHandler(AquariumFilter.NeedsCleaningEvent, handler);
}
}
public static void RemoveNeedsCleaningHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement uie = d as UIElement;
if (uie != null)
{
uie.RemoveHandler(AquariumFilter.NeedsCleaningEvent, handler);
}
}
添付イベントの識別子フィールド RegisterRoutedEvent を確立するために使用されるメソッドが、非添付ルーティング イベントを登録するために使用されるメソッドと実際には同じことに注意してください。 すべての添付イベントとルーティング イベントは、一元化された内部ストアに登録されます。 このイベント ストアの実装によって、「ルーティング イベントの概要」で説明されている "インターフェイスとしてのイベント" という概念が実現されます。
WPF 添付イベントの発生
通常は、WPF に定義された既存の添付イベントをコードから発生させる必要はありません。 これらのイベントは一般的な "サービス" 概念モデルに従い、InputManager などのサービス クラスがイベントを発生させる処理を担います。
ただし、RoutedEvent を基にする WPF の添付イベント モデルに基づいてカスタムの添付イベントを定義する場合は、RaiseEvent を使用して添付イベントを任意の UIElement または ContentElement から発生させることができます。 ルーティング イベント (添付または非添付) を発生させるには、特定の要素をイベント ソースとして要素ツリー内で宣言する必要があります。このソースが RaiseEvent 呼び出し元として報告されます。 ツリー内のどの要素が発生元として報告されるかを決定することは、作成するサービスの役目です。