預覽事件 (WPF .NET)

預覽事件,也稱為通道事件,是路由事件,從應用程式根項目向下周遊專案樹狀結構到引發事件的元素。 引發事件的 元素會回報為 Source 事件資料中的 。 並非所有事件案例都支援或需要預覽事件。 本文說明預覽事件存在的位置,以及應用程式或元件如何與其互動。 如需如何建立預覽事件的資訊,請參閱 如何建立自訂路由事件

重要

.NET 7 和 .NET 6 的桌面指南檔正在建置中。

必要條件

本文假設您已瞭解路由事件,而且您已閱讀 路由事件概觀 。 若要遵循本文中的範例,如果您熟悉可延伸的應用程式標記語言(XAML),並知道如何撰寫 Windows Presentation Foundation (WPF) 應用程式,它很有説明。

標示為已處理的預覽事件

將預覽事件標示為事件資料中所處理時,請小心。 將預覽事件標示為在引發之專案以外的專案上處理,可以防止引發預覽事件的專案處理事件。 有時會刻意將預覽事件標示為已處理。 例如,複合控制項可能會隱藏個別元件所引發的事件,並以完整控制項所引發的事件取代它們。 控制項的自訂事件可以根據元件狀態關聯性提供自訂事件資料和觸發程式。

針對 輸入事件 ,事件資料會由每個事件的預覽和非預覽(升階)對等專案共用。 如果您使用預覽事件類別處理常式將輸入事件標示為已處理,通常不會叫用反升輸入事件的類別處理常式。 或者,如果您使用預覽事件實例處理常式將事件標示為已處理,則通常不會叫用升階輸入事件的實例處理常式。 雖然您可以將類別和實例處理常式設定為叫用,即使事件標示為已處理,該處理常式組態並不常見。 如需類別處理及其與預覽事件關聯的詳細資訊,請參閱 將路由事件標示為已處理和類別處理

注意

並非所有預覽事件都是 通道 事件。 例如, PreviewMouseLeftButtonDown 輸入事件會透過元素樹狀結構追蹤向下路由,但是由 路由中的每個事件引發和重新引發 UIElement 的直接 路由事件。

依控制項處理事件隱藏

某些複合控制項會隱藏元件層級的輸入事件,以自訂的高階事件取代它們。 例如,WPF ButtonBase 會將反升輸入事件標示 MouseLeftButtonDown 為在其 OnMouseLeftButtonDown 方法中處理,並引發 Click 事件。 事件 MouseLeftButtonDown 及其事件資料仍會沿著元素樹狀目錄路由繼續,但由於事件在事件資料中標示為 Handled ,因此只會叫用設定為回應已處理事件的處理常式。

如果您想要讓應用程式根目錄的其他元素處理標示為已處理的路由事件,您可以:

下列範例會實作名為 的粗細自訂控制項,其中包含 componentWrapperTextBox 。 控制項會新增至 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>

每當發生擊鍵時, KeyDown 控制項 componentWrapper 會接聽其 TextBox 元件引發的反升事件。 在該發生時, componentWrapper 控制項:

  1. KeyDown將反升路由事件標示為已處理以隱藏事件。 因此,只會觸發程式 outerStackPanel 代碼後置中設定以回應 已處理 KeyDown 事件的處理常式。 outerStackPanel不會叫用 XAML 中為 KeyDown 事件附加的處理常式。

  2. 引發名為 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 事件處理常式:

  • PreviewKeyDown 事件處理常式附加至 outerStackPanel 。 由於預覽輸入路由事件在對等的反升路由事件之前,範例中的處理常式會在處理常式前面 KeyDown 執行, PreviewKeyDown 透過其共用事件資料隱藏預覽和反升事件。

  • KeyDown使用 UIElement.AddHandler(RoutedEvent, Delegate, Boolean) 程式碼後置中的 方法,將 handledEventsToo 事件處理常式附加至 outerStackPanel ,並將 參數設定為 true

注意

標記所處理之輸入事件的預覽或非預覽對等專案,都是隱藏控制群組件所引發事件的策略。 您所使用的方法取決於您的應用程式需求。

另請參閱