Vista previa de eventos (WPF .NET)

Los eventos de vista previa, también conocidos como eventos de tunelización, son eventos enrutados que atraviesan hacia abajo el árbol de elementos del elemento raíz de la aplicación al elemento que ha generado el evento. El elemento que genera un evento se notifica como el Source en los datos del evento. No todos los escenarios de eventos admiten ni requieren eventos de vista previa. En este artículo se describe dónde existen los eventos de vista previa y cómo pueden interactuar con ellos las aplicaciones o componentes. Para obtener información sobre cómo crear un evento de vista previa, consulte Creación de un evento enrutado personalizado.

Importante

La documentación de la guía de escritorio para .NET 7 y .NET 6 está en proceso de elaboración.

Requisitos previos

En el artículo se da por supuesto un conocimiento básico de los eventos enrutados y que ha leído Información general sobre eventos enrutados. Para seguir los ejemplos de este artículo, le ayudará estar familiarizado con el lenguaje XAML y saber cómo escribir aplicaciones de Windows Presentation Foundation (WPF).

Eventos de vista previa marcados como controlados

Tenga cuidado al marcar los eventos de vista previa como controlados en los datos de eventos. Marcar un evento de vista previa como controlado en un elemento distinto del elemento que lo ha generado puede impedir que el elemento que lo ha generado controle el evento. A veces, marcar los eventos de vista previa como controlados es intencionado. Por ejemplo, un control compuesto podría suprimir los eventos generados por componentes individuales y reemplazarlos por eventos generados por el control completo. Los eventos personalizados de un control pueden proporcionar datos de eventos personalizados y desencadenadores en función de las relaciones de estado del componente.

En el caso de los eventos de entrada, los datos de eventos se comparten mediante los equivalentes de vista previa y no vista previa (propagación) de cada evento. Si usa un controlador de clase de eventos de vista previa para marcar un evento de entrada como controlado, normalmente no se invocarán controladores de clase para el evento de entrada de propagación. O bien, si usa un controlador de instancia de evento de vista previa para marcar un evento como controlado, los controladores de instancia para el evento de entrada de propagación normalmente no se invocarán. Aunque puede configurar controladores de clase e instancia para que se invoquen incluso si un evento está marcado como controlado, esa configuración del controlador no es común. Para obtener más información sobre el control de clases y cómo se relaciona con los eventos de vista previa, consulte Marcado de eventos enrutados como controlados y control de clases.

Nota

No todos los eventos de vista previa son eventos de tunelización. Por ejemplo, el evento de entrada PreviewMouseLeftButtonDown sigue una ruta hacia abajo a través del árbol de elementos, pero es un evento enrutado directo que se genera y se regenera por cada UIElement en la ruta.

Solución alternativa a la supresión de eventos mediante controles

Algunos controles compuestos suprimen los eventos de entrada en el nivel de componente para reemplazarlos por un evento de alto nivel personalizado. Por ejemplo, el ButtonBase de WPF marca el evento de entrada de propagación MouseLeftButtonDown como controlado en su método OnMouseLeftButtonDown y genera el evento Click. El evento MouseLeftButtonDown y sus datos de evento todavía continúan a lo largo de la ruta del árbol de elementos, pero dado que el evento se marca como Handled en los datos del evento, solo se invocan los controladores configurados para responder a eventos controlados.

Si desea que otros elementos hacia la raíz de la aplicación controlen un evento enrutado marcado como controlado, puede:

  • Adjuntar controladores mediante una llamada al método UIElement.AddHandler(RoutedEvent, Delegate, Boolean) con el parámetro handledEventsToo establecido en true. Este enfoque requiere adjuntar el controlador de eventos en el código subyacente después de obtener una referencia de objeto para el elemento al que se adjuntará.

  • Si el evento marcado como controlado es un evento de entrada de propagación, adjunte controladores para el evento de vista previa emparejada, si está disponible. Por ejemplo, si un control suprime el evento MouseLeftButtonDown, puede adjuntar un controlador para el evento PreviewMouseLeftButtonDown en su lugar. Este enfoque solo funciona para los eventos de entrada de elementos base que implementan estrategias de enrutamiento de tunelización y de propagación y comparten datos de eventos.

En el ejemplo siguiente se implementa un control personalizado rudimentario denominado componentWrapper que contiene un TextBox. El control se agrega a un objeto StackPanel denominado 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>

El control componentWrapper escucha el evento KeyDown de propagación generado por su componente TextBox cada vez que se produce una pulsación de tecla. En esa ocurrencia, el control componentWrapper:

  1. Marca el evento enrutado de propagación KeyDown como controlado para suprimirlo. Como resultado, solo se desencadena el controlador outerStackPanel configurado en código subyacente para responder a eventos KeyDowncontrolados. El controlador outerStackPanel asociado en XAML para eventos KeyDown no se invoca.

  2. Genera un evento enrutado de propagación personalizado denominado CustomKey, que desencadena el controlador outerStackPanel para el evento 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

En el ejemplo se muestran dos soluciones alternativas para que el evento enrutado suprimido KeyDown invoque un controlador de eventos asociado a outerStackPanel:

  • Adjunte un controlador de eventos PreviewKeyDown a outerStackPanel. Dado que un evento enrutado de entrada de vista previa precede al evento enrutado equivalente de propagación, el controlador PreviewKeyDown del ejemplo se ejecuta antes que el controlador KeyDown que suprime, tanto los eventos de vista previa, como los de propagación a través de sus datos de eventos compartidos.

  • Adjunte un controlador de eventos KeyDown al outerStackPanel mediante el método UIElement.AddHandler(RoutedEvent, Delegate, Boolean) en el código subyacente, con el parámetro handledEventsToo establecido en true.

Nota

Marcar equivalentes de vista previa o no de vista previa de eventos de entrada como controlados son estrategias para suprimir los eventos generados por los componentes de un control. El enfoque que use depende de los requisitos de la aplicación.

Vea también