Información general sobre eventos adjuntos (WPF .NET)

El lenguaje XAML define un componente de lenguaje y un tipo de evento denominado evento adjunto. Los eventos adjuntos se pueden usar para definir un nuevo evento enrutado en una clase que no sea de elementos y generar ese evento en cualquier elemento del árbol. Para ello, debe registrar el evento adjunto como evento enrutado y proporcionar código de respaldo específico que admita la funcionalidad del evento adjunto. Dado que los eventos adjuntos están registrados como eventos enrutados, al generarse en un elemento se propagan a través del árbol de elementos.

Importante

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

Prerrequisitos

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

Sintaxis de los eventos adjuntos

En la sintaxis XAML, un evento adjunto se especifica por su nombre de evento y tipo de propietario, con el formato <owner type>.<event name>. Dado que el nombre del evento está calificado con el nombre de su tipo de propietario, la sintaxis permite que el evento se adjunte a cualquier elemento del que se pueda crear una instancia. Esta sintaxis también es aplicable a los controladores para eventos enrutados normales adjuntos a un elemento arbitrario en la ruta de eventos.

La sintaxis de atributo XAML siguiente adjunta el controlador AquariumFilter_Clean para el evento adjunto AquariumFilter.Clean al elemento aquarium1:

<aqua:Aquarium x:Name="aquarium1" Height="300" Width="400" aqua:AquariumFilter.Clean="AquariumFilter_Clean"/>

En este ejemplo, es necesario el prefijo aqua: porque las clases AquariumFilter y Aquarium existen en un ensamblado y un espacio de nombres de Common Language Runtime (CLR) diferentes.

También puede adjuntar controladores para eventos adjuntos en el código subyacente. Para ello, llame al método AddHandler en el objeto al que debe adjuntarse el controlador y pase el controlador y el identificador de evento como parámetros al método.

Cómo implementa WPF los eventos adjuntos

Los eventos adjuntos de WPF se implementan como eventos enrutados respaldados por un campo RoutedEvent. Como resultado, los eventos adjuntos se propagan a través del árbol de elementos después de que se generen. Por lo general, el objeto que genera el evento adjunto, conocido como origen del evento, es un origen del sistema o del servicio. Los orígenes del sistema o del servicio no son parte directa del árbol de elementos. En el caso de otros eventos adjuntos, el origen del evento podría ser un elemento del árbol, como un componente dentro de un control compuesto.

Escenarios de eventos adjuntos

En WPF, los eventos adjuntos se usan en determinadas áreas de características en las que hay una abstracción de nivel de servicio. Por ejemplo, WPF usa eventos adjuntos habilitados por las clases estáticas Mouse o Validation. Las clases que interactúan con un servicio o que lo usan pueden interactuar con un evento mediante sintaxis de eventos adjuntos o exponer el evento adjunto como evento enrutado. Esta última opción forma parte de cómo una clase puede integrar las funcionalidades del servicio.

El sistema de entrada de WPF emplea mucho los eventos adjuntos. Sin embargo, casi todos esos eventos adjuntos aparecen como eventos enrutados no adjuntos equivalentes a través de elementos base. Cada evento de entrada enrutado es miembro de la clase de elemento base y está respaldado por un "contenedor" de eventos CLR. Rara vez usará o controlará los eventos adjuntos directamente. Por ejemplo, es más fácil controlar el evento Mouse.MouseDown adjunto subyacente en un elemento UIElement a través del evento enrutado UIElement.MouseDown equivalente que mediante la sintaxis de los eventos adjuntos en XAML o el código subyacente.

Los eventos adjuntos cumplen un propósito de arquitectura al habilitar la expansión futura de los dispositivos de entrada. Por ejemplo, un dispositivo de entrada nuevo solo tendría que generar Mouse.MouseDown para simular la entrada del mouse y no tendría que derivar de Mouse para hacerlo. Este escenario implica el control de código del evento, ya que el control de XAML del evento adjunto no sería relevante.

Control de un evento adjunto

El proceso para codificar y controlar un evento adjunto es básicamente el mismo que para un evento enrutado no adjunto.

Como se ha indicado anteriormente, los eventos adjuntos existentes de WPF no suelen estar diseñados para administrarse directamente en WPF. Lo más frecuente es que el propósito de un evento adjunto sea habilitar un elemento dentro de un control compuesto para notificar su estado a un elemento principal dentro del control. En ese escenario, el evento se genera en el código y se basa en el control de clases en la clase principal pertinente. Por ejemplo, se espera que los elementos de un objeto Selector generen el evento adjunto Selected, que luego controla la clase Selector. La clase Selector convierte potencialmente el evento Selected en el evento enrutado SelectionChanged. Para obtener más información sobre los eventos enrutados y el control de clases, consulte Marcar eventos enrutados como controlados y control de clases.

Definición de un evento adjunto personalizado

Si va a derivar de clases base comunes de WPF, puede implementar el evento adjunto personalizado mediante la inclusión de dos métodos de descriptor de acceso en la clase. Estos métodos son:

  • Un método Add<event name>Handler, con un primer parámetro que es el elemento al que se ha adjuntado el controlador de eventos y un segundo parámetro que es el controlador de eventos que se va a agregar. El método debe ser public y static, sin ningún valor devuelto. El método llama al método de clase base AddHandler y pasa el evento enrutado y el controlador como argumentos. Este método admite la sintaxis de atributo de XAML para adjuntar un controlador de eventos a un elemento. Este método también habilita el acceso de código al almacén del controlador de eventos para el evento adjunto.

  • Un método Remove<event name>Handler, con un primer parámetro que es el elemento al que se ha adjuntado el controlador de eventos y un segundo parámetro que es el controlador de eventos que se va a quitar. El método debe ser public y static, sin ningún valor devuelto. El método llama al método de clase base RemoveHandler y pasa el evento enrutado y el controlador como argumentos. Este método habilita el acceso de código al almacén del controlador de eventos para el evento adjunto.

WPF implementa los eventos adjuntos como eventos enrutados porque el identificador de un elemento RoutedEvent lo define el sistema de eventos de WPF. Además, el enrutamiento de un evento es una extensión natural del concepto de nivel de lenguaje de XAML de un evento adjunto. Esta estrategia de implementación restringe el control de eventos adjuntos a las clases derivadas UIElement o a las clases derivadas ContentElement, porque solo esas clases tienen implementaciones de AddHandler.

Por ejemplo, el código siguiente define el evento adjunto Clean en la clase de propietario AquariumFilter, que no es una clase de elemento. El código define el evento adjunto como evento enrutado e implementa los métodos de descriptor de acceso requeridos.

public class AquariumFilter
{
    // Register a custom routed event using the bubble routing strategy.
    public static readonly RoutedEvent CleanEvent = EventManager.RegisterRoutedEvent(
        "Clean", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(AquariumFilter));

    // Provide an add handler accessor method for the Clean event.
    public static void AddCleanHandler(DependencyObject dependencyObject, RoutedEventHandler handler)
    {
        if (dependencyObject is not UIElement uiElement)
            return;

        uiElement.AddHandler(CleanEvent, handler);
    }

    // Provide a remove handler accessor method for the Clean event.
    public static void RemoveCleanHandler(DependencyObject dependencyObject, RoutedEventHandler handler)
    {
        if (dependencyObject is not UIElement uiElement)
            return;

        uiElement.RemoveHandler(CleanEvent, handler);
    }
}
Public Class AquariumFilter

    ' Register a custom routed event using the bubble routing strategy.
    Public Shared ReadOnly CleanEvent As RoutedEvent = EventManager.RegisterRoutedEvent(
        "Clean", RoutingStrategy.Bubble, GetType(RoutedEventHandler), GetType(AquariumFilter))

    ' Provide an add handler accessor method for the Clean event.
    Public Shared Sub AddCleanHandler(dependencyObject As DependencyObject, handler As RoutedEventHandler)
        Dim uiElement As UIElement = TryCast(dependencyObject, UIElement)

        If uiElement IsNot Nothing Then
            uiElement.[AddHandler](CleanEvent, handler)
        End If
    End Sub

    ' Provide a remove handler accessor method for the Clean event.
    Public Shared Sub RemoveCleanHandler(dependencyObject As DependencyObject, handler As RoutedEventHandler)
        Dim uiElement As UIElement = TryCast(dependencyObject, UIElement)

        If uiElement IsNot Nothing Then
            uiElement.[RemoveHandler](CleanEvent, handler)
        End If
    End Sub

End Class

El método RegisterRoutedEvent que devuelve el identificador de evento adjunto es el mismo método que se usa para registrar eventos enrutados no adjuntos. Tanto los eventos enrutados adjuntos como no adjuntos se registran en un almacén interno centralizado. Esta implementación del almacén de eventos habilita el concepto de "eventos como interfaz", que se describe en Información general sobre eventos enrutados.

A diferencia del "contenedor" de eventos CLR que se usa para respaldar eventos enrutados no adjuntos, los métodos de descriptor de acceso de eventos adjuntos pueden implementarse en clases que no deriven de UIElement o ContentElement. Esto es posible porque el código de respaldo de los eventos adjuntos llama a los métodos UIElement.AddHandler y UIElement.RemoveHandler en una instancia pasada a UIElement. En cambio, el contenedor CLR para eventos enrutados no adjuntos llama a esos métodos directamente en la clase propietaria, por lo que esa clase debe derivar de UIElement.

Generación de un evento adjunto de WPF

El proceso para generar un evento adjunto es esencialmente el mismo que para un evento enrutado no adjunto.

Normalmente, el código no tendrá que generar ningún evento adjunto definido por WPF existente, ya que esos eventos siguen el modelo conceptual de "servicio" general. En ese modelo, las clases de servicio, como InputManager, son responsables de generar eventos adjuntos definidos por WPF.

Si va a definir un evento adjunto personalizado mediante el modelo WPF de basar los eventos adjuntos en eventos enrutados, use el método UIElement.RaiseEvent para generar un evento adjunto en UIElement o ContentElement. Al generar un evento enrutado, ya sea adjunto o no, es necesario designar un elemento como origen del evento en el árbol de elementos. Después, ese origen se notifica como autor de la llamada RaiseEvent. Por ejemplo, para generar el evento enrutado adjunto AquariumFilter.Clean en aquarium1:

aquarium1.RaiseEvent(new RoutedEventArgs(AquariumFilter.CleanEvent));
aquarium1.[RaiseEvent](New RoutedEventArgs(AquariumFilter.CleanEvent))

En el ejemplo anterior, aquarium1 es el origen del evento.

Vea también