共用方式為


預覽事件 (WPF.NET)

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

必要條件

本文假設您已基本了解路由事件,而且您已閱讀路由事件概觀。 若要遵循本文中的範例,建議您先熟悉 Extensible Application Markup Language (XAML),並了解如何撰寫 Windows Presentation Foundation (WPF) 應用程式。

標示為已處理的預覽事件

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

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

注意

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

處理控制項的事件歸併

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

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

  • 呼叫 UIElement.AddHandler(RoutedEvent, Delegate, Boolean) 方法,並將參數 handledEventsToo 設定為 true 來附加處理常式。 此方法需要先獲得要附加至其中的元素的物件參考,然後再以程式碼後置的方式附加事件處理常式。

  • 如果標示為已處理的事件是事件反昇事件,則請附加對等預覽事件的處理常式 (如果有的話)。 例如,如果控制項隱藏 MouseLeftButtonDown 事件,您可以改為附加 PreviewMouseLeftButtonDown 事件的處理常式。 此方法僅適用於實作通道事件反昇路由策略並共用事件資料的基底元素輸入事件。

下列範例實作名為 componentWrapper 的基本自訂控制項,其中包含 TextBox。 控制項會新增至名為 outerStackPanelStackPanel

<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 控制項便會接聽其 TextBox 元件所引發的 KeyDown 事件反昇事件。 發生該情況時,componentWrapper 控制項:

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

  2. 引發名為 CustomKey 的自訂事件反昇路由事件,這會觸發 CustomKey 事件的 outerStackPanel 處理常式。

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。 由於預覽輸入路由事件先於對等的事件反昇路由事件,因此範例中的 PreviewKeyDown 處理常式比 KeyDown 處理常式還先執行,後者會透過共用事件資料歸併預覽和事件反昇事件。

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

注意

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

另請參閱