Compartir a través de


Marcar eventos enrutados como controlados y control de clases

Aunque no hay ninguna regla absoluta para cuándo marcar un evento enrutado como controlado, considere la posibilidad de marcar un evento como controlado si el código responde al evento de forma significativa. Un evento enrutado marcado como controlado continuará a lo largo de su ruta, pero solo se invocan controladores configurados para responder a eventos controlados. Básicamente, marcar un evento enrutado como controlado limita su acceso a los oyentes a lo largo de la ruta del evento.

Los controladores de eventos enrutados pueden ser controladores de instancia o controladores de clase. Los controladores de instancia controlan eventos enrutados en objetos o elementos XAML. Los controladores de clase controlan un evento enrutado en un nivel de clase y se invocan antes de que cualquier controlador de instancia responda al mismo evento en cualquier instancia de la clase. Cuando los eventos enrutados se marcan como controlados, a menudo se marcan como tales dentro de los controladores de clase. En este artículo se describen las ventajas y posibles problemas de marcado de eventos enrutados como controlados, los distintos tipos de eventos enrutados y controladores de eventos enrutados y la supresión de eventos en controles compuestos.

Prerrequisitos

El artículo supone un conocimiento básico de los eventos enrutados y que hayas leído Información general sobre eventos enrutados. Para seguir los ejemplos de este artículo, le ayuda si está familiarizado con el lenguaje de marcado extensible de aplicaciones (XAML) y sabe cómo escribir aplicaciones de Windows Presentation Foundation (WPF).

Cuándo marcar eventos enrutados como controlados

Normalmente, solo un controlador debe proporcionar una respuesta significativa para cada evento enrutado. Evite utilizar el sistema de eventos enrutados para ofrecer una respuesta significativa a través de varios controladores. La definición de lo que constituye una respuesta significativa es subjetiva y depende de la aplicación. Como guía general:

  • Entre las respuestas significativas se incluyen establecer el foco, modificar el estado público, establecer propiedades que afectan a la representación visual, generar nuevos eventos y controlar completamente un evento.
  • Entre las respuestas insignificantes se incluyen la modificación del estado privado sin impacto visual o mediante programación, el registro de eventos y el examen de los datos de eventos sin responder al evento.

Algunos controles de WPF suprimen los eventos de nivel de componente que no necesitan un control adicional marcandolos como controlados. Si desea controlar un evento marcado como controlado por un control, consulte Working around event suppression by controls (Solución de la supresión de eventos por controles).

Para marcar un evento como controlado, establezca en sus datos del evento el valor de la propiedad Handled. Aunque es posible revertir ese valor a false, la necesidad de hacerlo debe ser poco frecuente.

Vista previa y propagación de pares de eventos enrutados

Los pares de eventos enrutados de vista previa y de propagación son específicos de los eventos de entrada. Varios eventos de entrada implementan un par de eventos enrutados que hacen uso de tunelización y burbujeo, como y . El prefijo Preview indica que el evento burbujeante comienza cuando se completa el evento de vista previa. Cada par de eventos de vista previa y de propagación comparte la misma instancia de datos de eventos.

Los controladores de eventos enrutados se invocan en un orden que corresponde a la estrategia de enrutamiento de un evento:

  1. El evento de vista previa viaja desde el elemento raíz de la aplicación hasta el elemento que generó el evento enrutado. Los controladores de eventos en versión preliminar adjuntos al elemento raíz de la aplicación se invocan primero, seguidos de controladores asociados a elementos anidados sucesivos.
  2. Una vez completado el evento de vista previa, el evento de burbujeo asociado se desplaza desde el elemento que generó el evento enrutado hasta el elemento raíz de la aplicación. Los controladores de eventos de burbujeo asociados al mismo elemento que generó el evento enrutado se invocan primero, seguidos de los controladores asociados a elementos padre sucesivos.

Los eventos emparejados de vista previa y propagación forman parte de la implementación interna de varias clases wpF que declaran y generan sus propios eventos enrutados. Sin esa implementación interna de nivel de clase, los eventos enrutados de vista previa y propagación son completamente independientes y no compartirán datos de eventos, independientemente de la nomenclatura de eventos. Para obtener información sobre cómo implementar eventos enrutados de entrada de burbujeo o túnel en una clase personalizada, consulte Crear un evento enrutado personalizado.

Dado que cada par de eventos de previsualización y propagación comparte la misma instancia de datos del evento, si un evento enrutado de previsualización se marca como manejado, también se manejará su evento de propagación emparejado. Si un evento enrutado de propagación se marca como controlado, no afectará al evento de vista previa emparejada porque el evento de vista previa se completó. Tenga cuidado al marcar pares de eventos de entrada de vista previa y de propagación como controlado. Un evento de entrada de vista previa controlada no invocará ningún controlador de eventos registrado normalmente durante el resto de la ruta de tunelización y no se generará el evento de propagación emparejado. Un evento de entrada de propagación controlado no invocará ningún controlador de eventos registrado normalmente durante el resto de la ruta de propagación.

Controladores de eventos enrutados de instancia y clase

Los controladores de eventos enrutados pueden ser controladores de instancia o controladores de clase . Los controladores de clase para una clase determinada se invocan antes de que cualquier controlador de instancia responda al mismo evento en cualquier instancia de esa clase. Debido a este comportamiento, cuando los eventos enrutados se marcan como controlados, a menudo se marcan como tales dentro de los controladores de clase. Hay dos tipos de controladores de clases:

Controladores de eventos de instancia

Puedes adjuntar controladores de instancia a objetos o elementos XAML llamando directamente al AddHandler método . Los eventos enrutados de WPF implementan un contenedor de eventos de Common Language Runtime (CLR) que usa el AddHandler método para adjuntar controladores de eventos. Dado que la sintaxis de atributo XAML para adjuntar controladores de eventos da como resultado una llamada al contenedor de eventos CLR, adjuntar controladores en XAML, incluso, resulta en una llamada AddHandler. Para eventos controlados:

  • Los controladores que se unen mediante la sintaxis de atributo XAML o la firma común de AddHandler no son invocados.
  • Controladores que se anexaron mediante la AddHandler(RoutedEvent, Delegate, Boolean) sobrecarga con el handledEventsToo parámetro configurado a true son invocados. Esta sobrecarga está disponible para los casos poco frecuentes cuando es necesario responder a eventos controlados. Por ejemplo, algún elemento de un árbol de elementos ha marcado un evento como controlado, pero otros elementos más a lo largo de la ruta de eventos deben responder al evento controlado.

En el ejemplo XAML siguiente se agrega un control personalizado denominado componentWrapper, que ajusta un TextBox denominado componentTextBox, a un StackPanel denominado outerStackPanel. Se adjunta un controlador de eventos de instancia al evento PreviewKeyDown mediante la sintaxis de atributo XAML en componentWrapper. Como resultado, el controlador de instancia solo responderá a eventos de tunelización no controlados PreviewKeyDown generados por .componentTextBox

<StackPanel Name="outerStackPanel" VerticalAlignment="Center">
    <custom:ComponentWrapper
        x:Name="componentWrapper"
        TextBox.PreviewKeyDown="HandlerInstanceEventInfo"
        HorizontalAlignment="Center">
        <TextBox Name="componentTextBox" Width="200" />
    </custom:ComponentWrapper>
</StackPanel>

El constructor MainWindow adjunta un controlador de instancia para el evento de burbujeo KeyDown al componentWrapper utilizando la sobrecarga UIElement.AddHandler(RoutedEvent, Delegate, Boolean), con el parámetro handledEventsToo configurado en true. Como resultado, el controlador de eventos de instancia responderá a eventos no controlados y controlados.

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        // Attach an instance handler on componentWrapper that will be invoked by handled KeyDown events.
        componentWrapper.AddHandler(KeyDownEvent, new RoutedEventHandler(Handler.InstanceEventInfo),
            handledEventsToo: true);
    }

    // The handler attached to componentWrapper in XAML.
    public void HandlerInstanceEventInfo(object sender, KeyEventArgs e) => 
        Handler.InstanceEventInfo(sender, e);
}
Partial Public Class MainWindow
    Inherits Window

    Public Sub New()
        InitializeComponent()

        ' Attach an instance handler on componentWrapper that will be invoked by handled KeyDown events.
        componentWrapper.[AddHandler](KeyDownEvent, New RoutedEventHandler(AddressOf InstanceEventInfo),
                                      handledEventsToo:=True)
    End Sub

    ' The handler attached to componentWrapper in XAML.
    Public Sub HandlerInstanceEventInfo(sender As Object, e As KeyEventArgs)
        InstanceEventInfo(sender, e)
    End Sub

End Class

La implementación de código subyacente de ComponentWrapper se muestra en la sección siguiente.

Controladores de eventos de clase estática

Puede adjuntar controladores de eventos de clase estática llamando al RegisterClassHandler método en el constructor estático de una clase. Cada clase de una jerarquía de clases puede registrar su propio controlador de clase estática para cada evento enrutado. Como resultado, puede haber varios controladores de clases estáticos invocados para el mismo evento en cualquier nodo determinado de la ruta de eventos. Cuando se construye la ruta de evento para el evento, se agregan todos los controladores de clases estáticos para cada nodo a la ruta de eventos. El orden de invocación de controladores de clases estáticos en un nodo comienza con el controlador de clases estáticas más derivado, seguido de controladores de clases estáticos de cada clase base sucesiva.

Los manejadores de eventos de clases estáticas registrados usando la sobrecarga RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) con el parámetro handledEventsToo establecido en true responderán a eventos enrutados tanto no gestionados como gestionados.

Normalmente, los controladores de clases estáticos se registran para responder a eventos no controlados. En cuyo caso, si un controlador de clases derivada de un nodo marca un evento como controlado, no se invocarán controladores de clase base para ese evento. En ese escenario, el controlador de clases base se reemplaza eficazmente por el controlador de clases derivada. Los controladores de clases base suelen contribuir a controlar el diseño en áreas como la apariencia visual, la lógica de estado, el control de entrada y el control de comandos, por lo que tenga cuidado con la sustitución de ellos. Los controladores de clases derivados que no marcan un evento como controlado terminan complementando los controladores de clase base en lugar de reemplazarlos.

En el ejemplo de código siguiente se muestra la jerarquía de clases para el ComponentWrapper control personalizado al que se hizo referencia en el CÓDIGO XAML anterior. La ComponentWrapper clase deriva de la ComponentWrapperBase clase , que a su vez deriva de la StackPanel clase . El RegisterClassHandler método, que se usa en el constructor estático de las ComponentWrapper clases y ComponentWrapperBase , registra un controlador de eventos de clase estática para cada una de esas clases. El sistema de eventos WPF invoca al ComponentWrapper controlador de clases estáticas antes del ComponentWrapperBase controlador de clases estáticas.

public class ComponentWrapper : ComponentWrapperBase
{
    static ComponentWrapper()
    {
        // Class event handler implemented in the static constructor.
        EventManager.RegisterClassHandler(typeof(ComponentWrapper), KeyDownEvent, 
            new RoutedEventHandler(Handler.ClassEventInfo_Static));
    }

    // Class event handler that overrides a base class virtual method.
    protected override void OnKeyDown(KeyEventArgs e)
    {
        Handler.ClassEventInfo_Override(this, e);

        // Call the base OnKeyDown implementation on ComponentWrapperBase.
        base.OnKeyDown(e);
    }
}

public class ComponentWrapperBase : StackPanel
{
    // Class event handler implemented in the static constructor.
    static ComponentWrapperBase()
    {
        EventManager.RegisterClassHandler(typeof(ComponentWrapperBase), KeyDownEvent, 
            new RoutedEventHandler(Handler.ClassEventInfoBase_Static));
    }

    // Class event handler that overrides a base class virtual method.
    protected override void OnKeyDown(KeyEventArgs e)
    {
        Handler.ClassEventInfoBase_Override(this, e);

        e.Handled = true;
        Debug.WriteLine("The KeyDown routed event is marked as handled.");

        // Call the base OnKeyDown implementation on StackPanel.
        base.OnKeyDown(e);
    }
}
Public Class ComponentWrapper
    Inherits ComponentWrapperBase

    Shared Sub New()
        ' Class event handler implemented in the static constructor.
        EventManager.RegisterClassHandler(GetType(ComponentWrapper), KeyDownEvent,
                                          New RoutedEventHandler(AddressOf ClassEventInfo_Static))
    End Sub

    ' Class event handler that overrides a base class virtual method.
    Protected Overrides Sub OnKeyDown(e As KeyEventArgs)
        ClassEventInfo_Override(Me, e)

        ' Call the base OnKeyDown implementation on ComponentWrapperBase.
        MyBase.OnKeyDown(e)
    End Sub

End Class

Public Class ComponentWrapperBase
    Inherits StackPanel

    Shared Sub New()
        ' Class event handler implemented in the static constructor.
        EventManager.RegisterClassHandler(GetType(ComponentWrapperBase), KeyDownEvent,
                                          New RoutedEventHandler(AddressOf ClassEventInfoBase_Static))
    End Sub

    ' Class event handler that overrides a base class virtual method.
    Protected Overrides Sub OnKeyDown(e As KeyEventArgs)
        ClassEventInfoBase_Override(Me, e)

        e.Handled = True
        Debug.WriteLine("The KeyDown event is marked as handled.")

        ' Call the base OnKeyDown implementation on StackPanel.
        MyBase.OnKeyDown(e)
    End Sub

End Class

La implementación de código subyacente de los controladores de eventos de clase override de este ejemplo de código se describe en la sección siguiente.

Invalidar controladores de eventos de clase

Algunas clases base de elementos visuales exponen métodos virtuales vacíos On<nombre del evento> y OnPreview<nombre del evento> para cada uno de sus eventos de entrada enrutados públicos. Por ejemplo, UIElement implementa los OnKeyDown controladores de eventos virtuales y OnPreviewKeyDown , y muchos otros. Puede sobrescribir los controladores de eventos virtuales de la clase base para implementar controladores de eventos sobrescritos en sus clases derivadas. Por ejemplo, puede agregar un manejador de clases de sobrescritura para el evento DragEnter en cualquier clase derivada UIElement sobrescribiendo el método virtual OnDragEnter. La invalidación de métodos virtuales de clase base es una manera más sencilla de implementar controladores de clase que registrar controladores de clase en un constructor estático. Dentro de la sobrescritura, puede generar eventos, iniciar lógica específica de clase para cambiar las propiedades de los elementos en instancias específicas, marcar el evento como controlado o realizar otras acciones de lógica para el manejo de eventos.

A diferencia de los controladores de eventos de clase estática, el sistema de eventos wpF solo invoca controladores de eventos de clase de invalidación para la clase más derivada de una jerarquía de clases. La clase más derivada de una jerarquía de clases puede usar la palabra clave base para llamar a la implementación base del método virtual. En la mayoría de los casos, debe llamar a la implementación base, independientemente de si marca un evento como controlado. Solo debe omitir la llamada a la implementación base si la clase tiene un requisito para reemplazar la lógica de implementación base, si existe. Si llama a la implementación base antes o después de reemplazar el código depende de la naturaleza de la implementación.

En el ejemplo de código anterior, el método virtual de la clase OnKeyDown base se sobrescribe en las clases ComponentWrapper y ComponentWrapperBase. Dado que el sistema de eventos de WPF solo invoca el ComponentWrapper.OnKeyDown controlador de eventos de clase sobrescritura, ese controlador usa base.OnKeyDown(e) para llamar al ComponentWrapperBase.OnKeyDown controlador de eventos de clase sobrescritura, que a su vez usa base.OnKeyDown(e) para llamar al StackPanel.OnKeyDown método virtual. El orden de los eventos del ejemplo de código anterior es:

  1. El controlador de instancia asociado con componentWrapper es activado por el evento enrutado PreviewKeyDown.
  2. El controlador de clase estático asociado a componentWrapper se desencadena por el evento enrutado KeyDown.
  3. El controlador de clase estático asociado a componentWrapperBase se desencadena por el evento enrutado KeyDown.
  4. El controlador de clases de invalidación asociado a componentWrapper es desencadenado por el evento enrutado KeyDown.
  5. El controlador de clases de invalidación asociado a componentWrapperBase es desencadenado por el evento enrutado KeyDown.
  6. El KeyDown evento enrutado se marca como controlado.
  7. El controlador de instancia asociado con componentWrapper es activado por el evento enrutado KeyDown. El controlador se registró con el parámetro handledEventsToo establecido en true.

Supresión de eventos de entrada en controles compuestos

Algunos controles compuestos suprimen los eventos de entrada en el nivel de componente para reemplazarlos por un evento personalizado de alto nivel que contiene más información o implica un comportamiento más específico. Un control compuesto se compone por definición de varios controles prácticos o clases base de control. Un ejemplo clásico es el Button control , que transforma varios eventos del mouse en un Click evento enrutado. La Button clase base es ButtonBase, que deriva indirectamente de UIElement. Gran parte de la infraestructura de eventos necesaria para el procesamiento de entrada de control está disponible en el UIElement nivel . UIElement expone varios Mouse eventos como MouseLeftButtonDown y MouseRightButtonDown. UIElement también implementa los métodos virtuales vacíos OnMouseLeftButtonDown y OnMouseRightButtonDown como controladores de clases previamente registrados. ButtonBase invalida estos controladores de clase y, dentro del controlador de sobreescritura, establece la propiedad Handled en true y genera un evento Click. El resultado final para la mayoría de los oyentes es que los eventos MouseLeftButtonDown y MouseRightButtonDown están ocultos y el evento de alto nivel Click es visible.

Solución de la supresión de eventos de entrada

A veces, la supresión de eventos dentro de controles individuales puede interferir con la lógica de control de eventos en la aplicación. Por ejemplo, si la aplicación usó la sintaxis de atributo XAML para adjuntar un controlador para el MouseLeftButtonDown evento en el elemento raíz XAML, ese controlador no se invocará porque el Button control marca el MouseLeftButtonDown evento como controlado. Si desea que los elementos en la raíz de su aplicación se invoquen para un evento controlado y enrutado, puede:

  • Asocie 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 asociará.

  • Si el evento marcado como gestionado es un evento de entrada de burbujeo, adjunte manejadores para el evento de vista previa emparejado, si está disponible. Por ejemplo, si un control suprime el MouseLeftButtonDown evento, puede adjuntar un controlador para el PreviewMouseLeftButtonDown evento en su lugar. Este enfoque solo funciona para pares de eventos de entrada de vista previa y de propagación, que comparten datos de eventos. Tenga cuidado de no marcar el PreviewMouseLeftButtonDown como manejado, ya que eso suprimiría completamente el evento Click.

Para obtener un ejemplo de cómo solucionar la supresión de eventos de entrada, consulte Solución de la supresión de eventos por controles.

Consulte también