ルーティング イベントをいつ処理済みとしてマークするかに関する絶対的な規則はありませんが、コードがイベントに重要な方法で応答する場合は、イベントを処理済みとしてマークすることを検討してください。 処理済みとしてマークされたルーティング イベントは、そのルートに沿って続行されますが、処理されたイベントに応答するように構成されたハンドラーのみが呼び出されます。 基本的に、ルーティング イベントを処理済みとしてマークすると、イベント ルートに沿ったリスナーへの可視性が制限されます。
ルーティング イベント ハンドラーには、インスタンス ハンドラーまたはクラス ハンドラーのいずれかを指定できます。 インスタンス ハンドラーは、オブジェクトまたは XAML 要素のルーティング イベントを処理します。 クラス ハンドラーは、クラス レベルでルーティング イベントを処理し、クラスの任意のインスタンスで同じイベントに応答するインスタンス ハンドラーの前に呼び出されます。 ルーティング イベントが処理済みとしてマークされると、多くの場合、クラス ハンドラー内でそのようにマークされます。 この記事では、ルーティング イベントを処理済みとしてマークする利点と潜在的な落とし穴、ルーティング イベントとルーティング イベント ハンドラーの種類、複合コントロールでのイベント抑制について説明します。
[前提条件]
この記事では、ルーティング イベントに関する基本的な知識と、 ルーティング イベントの概要を読んだことを前提としています。 この記事の例に従うと、拡張アプリケーション マークアップ言語 (XAML) に慣れている場合や、Windows Presentation Foundation (WPF) アプリケーションを記述する方法を理解している場合に役立ちます。
ルーティングイベントを「処理済み」としてマークする際の判断基準
通常、ルーティングされたイベントごとに 1 つのハンドラーのみが重要な応答を提供する必要があります。 ルーティング イベント システムを使用して、複数のハンドラー間で重要な応答を提供しないようにします。 重要な応答を構成するものの定義は主観的であり、アプリケーションによって異なります。 一般的なガイダンス:
- 重要な応答には、フォーカスの設定、パブリック状態の変更、ビジュアル表現に影響するプロパティの設定、新しいイベントの発生、イベントの完全な処理が含まれます。
- 重要でない応答には、ビジュアルやプログラムに影響を与えずにプライベート状態を変更する、イベント ログを記録する、イベントに応答せずにイベント データを調べるなどがあります。
一部の WPF コントロールでは、それ以上処理する必要のないコンポーネント レベルのイベントを、処理されたイベントとしてマークすることで抑制されます。 コントロールによって処理済みとしてマークされたイベントを処理する場合は、「コントロール によるイベント抑制の回避」を参照してください。
イベントを 処理済みとしてマークするには、イベント データの Handled プロパティ値を true
に設定します。 その値を false
に戻ることは可能ですが、その必要性はまれです。
プレビューおよびバブリングのルーティングイベントのペア
プレビュー イベントとバブル ルーティング イベントのペアは、 入力イベントに固有です。
やなど、いくつかの入力イベントによってPreviewKeyDownとKeyDown ルーティング イベントのペアが実装されます。
Preview
プレフィックスは、プレビュー イベントが完了するとバブル イベントが開始されることを示します。 プレビュー イベントとバブル イベントの各ペアは、イベント データの同じインスタンスを共有します。
ルーティング イベント ハンドラーは、イベントのルーティング戦略に対応する順序で呼び出されます。
- プレビュー イベントは、アプリケーションのルート要素から、ルーティング イベントを発生させた要素まで移動します。 アプリケーション ルート要素にアタッチされたプレビュー イベント ハンドラーが最初に呼び出され、続いて連続する入れ子になった要素にアタッチされたハンドラーが呼び出されます。
- プレビュー イベントが完了すると、ペアのバブル イベントは、ルーティング イベントを発生させた要素からアプリケーションルート要素に移動します。 ルーティング イベントを発生させたのと同じ要素にアタッチされたバブル イベント ハンドラーが最初に呼び出され、続いて連続する親要素にアタッチされたハンドラーが呼び出されます。
ペアのプレビュー イベントとバブル イベントは、独自のルーティング イベントを宣言して発生させるいくつかの WPF クラスの内部実装の一部です。 クラス レベルの内部実装がないと、プレビュー イベントとバブル ルーティング イベントは完全に分離され、イベントの名前付けに関係なく、イベント データは共有されません。 カスタム クラスでバブルまたはトンネリング入力ルーティング イベントを実装する方法については、「 カスタム ルーティング イベントを作成する」を参照してください。
各プレビュー イベントとバブル イベント ペアはイベント データの同じインスタンスを共有するため、プレビュー ルーティング イベントが処理済みとしてマークされている場合は、ペアのバブル イベントも処理されます。 バブル ルーティング イベントが処理済みとしてマークされている場合、プレビュー イベントが完了したため、ペアのプレビュー イベントには影響しません。 プレビューおよびバブル入力イベントのペアを処理済みとしてマークする際には注意してください。 処理されたプレビュー入力イベントは、トンネリング ルートの残りの部分に対して通常登録されているイベント ハンドラーを呼び出しません。ペアのバブル イベントは発生しません。 処理されたバブル入力イベントは、バブル ルートの残りの部分に対して通常登録されているイベント ハンドラーを呼び出しません。
インスタンスとクラス経路指定イベントハンドラー
ルーティング イベント ハンドラーには、 インスタンス ハンドラーまたは クラス ハンドラーのいずれかを指定できます。 特定のクラスのクラス ハンドラーは、そのクラスの任意のインスタンスで同じイベントに応答するインスタンス ハンドラーの前に呼び出されます。 この動作により、ルーティング イベントが処理済みとしてマークされると、多くの場合、クラス ハンドラー内でそのようにマークされます。 クラス ハンドラーには、次の 2 種類があります。
- 静的クラス イベント ハンドラー。静的クラス コンストラクター内で RegisterClassHandler メソッドを呼び出すことによって登録されます。
- 基底クラスの仮想イベント メソッドをオーバーライドすることによって登録されるクラス イベント ハンドラーをオーバーライドします。 基本クラスの仮想イベント メソッドは主に入力イベント用に存在し、 名前は On<event name> および OnPreview<event name> で始まります。
インスタンス イベント ハンドラー
AddHandler メソッドを直接呼び出すことで、インスタンス ハンドラーをオブジェクトまたは XAML 要素にアタッチできます。 WPF ルーティング イベントは、 AddHandler
メソッドを使用してイベント ハンドラーをアタッチする共通言語ランタイム (CLR) イベント ラッパーを実装します。 イベント ハンドラーをアタッチするための XAML 属性構文は CLR イベント ラッパーの呼び出しになるため、XAML でハンドラーをアタッチしても AddHandler
呼び出しに解決されます。 処理されたイベントの場合:
- XAML 属性構文または
AddHandler
の共通シグネチャを使用してアタッチされたハンドラーは呼び出されません。 -
AddHandler(RoutedEvent, Delegate, Boolean) パラメーターが
handledEventsToo
に設定されているtrue
オーバーロードを使用してアタッチされたハンドラーが呼び出されます。 このオーバーロードは、処理されたイベントに応答する必要があるまれなケースで使用できます。 たとえば、要素ツリー内の一部の要素はイベントを処理済みとしてマークしていますが、イベント ルートに沿った他の要素は、処理されたイベントに応答する必要があります。
次の XAML サンプルでは、componentWrapper
という名前のカスタム コントロールを追加し、TextBoxという名前のcomponentTextBox
を StackPanel という名前のouterStackPanel
にラップします。
PreviewKeyDown イベントのインスタンス イベント ハンドラーは、XAML 属性構文を使用してcomponentWrapper
にアタッチされます。 その結果、インスタンス ハンドラーは、PreviewKeyDown
によって発生した未処理のcomponentTextBox
トンネリング イベントにのみ応答します。
<StackPanel Name="outerStackPanel" VerticalAlignment="Center">
<custom:ComponentWrapper
x:Name="componentWrapper"
TextBox.PreviewKeyDown="HandlerInstanceEventInfo"
HorizontalAlignment="Center">
<TextBox Name="componentTextBox" Width="200" />
</custom:ComponentWrapper>
</StackPanel>
MainWindow
コンストラクターは、KeyDown
パラメーターを componentWrapper
に設定して、UIElement.AddHandler(RoutedEvent, Delegate, Boolean) オーバーロードを使用して、handledEventsToo
バブル イベントのインスタンス ハンドラーをtrue
にアタッチします。 その結果、インスタンス イベント ハンドラーは、未処理のイベントと処理されたイベントの両方に応答します。
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Attach an instance handler on componentWrapper that will be invoked by handled KeyDown events.
componentWrapper.AddHandler(KeyDownEvent, new RoutedEventHandler(Handler.InstanceEventInfo),
handledEventsToo: true);
}
// The handler attached to componentWrapper in XAML.
public void HandlerInstanceEventInfo(object sender, KeyEventArgs e) =>
Handler.InstanceEventInfo(sender, e);
}
Partial Public Class MainWindow
Inherits Window
Public Sub New()
InitializeComponent()
' Attach an instance handler on componentWrapper that will be invoked by handled KeyDown events.
componentWrapper.[AddHandler](KeyDownEvent, New RoutedEventHandler(AddressOf InstanceEventInfo),
handledEventsToo:=True)
End Sub
' The handler attached to componentWrapper in XAML.
Public Sub HandlerInstanceEventInfo(sender As Object, e As KeyEventArgs)
InstanceEventInfo(sender, e)
End Sub
End Class
次のセクションでは、ComponentWrapper
のコードビハインドの実装を示します。
静的クラス イベント ハンドラー
クラスの静的コンストラクターで RegisterClassHandler メソッドを呼び出すことで、静的クラス イベント ハンドラーをアタッチできます。 クラス階層内の各クラスは、ルーティング イベントごとに独自の静的クラス ハンドラーを登録できます。 その結果、イベント ルート内の任意のノードで、同じイベントに対して複数の静的クラス ハンドラーが呼び出される可能性があります。 イベントのイベント ルートが構築されると、各ノードのすべての静的クラス ハンドラーがイベント ルートに追加されます。 ノードでの静的クラス ハンドラーの呼び出しの順序は、最も派生した静的クラス ハンドラーから始まり、その後に連続する各基底クラスの静的クラス ハンドラーが続きます。
RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) パラメーターが handledEventsToo
に設定されたtrue
オーバーロードを使用して登録された静的クラス イベント ハンドラーは、未処理のルーティング イベントと処理されたルーティング イベントの両方に応答します。
静的クラス ハンドラーは通常、未処理のイベントにのみ応答するように登録されます。 その場合、ノード上の派生クラス ハンドラーがイベントを処理済みとしてマークした場合、そのイベントの基底クラス ハンドラーは呼び出されません。 このシナリオでは、基底クラス ハンドラーは実質的に派生クラス ハンドラーに置き換えられます。 基底クラス ハンドラーは、多くの場合、外観、状態ロジック、入力処理、コマンド処理などの領域でのデザインの制御に役立ちます。そのため、置き換えることに注意してください。 イベントを処理済みとしてマークしない派生クラス ハンドラーは、基底クラス ハンドラーを置き換える代わりに補足します。
次のコード サンプルは、前の XAML で参照された ComponentWrapper
カスタム コントロールのクラス階層を示しています。
ComponentWrapper
クラスは、ComponentWrapperBase
クラスから派生し、StackPanel クラスから派生します。
RegisterClassHandler
クラスとComponentWrapper
クラスの静的コンストラクターで使用される ComponentWrapperBase
メソッドは、これらの各クラスの静的クラス イベント ハンドラーを登録します。 WPF イベント システムは、ComponentWrapper
静的クラス ハンドラーの前にComponentWrapperBase
静的クラス ハンドラーを呼び出します。
public class ComponentWrapper : ComponentWrapperBase
{
static ComponentWrapper()
{
// Class event handler implemented in the static constructor.
EventManager.RegisterClassHandler(typeof(ComponentWrapper), KeyDownEvent,
new RoutedEventHandler(Handler.ClassEventInfo_Static));
}
// Class event handler that overrides a base class virtual method.
protected override void OnKeyDown(KeyEventArgs e)
{
Handler.ClassEventInfo_Override(this, e);
// Call the base OnKeyDown implementation on ComponentWrapperBase.
base.OnKeyDown(e);
}
}
public class ComponentWrapperBase : StackPanel
{
// Class event handler implemented in the static constructor.
static ComponentWrapperBase()
{
EventManager.RegisterClassHandler(typeof(ComponentWrapperBase), KeyDownEvent,
new RoutedEventHandler(Handler.ClassEventInfoBase_Static));
}
// Class event handler that overrides a base class virtual method.
protected override void OnKeyDown(KeyEventArgs e)
{
Handler.ClassEventInfoBase_Override(this, e);
e.Handled = true;
Debug.WriteLine("The KeyDown routed event is marked as handled.");
// Call the base OnKeyDown implementation on StackPanel.
base.OnKeyDown(e);
}
}
Public Class ComponentWrapper
Inherits ComponentWrapperBase
Shared Sub New()
' Class event handler implemented in the static constructor.
EventManager.RegisterClassHandler(GetType(ComponentWrapper), KeyDownEvent,
New RoutedEventHandler(AddressOf ClassEventInfo_Static))
End Sub
' Class event handler that overrides a base class virtual method.
Protected Overrides Sub OnKeyDown(e As KeyEventArgs)
ClassEventInfo_Override(Me, e)
' Call the base OnKeyDown implementation on ComponentWrapperBase.
MyBase.OnKeyDown(e)
End Sub
End Class
Public Class ComponentWrapperBase
Inherits StackPanel
Shared Sub New()
' Class event handler implemented in the static constructor.
EventManager.RegisterClassHandler(GetType(ComponentWrapperBase), KeyDownEvent,
New RoutedEventHandler(AddressOf ClassEventInfoBase_Static))
End Sub
' Class event handler that overrides a base class virtual method.
Protected Overrides Sub OnKeyDown(e As KeyEventArgs)
ClassEventInfoBase_Override(Me, e)
e.Handled = True
Debug.WriteLine("The KeyDown event is marked as handled.")
' Call the base OnKeyDown implementation on StackPanel.
MyBase.OnKeyDown(e)
End Sub
End Class
このコード サンプルのオーバーライド クラス イベント ハンドラーの分離コード実装については、次のセクションで説明します。
クラス イベント ハンドラーをオーバーライドする
一部のビジュアル要素の基底クラスでは、パブリック ルーティング入力イベントごとに 空の On<event name> および OnPreview<event name> 仮想メソッドが公開されます。 たとえば、 UIElement は、 OnKeyDown と OnPreviewKeyDown の仮想イベント ハンドラーなどを実装します。 基底クラスの仮想イベント ハンドラーをオーバーライドして、派生クラスのオーバーライド クラス イベント ハンドラーを実装できます。 たとえば、DragEnter仮想メソッドをオーバーライドすることで、任意のUIElement
派生クラスにOnDragEnter イベントのオーバーライド クラス ハンドラーを追加できます。 基底クラスの仮想メソッドのオーバーライドは、クラス ハンドラーを静的コンストラクターに登録するよりも簡単にクラス ハンドラーを実装する方法です。 オーバーライド内では、イベントを発生させたり、クラス固有のロジックを開始してインスタンスの要素プロパティを変更したり、イベントを処理済みとしてマークしたり、その他のイベント処理ロジックを実行したりできます。
静的クラス イベント ハンドラーとは異なり、WPF イベント システムは、クラス階層内の最も派生クラスのオーバーライド クラス イベント ハンドラーのみを呼び出します。 クラス階層内で最も派生したクラスは、 基本 キーワードを使用して仮想メソッドの基本実装を呼び出すことができます。 ほとんどの場合、イベントを処理済みとしてマークするかどうかに関係なく、基本実装を呼び出す必要があります。 基底実装ロジックを置き換える必要があるクラスがある場合にのみ、基本実装の呼び出しを省略する必要があります。 オーバーライドするコードの前または後に基本実装を呼び出すかどうかは、実装の性質によって異なります。
前のコード サンプルでは、基底クラス OnKeyDown
仮想メソッドは、 ComponentWrapper
クラスと ComponentWrapperBase
クラスの両方でオーバーライドされます。 WPF イベント システムは ComponentWrapper.OnKeyDown
オーバーライド クラス イベント ハンドラーのみを呼び出すので、そのハンドラーは base.OnKeyDown(e)
を使用してクラス イベント ハンドラーを ComponentWrapperBase.OnKeyDown
オーバーライドし、 base.OnKeyDown(e)
を使用して StackPanel.OnKeyDown
仮想メソッドを呼び出します。 前のコード サンプルのイベントの順序は次のとおりです。
-
componentWrapper
にアタッチされたインスタンス ハンドラーは、PreviewKeyDown
ルーティング イベントによってトリガーされます。 -
componentWrapper
にアタッチされた静的クラス ハンドラーは、KeyDown
ルーティング イベントによってトリガーされます。 -
componentWrapperBase
にアタッチされた静的クラス ハンドラーは、KeyDown
ルーティング イベントによってトリガーされます。 -
componentWrapper
にアタッチされたオーバーライド クラス ハンドラーは、KeyDown
ルーティング イベントによってトリガーされます。 -
componentWrapperBase
にアタッチされたオーバーライド クラス ハンドラーは、KeyDown
ルーティング イベントによってトリガーされます。 -
KeyDown
ルーティング イベントは、処理済みとしてマークされます。 -
componentWrapper
にアタッチされたインスタンス ハンドラーは、KeyDown
ルーティング イベントによってトリガーされます。 ハンドラーは、handledEventsToo
パラメーターがtrue
に設定されて登録されました。
複合コントロールでの入力イベント抑制
一部の複合コントロールは、コンポーネント レベルで 入力イベント を抑制し、より多くの情報を運ぶか、より具体的な動作を意味するカスタマイズされた高レベルのイベントに置き換えます。 複合コントロールは、定義上、複数の実用的なコントロールまたはコントロールの基底クラスで構成されます。 従来の例として、さまざまなマウス イベントをButtonルーティング イベントに変換するClick コントロールがあります。
Button
基底クラスはButtonBaseであり、UIElementから間接的に派生します。 制御入力処理に必要なイベント インフラストラクチャの多くは、 UIElement
レベルで利用できます。
UIElement
は、MouseやMouseLeftButtonDownなど、いくつかのMouseRightButtonDownイベントを公開します。
UIElement
は、空の仮想メソッドである OnMouseLeftButtonDown を実装し、このメソッドを事前登録されたクラスハンドラーとして OnMouseRightButtonDown も実装します。
ButtonBase
は、これらのクラス ハンドラーをオーバーライドし、オーバーライド ハンドラー内で Handled プロパティを true
に設定し、 Click
イベントを発生させます。 ほとんどのリスナーの最終的な結果は、 MouseLeftButtonDown
イベントと MouseRightButtonDown
イベントが非表示になり、高レベルの Click
イベントが表示されます。
入力イベント抑制を回避する方法
個々のコントロール内のイベント抑制が、アプリケーションのイベント処理ロジックに干渉する場合があります。 たとえば、アプリケーションで XAML 属性構文を使用して XAML ルート要素の MouseLeftButtonDown イベントのハンドラーをアタッチした場合、 Button コントロールによって MouseLeftButtonDown
イベントが処理済みとしてマークされるため、そのハンドラーは呼び出されません。 処理されたルーティング イベントに対してアプリケーションのルートに向かって要素を呼び出す場合は、次のいずれかを実行できます。
UIElement.AddHandler(RoutedEvent, Delegate, Boolean) パラメーターを
handledEventsToo
に設定して、true
メソッドを呼び出してハンドラーをアタッチします。 この方法では、アタッチ先の要素のオブジェクト参照を取得した後、コードビハインド内でイベントハンドラーをアタッチする必要があります。処理済みとしてマークされたイベントがバブル入力イベントである場合は、ペアのプレビュー イベントのハンドラーをアタッチします (使用可能な場合)。 たとえば、コントロールが
MouseLeftButtonDown
イベントを抑制する場合は、代わりに PreviewMouseLeftButtonDown イベントのハンドラーをアタッチできます。 この方法は、イベント データを共有するプレビューとバブル入力イベントのペアでのみ機能します。PreviewMouseLeftButtonDown
イベントが完全に抑制されるため、Clickを処理済みとしてマークしないように注意してください。
入力イベントの抑制を回避する方法の例については、「 コントロールによるイベント抑制の回避」を参照してください。
こちらも参照ください
.NET Desktop feedback