Información general sobre eventos enrutados

En este tema se describe el concepto de eventos enrutados de Windows Presentation Foundation (WPF). En el tema se define la terminología de los eventos enrutados, se describe cómo se enrutan a través de un árbol de elementos, se resume cómo controlar los eventos enrutados y se explica cómo crear sus propios eventos enrutados personalizados.

Requisitos previos

En este tema se supone que tiene conocimientos básicos del Common Language Runtime (CLR) y de la programación orientada a objetos, así como de la noción de cómo se pueden conceptualizar como un árbol las relaciones entre los elementos de WPF. Para seguir los ejemplos de este tema, también debes comprender el lenguaje de marcado de aplicaciones extensible (XAML) y saber cómo escribir páginas o aplicaciones WPF muy básicas. Para más información, vea Tutorial: Mi primera aplicación de WPF y XAML en WPF.

¿Qué es un evento enrutado?

Los eventos enrutados se pueden considerar desde dos perspectivas: funcional y de implementación. Aquí se presentan ambas definiciones, dado que algunas personas encuentran que una es más útil que la otra.

Definición funcional: un evento enrutado es un tipo de evento que puede invocar controladores en varios agentes de escucha en un árbol de elementos, en lugar de simplemente en el objeto que ha generado el evento.

Definición de implementación: un evento enrutado es un evento CLR que está respaldado por una instancia de la clase RoutedEvent y que se procesa mediante el sistema de eventos de Windows Presentation Foundation (WPF).

Una aplicación típica de WPF contiene muchos elementos. Tanto si se crean en código como si se declaran en XAML, estos elementos se relacionan entre sí a través de un árbol de elementos. En función de la definición del evento, la ruta de eventos puede viajar en cualquiera de las dos direcciones, pero generalmente viaja desde el elemento de origen y, después, "se propaga" en sentido ascendente por el árbol de elementos hasta que llega a la raíz (normalmente una página o una ventana). Es posible que este concepto de propagación le resulte familiar si ha trabajado previamente con el modelo de objetos DHTML.

Considere el siguiente árbol de elementos simple:

<Border Height="50" Width="300" BorderBrush="Gray" BorderThickness="1">
  <StackPanel Background="LightGray" Orientation="Horizontal" Button.Click="CommonClickHandler">
    <Button Name="YesButton" Width="Auto" >Yes</Button>
    <Button Name="NoButton" Width="Auto" >No</Button>
    <Button Name="CancelButton" Width="Auto" >Cancel</Button>
  </StackPanel>
</Border>

Este árbol de elementos genera algo parecido a lo siguiente:

Yes, No, and Cancel buttons

En este árbol de elementos simplificado, la fuente de un evento Click es uno de los elementos Button, y el Button en el que se hiciese clic es el primer elemento que tiene la oportunidad de controlar el evento. Pero si no se adjuntó un controlador al Button del evento, el evento se propagará hasta el Button primario en el árbol de elementos, que es la StackPanel. Potencialmente, el evento se propaga hasta Border y, después, continúa hacia la raíz de la página del árbol de elementos (que no se muestra).

En otras palabras, la ruta de eventos de este evento Click es:

Button-->StackPanel-->Border-->...

Escenarios de nivel superior para los eventos enrutados

A continuación se muestra un breve resumen de los escenarios que han motivado el concepto de evento enrutado y por qué un evento CLR típico no resultaba adecuado para estos escenarios:

Encapsulación y composición de controles: varios controles de WPF tienen un modelo de contenido enriquecido. Por ejemplo, se puede colocar una imagen dentro de un control Button, lo que de hecho extiende el árbol visual del botón. Pero la imagen agregada no debe interrumpir el comportamiento de la prueba de posicionamiento que hace que un botón responda a un evento Click de su contenido, aunque el usuario haga clic en píxeles que técnicamente forman parte de la imagen.

Puntos de unión de controladores únicos: en Windows Forms, era necesario adjuntar varias veces el mismo controlador para procesar eventos que podrían desencadenarse desde varios elementos. Los eventos enrutados le permiten asociar ese controlador una sola vez, tal como se ha mostrado en el ejemplo anterior, y usar la lógica del controlador para determinar el origen del evento si fuera necesario. Por ejemplo, este podría ser el controlador para el XAML mostrado anteriormente:

private void CommonClickHandler(object sender, RoutedEventArgs e)
{
  FrameworkElement feSource = e.Source as FrameworkElement;
  switch (feSource.Name)
  {
    case "YesButton":
      // do something here ...
      break;
    case "NoButton":
      // do something ...
      break;
    case "CancelButton":
      // do something ...
      break;
  }
  e.Handled=true;
}
Private Sub CommonClickHandler(ByVal sender As Object, ByVal e As RoutedEventArgs)
  Dim feSource As FrameworkElement = TryCast(e.Source, FrameworkElement)
  Select Case feSource.Name
    Case "YesButton"
      ' do something here ...
    Case "NoButton"
      ' do something ...
    Case "CancelButton"
      ' do something ...
  End Select
  e.Handled=True
End Sub

Control de clases: los eventos enrutados permiten un controlador estático definido por la clase. Este controlador de clase tiene la oportunidad de controlar un evento antes de que pueda hacerlo cualquiera de los controladores de instancia asociados.

Hacer referencia a un evento sin reflexión: determinadas técnicas de código y de marcado requieren una manera de identificar un evento concreto. Un evento enrutado crea un campo RoutedEvent como identificador, lo que proporciona una sólida técnica de identificación de eventos que no requiere la reflexión estática o en tiempo de ejecución.

Cómo se implementan los eventos enrutados

Un evento enrutado es un evento de CLR respaldado por una instancia de la clase RoutedEvent y registrado en el sistema de eventos de WPF. La instancia RoutedEvent obtenida del registro normalmente se conserva como un miembro del campo publicstaticreadonly de la clase que registra y, por tanto, "posee" el evento enrutado. La conexión con el evento de CLR del mismo nombre (que a veces se denomina el evento "contenedor") se logra al invalidar las implementaciones add y remove del evento de CLR. Normalmente, add y remove se dejan como un valor predeterminado implícito que usa la sintaxis de eventos específica del lenguaje adecuada para agregar y quitar controladores de ese evento. El mecanismo de conexión y de respaldo del evento enrutado es conceptualmente similar al modo en que una propiedad de dependencia es una propiedad de CLR respaldada por la clase DependencyProperty y registrada en el sistema de propiedades de WPF.

En el ejemplo siguiente se muestra la declaración de un evento enrutado Tap personalizado, y se incluye el registro y la exposición del campo de identificador RoutedEvent y de las implementaciones add y remove para el evento de CLR Tap.

public static readonly RoutedEvent TapEvent = EventManager.RegisterRoutedEvent(
    "Tap", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyButtonSimple));

// Provide CLR accessors for the event
public event RoutedEventHandler Tap
{
        add { AddHandler(TapEvent, value); }
        remove { RemoveHandler(TapEvent, value); }
}
Public Shared ReadOnly TapEvent As RoutedEvent = EventManager.RegisterRoutedEvent("Tap", RoutingStrategy.Bubble, GetType(RoutedEventHandler), GetType(MyButtonSimple))

' Provide CLR accessors for the event
Public Custom Event Tap As RoutedEventHandler
    AddHandler(ByVal value As RoutedEventHandler)
        Me.AddHandler(TapEvent, value)
    End AddHandler

    RemoveHandler(ByVal value As RoutedEventHandler)
        Me.RemoveHandler(TapEvent, value)
    End RemoveHandler

    RaiseEvent(ByVal sender As Object, ByVal e As RoutedEventArgs)
        Me.RaiseEvent(e)
    End RaiseEvent
End Event

Controladores de eventos enrutados y XAML

Para agregar un controlador para un evento mediante XAML, declare el nombre del evento como un atributo en el elemento que actúa como agente de escucha de eventos. El valor del atributo es el nombre de su método de controlador implementado, que debe existir en la clase parcial del archivo de código subyacente.

<Button Click="b1SetColor">button</Button>

La sintaxis de XAML para agregar controladores de eventos de CLR estándar es la misma que la que se usa para agregar controladores de eventos enrutados, porque realmente se están agregando controladores al contenedor de eventos de CLR, que se basa en una implementación de eventos enrutados. Para más información sobre cómo agregar controladores de eventos en XAML, vea Información general sobre XAML (WPF).

Estrategias de enrutamiento

Los eventos enrutados usan una de estas tres estrategias de enrutamiento:

  • Propagación: se invocan los controladores de eventos en el origen del evento. Después, el evento enrutado va pasando por los elementos primarios sucesivos hasta alcanzar la raíz del árbol de elementos. La mayoría de los eventos enrutados usan la estrategia del enrutamiento de propagación. Los eventos con enrutamiento de propagación generalmente se usan para informar sobre cambios de entrada o de estado procedentes de controles distintos u otros elementos de la interfaz de usuario.

  • Directo: solo el propio elemento de origen tiene la oportunidad de invocar controladores como respuesta. Esto es análogo al "enrutamiento" que usa Windows Forms para los eventos. Pero a diferencia de un evento de CLR estándar, los eventos con enrutamiento directo admiten el control de clases (el control de clases se explica en una sección posterior) y los pueden usar EventSetter y EventTrigger.

  • Tunelización: inicialmente, se invocan los controladores de eventos en la raíz del árbol de elementos. Después, el evento enrutado viaja a través de los elementos secundarios sucesivos a lo largo de la ruta, hacia el elemento de nodo que es el origen del evento enrutado (el elemento que ha desencadenado el evento enrutado). Los eventos con enrutamiento de tunelización se suelen usar o controlar como parte de la composición de un control, de forma que los eventos de las partes compuestas se puedan suprimir o reemplazar deliberadamente por eventos que son específicos del control completo. Los eventos de entrada proporcionados en WPF se suelen implementar como un par de tunelización-propagación. Los eventos de tunelización también se conocen a veces como eventos de vista previa, debido a una convención de nomenclatura que se usa para los pares.

¿Por qué usar eventos enrutados?

Como desarrollador de aplicaciones, no siempre necesita saber ni preocuparse de si el evento que está controlando se implementa como un evento enrutado. Los eventos enrutados tienen un comportamiento especial, pero ese comportamiento es prácticamente invisible si está controlando un evento en el elemento donde se desencadena.

Los eventos enrutados demuestran su eficacia cuando se usa cualquiera de los escenarios sugeridos: definir los controladores comunes en una raíz común, componer un control personalizado o definir una clase de controles personalizada.

Los agentes de escucha y los orígenes de los eventos enrutados no necesitan compartir un evento común en su jerarquía. Cualquier UIElement o ContentElement puede ser un cliente de escucha de evento para cualquier evento enrutado. Por tanto, se puede usar todo el conjunto de eventos enrutados disponibles en la API activa como una "interfaz" conceptual en la que elementos dispares de la aplicación pueden intercambiar información sobre los eventos. Este concepto de "interfaz" para los eventos enrutados es especialmente aplicable a los eventos de entrada.

Los eventos enrutados también se pueden usar para comunicar datos a través del árbol de elementos, porque los datos de evento para cada evento se perpetúan en cada elemento de la ruta. Un elemento podría cambiar algo en los datos del evento, y ese cambio estaría disponible para el elemento siguiente de la ruta.

Aparte del aspecto del enrutamiento, hay otros dos motivos por los que cualquier evento de WPF se podría implementar como un evento enrutado en lugar de un evento CLR estándar. Si está implementando sus propios eventos, también podría considerar estos principios:

  • Ciertas características de plantillas y estilos de WPF como EventSetter y EventTrigger requieren que el evento al que se hace referencia sea un evento enrutado. Este es el escenario del identificador de eventos mencionado anteriormente.

  • Los eventos enrutados admiten un mecanismo de control de clases en el que la clase puede especificar métodos estáticos que tienen la oportunidad de controlar eventos enrutados antes de que cualquier controlador de instancias registrado tenga acceso a ellos. Esto es muy útil en el diseño de controles, porque una clase puede exigir comportamientos de clase orientados a eventos que no se puedan suprimir accidentalmente controlando un evento en una instancia.

Cada una de las consideraciones anteriores se explica en una sección independiente de este tema.

Agregar e implementar un controlador de eventos para un evento enrutado

Para agregar un controlador de eventos en XAML, simplemente agregue el nombre del evento a un elemento como un atributo y establezca el valor del atributo como el nombre del controlador de eventos que implementa un delegado adecuado, como en el ejemplo siguiente.

<Button Click="b1SetColor">button</Button>

b1SetColor es el nombre del controlador implementado que contiene el código que controla el evento Click. b1SetColor debe tener la misma firma que el delegado RoutedEventHandler, que es el delegado del controlador de eventos para el evento Click. El primer parámetro de todos los delegados de controlador de eventos enrutados especifica el elemento al que se agrega el controlador de eventos y el segundo parámetro especifica los datos para el evento.

void b1SetColor(object sender, RoutedEventArgs args)
{
  //logic to handle the Click event
}
Private Sub b1SetColor(ByVal sender As Object, ByVal args As RoutedEventArgs)
  'logic to handle the Click event
End Sub

RoutedEventHandler es el delegado del controlador de eventos enrutados básico. Para los eventos enrutados especializados para ciertos controles o escenarios, los delegados que deben usarse para los controladores de eventos enrutados también podrían volverse más especializados, de forma que puedan transmitir datos de evento especializados. Por ejemplo, en un escenario de entrada común, podría controlar un evento enrutado DragEnter. Su controlador debe implementar el delegado DragEventHandler. Por medio del delegado más concreto, se puede procesar DragEventArgs en el controlador y leer la propiedad Data, que contiene la carga del portapapeles de la operación de arrastrar.

Para obtener un ejemplo completo de cómo agregar un controlador de eventos a un elemento mediante XAML, vea Cómo: Controlar un evento enrutado.

Resulta sencillo agregar un controlador para un evento enrutado en una aplicación que se crea en el código. Los controladores de eventos enrutados siempre se pueden agregar a través de un método AddHandler auxiliar (que es el mismo método al que llama la copia de seguridad existente para add). Sin embargo, los eventos enrutados de WPF existentes suelen tener implementaciones de respaldo de lógica add y remove que permiten agregar los controladores para eventos enrutados mediante una sintaxis de eventos específica del lenguaje, que es más intuitiva que el método auxiliar. A continuación se muestra un ejemplo de uso del método del asistente:

void MakeButton()
 {
     Button b2 = new Button();
     b2.AddHandler(Button.ClickEvent, new RoutedEventHandler(Onb2Click));
 }
 void Onb2Click(object sender, RoutedEventArgs e)
 {
     //logic to handle the Click event
 }
Private Sub MakeButton()
     Dim b2 As New Button()
     b2.AddHandler(Button.ClickEvent, New RoutedEventHandler(AddressOf Onb2Click))
End Sub
 Private Sub Onb2Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
     'logic to handle the Click event     
 End Sub

En el siguiente ejemplo se muestra la sintaxis de operador de C# (Visual Basic tiene una sintaxis de operador ligeramente diferente debido al modo en que controla la desreferenciación):

void MakeButton2()
{
  Button b2 = new Button();
  b2.Click += new RoutedEventHandler(Onb2Click2);
}
void Onb2Click2(object sender, RoutedEventArgs e)
{
  //logic to handle the Click event
}
Private Sub MakeButton2()
  Dim b2 As New Button()
  AddHandler b2.Click, AddressOf Onb2Click2
End Sub
Private Sub Onb2Click2(ByVal sender As Object, ByVal e As RoutedEventArgs)
  'logic to handle the Click event     
End Sub

Para obtener un ejemplo de cómo agregar un controlador de eventos en el código, vea Agregar un controlador de eventos mediante código.

Si está usando Visual Basic, también puede usar la palabra clave Handles para agregar controladores como parte de las declaraciones de controlador. Para más información, vea Control de eventos en Visual Basic y WPF.

El concepto de controlado

Todos los eventos enrutados comparten una clase base común para los datos de eventos, RoutedEventArgs. RoutedEventArgs define la propiedad Handled, que adopta un valor booleano. El propósito de la propiedad Handled es habilitar cualquier controlador de eventos en la ruta para marcar el evento enrutado como controlado, al establecer el valor Handled en true. Una vez procesados por el controlador de un elemento a lo largo de la ruta, se informa de nuevo sobre los datos de evento compartidos a cada agente de escucha a lo largo de la ruta.

El valor de Handled afecta a cómo se notifica o se procesa un evento enrutado cuando viaja a lo largo de la ruta. Si Handled es true en los datos de evento para un evento enrutado, los controladores que realizan escuchas para detectar el evento enrutado en otros elementos ya no se suelen invocar para esa instancia concreta del evento. Esto se cumple tanto para los controladores adjuntos en XAML como para los controladores agregados mediante sintaxis de adjuntar controladores de eventos específicas del lenguaje como += o Handles. Para la mayoría de los escenarios de controladores comunes, al marcar un evento como controlado estableciendo Handled en true se "detendrá" el enrutamiento para una ruta de tunelización o una ruta de propagación, y también para cualquier evento que se controle en un punto de la ruta mediante un controlador de clase.

Pero hay un mecanismo "handledEventsToo" mediante el cual los agentes de escucha pueden seguir ejecutando controladores en respuesta a los eventos enrutados en cuyos datos de evento Handled sea true. Es decir, la ruta de eventos no se detiene realmente al marcar los datos de evento como controlados. Solo se puede usar el mecanismo handledEventsToo en código o en un elemento EventSetter:

Además del comportamiento que genera el estado Handled en los eventos enrutados, el concepto de Handled tiene implicaciones para el modo en que se diseña una aplicación y se escribe el código del controlador de eventos. Puede considerar Handled como un protocolo simple que está expuesto por eventos enrutados. La forma concreta de usar este protocolo depende de usted, pero el diseño conceptual de cómo se debe usar el valor de Handled es el siguiente:

  • Si un evento enrutado está marcado como controlado, no es necesario que los demás elementos a lo largo de esa ruta lo controlen de nuevo.

  • Si un evento enrutado no está marcado como controlado, entonces los demás agentes de escucha situados anteriormente a lo largo de la ruta han decidido no registrar un controlador o los controladores registrados han decidido no manipular los datos de evento y establecer Handled en true. (O bien, es posible que el cliente de escucha actual sea el primer punto de la ruta). Los controladores del cliente de escucha actual ahora tienen tres posibles cursos de acción:

    • No realizar ninguna acción; el evento sigue estando sin controlar y se enruta al agente de escucha siguiente.

    • Ejecutar código en respuesta al evento, pero tomar la determinación de que la acción realizada no ha sido lo suficientemente sustancial como para marcar el evento como controlado. El evento se enruta al agente de escucha siguiente.

    • Ejecutar código en respuesta al evento. Marcar el evento como controlado en los datos de evento pasados al controlador, porque la acción realizada se ha considerado lo suficientemente sustancial como para marcarlo como controlado. El evento se enruta al agente de escucha siguiente, pero con Handled=true en sus datos de evento, de forma que solo los agentes de escucha handledEventsToo tengan la oportunidad de invocar más controladores.

El comportamiento de enrutamiento mencionado anteriormente refuerza este diseño conceptual: es más difícil (aunque todavía posible mediante código o estilos) asociar controladores a los eventos enrutados que se invocan aunque un controlador anterior a lo largo de la ruta ya haya establecido Handled en true.

Para más información sobre Handled, el control de clases de los eventos enrutados y recomendaciones sobre cuándo es adecuado marcar un evento enrutado como Handled, vea Marcar eventos enrutados como controlados y control de clases.

En las aplicaciones, es bastante habitual controlar un evento enrutado de propagación solamente en el objeto que lo ha desencadenado, y no preocuparse en absoluto por las características de enrutado del evento. Pero es una buena práctica marcar el evento enrutado como controlado en los datos de evento para evitar efectos secundarios imprevistos en caso de que un elemento situado más arriba en el árbol de elementos también tenga un controlador asociado para ese mismo evento enrutado.

Controladores de clase

Si está definiendo una clase que deriva de alguna manera de DependencyObject, también puede definir y adjuntar un controlador de clase para un evento enrutado que es un miembro de evento declarado o heredado de su clase. Los controladores de clase se invocan antes que cualquier controlador de agente de escucha de instancia que esté asociado a una instancia de esa clase, cada vez que un evento enrutado alcanza una instancia de elemento en su ruta.

Algunos controles de WPF tienen el control de clase inherente para ciertos eventos enrutados. Esto podría dar la impresión de que el evento enrutado nunca se genera, pero en realidad está sujeto al control de clase y sus controladores de instancia todavía pueden controlar el evento enrutado si emplea ciertas técnicas. Además, muchas clases y controles base exponen métodos virtuales que se pueden usar para invalidar el comportamiento del control de clase. Para más información sobre cómo evitar el control de clase no deseado y cómo definir su propio control de clase en una clase personalizada, vea Marcar eventos enrutados como controlados y control de clases.

Evento adjuntos en WPF

El lenguaje XAML también define un tipo especial de evento denominado evento adjunto. Un evento adjunto permite agregar un controlador para un evento determinado a un elemento arbitrario. No es necesario que el elemento que controla el evento defina o herede el evento adjunto, y ni el objeto que genera potencialmente el evento ni la instancia que controla el destino deben definir o ser "propietarios" de ese evento como miembro de clase.

El sistema de entrada de WPF emplea mucho los eventos adjuntos. Pero casi todos estos eventos adjuntos se reenvían a través de elementos base. Los eventos de entrada aparecen como eventos enrutados no adjuntos equivalentes que son miembros de la clase de elemento base. Por ejemplo, el evento adjunto subyacente Mouse.MouseDown puede controlarse más fácilmente en cualquier UIElement usando MouseDown en ese UIElement en lugar de lidiar con la sintaxis del evento adjunto ya sea en XAML o en el código.

Para más información sobre los eventos adjuntos en WPF, vea Información general sobre eventos adjuntos.

Nombres de evento completos en XAML

Otro uso de una sintaxis similar a la sintaxis de eventos adjuntos nombreDeTipo.nombreDeEvento pero que no es en sentido estricto un uso de eventos adjuntos se produce al adjuntar controladores para eventos enrutados que son desencadenados por elementos secundarios. Los controladores se adjuntan a un elemento primario común, para aprovecharse del enrutamiento de eventos, aunque el evento enrutado pertinente no sea miembro del elemento primario común. Considere este ejemplo de nuevo:

<Border Height="50" Width="300" BorderBrush="Gray" BorderThickness="1">
  <StackPanel Background="LightGray" Orientation="Horizontal" Button.Click="CommonClickHandler">
    <Button Name="YesButton" Width="Auto" >Yes</Button>
    <Button Name="NoButton" Width="Auto" >No</Button>
    <Button Name="CancelButton" Width="Auto" >Cancel</Button>
  </StackPanel>
</Border>

Aquí, el agente de escucha del elemento primario donde se agrega el controlador es un StackPanel. Sin embargo, está añadiendo un controlador para un evento enrutado que se ha declarado que genera la clase Button (en realidad ButtonBase, pero puede Button mediante herencia). Button "posee" el evento, pero el sistema de eventos enrutados permite que los controladores de cualquier evento enrutado se asocien a cualquier cliente de escucha de eventos de UIElement o ContentElement que, de lo contrario, podría asociar clientes de escucha a un evento de Common Language Runtime (CLR). El espacio de nombres xmlns predeterminado para estos nombres de atributo de evento calificados suele ser el espacio de nombres xmlns de WPF predeterminado, pero también se pueden especificar espacios de nombres con prefijos para los eventos enrutados personalizados. Para más información sobre xmlns, vea Espacios de nombres y asignación de espacios de nombres XAML para WPF.

Eventos de entrada de WPF

En la plataforma WPF, con frecuencia se emplean los eventos enrutados como eventos de entrada. En WPF, a los nombres de los eventos enrutados con tunelización se les antepone la palabra "Preview" por convención. Los eventos de entrada suelen presentarse en parejas, donde uno es el evento de propagación y el otro es el evento de tunelización. Por ejemplo, el evento KeyDown y el evento PreviewKeyDown tienen la misma firma, siendo el primero la entrada de propagación y el segundo el evento de entrada de propagación. En ocasiones, los eventos de entrada solo tienen una versión de propagación o quizás solo una versión enrutada directa. En la documentación, los temas sobre eventos enrutados contienen referencias cruzadas a los temas relativos a los eventos enrutados similares con estrategias de enrutamiento alternativas, si existen dichos eventos enrutados, y las secciones de las páginas de referencia administradas clarifican la estrategia de enrutamiento de cada evento enrutado.

Los eventos de entrada de WPF que se presentan en parejas se implementan de forma que una única acción del usuario desde la entrada, como presionar un botón del mouse, desencadenará los dos eventos enrutados de la pareja secuencialmente. En primer lugar, se desencadena el evento de tunelización, que viaja por su ruta. Después se desencadena el evento de propagación y este viaja por su ruta. Los dos eventos comparten literalmente la misma instancia de datos de evento, porque la llamada al método RaiseEvent de la clase de implementación que genera el evento de propagación está a la escucha de los datos de evento procedentes del evento de tunelización y los vuelve a usar en el nuevo evento generado. Los agentes de escucha con controladores para el evento de tunelización tienen la primera oportunidad de marcar el evento enrutado como controlado (en primer lugar los controladores de clase y después los controladores de instancia). Si un elemento a lo largo de la ruta de tunelización ha marcado el evento enrutado como controlado, los datos del evento ya controlado se envían para el evento de propagación y no se invocarán los controladores adjuntos típicos para los eventos de entrada de propagación equivalentes. Externamente dará la impresión de que el evento de propagación controlado ni siquiera se ha desencadenado. Este comportamiento de control es útil para la composición de controles, donde podría ser conveniente que fuera el control final y no sus partes compuestas el que informara de todos los eventos de entrada basados en pruebas de posicionamiento o de los eventos de entrada basados en el foco. El elemento del control final está más próximo a la raíz en la composición y, por tanto, tiene la oportunidad de controlar desde la clase el evento de tunelización en primer lugar y posiblemente "reemplazar" dicho evento enrutado por un evento más específico del control, como parte del código que respalda la clase del control.

Para ilustrar cómo funciona el procesamiento de eventos de entrada, observe el ejemplo de evento de entrada siguiente. En la ilustración del árbol siguiente, leaf element #2 es el origen de un evento PreviewMouseDown y, después, de un evento MouseDown:

Event routing diagram

El orden de procesamiento de los eventos es el siguiente:

  1. PreviewMouseDown (túnel) en el elemento raíz.

  2. PreviewMouseDown (túnel) en el elemento intermedio n.º 1.

  3. PreviewMouseDown (túnel) en el elemento de origen n.º 2.

  4. MouseDown (propagación) en el elemento de origen n.º 2.

  5. MouseDown (propagación) en el elemento intermedio n.º 1.

  6. MouseDown (propagación) en el elemento raíz.

Un delegado de controlador de eventos enrutados proporciona referencias a dos objetos: el objeto que ha desencadenado el evento y el objeto en el que se ha invocado el controlador. El objeto en el que se ha invocado el controlador es el objeto sobre el que informa el parámetro sender. La propiedad Source de los datos del evento informa sobre el objeto en el que se ha desencadenado el evento por primera vez. Un evento enrutado también puede desencadenarlo y controlarlo el mismo objeto, en cuyo caso sender y Source son idénticos (esto es lo que ocurre con los pasos 3 y 4 de la lista del ejemplo de procesamiento de eventos).

Debido a la tunelización y la propagación, los elementos principales reciben eventos de entrada en los que Source es uno de sus elementos secundarios. Cuando es importante saber cuál es el elemento de origen, puede identificarlo teniendo acceso a la propiedad Source.

Normalmente, una vez que el evento de entrada se marca como Handled, no se invocan controladores adicionales. Lo habitual es marcar los eventos de entrada como controlados en cuanto se invoca un controlador que se ocupa del control lógico específico de la aplicación relacionado con el significado del evento de entrada.

La excepción a esta instrucción general sobre el estado Handled es que los controladores de eventos de entrada que se registran para omitir deliberadamente el estado Handled de los datos de evento todavía se invocarían a lo largo de cualquiera de las rutas. Para más información, vea Eventos de vista previa o Marcar eventos enrutados como controlados y control de clases.

El modelo de datos de evento compartido entre los eventos de tunelización y de propagación, y el desencadenamiento secuencial primero de los eventos de tunelización y después de los de propagación, no es un concepto que se cumpla de forma general para todos los eventos enrutados. Ese comportamiento se implementa específicamente según el modo en que los dispositivos de entrada de WPF deciden generar y conectar los pares de eventos de entrada. Implementar sus propios eventos de entrada es un escenario avanzado, pero también podría decidir seguir ese modelo para sus propios eventos de entrada.

Algunas clases eligen controlar ciertos eventos de entrada mediante clases, normalmente con la intención de volver a definir lo que significa un determinado evento de entrada controlado por el usuario dentro de ese control y de desencadenar un nuevo evento. Para más información, vea Marcar eventos enrutados como controlados y control de clases.

Para más información sobre la entrada y cómo interactúa con los eventos en escenarios de aplicación típicos, vea Información general sobre acciones del usuario.

EventSetters y EventTriggers

En los estilos, se puede incluir sintaxis predeclarada de control de eventos de XAML en el marcado mediante un EventSetter. Cuando se aplica el estilo, el controlador al que se hace referencia se agrega a la instancia que recibe el estilo. Puede declarar un EventSetter solo para un evento enrutado. A continuación se muestra un ejemplo. Tenga en cuenta que el método b1SetColor al que se hace referencia aquí está en un archivo de código subyacente.

<StackPanel
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  x:Class="SDKSample.EventOvw2"
  Name="dpanel2"
  Initialized="PrimeHandledToo"
>
  <StackPanel.Resources>
    <Style TargetType="{x:Type Button}">
      <EventSetter Event="Click" Handler="b1SetColor"/>
    </Style>
  </StackPanel.Resources>
  <Button>Click me</Button>
  <Button Name="ThisButton" Click="HandleThis">
    Raise event, handle it, use handled=true handler to get it anyway.
  </Button>
</StackPanel>

La ventaja que se obtiene aquí es que es probable que el estilo contenga gran cantidad de información de otro tipo que se podría aplicar a cualquier botón de la aplicación, y si EventSetter forma parte de ese estilo se promueve la reutilización de código incluso en el nivel de marcado. Además, un EventSetter abstrae los nombres de método para los controladores un paso más allá de la aplicación general y el marcado de página.

Otra sintaxis especializada que combina el evento enrutado con características de animación de WPF es EventTrigger. Como sucede con EventSetter, solo los eventos enrutados puede usarse para un EventTrigger. Normalmente, un EventTrigger se declara como parte de un estilo, pero un EventTrigger también puede declararse en elementos de nivel de página como parte de la colección Triggers, o en una ControlTemplate. Un EventTrigger permite especificar una Storyboard que se ejecuta cada vez que un evento enrutado llega a un elemento de su ruta que declara un EventTrigger para ese evento. La ventaja de un EventTrigger frente a simplemente controlar el evento y hacer que inicie un guión gráfico existente es que un EventTrigger proporciona un mejor control sobre el guión gráfico y su comportamiento en tiempo de ejecución. Para más información, vea Cómo: Utilizar desencadenadores de eventos para controlar un guión gráfico después de su inicio.

Más información sobre los eventos enrutados

En este tema se explican principalmente los eventos enrutados desde la perspectiva de describir los conceptos básicos y proporcionar orientación sobre cómo y cuándo responder a los eventos enrutados que ya existen en los distintos elementos y controles base. Pero puede crear sus propios eventos enrutados en su clase personalizada junto con toda la compatibilidad necesaria, como clases y delegados de datos de evento especializados. El propietario del evento enrutado puede ser cualquier clase, pero los eventos enrutados deben desencadenarlos y controlarlos las clases derivadas UIElement o ContentElement para que sean útiles. Para más información sobre los eventos personalizados, vea Crear un evento enrutado personalizado.

Vea también