ルーティング イベントの概要 (WPF .NET)
Windows Presentation Foundation (WPF) のアプリケーション開発者とコンポーネント作成者は、ルーティング イベントを使って、要素ツリーを介してイベントを伝達し、ツリー内の複数のリスナーでイベント ハンドラーを呼び出すことができます。 このような機能は、共通言語ランタイム (CLR) のイベントにはありません。 いくつかの WPF イベントがルーティング イベントです (ButtonBase.Click など)。 この記事では、ルーティング イベントの基本的な概念について説明し、ルーティング イベントにいつ、どのように応答するかについてのガイダンスを提供します。
前提条件
この記事では、共通言語ランタイム (CLR)、オブジェクト指向プログラミング、WPF 要素レイアウトをツリーとして概念化する方法についての基本的な知識を前提としています。 この記事に含まれる例を理解するには、Extensible Application Markup Language (XAML) に慣れていて、WPF アプリケーションの記述方法を理解していると役に立ちます。
ルーティング イベントとは
ルーティング イベントについては、機能または実装の観点から考えることができます。
機能の観点から見た場合、ルーティング イベントは、イベント ソースだけでなく、要素ツリー内の複数のリスナー上でハンドラーを呼び出すことができるイベントの種類です。 イベント リスナーとは、イベント ハンドラーが添付されて呼び出される要素です。 イベント ソースとは、最初にイベントを発生させた要素またはオブジェクトです。
実装の観点から見た場合、ルーティング イベントは、WPF イベント システムに登録され、RoutedEvent クラスのインスタンスによってサポートされ、WPF イベント システムによって処理されるイベントです。 通常、ルーティング イベントは CLR イベント "ラッパー" を使って実装され、CLR イベントと同様に XAML とコードビハインドでハンドラーを添付できます。
通常、WPF アプリケーションには多くの要素が含まれ、XAML で宣言されるか、コードでインスタンス化されていました。 アプリケーションの要素は、その要素ツリー内に存在します。 ルーティング イベントは、その定義方法に応じて、ソース要素でイベントが発生すると:
- 要素ツリーを介して、ソース要素からルート要素 (通常はページまたはウィンドウ) へと上にバブリングします。
- 要素ツリーを介して、ルート要素からソース要素へと下にトンネリングします。
- 要素ツリーを通過せず、ソース要素でのみ発生します。
次の要素ツリーの一部について考えてみます。
<Border Height="30" Width="200" BorderBrush="Gray" BorderThickness="1">
<StackPanel Background="LightBlue" Orientation="Horizontal" Button.Click="YesNoCancelButton_Click">
<Button Name="YesButton">Yes</Button>
<Button Name="NoButton">No</Button>
<Button Name="CancelButton">Cancel</Button>
</StackPanel>
</Border>
この要素ツリーは、次のようにレンダリングされます。
3 つのボタンはそれぞれ、Click のイベント ソースにすることができます。 いずれかのボタンがクリックされると、Click
イベントが発生し、ボタンからルート要素へと上にバブリングします。 Button 要素と Border 要素にはイベント ハンドラーが添付されていませんが、StackPanel には添付されています。 ツリーの上位にある表示されない他の要素にも、Click
イベント ハンドラーが添付されている可能性があります。 Click
イベントが StackPanel
要素に到達すると、それに添付されている YesNoCancelButton_Click
ハンドラーが WPF イベント システムによって呼び出されます。 この例で Click
イベントのイベント ルートは、Button
->StackPanel
->Border
-> 後続の親要素、となります。
Note
最初にルーティング イベントを発生させた要素は、イベント ハンドラー パラメーターでは RoutedEventArgs.Source として識別されます。 イベント リスナーは、イベント ハンドラーが添付されて呼び出される要素であり、イベント ハンドラー パラメーターでは sender として識別されます。
ルーティング イベントの最上位シナリオ
ルーティング イベントという概念の原動力となったシナリオをいくつか次に示し、一般的な CLR イベントと区別します。
コントロールの構成とカプセル化: WPF のさまざまなコントロールには、多機能のコンテンツ モデルが採用されています。 たとえば、Button の内部に画像を配置すると、ボタンのビジュアル ツリーを効果的に拡張できます。 ただし、追加された画像によってボタンのヒット テスト動作が中断されないようにする必要があります。ボタンは、ユーザーが画像ピクセルをクリックしたときに応答する必要があります。
単一ハンドラーの添付ポイント: 各ボタンの
Click
イベントに対してハンドラーを登録できましたが、ルーティング イベントを使うと、前の XAML の例に示すように単一ハンドラーを添付できます。 これにより、各ボタンのClick
イベントを登録しなくても、単一ハンドラーの下で要素ツリーを変更できます (さらなるボタンの追加や削除など)。Click
イベントが発生すると、ハンドラー ロジックによってイベントの発生元を特定できます。 次のハンドラー (前に示した XAML 要素ツリーで指定されています) には、そのロジックが含まれています。private void YesNoCancelButton_Click(object sender, RoutedEventArgs e) { FrameworkElement sourceFrameworkElement = e.Source as FrameworkElement; switch (sourceFrameworkElement.Name) { case "YesButton": // YesButton logic. break; case "NoButton": // NoButton logic. break; case "CancelButton": // CancelButton logic. break; } e.Handled = true; }
Private Sub YesNoCancelButton_Click(sender As Object, e As RoutedEventArgs) Dim frameworkElementSource As FrameworkElement = TryCast(e.Source, FrameworkElement) Select Case frameworkElementSource.Name Case "YesButton" ' YesButton logic. Case "NoButton" ' NoButton logic. Case "CancelButton" ' CancelButton logic. End Select e.Handled = True End Sub
クラス処理: ルーティング イベントでは、クラスで定義するクラス イベント ハンドラーがサポートされています。 クラス ハンドラーは、クラスのインスタンスで同じイベントのどのインスタンス ハンドラーよりも前にイベントを処理します。
リフレクションを使用しないイベントの参照: それぞれのルーティング イベントで RoutedEvent フィールド識別子が作成され、イベントを識別するために静的なまたは実行時のリフレクションを必要としない、堅牢なイベント識別手法が提供されます。
ルーティング イベントの実装方法
ルーティング イベントは、WPF イベント システムに登録され、RoutedEvent クラスのインスタンスによってサポートされ、WPF イベント システムによって処理されるイベントです。 登録から取得された RoutedEvent
インスタンスは、通常、それを登録したクラスの public static readonly
メンバーとして保持されます。 そのクラスは、イベントの "所有者" クラスと呼ばれます。 通常、ルーティング イベントは、同じ名前の CLR イベント "ラッパー" を実装します。 CLR イベント ラッパーには add
および remove
アクセサーが含まれており、言語固有のイベント構文を使って XAML とコードビハインドでハンドラーを添付できます。 add
および remove
アクセサーは CLR 実装をオーバーライドし、ルーティング イベントの AddHandler および RemoveHandler メソッドを呼び出します。 ルーティング イベントのバッキングおよび接続メカニズムは、依存関係プロパティが、DependencyProperty クラスによってバッキングされ WPF プロパティ システムに登録される CLR プロパティであるという概念に似ています。
次の例では、Tap
ルーティング イベントを登録し、返された RoutedEvent
インスタンスを格納し、CLR イベント ラッパーを実装します。
// Register a custom routed event using the Bubble routing strategy.
public static readonly RoutedEvent TapEvent = EventManager.RegisterRoutedEvent(
name: "Tap",
routingStrategy: RoutingStrategy.Bubble,
handlerType: typeof(RoutedEventHandler),
ownerType: typeof(CustomButton));
// Provide CLR accessors for adding and removing an event handler.
public event RoutedEventHandler Tap
{
add { AddHandler(TapEvent, value); }
remove { RemoveHandler(TapEvent, value); }
}
' Register a custom routed event using the Bubble routing strategy.
Public Shared ReadOnly TapEvent As RoutedEvent = EventManager.RegisterRoutedEvent(
name:="Tap",
routingStrategy:=RoutingStrategy.Bubble,
handlerType:=GetType(RoutedEventHandler),
ownerType:=GetType(CustomButton))
' Provide CLR accessors for adding and removing an event handler.
Public Custom Event Tap As RoutedEventHandler
AddHandler(value As RoutedEventHandler)
[AddHandler](TapEvent, value)
End AddHandler
RemoveHandler(value As RoutedEventHandler)
[RemoveHandler](TapEvent, value)
End RemoveHandler
RaiseEvent(sender As Object, e As RoutedEventArgs)
[RaiseEvent](e)
End RaiseEvent
End Event
ルーティング方法
ルーティング イベントは、3 つのルーティング方法のいずれかを使用します。
バブリング: 最初に、イベント ソースのイベント ハンドラーが呼び出されます。 その後、ルーティング イベントは後続の親要素にルーティングされ、要素ツリーのルートに到達するまでそれらのイベント ハンドラーが順番に呼び出されます。 ほとんどのルーティング イベントでは、このバブル ルーティング方法を使用します。 一般に、ルーティング イベントのバブリングは、複合コントロールやその他の UI 要素からの入力や状態変化を報告するために使用されます。
トンネリング: 最初に、要素ツリー ルートのイベント ハンドラーが呼び出されます。 その後、ルーティング イベントは後続の子要素にルーティングされ、イベント ソースに到達するまでそれらのイベント ハンドラーが順番に呼び出されます。 トンネリング ルートに続くイベントは、プレビュー イベントとも呼ばれます。 一般に、WPF 入力イベントは、プレビューとバブリングのペアとして実装されます。
直接: イベント ソースのイベント ハンドラーのみが呼び出されます。 この非ルーティング戦略は、標準の CLR イベントである、Windows フォームの UI フレームワーク イベントに似ています。 CLR イベントとは異なり、直接ルーティング イベントではクラス処理がサポートされ、EventSetters と EventTriggers で使用できます。
ルーティング イベントを使用する理由
アプリケーション開発者は、処理するイベントがルーティング イベントとして実装されているかどうかを常に確認する必要はありません。 ルーティング イベントの動作は独特ですが、それを発生させた要素のイベントを処理している限り、その動作の大部分は表面には見えません。 ただし、子要素によって発生したイベントを処理するために、イベント ハンドラーを親要素に添付する必要がある場合は、ルーティング イベントが重要になります (複合コントロール内など)。
ルーティング イベントのリスナーは、処理するルーティング イベントがそのクラスのメンバーであることを必要としません。 UIElement または ContentElement は、どのルーティング イベントのイベント リスナーであってもかまいません。 ビジュアル要素は UIElement
または ContentElement
から派生しているため、アプリケーション内の異なる要素間でのイベント情報の交換をサポートする概念的な "インターフェイス" として、ルーティング イベントを使用できます。 ルーティング イベントのこの "インターフェイス" 概念は、特に入力イベントに当てはまります。
ルーティング イベントは、イベント ルートに沿った要素間でのイベント情報の交換をサポートします。各リスナーがイベント データの同じインスタンスにアクセスできるためです。 1 つの要素がイベント データ内の何かを変更した場合、その変更はイベント ルート内の後続の要素からも見えます。
ルーティングの側面とは別に、次の理由から、標準の CLR イベントではなくルーティング イベントを実装することを選択する場合があります。
EventSetters や EventTriggers など、WPF のスタイル指定およびテンプレート機能の一部では、参照先のイベントがルーティング イベントである必要があります。
ルーティング イベントではクラス イベント ハンドラーがサポートされます。これは、リスナー クラスのインスタンスで同じイベントのどのインスタンス ハンドラーよりも前にイベントを処理します。 この機能はコントロールをデザインするときに便利です。クラス ハンドラーにより、インスタンス ハンドラーによって誤って抑止されることのない、イベント駆動型のクラス動作を適用できるためです。
ルーティング イベント ハンドラーを添付および実装する
XAML では、イベント リスナー要素の属性としてイベント名を宣言することで、イベント ハンドラーを要素に添付します。 属性値はハンドラー メソッドの名前です。 ハンドラー メソッドは、XAML ページのコードビハインド部分クラスで実装する必要があります。 イベント リスナーは、イベント ハンドラーが添付されて呼び出される要素です。
リスナー クラスのメンバー (継承またはそれ以外) であるイベントの場合は、次のようにハンドラーを添付できます。
<Button Name="Button1" Click="Button_Click">Click me</Button>
イベントがリスナーのクラスのメンバーでない場合は、<owner type>.<event name>
という形式でイベントの修飾名を使う必要があります。 たとえば、StackPanel クラスは Click イベントを実装していないため、その要素まで上にバブリングする Click
イベントのために StackPanel
にハンドラーを添付するには、修飾されたイベント名構文を使う必要があります。
<StackPanel Name="StackPanel1" Button.Click="Button_Click">
<Button>Click me</Button>
</StackPanel>
コードビハインド内のイベント ハンドラー メソッドのシグネチャは、ルーティング イベントのデリゲート型と一致する必要があります。 Click イベントの RoutedEventHandler デリゲートの sender
パラメーターは、イベント ハンドラーが添付される要素を指定します。 RoutedEventHandler
デリゲートの args
パラメーターには、イベント データが含まれます。 Button_Click
イベント ハンドラーの互換性のあるコードビハインド実装は、次のようになります。
private void Button_Click(object sender, RoutedEventArgs e)
{
// Click event logic.
}
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
' Click event logic.
End Sub
RoutedEventHandler は基本的なルーティング イベント ハンドラー デリゲートですが、一部のコントロールまたは実装シナリオでは、より特殊なイベント データをサポートする異なるデリゲートが必要です。 たとえば、DragEnter ルーティング イベントの場合、ハンドラーでは DragEventHandler デリゲートを実装する必要があります。 これにより、ハンドラー コードでは、ドラッグ操作のクリップボード ペイロードが含まれている、イベント データの DragEventArgs.Data プロパティにアクセスできます。
ルーティング イベント ハンドラーを追加するための XAML 構文は、標準の CLR イベント ハンドラーの場合と同じです。 イベント ハンドラーを XAML で追加する方法の詳細については、「WPF の XAML」を参照してください。 XAML を使ってイベント ハンドラーを要素に添付する方法の完全な例については、「方法 : ルーティング イベントを処理する」を参照してください。
コードを使ってルーティング イベントのイベント ハンドラーを要素に添付するには、通常、次の 2 つのオプションがあります。
AddHandler メソッドを直接呼び出します。 ルーティング イベント ハンドラーは、常にこのように添付できます。 次の例では、
AddHandler
メソッドを使ってボタンにClick
イベント ハンドラーを添付します。Button1.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(Button_Click));
Button1.[AddHandler](ButtonBase.ClickEvent, New RoutedEventHandler(AddressOf Button_Click))
ボタンの
Click
イベントのハンドラーを、イベントのルート内の別の要素 (StackPanel1
という名前の StackPanel など) に添付するには:StackPanel1.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(Button_Click));
StackPanel1.[AddHandler](ButtonBase.ClickEvent, New RoutedEventHandler(AddressOf Button_Click))
ルーティング イベントが CLR イベント ラッパーを実装する場合は、言語固有のイベント構文を使って、標準 CLR イベントの場合と同様にイベント ハンドラーを追加します。 既存の WPF ルーティング イベントのほとんどは CLR ラッパーを実装するため、言語固有のイベント構文が有効になります。 次の例では、言語固有の構文を使って、ボタンに
Click
イベント ハンドラーを添付します。Button1.Click += Button_Click;
AddHandler Button1.Click, AddressOf Button_Click
コードでイベント ハンドラーを添付する方法の例については、「コードを使用してイベント ハンドラーを追加する方法」を参照してください。 Visual Basic でコーディングする場合は、Handles
キーワードを使用して、ハンドラー宣言の一部としてハンドラーを追加することもできます。 詳細については、「Visual Basic と WPF のイベント処理」を参照してください。
処理済みの概念
すべてのルーティング イベントは、イベント データの共通の基底クラス (RoutedEventArgs クラス) を共有します。 RoutedEventArgs
クラスはブール値 Handled プロパティを定義します。 Handled
プロパティの目的は、イベント ルートに沿ったすべてのイベント ハンドラーで、ルーティング イベントを "処理済み" としてマークできるようにすることです。 イベントを処理済みとしてマークするには、イベント ハンドラー コードで Handled
の値を true
に設定します。
Handled
の値は、ルーティング イベントがイベント ルートをたどる過程でどのように処理されるかに影響します。 ルーティング イベントの共有イベント データ内で Handled
が true
である場合、イベント ルートに沿って他の要素に添付されたハンドラーは、通常、その特定のイベント インスタンスに対して呼び出されません。 ほとんどの一般的なハンドラー シナリオでは、イベントを処理済みとしてマークすると、イベント ルートに沿った後続のハンドラー (インスタンス ハンドラーかクラス ハンドラーかに関わらず) がその特定のイベント インスタンスに応答するのを実質的に停止できます。 ただし、処理済みとしてマークされているルーティング イベントにイベント ハンドラーで応答する必要があるまれなケースでは、次のことができます。
AddHandler(RoutedEvent, Delegate, Boolean) オーバーロードを使い、
handledEventsToo
パラメーターをtrue
に設定して、コードビハインドでハンドラーを添付します。EventSetter
の HandledEventsToo 属性をtrue
に設定します。
Handled
の概念は、アプリケーションの設計方法とイベント ハンドラーのコーディング方法に影響を与える場合があります。 Handled
は、ルーティング イベントを処理するためのシンプルなプロトコルとして概念化できます。 このプロトコルをどのように使うかはユーザーの自由ですが、Handled
パラメーターの想定される使い方は次のようになります。
ルーティング イベントが処理済みとしてマークされている場合、ルート上の他の要素でもう一度処理する必要はありません。
ルーティング イベントが処理済みとしてマークされない場合は、イベント ルートの早期のリスナーがそのイベントのハンドラーを持たないか、登録されたどのハンドラーも、そのイベントを処理済みとしてマークすることが正当化されるような方法でそのイベントに応答しませんでした。 現在のリスナーのハンドラーでは、次の 3 つのアクションを選択できます。
何もアクションを実行しません。 イベントは未処理のままで、ツリー内の次のリスナーにルーティングされます。
イベントに応答してコードを実行しますが、そのイベントを処理済みとしてマークすることが正当化される程の処理ではありません。 イベントは未処理のままで、ツリー内の次のリスナーにルーティングされます。
イベントに応答してコードを実行し、そのイベントを処理済みとしてマークすることが正当化されます。 イベント データでイベントを処理済みとしてマークします。 イベントはツリー内の次のリスナーに引き続きルーティングされますが、ほとんどのリスナーはそれ以上のハンドラーを呼び出しません。 例外は、
handledEventsToo
をtrue
に設定して特別に登録されたハンドラーを持つリスナーです。
ルーティング イベントの処理について詳しくは、「ルーティング イベントの処理済みとしてのマーキング、およびクラス処理」をご覧ください。
バブリング ルーティング イベントを、それを発生させたオブジェクトでしか処理しない開発者にとっては、他のリスナーは気にならないかもしれませんが、それでもイベントを処理済みとしてマークすることをお勧めします。 そうすることにより、イベント ルートをたどったある要素に同じルーティング イベントのハンドラーがあった場合に、予期しない副作用を回避できます。
クラス ハンドラー
ルーティング イベント ハンドラーは、"インスタンス" ハンドラーまたは "クラス" ハンドラーのいずれかにすることができます。 特定のクラスのクラス ハンドラーは、そのクラスのインスタンスで同じイベントに応答するインスタンス ハンドラーの前に呼び出されます。 この動作により、ルーティング イベントが処理済みとしてマークされるときは、多くの場合、クラス ハンドラー内でそのようにマークされます。 クラス ハンドラーには次の 2 つの種類があります。
- 静的クラスのイベント ハンドラー: 静的クラス コンストラクター内で RegisterClassHandler メソッドを呼び出すことによって登録されます。
- オーバーライド クラス イベント ハンドラー: 基底クラスの仮想イベント メソッドをオーバーライドすることによって登録されます。 基底クラスの仮想イベント メソッドは、主に入力イベント用に存在し、名前が On<イベント名> および OnPreview<イベント名> で始まります。
一部の WPF コントロールには、特定のルーティング イベントに対する固有のクラス処理が存在します。 クラス処理により、一見するとルーティング イベントが発生しなかったように見えるかもしれませんが、実際にはクラス ハンドラーによって処理済みとしてマークされています。 イベント ハンドラーで処理済みのイベントに応答する必要がある場合は、handledEventsToo
を true
に設定してハンドラーを登録できます。 独自のクラス ハンドラーの実装と、望ましくないクラス処理の回避の両方について詳しくは、「ルーティング イベントの処理済みとしてのマーキング、およびクラス処理」を参照してください。
WPF の添付イベント
XAML 言語では、"添付イベント" と呼ばれる特殊な種類のイベントも定義します。 添付イベントを使うと、非要素クラスで新しいルーティング イベントを定義し、ツリー内の任意の要素でそのイベントを発生させることができます。 これを行うには、添付イベントをルーティング イベントとして登録し、添付イベントの機能をサポートする特定のバッキング コードを提供する必要があります。 添付イベントはルーティング イベントとして登録されるため、要素で発生すると、要素ツリーを介して伝達されます。
XAML の構文では、添付イベントは、そのイベント名と所有者の型の "両方" を使って、<owner type>.<event name>
という形式で指定されます。 イベント名はその所有者の型の名前で修飾されるため、構文では、インスタンス化できる任意の要素にイベントを添付できます。 この構文は、イベント ルートに沿って任意の要素に添付される通常のルーティング イベントのハンドラーにも適用できます。 また、コードビハインドで、ハンドラーを添付する必要があるオブジェクトの AddHandler メソッドを呼び出すことで、添付イベントのハンドラーを添付することもできます。
WPF 入力システムでは、添付イベントを多用します。 ただし、それらの添付イベントのほとんどすべては、基本要素を介して同等の非添付ルーティング イベントとして公開されます。 添付イベントを直接使用または処理することはほとんどありません。 たとえば、XAML またはコードビハインドで添付イベント構文を使うより、同等の UIElement.MouseDown ルーティング イベントを介して、UIElement で基になる添付 Mouse.MouseDown イベントを処理する方が簡単です。
WPF の添付イベントの詳細については、「添付イベントの概要」を参照してください。
XAML の修飾イベント名
<owner type>.<event name>
構文は、イベント名をその所有者の型の名前で修飾します。 この構文を使うと、そのイベントをクラスのメンバーとして実装する要素だけでなく、任意の要素にイベントを添付できます。 この構文は、XAML で添付イベントに対して、またはイベント ルートに沿った任意の要素でルーティング イベントに対してハンドラーを添付する場合に適用できます。 子要素で発生したルーティング イベントを処理するために、親要素にハンドラーを添付するシナリオを考えてみましょう。 親要素がメンバーとしてそのルーティング イベントを持っていない場合は、修飾イベント名の構文を使う必要があります。 次に例を示します。
<StackPanel Name="StackPanel1" Button.Click="Button_Click">
<Button>Click me</Button>
</StackPanel>
この例では、イベント ハンドラーが追加される親要素のリスナーは StackPanel です。 ただし、Click ルーティング イベントは ButtonBase クラスで実装および発生され、継承を通じて Button クラスで使用できます。 Button クラスは Click
イベントを "所有" しますが、ルーティング イベント システムでは、任意のルーティング イベントのハンドラーを、それ以外の場合であれば CLR イベントのリスナーを設定できる任意の UIElement または ContentElement インスタンス リスナーに添付することが許可されています。 これらの修飾イベント属性名の既定の xmlns
名前空間は、通常、既定の WPF xmlns
名前空間ですが、カスタム ルーティング イベント用のプレフィックスを持つ名前空間を指定することもできます。 xmlns
について詳しくは、「XAML 名前空間および WPF XAML の名前空間の割り当て」を参照してください。
WPF の入力イベント
WPF プラットフォームにおけるルーティング イベントのよくある使用例として、入力イベントがあります。 慣例により、トンネリング ルートをたどる WPF ルーティング イベントの名前は、先頭に "Preview" が付きます。 Preview というプレフィックスは、ペアのバブリング イベントが開始される前にプレビュー イベントが完了することを示します。 多くの場合、入力イベントはペアになります。1 つはプレビュー イベント、もう 1 つはバブリング ルーティング イベントです。 たとえば、PreviewKeyDown とKeyDown です。 このイベント ペアは、イベント データの同じインスタンスを共有し、PreviewKeyDown
と KeyDown
は KeyEventArgs 型になります。 入力イベントによっては、バブリング バージョンだけ、または直接ルーティング バージョンだけを持つ場合もあります。 API ドキュメントでは、ルーティング イベントのトピックはルーティング イベントのペアを相互参照し、ルーティング イベントごとのルーティング戦略を明確にしています。
ペアになった WPF 入力イベントを実装する場合、マウス ボタンを押すなど、入力デバイスからの 1 回のユーザー操作で、プレビューとバブリングのルーティング イベントが順番に発生するようにします。 まず、プレビュー イベントが発生し、そのルートが完了します。 プレビュー イベントが完了すると、バブリング イベントが発生し、そのルートが完了します。 バブリング イベントを発生させる実装クラスの RaiseEvent メソッド呼び出しでは、プレビュー イベントのイベント データをバブリング イベントに再利用します。
処理済みとしてマークされたプレビュー入力イベントでは、プレビュー ルートの残りの部分に対して通常登録されているイベント ハンドラーは呼び出されず、ペアになったバブリング イベントは発生しません。 この処理動作は、ヒット テスト ベースの入力イベントまたはフォーカス ベースの入力イベントをコントロールの最上位レベルで報告したい、複合コントロールのデザイナーにとって便利です。 コントロールの最上位の要素には、コントロールのサブコンポーネントからのプレビュー イベントをクラス処理して、最上位のコントロール固有のイベントに "置き換える" 機会があります。
入力イベント処理のしくみを説明するため、次の入力イベント例について考えます。 次のツリー図では、leaf element #2
がペアになった PreviewMouseDown
および MouseDown
イベント両方の発生源です。
リーフ要素 #2 でのマウス ボタンを押す操作に続くイベント処理の順序は、次のとおりです。
- ルート要素での
PreviewMouseDown
トンネリング イベント。 - 中間要素 #1 での
PreviewMouseDown
トンネリング イベント。 - リーフ要素 #2 での
PreviewMouseDown
トンネリング イベント。これはソース要素です。 - リーフ要素 #2 での
MouseDown
バブリング イベント。これはソース要素です。 - 中間要素 #1 での
MouseDown
バブリング イベント。 - ルート要素での
MouseDown
バブリング イベント。
ルーティング イベント ハンドラー デリゲートは、イベントを発生させたオブジェクトと、ハンドラーが呼び出されたオブジェクトの両方への参照を提供します。 イベントを最初に発生させたオブジェクトは、イベント データの Source プロパティによって報告されます。 ハンドラーが呼び出されたオブジェクトは、sender パラメーターによって報告されます。 任意のルーティング イベント インスタンスで、イベントを発生させたオブジェクトはイベントが要素ツリーを通過しても変更されませんが、sender
は変更されます。 前の図の手順 3 と 4 では、Source
と sender
は同じオブジェクトです。
入力イベント ハンドラーが、イベントに対処するために必要なアプリケーション固有のロジックを完了した場合は、入力イベントを処理済みとしてマークする必要があります。 通常、入力イベントが Handled とマークされると、イベント ルートをさらにたどったハンドラーは呼び出されません。 ただし、handledEventsToo
パラメーターを true
に設定して登録されている入力イベント ハンドラーは、イベントが処理済みとしてマークされている場合でも呼び出されます。 詳しくは、「プレビュー イベント」と「ルーティング イベントの処理済みとしてのマーキング、およびクラス処理」を参照してください。
プレビュー イベントとバブリング イベントのペアの概念 (共有イベント データと後続のプレビュー イベント、バブリング イベントの発生) は、一部の WPF 入力イベントにのみ適用され、すべてのルーティング イベントには適用されません。 高度なシナリオに対処するために独自の入力イベントを実装する場合は、WPF 入力イベントのペアのアプローチに従うことを検討してください。
入力イベントに応答する独自の複合コントロールを実装する場合は、プレビュー イベントを使って、サブコンポーネントで発生した入力イベントを抑制し、完全なコントロールを表す最上位のイベントに置き換えることを検討してください。 詳しくは、「ルーティング イベントの処理済みとしてのマーキング、およびクラス処理」を参照してください。
WPF 入力システムと、一般的なアプリケーション シナリオにおける入力とイベントの対話方法について詳しくは、「入力の概要」を参照してください。
EventSetter と EventTrigger
マークアップ スタイルで、EventSetter を使ってあらかじめ宣言した XAML イベント処理構文を含めることができます。 XAML が処理されると、参照されているハンドラーが、スタイルが適用されるインスタンスに追加されます。 EventSetter
は、ルーティング イベントに対してのみ宣言できます。 次の例では、参照されている ApplyButtonStyle
イベント ハンドラー メソッドはコードビハインドで実装されています。
<StackPanel>
<StackPanel.Resources>
<Style TargetType="{x:Type Button}">
<EventSetter Event="Click" Handler="ApplyButtonStyle"/>
</Style>
</StackPanel.Resources>
<Button>Click me</Button>
<Button Click="Button_Click">Click me</Button>
</StackPanel>
Style
ノードには、指定した型のコントロールに関連する他のスタイル情報が既に含まれている可能性があります。また、EventSetter をこれらのスタイルの一部にすると、マークアップ レベルでもコードの再利用が促進されます。 また、EventSetter
によって、一般的なアプリケーションやページのマークアップよりも進んだハンドラーのメソッド名の抽象化が実現されます。
ルーティング イベントと WPF アニメーション機能を結合したもう 1 つの特別な構文が、EventTrigger です。 EventSetter
と同様に、EventTrigger
はルーティング イベントに対してのみ宣言できます。 通常、EventTrigger
はスタイルの一部として宣言しますが、EventTrigger
をページ レベルの要素で Triggers コレクションの一部として (つまり ControlTemplate 内で) 宣言することもできます。 EventTrigger
を使用すると、ルーティング イベントに対する EventTrigger
を宣言した要素にそのイベントが経路で到達するたびに Storyboard が実行されるよう指定できます。 単にイベントを処理して既存のストーリーボードが開始されるようにする方法と比べて EventTrigger
が優れている点は、EventTrigger
の方がストーリーボードとそのランタイム動作をよりよく制御できることです。 詳しくは、「開始後のストーリーボードをイベント トリガーを使用して制御する」を参照してください。
ルーティング イベントの詳細
この記事に含まれる概念とガイダンスは、独自のクラスでカスタム ルーティング イベントを作成する際の出発点として使うことができます。 また、特殊なイベント データ クラスとデリゲートを使ってカスタム イベントをサポートすることもできます。 ルーティング イベントの所有者はどのクラスでもかまいませんが、利便性のため、ルーティング イベントの発生と処理は UIElement または ContentElement の派生クラスで行われる必要があります。 カスタム イベントについて詳しくは、カスタム ルーティング イベントの作成に関する記事を参照してください。
参照
.NET Desktop feedback