Compartir a través de


Marcar eventos enrutados como controlados y control de clases

Los controladores de un evento enrutado pueden marcar el evento como controlado en los datos del evento. Controlar el evento acorta de forma efectiva la ruta. El control de clases es un concepto de programación que admiten los eventos enrutados. Un controlador de clases tiene la oportunidad de controlar un evento enrutado determinado en un nivel de clase con un controlador que se invoca antes que cualquier controlador de instancias de cualquier instancia de la clase.

Requisitos previos

En este tema se profundiza sobre los conceptos presentados en Información general sobre eventos enrutados.

Cuándo se deben marcar los eventos como controlados

Definir el valor de la propiedad Handled como true en los datos de evento de un evento enrutado se conoce como "marcar el evento como controlado". No hay ninguna regla absoluta sobre cuándo se deben marcar los eventos enrutados como controlados como autor de una aplicación o como autor de control que responde a eventos enrutados existentes o implementa nuevos eventos enrutados. En su mayor parte, el concepto de "controlado" integrado en los datos de evento del evento enrutado debe usarse como protocolo limitado para las respuestas de su propia aplicación a los diversos eventos enrutados expuestos en las API de WPF, así como para cualquier evento enrutado personalizado. Otra forma de considerar la cuestión del "control" es que, generalmente, debería marcar un evento enrutado como controlado si el código respondió al evento enrutado de manera significativa y relativamente completa. Normalmente, no debería haber más de una respuesta significativa que requiera implementaciones del controlador independientes para cualquier instancia de evento enrutado. Si se necesitan más respuestas, se debe implementar el código necesario a través de la lógica de la aplicación encadenada con un único controlador, en lugar de usar el sistema de eventos enrutados para el reenvío. El concepto de "significativo" también es subjetivo y depende de la aplicación o el código. Como guía general, estos son algunos ejemplos de "respuesta significativa": establecer el foco, modificar el estado público, establecer propiedades que afectan a la representación visual y generar otros eventos nuevos. Ejemplos de respuestas no significativas: modificar el estado privado (sin impacto visual ni representación de programación), registrar eventos o examinar los argumentos de un evento y decidir no responder a él.

El comportamiento del sistema de eventos enrutados refuerza este modelo de "respuesta significativa" para usar el estado controlado de un evento enrutado, ya que los controladores agregados en XAML o la firma común de AddHandler no se invocan en respuesta a un evento enrutado donde los datos del evento ya están marcados como controlados. Debe hacer el esfuerzo de agregar un controlador con la versión del parámetro handledEventsToo (AddHandler(RoutedEvent, Delegate, Boolean)) a fin de controlar los eventos enrutados que han marcado como controlados participantes anteriores de la ruta de eventos.

En algunas circunstancias, los propios controles marcan ciertos eventos enrutados como controlados. Un evento enrutado controlado representa una decisión de los autores de control de WPF por la que las acciones del control de respuesta al evento enrutado son significativas o completas como parte de la implementación del control, y el evento no necesita ningún otro control. Normalmente, para hacerlo, se agrega un controlador de clase para un evento o se reemplaza uno de los elementos virtuales de controlador de clase que existen en una clase base. Si es necesario, puede resolver el control de eventos. Consulte Solución de la supresión de eventos mediante controles más adelante en este tema.

Eventos de "versión preliminar" (tunelización) frente a eventos de propagación y control de eventos

Los eventos enrutados de vista previa son eventos que siguen una ruta de tunelización a través del árbol de elementos. "Preview", expresado en la convención de nomenclatura, indica el principio general para los eventos de entrada que establece que los eventos enrutados de vista previa (tunelización) se generan antes que el evento enrutado de propagación equivalente. Además, los eventos enrutados de entrada que tienen un par de tunelización y propagación tienen una lógica de control distinta. Si un agente de escucha de eventos marca el evento enrutado de tunelización/vista previa, el evento enrutado de propagación se marcará como controlado incluso antes de que cualquier agente de escucha de evento enrutado de propagación lo reciba. Los eventos enrutados de tunelización y propagación son, técnicamente, eventos independientes, pero comparten deliberadamente la misma instancia de datos de evento para habilitar este comportamiento.

La conexión entre los eventos enrutados de tunelización y propagación se realiza mediante la implementación interna de cómo cualquier clase de WPF determinada genera sus propios eventos enrutados declarados, y este es el caso de los eventos enrutados de entrada emparejados. Sin embargo, a menos que exista esta implementación de nivel de clase, no hay ninguna conexión entre un evento enrutado de tunelización y uno de propagación que comparten el esquema de nomenclatura: sin tal implementación, serían dos eventos enrutados completamente independientes y no se generarían en secuencia ni compartirían datos de evento.

Para obtener más información sobre cómo implementar pares de eventos enrutados de entrada de tunelización y propagación en una clase personalizada, consulte Cómo: Crear un evento enrutado personalizado.

Controladores de clases y controladores de instancias

Los eventos enrutados consideran dos tipos distintos de agentes de escucha para el evento: de clase y de instancia. Los agentes de escucha de clase existen porque los tipos han llamado a una API de EventManager concreta, RegisterClassHandler, en su constructor estático, o han invalidado un método virtual de controlador de clase de una clase base de elemento. Los agentes de escucha de instancia son instancias o elementos de una clase concreta en que se han adjuntado uno o varios controladores para ese evento enrutado mediante una llamada a AddHandler. Los eventos enrutados de WPF existentes llaman a AddHandler como parte de las implementaciones del contenedor de eventos del Common Language Runtime (CLR) add{} y remove{} del evento, que es también la forma en que se habilita el sencillo mecanismo XAML de adjuntar controladores de eventos a través de una sintaxis de atributos. Por lo tanto, incluso el simple uso de XAML equivale en última instancia a una llamada AddHandler.

Se comprueban las implementaciones de controlador registradas de los elementos del árbol visual. Los controladores se pueden invocar a lo largo de la ruta en el orden inherente al tipo de la estrategia de enrutamiento para el evento enrutado. Por ejemplo, los eventos enrutados de propagación invocan primero los controladores adjuntos al mismo elemento que generó el evento enrutado. A continuación, el evento enrutado "se propaga" en el siguiente elemento primario y así sucesivamente hasta que se alcanza el elemento raíz de aplicación.

Desde la perspectiva del elemento raíz en una ruta de propagación, si el control de clases o cualquier elemento más cercano al origen del evento enrutado invoca controladores que marcan los argumentos de evento como controlados, no se invocan controladores en los elementos raíz y la ruta del evento se acorta eficazmente antes de llegar al elemento raíz. Sin embargo, la ruta no se detiene por completo, ya que se pueden agregar controladores mediante la condición especial de que se deben invocar igualmente, incluso si un controlador de clase o de instancia ha marcado el evento enrutado como controlado. Esto se explica en Adición de controladores de instancias que se generan incluso cuando los eventos se marcan como controlados, más adelante en este tema.

En un nivel más profundo que la ruta del evento, también puede haber varios controladores de clases que actúen en cualquier instancia de una clase. Esto se debe a que el modelo de control de clase para los eventos enrutados permite que todas las clases posibles de una jerarquía de clases registren su propio controlador de clase para cada evento enrutado. Cada controlador de clase se agrega a un almacén interno y, cuando se construye la ruta de eventos para una aplicación, todos los controladores de clase se agregan a la ruta del evento. Los controladores de clase se agregan a la ruta de modo que el controlador de la clase más derivada se invoca primero y los controladores de clase de cada clase base sucesiva se invocan después. Por lo general, los controladores de clase no están registrados por lo que también responden a eventos enrutados que ya se marcaron como controlados. Por consiguiente, este mecanismo de control de clases habilita una de dos opciones:

  • Las clases derivadas pueden complementar el control de clases que se hereda de la clase base agregando un controlador que no marca el evento enrutado como controlado, porque el controlador de la clase base se invocará después del controlador de la clase derivada.

  • Las clases derivadas pueden reemplazar el control de clases de la clase base agregando un controlador de clase que marque el evento enrutado como controlado. Debe tener cuidado con este enfoque, porque es posible que cambie el diseño del control de base deseado en áreas como el aspecto visual, la lógica de estado, el control de entrada y la gestión de comandos.

Control de clases de eventos enrutados mediante clases base de control

En cada nodo de elemento dado de una ruta de eventos, los agentes de escucha de clase tienen la oportunidad de responder al evento enrutado antes que cualquier agente de escucha de instancia en el elemento. Por este motivo, los controladores de clase se usan a veces para suprimir eventos enrutados que una implementación de clase de control determinada no quiere que se propaguen más o para proporcionar un control especial de ese evento enrutado que es una característica de la clase. Por ejemplo, una clase podría generar su propio evento específico de clase con información más específica sobre el significado de alguna condición de entrada de usuario en el contexto de esa clase en particular. La implementación de la clase, a continuación, puede marcar el evento enrutado más general como controlado. Los controladores de clase se suelen agregar de modo que no se invocan para eventos enrutados en que los datos de evento compartidos ya estaban marcados como completados, pero para casos atípicos también hay una firma RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) que registra los controladores de clase que se invocarán incluso si los eventos enrutados están marcados como controlados.

Elementos virtuales de controlador de clase

Algunos elementos, particularmente los elementos base, como UIElement, exponen métodos virtuales "On*Event" y "OnPreview*Event" vacíos que corresponden a su lista de eventos enrutados públicos. Estos métodos virtuales se pueden invalidar para implementar un controlador de clase para el evento enrutado. Las clases de elementos base registran estos métodos virtuales como su controlador de clase para cada evento enrutado usando RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) como se describió anteriormente. Los métodos virtuales On*Event facilitan la implementación del control de clases para los eventos enrutados pertinentes, sin necesidad de una inicialización especial en constructores estáticos para cada tipo. Por ejemplo, puede agregar el control de la clase para el evento DragEnter en cualquier clase derivada UIElement anulando el método virtual OnDragEnter. Dentro de la invalidación, podría controlar el evento enrutado, generar otros eventos, iniciar una lógica específica de clase que podría cambiar las propiedades de elemento en instancias, o cualquier combinación de esas acciones. Por lo general, debe llamar a la implementación base en dichas invalidaciones incluso si marca el evento como controlado. Se recomienda llamar a la implementación base porque el método virtual se encuentra en la clase base. El patrón virtual protegido estándar de llamar a las implementaciones base desde cada elemento virtual, básicamente, reemplaza y es comparable a un mecanismo similar que es nativo al control de clases de eventos enrutados, mediante el cual se llama a los controladores de clase de todas las clases de una jerarquía de clases en una instancia determinada, comenzando por el controlador de la clase más derivada y continuando hasta el controlador de la clase base. Solo se debe omitir la llamada de implementación base si la clase tiene un requisito deliberado para cambiar la lógica de control de clase base. Si se llama a la implementación base antes o después, el código de invalidación dependerá de la naturaleza de la implementación.

Control de clase de evento de entrada

Los métodos virtuales de controlador de clase se registran de modo que solo se invocan en casos donde ya no se marcan los datos de evento compartidos como controlados. Además, solo para los eventos de entrada, las versiones de tunelización y propagación, normalmente, se producen en secuencia y comparten datos de eventos. Esto supone que, para un par determinado de controladores de clase de eventos de entrada, donde uno es la versión de tunelización y el otro, la de propagación, no es recomendable marcar el evento como controlado inmediatamente. Si implementa el método virtual de control de clase de tunelización para marcar el evento como controlado, el controlador de clase de propagación no se podrá invocar (además de impedir que se invoque cualquier controlador de instancias registrado normalmente para el evento de tunelización o propagación).

Una vez completado el control de clases en un nodo, se consideran los agentes de escucha de instancia.

Adición de controladores de instancias que se generan incluso cuando los eventos se marcan como controlados

El método AddHandler proporciona una sobrecarga determinada que le permite agregar controladores que invocará el sistema de eventos cada vez que un evento alcance el elemento de control de la ruta, incluso si algún otro controlador ya ha ajustado los datos del evento para marcarlo como controlado. Normalmente, esto no se hace. Por lo general, se pueden escribir controladores para ajustar todas las áreas del código de aplicación que podrían verse afectadas por un evento, independientemente de dónde se controlara en un árbol de elementos, incluso si se quieren varios resultados finales. Además, normalmente, solo hay un elemento que necesite responder a ese evento y la lógica de aplicación adecuada ya se ha ejecutado. Pero la sobrecarga handledEventsToo está disponible para los casos excepcionales en que otro elemento de una composición de control o árbol de elementos ya se ha marcado como controlado, pero otros elementos superiores o inferiores del árbol de elementos (dependiendo de la ruta) aún quieren tener sus propios controladores invocados.

Cuándo se deben marcar los eventos controlados como no controlados

Por lo general, los eventos enrutados marcados como controlados no se deben marcar como no controlados Handled( revertido a false), ni si quiera por los controladores que actúan sobre handledEventsToo. Sin embargo, algunos eventos de entrada tienen representaciones de eventos de alto nivel y de nivel inferior que pueden superponerse cuando el evento de alto nivel se ve en una posición en el árbol y el evento de bajo nivel en otra posición. Por ejemplo, considere el caso de que un elemento secundario escuche un evento clave de alto nivel, como TextInput, mientras un elemento primario escucha un evento de bajo nivel, como KeyDown. Si el elemento primario controla el evento de bajo nivel, el evento de nivel superior se puede suprimir incluso en el elemento secundario que, intuitivamente, debería tener la primera oportunidad de controlar el evento.

En estas situaciones, puede ser necesario agregar controladores a los elementos primarios y secundarios para el evento de bajo nivel. La implementación del controlador del elemento secundario puede marcar el evento de bajo nivel como controlado, pero la implementación del controlador del elemento primario lo marcaría de nuevo como no controlado para que los elementos superiores del árbol (así como el evento de alto nivel) puedan tener la oportunidad de responder. Esta situación debería ser poco frecuente.

Supresión deliberada de eventos de entrada para la composición de control

El escenario principal donde se utiliza el control de clases de eventos enrutados es para los eventos de entrada y los controles compuestos. Un control compuesto está, por definición, compuesto por varios controles prácticos o clases base de control. A menudo, el autor del control quiere amalgamar todos los posibles eventos de entrada que cada uno de los subcomponentes pueda generar a fin de notificar todo el control como el origen del evento singular. En algunos casos, puede que el autor del control quiera suprimir los eventos de los componentes por completo o sustituir un evento definido por el componente que lleva más información o implica un comportamiento más concreto. El ejemplo canónico que resulta evidente para cualquier autor de componentes es cómo Button de Windows Presentation Foundation (WPF) controla cualquier evento de mouse que finalmente como el evento intuitivo que tienen todos los botones: un evento Click.

La clase base Button (ButtonBase) deriva de Control, que a su vez deriva de FrameworkElement y UIElement, y gran parte de la infraestructura de eventos necesaria para el procesamiento de entradas de control está disponible en el nivel UIElement. En particular, UIElement procesa los eventos generales Mouse que controlan la comprobación de visitas para el cursor del mouse dentro de sus límites, y proporciona eventos distintos para las acciones de botón más comunes, como MouseLeftButtonDown. UIElement también proporciona un virtual vacío OnMouseLeftButtonDown como el controlador de clase prerregistrado para MouseLeftButtonDown, y ButtonBase lo invalida. Del forma similar, ButtonBase utiliza controladores de clase para MouseLeftButtonUp. En las invalidaciones, en las que se pasan los datos del evento, las implementaciones marcan esa instancia RoutedEventArgs como controlada estableciendo Handled en true, y esos mismos datos de evento continúan a lo largo del resto de la ruta a otros controladores de clase y también a los controladores de instancias o establecedores de eventos. Además, la invalidación de OnMouseLeftButtonUp será la siguiente en iniciar el evento Click. El resultado final para la mayoría de los agentes de escucha es que los eventos MouseLeftButtonDown y MouseLeftButtonUp "desaparecerán" y se reemplazarán en su lugar por Click, un evento que contiene más significado porque se sabe que se originó desde un botón true y no desde una parte compuesta del botón ni desde cualquier otro elemento por completo.

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

A veces, este comportamiento de supresión de eventos en controles individuales puede interferir con algunas intenciones más generales de la lógica de control de eventos de la aplicación. Por ejemplo, si por alguna razón su aplicación tuviera un controlador para MouseLeftButtonDown ubicado en el elemento raíz de la aplicación, notaría que cualquier clic del mouse en un botón no invocaría los controladores MouseLeftButtonDown o MouseLeftButtonUp en el nivel raíz. En realidad, el evento en sí se propagó hacia arriba (de nuevo, las rutas de evento no están finalizadas realmente, pero el sistema de eventos enrutados cambia su comportamiento de invocación de controlador después de marcarse como controlado). Cuando el evento enrutado llega al botón, el control de la clase ButtonBase marca el MouseLeftButtonDown controlado porque deseaba sustituir el evento Click con más significado. Por lo tanto, cualquier controlador estándar MouseLeftButtonDown más arriba en la ruta no se invocaría. Hay dos técnicas que puede usar para asegurarse de que los controladores se invoquen en estas circunstancias.

La primera técnica consiste en añadir deliberadamente el controlador utilizando la firma handledEventsToo de AddHandler(RoutedEvent, Delegate, Boolean). Una limitación de este enfoque es que esta técnica para adjuntar un controlador de eventos solo es posible desde el código, no desde el marcado. La sintaxis simple de especificar el nombre del controlador de eventos como un valor de atributo de evento a través del Lenguaje de marcado de aplicaciones extensible (XAML) no permite ese comportamiento.

La segunda técnica solo funciona para los eventos de entrada, donde se emparejan las versiones de tunelización y propagación del evento enrutado. Para estos eventos enrutados, también puede agregar controladores al evento enrutado equivalente de tunelización/vista previa. El evento enrutado se tunelizará por la ruta partiendo de la raíz, por lo que el código de control de clase del botón no lo interceptaría, siempre que haya adjuntado el controlador Preview en algún nivel de elemento antecesor en el árbol de elementos de la aplicación. Si usa este enfoque, tenga cuidado al marcar cualquier evento Preview como controlado. Para el ejemplo con PreviewMouseLeftButtonDown controlado en el elemento raíz, si usted marcó el evento como Handled en la implementación del controlador, en realidad, suprimiría el evento Click. Normalmente, no es un comportamiento deseable.

Vea también