プレビュー イベント (トンネリング イベントとも呼ばれます) は、アプリケーション ルート要素からイベントを発生させた要素に要素ツリーを下方に走査するルーティング イベントです。 イベントを発生させる要素は、イベント データ内の Source として報告されます。 すべてのイベント シナリオでプレビュー イベントがサポートまたは必要なわけではありません。 この記事では、プレビュー イベントが存在する場所と、アプリケーションまたはコンポーネントがそれらを操作する方法について説明します。 プレビュー イベントを作成する方法については、「 カスタム ルーティング イベントを作成する方法」を参照してください。
[前提条件]
この記事では、ルーティング イベントに関する基本的な知識と、 ルーティング イベントの概要を読んだことを前提としています。 この記事の例に従うと、拡張アプリケーション マークアップ言語 (XAML) に慣れている場合や、Windows Presentation Foundation (WPF) アプリケーションを記述する方法を理解している場合に役立ちます。
処理済みとしてマークされたプレビュー イベント
プレビュー イベントをイベント データで処理されるものとしてマークする場合は注意してください。 プレビュー イベントを発生させた要素以外の要素で処理されたイベントとしてマークすると、イベントを発生させた要素がイベントを処理できなくなる可能性があります。 プレビュー イベントを処理されたイベントとしてマークすることが意図的な場合があります。 たとえば、複合コントロールでは、個々のコンポーネントによって発生したイベントを抑制し、完全なコントロールによって発生したイベントに置き換えることができます。 コントロールのカスタム イベントは、コンポーネントの状態リレーションシップに基づいて、カスタマイズされたイベント データとトリガーを提供できます。
入力イベントの場合、イベント データは、各イベントのプレビューと非プレビュー (バブル) の両方で共有されます。 プレビュー イベント クラス ハンドラーを使用して入力イベントを処理済みとしてマークする場合、通常、バブル入力イベントのクラス ハンドラーは呼び出されません。 または、プレビュー イベント インスタンス ハンドラーを使用してイベントを処理済みとしてマークする場合、通常、バブル入力イベントのインスタンス ハンドラーは呼び出されません。 イベントが処理済みとしてマークされている場合でも、呼び出されるクラスハンドラーとインスタンス ハンドラーを構成できますが、そのハンドラー構成は一般的ではありません。 クラス処理とプレビュー イベントとの関係の詳細については、「ルーティング イベントを処理済み としてマークする」および「クラス処理」を参照してください。
注
すべてのプレビュー イベントが トンネリング イベントであるわけではありません。 たとえば、PreviewMouseLeftButtonDown入力イベントは要素ツリーを通る下向きのルートに従いますが、ルート内の各によって発生および再評価されるUIElementルーティング イベントです。
コントロールによるイベント抑制の回避
一部の複合コントロールでは、コンポーネント レベルで入力イベントを抑制し、カスタマイズされた高レベルのイベントに置き換えます。 たとえば、WPF ButtonBase は、 MouseLeftButtonDown バブル入力イベントを OnMouseLeftButtonDown メソッドで処理としてマークし、 Click イベントを発生させます。
MouseLeftButtonDown
イベントとそのイベント データは要素ツリー ルートに沿って引き続き実行されますが、イベントはイベント データでHandledとしてマークされているため、処理されたイベントに応答するように構成されたハンドラーのみが呼び出されます。
処理済みとしてマークされたルーティング イベントをアプリケーションのルートに向かって他の要素で処理する場合は、次のいずれかを実行できます。
UIElement.AddHandler(RoutedEvent, Delegate, Boolean) メソッドを呼び出し、パラメーター
handledEventsToo
をtrue
に設定してハンドラーをアタッチします。 この方法では、アタッチ先の要素へのオブジェクト参照を取得した後、分離コードでイベント ハンドラーをアタッチする必要があります。処理済みとしてマークされたイベントがバブル イベントである場合は、同等のプレビュー イベントのハンドラーをアタッチします (使用可能な場合)。 たとえば、コントロールが MouseLeftButtonDown イベントを抑制する場合は、代わりに PreviewMouseLeftButtonDown イベントのハンドラーをアタッチできます。 この方法は、トンネリングとバブル ルーティングの両方の戦略 を 実装し、イベント データ を共有する 基本要素入力イベントでのみ機能します。
次の例では、componentWrapper
を含む TextBox という基本的なカスタム コントロールを実装します。 コントロールは、StackPanelという名前のouterStackPanel
に追加されます。
<StackPanel Name="outerStackPanel"
VerticalAlignment="Center"
custom:ComponentWrapper.CustomKey="Handler_PrintEventInfo"
TextBox.KeyDown="Handler_PrintEventInfo"
TextBox.PreviewKeyDown="Handler_PrintEventInfo" >
<custom:ComponentWrapper
x:Name="componentWrapper"
TextBox.KeyDown="ComponentWrapper_KeyDown"
custom:ComponentWrapper.CustomKey="Handler_PrintEventInfo"
HorizontalAlignment="Center">
<TextBox Name="componentTextBox" Width="200" KeyDown="Handler_PrintEventInfo" />
</custom:ComponentWrapper>
</StackPanel>
componentWrapper
コントロールは、キーストロークが発生するたびに、KeyDown コンポーネントによって発生するTextBox
バブル イベントをリッスンします。 その場合、 componentWrapper
コントロールは次の操作を行います。
KeyDown
バブリングルーティングイベントを処理済みとしてマークして、その伝播を抑制します。 その結果、outerStackPanel
イベントに応答するように分離コードで構成されたKeyDown
ハンドラーのみがトリガーされます。 イベント用に XAML にアタッチされた ハンドラーは呼び出されません。CustomKey
イベントのouterStackPanel
ハンドラーをトリガーする、CustomKey
という名前のカスタム バブル ルーティング イベントを発生させます。
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Attach a handler on outerStackPanel that will be invoked by handled KeyDown events.
outerStackPanel.AddHandler(KeyDownEvent, new RoutedEventHandler(Handler_PrintEventInfo),
handledEventsToo: true);
}
private void ComponentWrapper_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
Handler_PrintEventInfo(sender, e);
Debug.WriteLine("KeyDown event marked as handled on componentWrapper.\r\n" +
"CustomKey event raised on componentWrapper.");
// Mark the event as handled.
e.Handled = true;
// Raise the custom click event.
componentWrapper.RaiseCustomRoutedEvent();
}
private void Handler_PrintEventInfo(object sender, System.Windows.Input.KeyEventArgs e)
{
string senderName = ((FrameworkElement)sender).Name;
string sourceName = ((FrameworkElement)e.Source).Name;
string eventName = e.RoutedEvent.Name;
string handledEventsToo = e.Handled ? " Parameter handledEventsToo set to true." : "";
Debug.WriteLine($"Handler attached to {senderName} " +
$"triggered by {eventName} event raised on {sourceName}.{handledEventsToo}");
}
private void Handler_PrintEventInfo(object sender, RoutedEventArgs e)
{
string senderName = ((FrameworkElement)sender).Name;
string sourceName = ((FrameworkElement)e.Source).Name;
string eventName = e.RoutedEvent.Name;
string handledEventsToo = e.Handled ? " Parameter handledEventsToo set to true." : "";
Debug.WriteLine($"Handler attached to {senderName} " +
$"triggered by {eventName} event raised on {sourceName}.{handledEventsToo}");
}
// Debug output:
//
// Handler attached to outerStackPanel triggered by PreviewKeyDown event raised on componentTextBox.
// Handler attached to componentTextBox triggered by KeyDown event raised on componentTextBox.
// Handler attached to componentWrapper triggered by KeyDown event raised on componentTextBox.
// KeyDown event marked as handled on componentWrapper.
// CustomKey event raised on componentWrapper.
// Handler attached to componentWrapper triggered by CustomKey event raised on componentWrapper.
// Handler attached to outerStackPanel triggered by CustomKey event raised on componentWrapper.
// Handler attached to outerStackPanel triggered by KeyDown event raised on componentTextBox. Parameter handledEventsToo set to true.
}
public class ComponentWrapper : StackPanel
{
// Register a custom routed event using the Bubble routing strategy.
public static readonly RoutedEvent CustomKeyEvent =
EventManager.RegisterRoutedEvent(
name: "CustomKey",
routingStrategy: RoutingStrategy.Bubble,
handlerType: typeof(RoutedEventHandler),
ownerType: typeof(ComponentWrapper));
// Provide CLR accessors for assigning an event handler.
public event RoutedEventHandler CustomKey
{
add { AddHandler(CustomKeyEvent, value); }
remove { RemoveHandler(CustomKeyEvent, value); }
}
public void RaiseCustomRoutedEvent()
{
// Create a RoutedEventArgs instance.
RoutedEventArgs routedEventArgs = new(routedEvent: CustomKeyEvent);
// Raise the event, which will bubble up through the element tree.
RaiseEvent(routedEventArgs);
}
}
Partial Public Class MainWindow
Inherits Window
Public Sub New()
InitializeComponent()
' Attach a handler on outerStackPanel that will be invoked by handled KeyDown events.
outerStackPanel.[AddHandler](KeyDownEvent, New RoutedEventHandler(AddressOf Handler_PrintEventInfo),
handledEventsToo:=True)
End Sub
Private Sub ComponentWrapper_KeyDown(sender As Object, e As KeyEventArgs)
Handler_PrintEventInfo(sender, e)
Debug.WriteLine("KeyDown event marked as handled on componentWrapper." &
vbCrLf & "CustomKey event raised on componentWrapper.")
' Mark the event as handled.
e.Handled = True
' Raise the custom click event.
componentWrapper.RaiseCustomRoutedEvent()
End Sub
Private Sub Handler_PrintEventInfo(sender As Object, e As KeyEventArgs)
Dim senderName As String = CType(sender, FrameworkElement).Name
Dim sourceName As String = CType(e.Source, FrameworkElement).Name
Dim eventName As String = e.RoutedEvent.Name
Dim handledEventsToo As String = If(e.Handled, " Parameter handledEventsToo set to true.", "")
Debug.WriteLine($"Handler attached to {senderName} " &
$"triggered by {eventName} event raised on {sourceName}.{handledEventsToo}")
End Sub
Private Sub Handler_PrintEventInfo(sender As Object, e As RoutedEventArgs)
Dim senderName As String = CType(sender, FrameworkElement).Name
Dim sourceName As String = CType(e.Source, FrameworkElement).Name
Dim eventName As String = e.RoutedEvent.Name
Dim handledEventsToo As String = If(e.Handled, " Parameter handledEventsToo set to true.", "")
Debug.WriteLine($"Handler attached to {senderName} " &
$"triggered by {eventName} event raised on {sourceName}.{handledEventsToo}")
End Sub
' Debug output
'
' Handler attached to outerStackPanel triggered by PreviewKeyDown event raised on componentTextBox.
' Handler attached to componentTextBox triggered by KeyDown event raised on componentTextBox.
' Handler attached to componentWrapper triggered by KeyDown event raised on componentTextBox.
' KeyDown event marked as handled on componentWrapper.
' CustomKey event raised on componentWrapper.
' Handler attached to componentWrapper triggered by CustomKey event raised on componentWrapper.
' Handler attached to outerStackPanel triggered by CustomKey event raised on componentWrapper.
' Handler attached to outerStackPanel triggered by KeyDown event raised on componentTextBox. Parameter handledEventsToo set to true.
End Class
Public Class ComponentWrapper
Inherits StackPanel
' Register a custom routed event with the Bubble routing strategy.
Public Shared ReadOnly CustomKeyEvent As RoutedEvent =
EventManager.RegisterRoutedEvent(
name:="CustomKey",
routingStrategy:=RoutingStrategy.Bubble,
handlerType:=GetType(RoutedEventHandler),
ownerType:=GetType(ComponentWrapper))
' Provide CLR accessors to support event handler assignment.
Public Custom Event CustomKey As RoutedEventHandler
AddHandler(value As RoutedEventHandler)
[AddHandler](CustomKeyEvent, value)
End AddHandler
RemoveHandler(value As RoutedEventHandler)
[RemoveHandler](CustomKeyEvent, value)
End RemoveHandler
RaiseEvent(sender As Object, e As RoutedEventArgs)
[RaiseEvent](e)
End RaiseEvent
End Event
Public Sub RaiseCustomRoutedEvent()
' Create a RoutedEventArgs instance & raise the event,
' which will bubble up through the element tree.
Dim routedEventArgs As New RoutedEventArgs(routedEvent:=CustomKeyEvent)
[RaiseEvent](routedEventArgs)
End Sub
End Class
この例では、抑制された KeyDown
ルーティング イベントを取得して、 outerStackPanel
にアタッチされたイベント ハンドラーを呼び出す 2 つの回避策を示します。
PreviewKeyDownに
outerStackPanel
イベント ハンドラーをアタッチします。 プレビュー入力ルーティング イベントは同等のバブル ルーティング イベントの前に存在するため、この例のPreviewKeyDown
ハンドラーは、共有イベント データを介してプレビュー イベントとバブル イベントの両方を抑制するKeyDown
ハンドラーの前に実行されます。KeyDown
パラメーターをouterStackPanel
に設定して、分離コードの UIElement.AddHandler(RoutedEvent, Delegate, Boolean) メソッドを使用して、handledEventsToo
イベント ハンドラーをtrue
にアタッチします。
注
入力イベントのプレビューまたはプレビュー以外の同等のイベントを処理対象としてマークすることは、コントロールのコンポーネントによって発生するイベントを抑制するための戦略です。 使用する方法は、アプリケーションの要件によって異なります。
こちらも参照ください
.NET Desktop feedback