ルーティング イベントの概要

このトピックでは、Windows Presentation Foundation (WPF) のルーティング イベントの概念について説明します。 ここでは、ルーティング イベントの用語を定義し、要素ツリーを通じたルーティング イベントのルーティング方法、ルーティング イベントの処理方法、カスタム ルーティング イベントの作成方法について説明します。

必須コンポーネント

このトピックでは、共通言語ランタイム (CLR) とオブジェクト指向プログラミングのほか、ツリー形式として概念化できる WPF 要素間のリレーションシップに関する基礎知識があることを前提にしています。 このトピックの例に従うためには、Extensible Application Markup Language (XAML) について理解し、非常に基本的な WPF アプリケーションやページを記述する方法についても理解する必要があります。 詳細については、チュートリアル: 初めての WPF デスクトップ アプリケーションに関するページと「WPF の XAML」を参照してください。

ルーティング イベントとは

ルーティング イベントについては、機能または実装の観点から考えることができます。 どちらを便利と感じるかは個人によって異なるため、ここでは両方の見方を提示します。

機能定義: ルーティング イベントは、イベントを生成したオブジェクト上だけでなく、要素ツリー内の複数のリスナー上でハンドラーを呼び出すことができる種類のイベントです。

実装定義: ルーティング イベントは、RoutedEvent クラスのインスタンスによってサポートされ、Windows Presentation Foundation (WPF) イベント システムによって処理される CLR イベントです。

一般的な WPF アプリケーションには、多数の要素が含まれます。 コードで作成したか、XAML で宣言したかにかかわらず、これらの要素は互いに要素ツリー リレーションシップにあります。 イベントのルーティングは、イベント定義に従って 2 つの方向のいずれかをたどることができますが、一般にはルーティングは発生元要素から要素ツリーに沿って "浮上" し、最終的には要素ツリーのルート (通常はページまたはウィンドウ) に到達します。 このバブル (浮上) の概念は、DHTML オブジェクト モデルで使用される概念と似ています。

たとえば、次のような単純な要素ツリーがあるとします。

<Border Height="50" Width="300" BorderBrush="Gray" BorderThickness="1">
  <StackPanel Background="LightGray" Orientation="Horizontal" Button.Click="CommonClickHandler">
    <Button Name="YesButton" Width="Auto" >Yes</Button>
    <Button Name="NoButton" Width="Auto" >No</Button>
    <Button Name="CancelButton" Width="Auto" >Cancel</Button>
  </StackPanel>
</Border>

この要素ツリーでは、次のようなものが生成されます。

Yes, No, and Cancel buttons

この簡略化された要素ツリーでは、Click イベントのソースは Button 要素の 1 つであり、いずれの Button がクリックされた場合でも、イベントを処理する機会が与えられる最初の要素になります。 ただし、Button にアタッチされたハンドラーがイベントに対して動作しない場合、イベントは要素ツリーの Button 親 (StackPanel) までバブルされます。 イベントは Border までバブルされた後、要素ツリーのページ ルート (図示されていません) までバブルされる可能性があります。

つまり、この Click イベントのイベント ルートは次のようになります。

Button-->StackPanel-->Border-->...

ルーティング イベントのトップレベルのシナリオ

ルーティング イベントの概念が生まれる契機となったシナリオと、このシナリオに通常の CLR イベントが適していない理由について簡単に説明します。

コントロールの構成とカプセル化: WPF のさまざまなコントロールには、多機能のコンテンツ モデルが採用されています。 たとえば、Button の内部に画像を配置すると、ボタンのビジュアル ツリーを効果的に拡張できます。 ただし、厳密には画像の一部であるピクセルをユーザーがクリックした場合でも、この追加した画像によって、ボタンがコンテンツの Click に応答するヒットテスト動作が中断されないようにする必要があります。

単一ハンドラーのアタッチ ポイント: Windows フォームでは、複数の要素から発生する可能性のあるイベントを処理するために、同じハンドラーを複数回アタッチする必要があります。 ルーティング イベントを使用すると、前の例に示したとおり、ハンドラーを一度だけアタッチし、必要に応じてハンドラーのロジックを使用してイベントの発生元を特定することができます。 たとえば、前に示した XAML では次のようなハンドラーを使用します。

private void CommonClickHandler(object sender, RoutedEventArgs e)
{
  FrameworkElement feSource = e.Source as FrameworkElement;
  switch (feSource.Name)
  {
    case "YesButton":
      // do something here ...
      break;
    case "NoButton":
      // do something ...
      break;
    case "CancelButton":
      // do something ...
      break;
  }
  e.Handled=true;
}
Private Sub CommonClickHandler(ByVal sender As Object, ByVal e As RoutedEventArgs)
  Dim feSource As FrameworkElement = TryCast(e.Source, FrameworkElement)
  Select Case feSource.Name
    Case "YesButton"
      ' do something here ...
    Case "NoButton"
      ' do something ...
    Case "CancelButton"
      ' do something ...
  End Select
  e.Handled=True
End Sub

クラス処理: ルーティング イベントでは、クラスに定義した静的ハンドラーを使用できます。 このクラス ハンドラーでは、アタッチされたどのインスタンス ハンドラーよりも先にイベントを処理できます。

リフレクションを使用しないイベントの参照: 一部のコードとマークアップ手法には、特定のイベントを識別する方法が必要です。 ルーティング イベントによって識別子として作成される RoutedEvent フィールドは、信頼性の高いイベント識別テクニックとして利用でき、静的または実行時のリフレクションを必要としません。

ルーティング イベントの実装方法

ルーティング イベントは、RoutedEvent クラスのインスタンスによってバッキングされ、WPF イベント システムに登録される CLR イベントです。 登録から取得された RoutedEvent インスタンスは、通常、ルーティング イベントを登録し、したがって "所有" しているクラスの publicstaticreadonly フィールド メンバーとして保持されます。 一意の名前を持つ CLR イベント ("ラッパー" イベントとも呼ばれます) への接続は、CLR イベントの add 実装と remove 実装をオーバーライドすることにより得られます。 通常、addremove は、暗黙の既定のままにされ、そのイベントのハンドラーの追加や削除に言語固有の適切なイベント構文が使用されます。 ルーティング イベントのバッキングおよび接続メカニズムは、依存関係プロパティが、DependencyProperty クラスによってバッキングされ、WPF プロパティ システムに登録される CLR プロパティであるという概念に似ています。

RoutedEvent 識別子フィールドと Tap CLR イベントの add 実装および remove 実装の登録と公開を含む、カスタムの Tap ルーティング イベントの宣言を次の例に示します。

public static readonly RoutedEvent TapEvent = EventManager.RegisterRoutedEvent(
    "Tap", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyButtonSimple));

// Provide CLR accessors for the event
public event RoutedEventHandler Tap
{
        add { AddHandler(TapEvent, value); }
        remove { RemoveHandler(TapEvent, value); }
}
Public Shared ReadOnly TapEvent As RoutedEvent = EventManager.RegisterRoutedEvent("Tap", RoutingStrategy.Bubble, GetType(RoutedEventHandler), GetType(MyButtonSimple))

' Provide CLR accessors for the event
Public Custom Event Tap As RoutedEventHandler
    AddHandler(ByVal value As RoutedEventHandler)
        Me.AddHandler(TapEvent, value)
    End AddHandler

    RemoveHandler(ByVal value As RoutedEventHandler)
        Me.RemoveHandler(TapEvent, value)
    End RemoveHandler

    RaiseEvent(ByVal sender As Object, ByVal e As RoutedEventArgs)
        Me.RaiseEvent(e)
    End RaiseEvent
End Event

ルーティング イベント ハンドラーと XAML

XAML を使用してイベントのハンドラーを追加するには、イベント リスナーである要素の属性としてイベント名を宣言します。 属性の値は、実装したハンドラー メソッドの名前です。このメソッドは、分離コード ファイルの部分クラス内に存在する必要があります。

<Button Click="b1SetColor">button</Button>

標準の CLR イベント ハンドラーを追加する XAML 構文は、ルーティング イベント ハンドラーを追加する構文と同じです。これは、実際にはハンドラーを CLR イベント ラッパーに追加しているためです。このラッパーの内部にルーティング イベント実装が存在します。 イベント ハンドラーを XAML で追加する方法の詳細については、「WPF の XAML」を参照してください。

ルーティング方法

ルーティング イベントは、3 つのルーティング方法のいずれかを使用します。

  • バブリング: イベント ソースに対してイベント ハンドラーが呼び出されます。 ルーティング イベントは、次に、要素ツリー ルートに到達するまで、連続する親要素にルーティングします。 ほとんどのルーティング イベントでは、このバブル ルーティング方法を使用します。 バブル ルーティング イベントは、一般に個別のコントロールまたはその他の UI 要素からの入力や状態変化を報告するために使用されます。

  • 直接: ソース要素自体のみに、応答としてハンドラーを呼び出す機会が与えられます。 これは、Windows フォームでイベントに使用されている "ルーティング" に似ています。 ただし、標準の CLR イベントとは異なり、直接ルーティング イベントはクラス処理をサポートし (クラス処理については次のセクションで説明します)、EventSetter および EventTrigger で使用できます。

  • トンネリング: 要素ツリー ルートのイベント ハンドラーが最初に呼び出されます。 ルーティング イベントは、次に、経路沿いにルーティング イベント ソース (ルーティング イベントを発生させた要素) のノード要素まで、連続する子要素間の経路をたどります。 多くの場合にトンネル ルーティング イベントは、コントロールの複合部分として使用または処理されます。たとえば、複合部分で発生したイベントは、完全なコントロールに固有のイベントによって意図的に抑止されるか置き換えられます。 多くの場合、WPF で提供される入力イベントは、トンネルとバブルのペアとして実装されます。 トンネル イベントは、このペアに使用される名前付け規則から、プレビュー イベントと呼ばれることもあります。

ルーティング イベントを使用する理由

アプリケーションを開発するときに、処理するイベントがルーティング イベントとして実装されているかどうかを常に確認する必要はありません。 ルーティング イベントの動作は独特ですが、イベントを発生元の要素で処理する限り、動作はあまり表面には見えません。

ルーティング イベントが効果を発揮するのは、共通ハンドラーを共通ルートに定義する、独自のコントロールを複合化する、カスタム コントロール クラスを定義するなどの、特定のシナリオの場合です。

ルーティング イベント リスナーとルーティング イベント ソースは、階層内で共通イベントを共有する必要はありません。 UIElement または ContentElement は、どのルーティング イベントのイベント リスナーであってもかまいません。 したがって、ワーキング API セットで使用可能なすべてのルーティング イベントを概念的 "インターフェイス" として使用できます。これにより、アプリケーション内の異なる要素間でイベント情報を交換できます。 ルーティング イベントのこの "インターフェイス" 概念は、特に入力イベントに当てはまります。

また、ルーティング イベントは、要素ツリー間の通信にも使用できます。これは、イベントのイベント データが経路上の各要素に永続的に保持されるためです。 ある要素でイベント データの一部を変更した場合、この変更は経路上の次の要素で使用できます。

ルーティングの面以外でも、特定の WPF イベントを標準の CLR イベントではなくルーティング イベントとして実装する理由が他に 2 つあります。 独自のイベントを実装している場合は、次の原則も考慮します。

  • EventSetterEventTrigger など、WPF の特定のスタイル指定およびテンプレート機能では、参照先のイベントがルーティング イベントである必要があります。 これは、前に説明したイベント識別子シナリオです。

  • ルーティング イベントは、クラス処理機構をサポートしています。この機構により、クラスに静的メソッドを指定して、登録されたインスタンス ハンドラーがルーティング イベントにアクセスする前にこの静的メソッドでイベントを処理することができます。 インスタンスでのイベント処理によって誤って抑止されずにイベント駆動のクラス動作を適用できるため、これはコントロールをデザインするときに便利です。

前に示した一連の考慮事項については、このトピックのセクションで個別に説明します。

ルーティング イベントのイベント ハンドラーの追加と実装

XAML でイベント ハンドラーを追加するには、次の例のように、イベント名を属性として要素に追加し、その属性値を、適切なデリゲートを実装するイベント ハンドラーの名前として設定するだけです。

<Button Click="b1SetColor">button</Button>

b1SetColor は、Click イベントを処理するコードが含まれる実装済みハンドラーの名前です。 b1SetColor は、RoutedEventHandler デリゲート、つまり Click イベント用のイベント ハンドラー デリゲートと同じシグネチャを持つ必要があります。 すべてのルーティング イベント ハンドラー デリゲートの 1 番目のパラメーターでは、イベント ハンドラーの追加先の要素を指定し、2 番目のパラメーターでは、イベントのデータを指定します。

void b1SetColor(object sender, RoutedEventArgs args)
{
  //logic to handle the Click event
}
Private Sub b1SetColor(ByVal sender As Object, ByVal args As RoutedEventArgs)
  'logic to handle the Click event
End Sub

RoutedEventHandler は、基本的なルーティング イベント ハンドラー デリゲートです。 特定のコントロールやシナリオに特化したルーティング イベントの場合、ルーティング イベント ハンドラーに使用するデリゲートも、より特化したものとなることがあるため、特別なイベント データを転送できます。 たとえば、一般的な入力のシナリオで、DragEnter ルーティング イベントを処理することがあります。 ハンドラーでは、DragEventHandler デリゲートを実装する必要があります。 最も限定的なデリゲートを使用することにより、DragEventArgs をハンドラーで処理して、ドラッグ操作のクリップボード ペイロードが含まれる Data プロパティを読み取ることができます。

XAML を使用してイベント ハンドラーを要素に追加する方法の詳細な例については、「ルーティング イベントを処理する」を参照してください。

コードで作成されたアプリケーションでルーティング イベントのハンドラーを追加するのは簡単です。 ルーティング イベント ハンドラーは、ヘルパー メソッド AddHandler を介していつでも追加することができます (これは add に対して既存のバッキングが呼び出すものと同じメソッドです)。ただし、一般的に、既存の WPF ルーティング イベントには add および remove ロジックのバッキング実装があり、ルーティング イベントのハンドラーを言語固有のイベント構文によって追加することができます。これはヘルパー メソッドよりも直感的な構文です。 ヘルパー メソッドの使用例を次に示します。

void MakeButton()
 {
     Button b2 = new Button();
     b2.AddHandler(Button.ClickEvent, new RoutedEventHandler(Onb2Click));
 }
 void Onb2Click(object sender, RoutedEventArgs e)
 {
     //logic to handle the Click event
 }
Private Sub MakeButton()
     Dim b2 As New Button()
     b2.AddHandler(Button.ClickEvent, New RoutedEventHandler(AddressOf Onb2Click))
End Sub
 Private Sub Onb2Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
     'logic to handle the Click event     
 End Sub

C# の演算子構文を次の例に示します (Visual Basic の演算子構文は逆参照を処理するため少し異なります)。

void MakeButton2()
{
  Button b2 = new Button();
  b2.Click += new RoutedEventHandler(Onb2Click2);
}
void Onb2Click2(object sender, RoutedEventArgs e)
{
  //logic to handle the Click event
}
Private Sub MakeButton2()
  Dim b2 As New Button()
  AddHandler b2.Click, AddressOf Onb2Click2
End Sub
Private Sub Onb2Click2(ByVal sender As Object, ByVal e As RoutedEventArgs)
  'logic to handle the Click event     
End Sub

コードでイベント ハンドラーを追加する方法の例については、「コードを使用してイベント ハンドラーを追加する」を参照してください。

Visual Basic を使用する場合、Handles キーワードを使用して、ハンドラー宣言の一部としてハンドラーを追加することもできます。 詳細については、「Visual Basic と WPF のイベント処理」を参照してください。

処理済みの概念

すべてのルーティング イベントは、共通のイベント データ基底クラス RoutedEventArgs を共有します。 RoutedEventArgs を使用して、ブール値を受け取る Handled プロパティを定義します。 Handled プロパティの目的は、Handled の値を true に設定することにより、経路上の任意のイベント ハンドラーでルーティング イベントを "処理済み" としてマークできるようにすることです。 経路上の 1 つの要素のハンドラーで処理された後、共有イベント データが経路上の各リスナーに再び報告されます。

Handled の値は、ルーティング イベントが経路をたどる過程でどのように報告または処理されるかに影響します。 ルーティング イベントのイベント データで Handledtrue の場合、その特定のイベント インスタンスでは以後、他の要素上でそのルーティング イベントをリッスンするハンドラーが呼び出されることは通常ありません。 これは、XAML でアタッチされたハンドラーにも、+=Handles など、言語固有のイベント ハンドラー アタッチ構文によって追加されたハンドラーにも当てはまります。 最も一般的なハンドラーのシナリオでは、Handledtrue に設定してイベントを処理済みとしてマークすると、トンネル ルーティングやバブル ルーティングだけでなく経路上でクラス ハンドラーによって処理されるすべてのイベントでもルーティングが "停止" されます。

ただし、イベント データの Handledtrue であるルーティング イベントへの応答としてリスナーがハンドラーを実行できる "handledEventsToo" メカニズムも存在します。 つまり、イベント データを処理済みとしてマークしてもイベントのルーティングは完全には停止されません。 handledEventsToo メカニズムは、コードまたは EventSetter でのみ使用できます。

  • コードでは、一般的な CLR イベントで機能する言語固有のイベント構文を使用するのではなく、WPF メソッド AddHandler(RoutedEvent, Delegate, Boolean) を呼び出してハンドラーを追加します。 handledEventsToo の値として true を指定します。

  • EventSetter で、HandledEventsToo 属性を true に設定します。

ルーティング イベントの Handled 状態によって発生する動作に加え、Handled の概念は、アプリケーションの設計方法とイベント ハンドラー コードの記述方法に影響します。 Handled は、ルーティング イベントによって公開される簡単なプロトコルであると概念化することができます。 このプロトコルの細かい使用方法は任意ですが、Handled の値の使用方法に関する概念設計は次のとおりです。

  • ルーティング イベントが処理済みとしてマークされている場合、このイベントを経路上の他の要素でもう一度処理する必要はありません。

  • ルーティング イベントが処理済みとしてマークされていない場合、経路上の前方に位置する他のリスナーがハンドラーを登録しないよう選択したか、登録済みハンドラーがイベント データを操作しないよう選択し、Handledtrue に設定したかのどちらかです (または、現在のリスナーが経路上の出発点である可能性もあります)。現在のリスナーのハンドラーでは、次の 3 つのアクションを選択できます。

    • アクションをまったく行いません。イベントは未処理のまま、次のリスナーにルーティングされます。

    • イベントに応答してコードを実行しますが、実行したアクションはイベントを処理済みとしてマークするのに十分とは確定されません。 イベントは次のリスナーにルーティングされます。

    • イベントに応答してコードを実行します。 実行したアクションはイベントを処理済みとしてマークするのに十分と考えられるため、ハンドラーに渡されたイベント データでイベントを処理済みとしてマークします。 イベントは次のリスナーにルーティングされますが、イベント データに Handled=true があるため、他のハンドラーを呼び出す機会が与えられるのは handledEventsToo リスナーだけです。

この概念設計は、前に説明したルーティング動作により補強されます。そのため、経路上の前方のハンドラーによって Handledtrue に既に設定されている状況で、ルーティング イベントに対して呼び出されるルーティング イベントのハンドラーをアタッチすることは、さらに難しくなります (ただしコードやスタイルでは可能)。

Handled、ルーティング イベントのクラス処理、およびルーティング イベントを Handled とマークすることが適切な場合の推奨事項の詳細については、「ルーティング イベントの処理済みとしてのマーキング、およびクラス処理」を参照してください。

アプリケーションでは、バブル ルーティング イベントを発生元のオブジェクトで処理するのが一般的であり、イベントのルーティング特性についてはまったく考慮されません。 ただし、その場合でも、イベント データでルーティング イベントを処理済みとしてマークすることをお勧めします。そうすれば、要素ツリーの後方にある要素でそれと同じルーティング イベントにハンドラーがアタッチされている場合に起こりうる、予期しない副作用を回避できます。

クラス ハンドラー

DependencyObject から派生したクラスを定義する場合、クラスに宣言または継承されたイベント メンバーであるルーティング イベントのクラス ハンドラーを定義およびアタッチすることもできます。 ルーティング イベントが経路上の要素インスタンスに到達すると、クラスのインスタンスにアタッチされたどのインスタンス リスナー ハンドラーよりも先に、クラス ハンドラーが呼び出されます。

一部の WPF コントロールには、特定のルーティング イベントに対する固有のクラス処理が存在します。 このため、ルーティング イベントが発生していないように見えても、実際にはクラス処理されている場合があります。また、特定の手法を使用した場合、インスタンス ハンドラーでも処理される可能性があります。 また、多くの基底クラスとコントロールからは、クラス処理の動作をオーバーライドするために使用できる仮想メソッドが提供されています。 望ましくないクラス処理の回避方法とカスタム クラスを使った独自のクラス処理定義方法の詳細については、「ルーティング イベントの処理済みとしてのマーキング、およびクラス処理」を参照してください。

WPF の添付イベント

XAML 言語では、"添付イベント" と呼ばれる特殊な種類のイベントも定義します。 添付イベントを使用して、任意の要素に特定のイベントのハンドラーを追加できます。 イベントを処理する要素が添付イベントを定義または継承する必要はありません。また、イベントを発生させる可能性のあるオブジェクトでも、インスタンスを処理する添付先でも、そのイベントを定義したりクラス メンバーとして "所有" したりする必要はありません。

WPF 入力システムでは、添付イベントを多用します。 ただし、ほとんどすべての添付イベントは基本要素間で転送されます。 入力イベントは、基本要素クラスのメンバーである、非添付のルーティング イベントに等しいものとして表されます。 たとえば、基になる添付イベント Mouse.MouseDown を特定の UIElement でより簡単に処理するには、添付イベント構文を XAML またはコードで使用するのではなく、その UIElementMouseDown を使用します。

WPF の添付イベントの詳細については、「添付イベントの概要」を参照してください。

XAML の修飾イベント名

typename.eventname 添付イベント構文に似ているが厳密に言えば添付イベントの使用方法ではない、もう 1 つの構文使用方法では、子要素で発生したルーティング イベントのハンドラーをアタッチします。 対応するルーティング イベントが共通の親要素にメンバーとして含まれない場合でも、共通の親要素にハンドラーをアタッチしてイベントのルーティングを利用します。 次の例をもう一度検討します。

<Border Height="50" Width="300" BorderBrush="Gray" BorderThickness="1">
  <StackPanel Background="LightGray" Orientation="Horizontal" Button.Click="CommonClickHandler">
    <Button Name="YesButton" Width="Auto" >Yes</Button>
    <Button Name="NoButton" Width="Auto" >No</Button>
    <Button Name="CancelButton" Width="Auto" >Cancel</Button>
  </StackPanel>
</Border>

ここでは、ハンドラーが追加された親要素のリスナーは StackPanel です。 ただし、追加しているのは、Button クラスで宣言され生成されるルーティング イベントのハンドラーです (実際には ButtonBase ですが、継承により Button に使用できます)。 Button がこのイベントを "所有" しますが、ルーティング イベント システムでは、任意のルーティング イベントのハンドラーを任意の UIElement インスタンス リスナー、またはそれ以外の場合であれば共通言語ランタイム (CLR) イベントのリスナーをアタッチできる ContentElement インスタンス リスナーにアタッチすることが許可されています。 これらの修飾イベント属性名の既定の xmlns 名前空間は、通常、既定の WPF xmlns 名前空間ですが、カスタム ルーティング イベント用のプレフィックスを持つ名前空間を指定することもできます。 xmlns の詳細については、「XAML 名前空間および WPF XAML の名前空間の割り当て」を参照してください。

WPF の入力イベント

WPF プラットフォームにおけるルーティング イベントの主な使用例として、入力イベントがあります。 WPF では、トンネル ルーティング イベントの名前に "Preview" をプレフィックスとして付加するのが慣例です。 入力イベントは、多くの場合、バブル イベントとトンネル イベントがペアになって構成されます。 たとえば、KeyDown イベントと PreviewKeyDown イベントは同じシグネチャを持ち、前者がバブル入力イベント、後者がトンネル入力イベントです。 入力イベントによっては、バブル形式だけ、または直接ルーティング形式だけを持つ場合もあります。 このドキュメント内のルーティング イベントに関するトピックでは、他のルーティング方法を使用する類似ルーティング イベントがある場合は相互参照を示します。またマネージド リファレンス ページでは、各ルーティング イベントのルーティング方法について説明します。

ペアになった WPF 入力イベントを実装する場合、マウス ボタンを押すなどの入力による 1 回のユーザー操作で、ペアになった両方のルーティング イベントが順番に発生するようにします。 まず、トンネル イベントが発生し、その経路をたどります。 次に、バブル イベントが発生し、その経路をたどります。 2 つのイベントは、文字どおり同じイベント データ インスタンスを共有します。これは、バブル イベントを発生させた実装クラスの RaiseEvent メソッド呼び出しでは、トンネル イベントからのイベント データがリッスンされ、このデータが新たに発生するイベントで再利用されるためです。 トンネル イベントのハンドラーを持つリスナーは、ルーティング イベントを処理済みとしてマークする機会を最初に与えられます (最初がクラス ハンドラーで、その次がインスタンス ハンドラーです)。 トンネル経路上の要素がルーティング イベントを処理済みとしてマークした場合、処理済みイベントのデータがバブル イベントに送信され、同等のバブル入力イベントにアタッチされた標準のハンドラーは呼び出されません。 外部からは、処理済みのバブル イベントが発生しなかったように見えます。 この処理動作が便利なのは、コントロールの複合化の場合です。この場合、すべてのヒット テスト ベースの入力イベントまたはフォーカス ベースの入力イベントが、コントロールの複合部分ではなく最終的なコントロールから報告される必要があるためです。 最終的なコントロール要素は、複合のルート近くにあるため、トンネル イベントを最初にクラス処理し、コントロール クラスをサポートするコードの一部としてそのルーティング イベントをコントロール固有のイベントで "置き換える" 機会があります。

入力イベント処理のしくみを説明するため、次の入力イベント例について考えます。 次のツリー図の leaf element #2 は、PreviewMouseDownMouseDown の両方のイベントの発生元です。

Event routing diagram

次の順序でイベントが処理されます。

  1. ルート要素の PreviewMouseDown (トンネル)。

  2. 中間要素 #1 の PreviewMouseDown (トンネル)。

  3. ソース要素 #2 の PreviewMouseDown (トンネル)。

  4. ソース要素 #2 の MouseDown (バブル)。

  5. 中間要素 #1 の MouseDown (バブル)。

  6. ルート要素の MouseDown (バブル)。

ルーティング イベント ハンドラー デリゲートは、2 つのオブジェクトへの参照を提供します。その 2 つは、イベントを発生したオブジェクトと、ハンドラーが呼び出されたオブジェクトです。 ハンドラーが呼び出されたオブジェクトは、sender パラメーターによって報告されるオブジェクトです。 イベントが最初に発生したオブジェクトは、イベント データの Source プロパティによって報告されます。 ルーティング イベントを発生させるオブジェクトと処理するオブジェクトが同じ場合もあります。その場合、senderSource は同じです (前のイベント処理例の手順 3 と手順 4 がこれに該当します)。

トンネルとバブルにより、親要素は、Source がその子要素のいずれかである入力イベントを受け取ります。 どれがソース要素であるかを知る必要がある場合、Source プロパティにアクセスするとソース要素を特定できます。

通常、いったん入力イベントが Handled としてマークされると、ハンドラーはそれ以上呼び出されません。 一般に、入力イベントが意味することをアプリケーション固有の方法で論理処理するハンドラーが呼び出されたら、直ちに入力イベントを処理済みとしてマークする必要があります。

Handled 状態に関するこの一般論に対する例外として、イベント データの Handled 状態を意図的に無視するよう登録された入力イベント ハンドラーは、どちらの経路上でも呼び出されます。 詳細については、「プレビュー イベント」または「ルーティング イベントの処理済みとしてのマーキング、およびクラス処理」を参照してください。

トンネル イベントとバブル イベント間の共有イベント データ モデルの概念も、トンネル イベントとバブル イベントの順次発生の概念も、すべてのルーティング イベントに当てはまるとは限りません。 その動作は、ペアになった入力イベントを WPF 入力デバイスがどのように発生させ接続するよう選択するかによって、具体的に実装されます。 独自の入力イベントの実装は高度なシナリオですが、独自の入力イベントでそうしたモデルを採用することもできます。

一部のクラスでは、特定の入力イベントをクラス処理します。通常、その目的は、ユーザーによる特定の入力イベントの意味をそのコントロール内で再定義して、新しいイベントを発生させることです。 詳細については、「ルーティング イベントの処理済みとしてのマーキング、およびクラス処理」を参照してください。

入力と一般的なアプリケーション シナリオにおける入力とイベントの対話方法の詳細については、「入力の概要」を参照してください。

EventSetter と EventTrigger

EventSetter を使用すると、スタイルのマークアップ内に、あらかじめ宣言した XAML イベント処理構文を含めることができます。 そのスタイルを適用すると、参照されているハンドラーが、スタイルが適用されるインスタンスに追加されます。 EventSetter は、ルーティング イベントに対してのみ宣言できます。 次に例を示します。 ここで参照されている b1SetColor メソッドは、分離コード ファイル内にあります。

<StackPanel
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  x:Class="SDKSample.EventOvw2"
  Name="dpanel2"
  Initialized="PrimeHandledToo"
>
  <StackPanel.Resources>
    <Style TargetType="{x:Type Button}">
      <EventSetter Event="Click" Handler="b1SetColor"/>
    </Style>
  </StackPanel.Resources>
  <Button>Click me</Button>
  <Button Name="ThisButton" Click="HandleThis">
    Raise event, handle it, use handled=true handler to get it anyway.
  </Button>
</StackPanel>

ここでの利点は、スタイルに、アプリケーションの任意のボタンに適用可能なその他の情報が大量に含まれている可能性があることです。そのスタイルに EventSetter を含めることによって、マークアップ レベルでもコードの再利用が促進されます。 また、EventSetter によって、一般的なアプリケーションやページのマークアップよりも一歩進んだハンドラーのメソッド名の抽象化が実現されます。

ルーティング イベントと WPF アニメーション機能を結合したもう 1 つの特別な構文が、EventTrigger です。 EventSetter と同様、EventTrigger にもルーティング イベントのみを使用できます。 通常、EventTrigger はスタイルの一部として宣言しますが、EventTrigger をページ レベルの要素で Triggers コレクションの一部として (つまり ControlTemplate 内で) 宣言することもできます。 EventTrigger を使用すると、ルーティング イベントに対する EventTrigger を宣言した要素にそのイベントが経路で到達するたびに Storyboard が実行されるよう指定できます。 単にイベントを処理して既存のストーリーボードが開始されるようにする方法と比べて EventTrigger が優れている点は、EventTrigger の方がストーリーボードとそのランタイム動作をよりよく制御できることです。 詳細については、「開始後のストーリーボードをイベント トリガーを使用して制御する」を参照してください。

ルーティング イベントの詳細

このトピックの主な目的は、ルーティング イベントの基本概念を説明し、さまざまな基本要素や基本コントロール内の既存のルーティング イベントに応答する方法とタイミングについて解説することです。 しかし、独自のルーティング イベントを、特殊なイベント データ クラスやデリゲートなど、必要な支援機能すべてと共に、カスタム クラスに作成することもできます。 ルーティング イベントの所有者はどのクラスでもかまいませんが、利便性のため、ルーティング イベントの発生と処理は UIElement または ContentElement の派生クラスで行われる必要があります。 カスタム イベントの詳細については、「カスタム ルーティング イベントを作成する」を参照してください。

参照