События предварительного просмотра (WPF .NET)

События предварительного просмотра, также называемые событиями туннелирования. Это перенаправленные события, которые проходят вниз по дереву элементов от корневого элемента приложения к элементу, вызвавшему событие. Элемент, который вызывает событие, в данных события указан как Source. Не во всех сценариях событий поддерживаются или требуются события предварительного просмотра. В этой статье описываются происхождение событий предварительного просмотра и то, как приложения или компоненты могут взаимодействовать с ними. Сведения о создании событий предварительного просмотра см. в статье Практическое руководство. Создание пользовательских перенаправленных событий.

Важно!

Документация по рабочему столу для .NET 7 и .NET 6 находится в стадии разработки.

Необходимые компоненты

Для понимания статьи нужно иметь общее представление о перенаправленных событиях и прочитать статью Общие сведения о перенаправленных событиях. Чтобы понимать примеры в этой статье полезно познакомиться с языком 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 делает следующее:

  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. Так как входное перенаправленное событие предварительного просмотра предшествует эквивалентному перенаправленному событию с восходящей маршрутизацией, обработчик PreviewKeyDown в данном примере выполняется перед обработчиком KeyDown, который подавляет события предварительного просмотра и события с восходящей маршрутизацией в своих общих данных событий.

  • Присоедините обработчик событий KeyDown к outerStackPanel с помощью метода UIElement.AddHandler(RoutedEvent, Delegate, Boolean) в коде программной части с параметром handledEventsToo, которому задано значение true.

Примечание.

Маркировка эквивалентов входных событий с предварительным просмотром или без него как обработанных — это две стратегии подавления событий, вызванных компонентами элемента управления. Используемый подход зависит от требований приложения.

См. также