ルーティング イベントのハンドラーは、イベント データ内で処理されたイベントをマークできます。 イベントを処理すると、ルートが効果的に短縮されます。 クラス処理は、ルーティング イベントによってサポートされるプログラミングの概念です。 クラス ハンドラーには、クラスの任意のインスタンス上のインスタンス ハンドラーの前に呼び出されるハンドラーを使用して、クラス レベルで特定のルーティング イベントを処理する機会があります。
前提 条件
このトピックでは、「ルーティング イベントの概要」で導入された概念について詳しく説明します。
イベントを処理済みとしてマークするタイミング
ルーティング イベントのイベント データに true
するように Handled プロパティの値を設定すると、これは "処理されたイベントのマーキング" と呼ばれます。 ルーティング イベントを、アプリケーション作成者として、または既存のルーティング イベントに応答するか、新しいルーティング イベントを実装するコントロール作成者として、いつ処理済みとしてマークするかを示す絶対ルールはありません。 ほとんどの場合、ルーティング イベントのイベント データで実行される "処理済み" の概念は、WPF API で公開されているさまざまなルーティング イベントに対する独自のアプリケーションの応答およびカスタム ルーティング イベントに対する制限付きプロトコルとして使用する必要があります。 "処理済み" の問題を考慮するもう 1 つの方法は、コードがルーティング イベントに重大かつ比較的完全な方法で応答した場合に、通常はルーティング イベントを処理済みとしてマークする必要があるということです。 通常、1 回のルーティング イベントの発生に対して個別のハンドラー実装を必要とする重要な応答が複数存在しないようにする必要があります。 さらに多くの応答が必要な場合は、転送にルーティング イベント システムを使用するのではなく、1 つのハンドラー内でチェーンされたアプリケーション ロジックを使用して必要なコードを実装する必要があります。 "重要" の概念も主観的であり、アプリケーションまたはコードによって異なります。 一般的なガイダンスとして、フォーカスの設定、パブリック状態の変更、ビジュアル表現に影響するプロパティの設定、その他の新しいイベントの発生など、"重要な応答" の例がいくつかあります。 重要でない応答の例としては、プライベート状態の変更 (視覚的な影響なし、プログラムによる表現)、イベントのログ記録、イベントの引数の確認、それに応答しないことを選択するなどがあります。
ルーティング イベント システムの動作は、XAML で追加されたハンドラーまたは AddHandler の共通シグネチャが、イベント データが既に処理済みとしてマークされているルーティング イベントに応答して呼び出されないため、ルーティング イベントの処理済み状態を使用するためのこの "重要な応答" モデルを強化します。 以前の参加者によって処理されたとマークされたルーティング イベントを処理するためには、handledEventsToo
パラメーター バージョン (AddHandler(RoutedEvent, Delegate, Boolean)) を使用してハンドラーを追加するという手間をかける必要があります。
状況によっては、コントロール自体が特定のルーティング イベントを処理済みとしてマークします。 処理されたルーティング イベントは、ルーティング イベントに応答するコントロールのアクションがコントロールの実装の一部として重要または完了であり、イベントがそれ以上処理する必要がないことを WPF コントロールの作成者が決定することを表します。 通常、これはイベントのクラス ハンドラーを追加するか、基底クラスに存在するクラス ハンドラー仮想のいずれかをオーバーライドすることによって行われます。 必要に応じて、このイベント処理を回避する方法があります。このトピックで後述する コントロールによるイベント抑制の回避 を参照してください。
"プレビュー(トンネリング)イベントとバブリングイベント、およびイベント処理"
プレビュー ルーティング イベントは、要素ツリーを通るトンネリング ルートに従うイベントです。 名前付け規則で表される "プレビュー" は、プレビュー (トンネリング) ルーティング イベントが同等のバブリング ルーティング イベントの前に発生する入力イベントの一般的な原則を示すものです。 また、トンネリングとバブルのペアを持つ入力ルーティング イベントには、個別の処理ロジックがあります。 トンネリング/プレビュー ルーティング イベントがイベント リスナーによって処理済みとしてマークされている場合、バブル ルーティング イベントのリスナーが受信する前であっても、バブル ルーティング イベントは処理済みとしてマークされます。 トンネリングおよびバブル ルーティング イベントは、技術的に独立したイベントですが、この動作を有効にするために、イベント データの同じインスタンスを意図的に共有します。
トンネリング イベントとバブル ルーティング イベントの間の接続は、特定の WPF クラスが独自に宣言されたルーティング イベントを発生させる方法の内部実装によって実現されます。これは、ペアの入力ルーティング イベントに当てはまります。 ただし、このクラス レベルの実装が存在しない限り、トンネリング ルーティング イベントと、名前付けスキームを共有するバブル ルーティング イベントの間に接続はありません。そのような実装がないと、2 つの完全に分離されたルーティング イベントになり、イベント データの順序や共有は行われません。
カスタム クラスでトンネル/バブル入力ルーティング イベント ペアを実装する方法の詳細については、「カスタム ルーティング イベントを作成する」を参照してください。
クラス ハンドラーとインスタンス ハンドラー
ルーティング イベントでは、イベントに対する 2 種類のリスナー (クラス リスナーとインスタンス リスナー) が考慮されます。 クラス リスナーは、型が静的コンストラクターで特定の EventManager API 、RegisterClassHandlerを呼び出したか、要素の基底クラスからクラス ハンドラーの仮想メソッドをオーバーライドしたために存在します。 インスタンス リスナーは、AddHandlerの呼び出しによって、そのルーティング イベントに対して 1 つ以上のハンドラーがアタッチされている特定のクラス インスタンス/要素です。 既存の WPF ルーティング イベントは、共通言語ランタイム (CLR) イベント ラッパーの一部として AddHandler の呼び出しを行い、{} を追加し、イベントの実装{} 削除します。これは、属性構文を使用してイベント ハンドラーをアタッチする単純な XAML メカニズムも有効にする方法です。 したがって、単純な XAML の使用であっても、最終的には AddHandler 呼び出しに相当します。
ビジュアル ツリー内の要素は、登録済みのハンドラー実装についてチェックされます。 ハンドラーは、ルーティング イベントのルーティング戦略の種類に固有の順序で、ルート全体で呼び出される可能性があります。 たとえば、バブル ルーティング イベントは、ルーティング イベントを発生させたのと同じ要素にアタッチされているハンドラーを最初に呼び出します。 その後、ルーティング イベントは次の親要素に "浮上" します。アプリケーションのルート要素に到達するまで、これが繰り返されます。
バブル ルート内のルート要素の観点から見ると、イベント引数を処理対象としてマークするルーティング イベント呼び出しハンドラーのソースに近いクラス処理または要素が呼び出された場合、ルート要素のハンドラーは呼び出されず、イベント ルートはそのルート要素に到達する前に実質的に短縮されます。 ただし、クラス ハンドラーまたはインスタンス ハンドラーによってルーティング イベントが処理済みとしてマークされている場合でも、呼び出す必要がある特別な条件付きを使用してハンドラーを追加できるため、ルートは完全には停止されません。 詳細については、このトピックで後述する「イベントが処理済みとしてマークされていても呼び出されるインスタンス ハンドラーの追加」をご覧ください。
イベント ルートよりも深いレベルでは、1 つのクラスの特定のインスタンスに対して動作する複数のクラス ハンドラーも存在する可能性があります。 これは、ルーティング イベントのクラス処理モデルにより、クラス階層内のすべての可能なクラスを有効にして、各ルーティング イベントに対して独自のクラス ハンドラーを登録できるためです。 各クラス ハンドラーは内部ストアに追加され、アプリケーションのイベント ルートが構築されると、クラス ハンドラーがすべてイベント ルートに追加されます。 クラス ハンドラーがルートに追加され、最も派生したクラス ハンドラーが最初に呼び出され、後続の各基底クラスのクラス ハンドラーが次に呼び出されます。 一般に、クラス ハンドラーは登録されないため、既に処理済みとしてマークされたルーティング イベントにも応答します。 そのため、このクラス処理メカニズムでは、次の 2 つの選択肢のいずれかを使用できます。
基底クラス ハンドラーは派生クラス ハンドラーの後で呼び出されるため、派生クラスは、処理されたルーティング イベントをマークしないハンドラーを追加することで、基底クラスから継承されるクラス処理を補完できます。
派生クラスは、処理されたルーティング イベントをマークするクラス ハンドラーを追加することで、基底クラスからのクラス処理を置き換えることができます。 この方法では、外観、状態ロジック、入力処理、コマンド処理などの領域で目的の基本コントロールの設計が変更される可能性があるため、注意が必要です。
コントロール基底クラスによるルーティング イベントのクラス処理
イベント ルート内の各要素ノードで、クラス リスナーは、要素のインスタンス リスナーが可能になる前に、ルーティング イベントに応答する機会を持ちます。 このため、クラス ハンドラーは、特定のコントロール クラスの実装がそれ以上伝達したくないルーティング イベントを抑制したり、クラスの特徴であるルーティング イベントの特別な処理を提供したりするために使用される場合があります。 たとえば、クラスは、特定のクラスのコンテキストにおけるユーザー入力条件の意味に関する詳細を含む独自のクラス固有のイベントを発生させる可能性があります。 クラスの実装では、より一般的なルーティング イベントを処理済みとしてマークする場合があります。 クラス ハンドラーは通常、共有イベント データが既に処理済みとしてマークされているルーティング イベントに対して呼び出されないように追加されますが、非典型的な場合には、ルーティング イベントが処理済みとマークされている場合でも呼び出すクラス ハンドラーを登録する RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) シグネチャもあります。
クラス ハンドラー仮想メソッド
一部の要素 、特に UIElementなどの基本要素は、パブリック ルーティング イベントの一覧に対応する空の "On*Event" および "OnPreview*Event" 仮想メソッドを公開します。 これらの仮想メソッドをオーバーライドして、そのルーティング イベントのクラス ハンドラーを実装できます。 基底要素クラスは、前述のように、RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) を使用して、このようなルーティング イベントごとにこれらの仮想メソッドをクラス ハンドラーとして登録します。 On*Event 仮想メソッドを使用すると、各型の静的コンストラクターで特別な初期化を必要とせずに、関連するルーティング イベントのクラス処理を実装する方がはるかに簡単になります。 たとえば、OnDragEnter 仮想メソッドをオーバーライドすることで、任意の UIElement 派生クラスで DragEnter イベントのクラス処理を追加できます。 オーバーライド内では、ルーティング イベントの処理、他のイベントの発生、インスタンスの要素プロパティを変更する可能性があるクラス固有のロジックの開始、またはこれらのアクションの任意の組み合わせを行うことができます。 通常は、処理されたイベントをマークする場合でも、このようなオーバーライドで基本実装を呼び出す必要があります。 仮想メソッドは基底クラス上にあるため、基本実装の呼び出しを強くお勧めします。 各仮想から基本実装を呼び出す標準的な保護された仮想パターンは、基本的にルーティング イベント クラス処理にネイティブである同様のメカニズムを置き換え、並列化します。このメカニズムにより、クラス階層内のすべてのクラスのクラス ハンドラーが、特定のインスタンスで呼び出され、最も派生クラスのハンドラーから始まり、基底クラス ハンドラーに進みます。 基底クラスの処理ロジックを変更する意図的な要件がクラスにある場合にのみ、基本実装呼び出しを省略する必要があります。 オーバーライドするコードの前または後に基本実装を呼び出すかどうかは、実装の性質によって異なります。
入力イベント クラスの処理
クラス ハンドラーの仮想メソッドはすべて、共有イベント データがまだ処理済みとしてマークされていない場合にのみ呼び出されるように登録されます。 また、入力イベントに固有のしくみとして、一般に、トンネル バージョンとバブル バージョンのイベントが順番に発生し、両方のバージョンでイベント データが共有されます。 これは、入力イベントのクラスハンドラーの特定のペアにおいて、一方がトンネリング版で、もう一方がバブリング版である場合、イベントをすぐに「処理済み」としてマークしない方が良いかもしれないことを意味します。 トンネリング クラス処理仮想メソッドを実装してイベント処理をマークすると、バブル クラス ハンドラーが呼び出されなくなります (また、トンネリング イベントまたはバブル イベントに対して通常登録されているインスタンス ハンドラーが呼び出されないようにします)。
ノードでのクラス処理が完了すると、インスタンス リスナーが考慮されます。
イベントが処理済みとしてマークされていても呼び出されるインスタンス ハンドラーの追加
AddHandler メソッドは特定のオーバーロードを提供します。これにより、イベントがルート内の処理要素に到達するたびに、イベント システムによって呼び出されるハンドラーを追加できます。これは、他のハンドラーがイベント データを既に調整して、そのイベントを処理済みとしてマークしている場合でも同様です。 これは通常は行われません。 一般に、ハンドラーは、複数の最終結果が必要な場合でも、要素ツリーで処理された場所に関係なく、イベントの影響を受ける可能性があるアプリケーション コードのすべての領域を調整するために記述できます。 また、通常、そのイベントに応答する必要がある要素は 1 つだけであり、適切なアプリケーション ロジックは既に発生しています。 ただし、handledEventsToo
オーバーロードは、要素ツリーまたはコントロール合成の他の要素が既にイベントを処理済みとしてマークしているが、要素ツリーの上位または下位の他の要素が独自のハンドラーを呼び出す必要がある例外的な場合に使用できます。
処理済みイベントを未処理としてマークするタイミング
一般には、処理済みとしてマークされているルーティング イベントを未処理としてマークする (Handled の設定を false
に戻す) ことはお勧めしません。これは、handledEventsToo
で動作するハンドラーの場合も同じです。 ただし、一部の入力イベントには上位レベルと下位レベルのイベント表現があり、ツリー内のある位置で高レベルのイベントが表示され、下位レベルのイベントが別の位置で見られる場合に重複する可能性があります。 たとえば、子要素が TextInput などの高レベルのキー イベントをリッスンし、親要素が KeyDownなどの低レベルのイベントをリッスンする場合を考えます。 親要素が低レベルのイベントを処理する場合、直感的にイベントを処理する最初の機会が必要な子要素でも、上位レベルのイベントを抑制できます。
このような状況では、下位イベントの親要素と子要素の両方にハンドラーを追加することが必要な場合があります。 子要素ハンドラーの実装では、低レベルのイベントを処理済みとしてマークできますが、親要素ハンドラーの実装では、ツリーの上位の要素 (および高レベルのイベント) が応答できるように、再び未処理に設定されます。 この状況はかなりまれです。
コントロール合成の入力イベントを意図的に抑制する
ルーティング イベントのクラス処理が使用される主なシナリオは、入力イベントと複合コントロールです。 複合コントロールは、定義上、複数の実用的なコントロールまたはコントロールの基底クラスで構成されます。 多くの場合、コントロールの作成者は、コントロール全体を単一のイベント ソースとして報告するために、各サブコンポーネントが発生する可能性のあるすべての入力イベントを正規化したいと考えています。 場合によっては、コントロール作成者は、コンポーネントからのイベントを完全に抑制したり、より多くの情報を含むコンポーネント定義イベントに置き換えたり、より具体的な動作を意味したりする場合があります。 コンポーネント作成者にすぐに表示される標準的な例は、Windows Presentation Foundation (WPF) Button がマウス イベントを処理し、最終的にすべてのボタンが持つ直感的なイベント (Click イベント) に解決する方法です。
Button 基底クラス (ButtonBase) は、FrameworkElement と UIElementから派生する Control から派生し、制御入力処理に必要なイベント インフラストラクチャの多くは、UIElement レベルで使用できます。 特に、UIElement は、マウス カーソルの境界内でのヒット テストを処理する一般的な Mouse イベントを処理し、最も一般的なボタン アクション (MouseLeftButtonDownなど) に個別のイベントを提供します。 UIElement では、MouseLeftButtonDownの事前登録されたクラス ハンドラーとして空の仮想 OnMouseLeftButtonDown も提供され、ButtonBase によってオーバーライドされます。 同様に、ButtonBase は MouseLeftButtonUpにクラス ハンドラーを使用します。 イベント データが渡されるオーバーライドでは、実装は、Handled を true
に設定してインスタンス RoutedEventArgs 処理済みとしてマークし、その同じイベント データは、他のクラス ハンドラーおよびインスタンス ハンドラーまたはイベント セッターへのルートの残りの部分に沿って続行されます。 また、OnMouseLeftButtonUp のオーバーライドでは、次に Click イベントが生成されます。 ほとんどのリスナーの最終的な結果は、MouseLeftButtonDown イベントと MouseLeftButtonUp イベントが"消える"ことになり、代わりに Clickに置き換えられます。これは、このイベントがボタンの複合部分ではなく、真のボタンまたは他の要素から完全に発生したことがわかっているため、より意味を持つイベントです。
コントロールによるイベント抑制の回避
個々のコントロール内でのこのイベント抑制動作が、アプリケーションのイベント処理ロジックのより一般的な意図を妨げる場合があります。 たとえば、何らかの理由でアプリケーションのルート要素に MouseLeftButtonDown のハンドラーがある場合、ボタンをマウスでクリックしても、ルート レベルで MouseLeftButtonDown または MouseLeftButtonUp ハンドラーが呼び出されないことに気付くでしょう。 イベント自体は実際にバブルアップしました (ここでも、イベント ルートは本当に終了しませんが、ルーティング イベント システムは、処理済みとしてマークされた後にハンドラー呼び出しの動作を変更します)。 ルーティング イベントがボタンに到達すると、より多くの意味を持つ Click イベントに置き換えるために、ButtonBase のクラス処理によって MouseLeftButtonDown が処理済みとしてマークされます。 そのため、ルートのさらに上にある標準 MouseLeftButtonDown ハンドラーは呼び出されません。 この状況でハンドラーを確実に呼び出すために使用できる手法は 2 つあります。
最初の手法は、AddHandler(RoutedEvent, Delegate, Boolean)の handledEventsToo
シグネチャを使用してハンドラーを意図的に追加することです。 この方法の制限は、イベント ハンドラーをアタッチするこの手法は、マークアップからではなく、コードからのみ可能です。 Extensible Application Markup Language (XAML) を使用してイベント ハンドラー名をイベント属性値として指定する単純な構文では、その動作は有効になりません。
2 番目の手法は、ルーティング イベントのトンネリング バージョンとバブル バージョンがペアになっている入力イベントに対してのみ機能します。 代わりに、これらのルーティング イベントに対して、プレビューまたはトンネリングに対応する同等のルーティング イベントにハンドラーを追加することができます。 そのルーティング イベントはルートからトンネリングを開始するため、アプリケーション要素ツリーの先祖要素のレベルにプレビュー ハンドラーをアタッチしておけば、イベントがボタン クラス処理コードによってインターセプトされなくなります。 この方法を使用する場合は、プレビュー イベントを処理済みとしてマークする際に注意が必要です。 ルート要素で処理される PreviewMouseLeftButtonDown で指定された例では、ハンドラーの実装でイベントを Handled としてマークした場合、実際には Click イベントを抑制します。 これは通常、望ましくない動作です。
関連項目
- EventManager
- プレビュー イベント
- カスタム ルーティング イベント を作成する
- ルーティング イベントの概要
.NET Desktop feedback