添付イベントの概要 (WPF .NET)

Extensible Application Markup Language (XAML) では、"添付イベント" と呼ばれる言語コンポーネントとイベントの種類が定義されています。 添付イベントを使うと、非要素クラスで新しいルーティング イベントを定義し、ツリー内の任意の要素でそのイベントを発生させることができます。 これを行うには、添付イベントをルーティング イベントとして登録し、添付イベントの機能をサポートする特定のバッキング コードを提供する必要があります。 添付イベントはルーティング イベントとして登録されるため、要素で発生すると、要素ツリーを介して伝達されます。

重要

.NET 7 と .NET 6 用のデスクトップ ガイド ドキュメントは作成中です。

前提条件

この記事では、Windows Presentation Foundation (WPF) のルーティング イベントに関する基本的な知識を持ち、ルーティング イベントの概要WPF での XAML に関する記事を読んでいることを前提としています。 この記事の例に従う場合は、XAML を使い慣れていて、WPF アプリケーションの記述方法を理解していると役に立ちます。

添付イベントの構文

XAML の構文では、添付イベントは、そのイベント名とその所有者の型の "両方" を使って、<owner type>.<event name> という形式で指定されます。 イベント名はその所有者の型の名前で修飾されるため、構文では、インスタンス化できる任意の要素にイベントを添付できます。 この構文は、イベント ルートに沿って任意の要素に添付される通常のルーティング イベントのハンドラーにも適用できます。

次の XAML 属性構文では、AquariumFilter.Clean 添付イベントの AquariumFilter_Clean ハンドラーが aquarium1 要素に添付されます。

<aqua:Aquarium x:Name="aquarium1" Height="300" Width="400" aqua:AquariumFilter.Clean="AquariumFilter_Clean"/>

この例では、AquariumFilter クラスと Aquarium クラスが共通言語ランタイム (CLR) の異なる名前空間とアセンブリに存在するため、aqua: プレフィックスが必要です。

また、分離コード内の添付イベントのハンドラーを添付することもできます。 これを行うには、ハンドラーを添付する必要があるオブジェクトで AddHandler メソッドを呼び出し、イベント識別子とハンドラーをパラメーターとしてメソッドに渡します。

WPF での添付イベントの実装方法

WPF の添付イベントは、RoutedEvent フィールドによってサポートされるルーティング イベントとして実装されます。 その結果、添付イベントは、発生した後に要素ツリーを介して伝達されます。 一般に、添付イベントが発生するオブジェクト (イベント ソースと呼ばれます) は、システムまたはサービス ソースです。 システムまたはサービス ソースは、要素ツリーの直接的な部分ではありません。 他の添付イベントの場合、イベント ソースは、複合コントロール内のコンポーネントのようなツリーの要素である可能性があります。

添付イベントのシナリオ

WPF では、添付イベントは、サービス レベルの抽象化がある特定の機能領域で使われます。 たとえば、WPF では、static の Mouse または Validation クラスによって有効にされた添付イベントを使います。 サービスと対話する、またはサービスを使用するクラスは、添付イベント構文を使ってイベントと対話するか、添付イベントをルーティング イベントとして公開することができます。 後者のオプションは、クラスがサービスの機能を統合する方法の一部です。

WPF 入力システムでは、添付イベントを多用します。 ただし、それらの添付イベントのほとんどすべては、基本要素を介して同等の非添付ルーティング イベントとして公開されます。 各ルーティング入力イベントは基本要素クラスのメンバーであり、CLR イベントの "ラッパー" でサポートされます。 添付イベントを直接使用または処理することはほとんどありません。 たとえば、XAML または分離コードで添付イベント構文を使うより、同等の UIElement.MouseDown ルーティング イベントを介して、UIElement で基になる添付 Mouse.MouseDown イベントを処理する方が簡単です。

添付イベントは、入力デバイスの将来の拡張を可能にすることで、アーキテクチャの目的を果たします。 たとえば、新しい入力デバイスでは、Mouse.MouseDown を発生させてマウス入力をシミュレートするだけで済み、これを行うために Mouse から派生する必要はありません。 添付イベントの XAML 処理は関連がないため、そのシナリオにはイベントのコード処理が含まれます。

添付イベントを処理する

添付イベントのコーディングと処理のプロセスは、基本的に非添付ルーティング イベントの場合と同じです。

前述のように、既存の WPF 添付イベントは、通常、WPF で直接処理することを意図されていません。 多くの場合、添付イベントの目的は、複合コントロール内の要素がその状態をコントロール内の親要素に報告できるようにすることです。 そのシナリオでは、イベントはコードで発生し、関連する親クラスでのクラス処理に依存します。 たとえば、Selector 内の項目は Selected 添付イベントを発生させることが予想されます。その場合それは、Selector クラスによって処理されるクラスです。 Selector クラスは、Selected イベントを SelectionChanged ルーティング イベントに変換する可能性があります。 ルーティング イベントとクラス処理について詳しくは、「ルーティング イベントの処理済みとしてのマーキング、およびクラス処理」をご覧ください。

カスタム添付イベントを定義する

WPF の一般的な基底クラスから派生している場合は、クラスに 2 つのアクセサー メソッドを含めることで、カスタム添付イベントを実装できます。 それらのメソッドは次のとおりです。

  • Add<イベント名>Handler メソッド。1 つ目のパラメーターはイベント ハンドラーが添付される要素で、2 つ目のパラメーターは追加するイベント ハンドラーです。 メソッドには publicstatic を指定する必要があります。戻り値はありません。 このメソッドは AddHandler 基底クラス メソッドを呼び出して、ルーティング イベントとハンドラーを引数として渡します。 このメソッドは、イベント ハンドラーを要素に添付するための XAML 属性構文をサポートしています。 また、このメソッドを使うと、添付イベントのイベント ハンドラー ストアにコードからアクセスできます。

  • Remove<イベント名>Handler メソッド。1 つ目のパラメーターはイベント ハンドラーが添付される要素で、2 つ目のパラメーターは削除するイベント ハンドラーです。 メソッドには publicstatic を指定する必要があります。戻り値はありません。 このメソッドは RemoveHandler 基底クラス メソッドを呼び出して、ルーティング イベントとハンドラーを引数として渡します。 このメソッドを使うと、添付イベントのイベント ハンドラー ストアにコードからアクセスできます。

RoutedEvent の識別子は WPF のイベント システムによって定義されているため、WPF では添付イベントがルーティング イベントとして実装されます。 また、イベントのルーティングは、添付イベントの XAML 言語レベルの概念の自然な拡張です。 この実装戦略では、添付イベントの処理が UIElement 派生クラスまたは ContentElement 派生クラスに制限されます。これは、それらのクラスだけに AddHandler の実装があるためです。

たとえば、次のコードでは、要素クラスではない AquariumFilter 所有者クラスで、Clean 添付イベントが定義されています。 このコードでは、添付イベントがルーティング イベントとして定義され、必要なアクセサー メソッドが実装されています。

public class AquariumFilter
{
    // Register a custom routed event using the bubble routing strategy.
    public static readonly RoutedEvent CleanEvent = EventManager.RegisterRoutedEvent(
        "Clean", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(AquariumFilter));

    // Provide an add handler accessor method for the Clean event.
    public static void AddCleanHandler(DependencyObject dependencyObject, RoutedEventHandler handler)
    {
        if (dependencyObject is not UIElement uiElement)
            return;

        uiElement.AddHandler(CleanEvent, handler);
    }

    // Provide a remove handler accessor method for the Clean event.
    public static void RemoveCleanHandler(DependencyObject dependencyObject, RoutedEventHandler handler)
    {
        if (dependencyObject is not UIElement uiElement)
            return;

        uiElement.RemoveHandler(CleanEvent, handler);
    }
}
Public Class AquariumFilter

    ' Register a custom routed event using the bubble routing strategy.
    Public Shared ReadOnly CleanEvent As RoutedEvent = EventManager.RegisterRoutedEvent(
        "Clean", RoutingStrategy.Bubble, GetType(RoutedEventHandler), GetType(AquariumFilter))

    ' Provide an add handler accessor method for the Clean event.
    Public Shared Sub AddCleanHandler(dependencyObject As DependencyObject, handler As RoutedEventHandler)
        Dim uiElement As UIElement = TryCast(dependencyObject, UIElement)

        If uiElement IsNot Nothing Then
            uiElement.[AddHandler](CleanEvent, handler)
        End If
    End Sub

    ' Provide a remove handler accessor method for the Clean event.
    Public Shared Sub RemoveCleanHandler(dependencyObject As DependencyObject, handler As RoutedEventHandler)
        Dim uiElement As UIElement = TryCast(dependencyObject, UIElement)

        If uiElement IsNot Nothing Then
            uiElement.[RemoveHandler](CleanEvent, handler)
        End If
    End Sub

End Class

添付イベントの識別子を返す RegisterRoutedEvent メソッドは、非添付ルーティング イベントの登録に使われるのと同じメソッドです。 添付と非添付どちらのルーティング イベントも、一元化された内部ストアに登録されます。 このイベント ストアの実装により、"インターフェイスとしてのイベント" という概念が可能になります。これについては、「ルーティング イベントの概要」をご覧ください。

非添付ルーティング イベントをサポートするために使われる CLR イベント "ラッパー" とは異なり、添付イベント アクセサー メソッドは、UIElement または ContentElement から派生していないクラスで実装できます。 このようなことができるのは、添付イベントのバッキング コードが、渡された UIElement インスタンスで UIElement.AddHandlerUIElement.RemoveHandler メソッドを呼び出すためです。 これに対し、非添付ルーティング イベントの CLR ラッパーは、それらのメソッドを所有クラスで直接呼び出すので、そのクラスは UIElement から派生する必要があります。

WPF 添付イベントを発生させる

添付イベントを発生させるプロセスは、実質的に、非添付ルーティング イベントの場合と同じです。

通常、それらのイベントは一般的な "サービス" 概念モデルに従っているため、WPF で定義された既存の添付イベントをコードで発生させる必要はありません。 そのモデルでは、InputManager などのサービス クラスが、WPF で定義された添付イベントを発生させます。

ルーティング イベントの基になる添付イベントの WPF モデルを使ってカスタム添付イベントを定義するときは、UIElement.RaiseEvent メソッドを使って、任意の UIElement または ContentElement で添付イベントを発生させます。 ルーティング イベントを発生させるときは、添付であってもなくても、要素ツリー内の要素をイベント ソースとして指定する必要があります。 その後、そのソースが RaiseEvent の呼び出し元として報告されます。 たとえば、aquarium1AquariumFilter.Clean 添付ルーティング イベントを発生させるには、次のようにします。

aquarium1.RaiseEvent(new RoutedEventArgs(AquariumFilter.CleanEvent));
aquarium1.[RaiseEvent](New RoutedEventArgs(AquariumFilter.CleanEvent))

前の例では、aquarium1 がイベント ソースです。

関連項目