Eventos de visualização (WPF .NET)

Os eventos de visualização, também conhecidos como eventos de túnel, são eventos roteados que percorrem a árvore de elementos do elemento raiz do aplicativo para o elemento que gerou o evento. O elemento que gera um evento é relatado como o Source nos dados do evento. Nem todos os cenários de eventos dão suporte ou exigem eventos de visualização. Este artigo descreve onde existem eventos de visualização e como os aplicativos ou componentes podem interagir com eles. Para obter informações sobre como criar um evento de visualização, consulte Como criar um evento roteado personalizado.

Pré-requisitos

O artigo pressupõe um conhecimento básico de eventos roteados e que você leu Visão geral de eventos roteados. Para seguir os exemplos neste artigo, é útil se você estiver familiarizado com XAML (Extensible Application Markup Language) e souber como escrever aplicativos WPF (Windows Presentation Foundation).

Visualizar eventos marcados como manipulados

Tenha cuidado ao marcar eventos de visualização como manipulados nos dados do evento. Marcar um evento de visualização como manipulado em um elemento diferente do elemento que o gerou pode impedir que o elemento que o gerou manipule o evento. Às vezes, marcar eventos de visualização como manipulados é intencional. Por exemplo, um controle composto pode suprimir eventos gerados por componentes individuais e substituí-los por eventos gerados pelo controle completo. Os eventos personalizados para um controle podem fornecer dados de evento personalizados e disparar com base nas relações de estado do componente.

Para eventos de entrada, os dados do evento são compartilhados pelos equivalentes de visualização e não visualização (propagação) de cada evento. Se você usar um manipulador de classe de evento de visualização para marcar um evento de entrada como manipulado, os manipuladores de classe para o evento de entrada de propagação normalmente não serão invocados. Ou, se você usar um manipulador de instâncias de evento de visualização para marcar um evento como manipulado, os manipuladores de instâncias para o evento de entrada de propagação normalmente não serão invocados. Embora você possa configurar manipuladores de classe e instância para serem invocados mesmo que um evento seja marcado como manipulado, essa configuração de manipulador não é comum. Para obter mais informações sobre a manipulação de classe e como ela se relaciona com eventos de visualização, consulte Marcando eventos roteados como manipulados e manipulação de classe.

Observação

Nem todos os eventos de visualização são eventos de túnel. Por exemplo, o PreviewMouseLeftButtonDown evento de entrada segue uma rota descendente por meio da árvore de elementos, mas é um evento roteado direto que é gerado e regerado por cada um UIElement na rota.

Trabalhando em torno da supressão de eventos por controles

Alguns controles compostos suprimem eventos de entrada no nível do componente para substituí-los por um evento de alto nível personalizado. Por exemplo, o WPF ButtonBase marca o evento de entrada de propagação como manipulado MouseLeftButtonDown em seu OnMouseLeftButtonDown método e gera o Click evento. O MouseLeftButtonDown evento e seus dados de evento ainda continuam ao longo da rota da árvore de elementos, mas como o evento é marcado como Handled em dados de evento, somente os manipuladores configurados para responder a eventos manipulados são chamados.

Se você quiser que outros elementos em direção à raiz do seu aplicativo manipulem um evento roteado marcado como manipulado, você pode:

  • Anexe manipuladores chamando o UIElement.AddHandler(RoutedEvent, Delegate, Boolean) método e definindo o parâmetro handledEventsToo como true. Essa abordagem requer anexar o manipulador de eventos no code-behind, depois de obter uma referência de objeto ao elemento ao qual ele será anexado.

  • Se o evento marcado como manipulado for um evento de propagação, anexe manipuladores para o evento de visualização equivalente, se disponível. Por exemplo, se um controle suprimir o MouseLeftButtonDown evento, você poderá anexar um manipulador para o PreviewMouseLeftButtonDown evento. Essa abordagem só funciona para eventos de entrada de elemento base que implementam estratégias de roteamento de tunelamento e bolha e compartilham dados de eventos.

O exemplo a seguir implementa um controle personalizado rudimentar chamado componentWrapper que contém um TextBox. O controle é adicionado a um 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>

O componentWrapper controle escuta o KeyDown evento de propagação gerado por seu TextBox componente sempre que ocorre um pressionamento de tecla. Nessa ocorrência, o componentWrapper controle:

  1. Marca o evento roteado de propagação como manipulado KeyDown para suprimi-lo. Como resultado, somente o outerStackPanel manipulador configurado no code-behind para responder a eventos manipulados KeyDown é disparado. O outerStackPanel manipulador anexado em XAML para KeyDown eventos não é invocado.

  2. Gera um evento roteado de propagação personalizado chamado CustomKey, que aciona o outerStackPanel manipulador para o CustomKey evento.

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

O exemplo demonstra duas soluções alternativas para fazer com que o evento roteado suprimido KeyDown invoque um manipulador de eventos anexado outerStackPanelao :

  • Anexe um PreviewKeyDown manipulador de eventos ao outerStackPanel. Como um evento roteado de entrada de visualização precede o evento roteado de propagação equivalente, o PreviewKeyDown manipulador no exemplo é executado à frente do KeyDown manipulador que suprime os eventos de visualização e propagação por meio de seus dados de evento compartilhados.

  • Anexe um KeyDown manipulador de eventos ao outerStackPanel usando o UIElement.AddHandler(RoutedEvent, Delegate, Boolean) método no code-behind, com o handledEventsToo parâmetro definido como true.

Observação

Marcar equivalentes de visualização ou não visualização de eventos de entrada como manipulados são estratégias para suprimir eventos gerados pelos componentes de um controle. A abordagem que você usa depende dos requisitos do seu aplicativo.

Confira também