Cómo crear un evento enrutado personalizado (WPF .NET)

Los desarrolladores de la aplicación y los autores de componentes de Windows Presentation Foundation (WPF) pueden crear eventos enrutados personalizados para ampliar la funcionalidad de los eventos de Common Language Runtime (CLR). Para obtener información sobre las funcionalidades de eventos enrutados, consulte Por qué usar eventos enrutados. Este artículo cubre los aspectos básicos de la 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).

Pasos de eventos enrutados

Los pasos básicos para crear un evento enrutado son los siguientes:

  1. Registre RoutedEvent mediante el método RegisterRoutedEvent.

  2. La llamada de registro devuelve una instancia RoutedEvent, conocida como identificador de evento enrutado, que contiene el nombre del evento registrado, la estrategia de enrutamiento y otros detalles del evento. Asigne el identificador a un campo estático de solo lectura. Por convención:

    • El identificador de un evento enrutado con una estrategia de propagación se denomina <event name>Event. Por ejemplo, si el nombre del evento es Tap, el identificador debe denominarse TapEvent.
    • El identificador de un evento enrutado con una estrategia de tunelización se denomina Preview<event name>Event. Por ejemplo, si el nombre del evento es Tap, el identificador debe denominarse PreviewTapEvent.
  3. Defina los descriptores de acceso de evento de CLR add y remove. Sin los descriptores de acceso de evento CLR, solo podrá añadir o quitar controladores de evento mediante llamadas directas a los métodos UIElement.AddHandler y UIElement.RemoveHandler. Con los descriptores de acceso de evento CLR, se obtienen estos mecanismos de asignación de controlador de eventos:

    • Para el lenguaje XAML, puede usar la sintaxis de atributo para agregar controladores de eventos.
    • Para C#, puede usar los operadores += y -= para agregar o quitar controladores de eventos.
    • Para VB, puede usar las instrucciones AddHandler y RemoveHandler para agregar o quitar controladores de eventos.
  4. Agregue lógica personalizada para desencadenar el evento enrutado. Por ejemplo, la lógica podría desencadenar el evento en función de la entrada del usuario y el estado de la aplicación.

Ejemplo

En el ejemplo siguiente se implementa la clase CustomButton en una biblioteca de control personalizado. La clase CustomButton, que se deriva de Button:

  1. Registra un objeto RoutedEvent con nombre ConditionalClick mediante el método RegisterRoutedEvent y especifica la estrategia de propagación durante el registro.
  2. Asigna la instancia RoutedEvent devuelta desde la llamada de registro a un campo estático de solo lectura denominado ConditionalClickEvent.
  3. Define los descriptores de acceso de evento CLR add y remove.
  4. Agrega lógica personalizada para generar el evento enrutado personalizado cuando se hace clic en CustomButton y se aplica una condición externa. Aunque el código de ejemplo genera el evento enrutado ConditionalClick desde dentro del método virtual invalidado OnClick, puede generar el evento de cualquier manera que elija.
public class CustomButton : Button
{
    // Register a custom routed event using the Bubble routing strategy.
    public static readonly RoutedEvent ConditionalClickEvent = EventManager.RegisterRoutedEvent(
        name: "ConditionalClick",
        routingStrategy: RoutingStrategy.Bubble,
        handlerType: typeof(RoutedEventHandler),
        ownerType: typeof(CustomButton));

    // Provide CLR accessors for assigning an event handler.
    public event RoutedEventHandler ConditionalClick
    {
        add { AddHandler(ConditionalClickEvent, value); }
        remove { RemoveHandler(ConditionalClickEvent, value); }
    }

    void RaiseCustomRoutedEvent()
    {
        // Create a RoutedEventArgs instance.
        RoutedEventArgs routedEventArgs = new(routedEvent: ConditionalClickEvent);

        // Raise the event, which will bubble up through the element tree.
        RaiseEvent(routedEventArgs);
    }

    // For demo purposes, we use the Click event as a trigger.
    protected override void OnClick()
    {
        // Some condition combined with the Click event will trigger the ConditionalClick event.
        if (DateTime.Now > new DateTime())
            RaiseCustomRoutedEvent();

        // Call the base class OnClick() method so Click event subscribers are notified.
        base.OnClick();
    }
}
Public Class CustomButton
    Inherits Button

    ' Register a custom routed event with the Bubble routing strategy.
    Public Shared ReadOnly ConditionalClickEvent As RoutedEvent = EventManager.RegisterRoutedEvent(
        name:="ConditionalClick",
        routingStrategy:=RoutingStrategy.Bubble,
        handlerType:=GetType(RoutedEventHandler),
        ownerType:=GetType(CustomButton))

    ' Provide CLR accessors to support event handler assignment.
    Public Custom Event ConditionalClick As RoutedEventHandler

        AddHandler(value As RoutedEventHandler)
            [AddHandler](ConditionalClickEvent, value)
        End AddHandler

        RemoveHandler(value As RoutedEventHandler)
            [RemoveHandler](ConditionalClickEvent, value)
        End RemoveHandler

        RaiseEvent(sender As Object, e As RoutedEventArgs)
            [RaiseEvent](e)
        End RaiseEvent

    End Event

    Private Sub RaiseCustomRoutedEvent()

        ' Create a RoutedEventArgs instance.
        Dim routedEventArgs As New RoutedEventArgs(routedEvent:=ConditionalClickEvent)

        ' Raise the event, which will bubble up through the element tree.
        [RaiseEvent](routedEventArgs)

    End Sub

    ' For demo purposes, we use the Click event as a trigger.
    Protected Overrides Sub OnClick()

        ' Some condition combined with the Click event will trigger the ConditionalClick event.
        If Date.Now > New DateTime() Then RaiseCustomRoutedEvent()

        ' Call the base class OnClick() method so Click event subscribers are notified.
        MyBase.OnClick()

    End Sub
End Class

En el ejemplo se incluye una aplicación WPF independiente que usa el marcado XAML para agregar una instancia de CustomButton a StackPanel y para asignar el método Handler_ConditionalClick como controlador de eventos ConditionalClick para los elementos CustomButton y StackPanel1.

<Window x:Class="CodeSample.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:custom="clr-namespace:WpfControl;assembly=WpfControlLibrary"
        Title="How to create a custom routed event" Height="100" Width="300">

    <StackPanel Name="StackPanel1" custom:CustomButton.ConditionalClick="Handler_ConditionalClick">
        <custom:CustomButton
            Name="customButton"
            ConditionalClick="Handler_ConditionalClick"
            Content="Click to trigger a custom routed event"
            Background="LightGray">
        </custom:CustomButton>
    </StackPanel>
</Window>

En el código subyacente, la aplicación WPF define el método de controlador de eventos Handler_ConditionalClick. Los métodos de controlador de eventos solo se pueden implementar en código subyacente.

// The ConditionalClick event handler.
private void Handler_ConditionalClick(object sender, RoutedEventArgs e)
{
    string senderName = ((FrameworkElement)sender).Name;
    string sourceName = ((FrameworkElement)e.Source).Name;

    Debug.WriteLine($"Routed event handler attached to {senderName}, " +
        $"triggered by the ConditionalClick routed event raised on {sourceName}.");
}

// Debug output when CustomButton is clicked:
// Routed event handler attached to CustomButton,
//     triggered by the ConditionalClick routed event raised on CustomButton.
// Routed event handler attached to StackPanel1,
//     triggered by the ConditionalClick routed event raised on CustomButton.
' The ConditionalClick event handler.
Private Sub Handler_ConditionalClick(sender As Object, e As RoutedEventArgs)

    Dim sourceName As String = CType(e.Source, FrameworkElement).Name
    Dim senderName As String = CType(sender, FrameworkElement).Name

    Debug.WriteLine($"Routed event handler attached to {senderName}, " +
        $"triggered by the ConditionalClick routed event raised on {sourceName}.")

End Sub

' Debug output when CustomButton is clicked:
' Routed event handler attached to CustomButton,
'     triggered by the ConditionalClick routed event raised on CustomButton.
' Routed event handler attached to StackPanel1,
'     triggered by the ConditionalClick routed event raised on CustomButton.

Cuando se hace clic en CustomButton:

  1. El evento enrutado ConditionalClick se genera en CustomButton.
  2. Se desencadena el controlador de eventos Handler_ConditionalClick asociado a CustomButton.
  3. El evento enrutado ConditionalClick recorre el árbol de elementos hasta StackPanel1.
  4. Se desencadena el controlador de eventos Handler_ConditionalClick asociado a StackPanel1.
  5. El evento enrutado ConditionalClick continúa hacia arriba en el árbol de elementos, lo que podría desencadenar otros controladores de eventos ConditionalClick asociados a otros elementos recorridos.

El controlador de eventos Handler_ConditionalClick obtiene la siguiente información sobre el evento que lo desencadenó:

  • Objeto emisor, que es el elemento al que está asociado el controlador de eventos. El objeto sender será CustomButton la primera vez que se ejecute el controlador, y StackPanel1 la segunda.
  • El objeto RoutedEventArgs.Source, que es el elemento que generó originalmente el evento. En este ejemplo, Source siempre es CustomButton.

Nota

Una diferencia clave entre un evento enrutado y un evento CLR es que un evento enrutado atraviesa el árbol de elementos, buscando controladores, mientras que un evento CLR no atraviesa el árbol de elementos, y los controladores solo se pueden adjuntar al objeto de origen que generó el evento. Como resultado, un evento enrutado sender puede ser cualquier elemento transversal en el árbol de elementos.

Puede crear un evento de tunelización del mismo modo que un evento de propagación, salvo que establecerá la estrategia de enrutamiento en la llamada de registro de eventos a Tunnel. Para más información sobre los eventos de tunelización, vea Eventos de entrada de WPF.

Vea también