Marcar eventos enrutados como controlados y control de clases
Los controladores para un evento enrutado pueden marcar el evento como controlado dentro de los datos del evento. Al controlar el evento, de hecho se acortará la ruta. El control de clases es un concepto de programación admitido por 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.
Este tema contiene las secciones siguientes.
- Requisitos previos
- Cuándo se deben marcar los eventos como controlados
- Eventos "preview (tunelización)" frente aeventos de propagación y el control de eventos
- Controladores de clases y controladores de instancias
- Control de clases de los eventos enrutados por las clases base de los controles
- Agregar controladores de instancia que se desencadenan incluso si los eventos están marcados como controlados
- Supresión deliberada de eventos de entrada para la composición de controles
- Temas relacionados
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
El hecho de establecer el valor de la propiedad Handled en true en los datos de evento para un evento enrutado se denomina "marcar el evento como controlado". No hay ninguna regla absoluta que determine cuándo debe marcar los eventos enrutados como controlados, ni como autor de una aplicación ni como autor de un control que responde a los eventos enrutados existentes o implementa nuevos eventos enrutados. En la mayoría de los casos, el concepto de "controlado" entendido como transportado en los datos de evento del evento enrutado se debería utilizar como un protocolo limitado para las respuestas de su propia aplicación a los distintos eventos enrutados expuestos en las APIs de WPF, así como para cualquier evento enrutado personalizado. Otra manera de abordar el problema "controlado" consiste en tener en cuenta que generalmente debería marcar un evento enrutado como controlado si su código respondió al evento enrutado de una manera significativa y relativamente completa. Lo normal es que no sea necesario que haya más de una respuesta significativa que necesite implementaciones del controlador independientes para cada aparición de un evento enrutado. Si se necesitan más respuestas, el código necesario se debería implementar a través de lógica de la aplicación que se encadena dentro de un único controlador en lugar de utilizar el sistema de eventos enrutados para el reenvío. El concepto de "significativo" también es subjetivo y depende de su aplicación o su código. Como orientación general, algunos ejemplos de "respuesta significativa" podrían ser: establecer el foco, modificar el estado público, establecer propiedades que afectan a la representación visual y desencadenar otros eventos nuevos. Como ejemplos de respuestas no significativas podríamos mencionar: modificar el estado privado (sin impacto visual o representación de programación), registrar eventos, o examinar los argumentos de un evento y decidir no responder al mismo.
El comportamiento del sistema de eventos enrutados refuerza este modelo de "respuesta significativa" para utilizar el estado controlado de un evento enrutado, porque los controladores agregados en XAML o la firma común de AddHandler no se invocan en respuesta a un evento enrutado cuyos datos de evento ya están marcados como controlados. Debe realizar el esfuerzo adicional de agregar un controlador con la versión de parámetros de handledEventsToo (AddHandler(RoutedEvent, Delegate, Boolean)) para controlar eventos enrutados que han sido marcados como controlados por participantes anteriores en la ruta de eventos.
En algunas circunstancias, son los propios controles quienes marcan ciertos eventos enrutados como controlados. Un evento enrutado controlado representa una decisión de los autores de controles de WPF en el sentido de que las acciones del control como respuesta al evento enrutado son significativas o completas como parte de la implementación del control, y el evento no necesita ninguna operación de control adicional. Normalmente esto se hace agregando un controlador de clases para un evento o reemplazando uno de los elementos virtuales de controlador de clases que existe en una clase base. Todavía puede evitar este control de eventos si lo considera necesario; vea Evitar la supresión de eventos por parte de los controles más adelante en este tema.
Eventos "preview (tunelización)" frente aeventos de propagación y el control de eventos
Los eventos enrutados Preview son eventos que siguen una ruta de túnel a través del árbol de elementos. El prefijo "Preview" expresado en la convención de nomenclatura es indicativo del principio general para los eventos de entrada según el cual los eventos enrutados Preview (de túnel) se desencadenan antes que el evento enrutado de propagación equivalente. Además, los eventos enrutados de entrada que tienen una pareja de túnel y de propagación tienen una lógica de control distinta. Si un agente de escucha de eventos marca el evento enrutado de túnel/preview como controlado, el evento enrutado de propagación se marcará como controlado incluso antes de que lo reciba cualquier agente de escucha del evento enrutado de propagación. Técnicamente, los eventos enrutados de túnel y de propagación son eventos independientes, pero comparten deliberadamente la misma instancia de los datos de evento para permitir este comportamiento.
La conexión entre los eventos enrutados de túnel y de propagación se realiza mediante la implementación interna de cómo cualquier clase de WPF desencadena sus propios eventos enrutados declarados, y esto se cumple para los eventos enrutados de entrada emparejados. Pero a menos que exista esta implementación en el nivel de clase, no hay ninguna conexión entre un evento enrutado de túnel y un evento enrutado de propagación que comparten el mismo esquema de nomenclatura: sin tal implementación, serían dos eventos enrutados completamente independientes y no se desencadenarían uno tras otro ni compartirían los datos de evento.
Para obtener más información sobre cómo implementar pares de eventos enrutados de entrada de túnel y propagación en una clase personalizada, vea 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: los agentes de escucha de clase y los agentes de escucha de instancia. Los agentes de escucha de clase existen porque los tipos han llamado a una API de EventManager determinada, RegisterClassHandler, en su constructor estático o han reemplazado un método virtual de controlador de clases de una clase base de elemento. Los agentes de escucha de instancia son instancias o elementos de una clase determinada a los que se ha adjuntado uno o varios controladores para ese evento enrutado mediante una llamada a AddHandler. Los eventos enrutados existentes de WPF realizan llamadas a AddHandler como parte de las implementaciones add{} y remove{} del contenedor de eventos common language runtime (CLR) del evento, que también es el modo en que se habilita el mecanismo sencillo de XAML para adjuntar controladores de eventos a través de una sintaxis de atributos. Por consiguiente, en última instancia, incluso el uso sencillo de XAML equivale a una llamada a AddHandler.
Los elementos del árbol visual se comprueban para ver si tienen implementaciones del controlador registradas. Los controladores se invocan potencialmente a lo largo de la ruta, en el orden que es inherente al tipo de estrategia de enrutamiento para ese evento enrutado. Por ejemplo los eventos enrutados de propagación invocarán primero los controladores que están asociados al mismo elemento que desencadenó el evento enrutado. A continuación, el evento enrutado se va "propagando" sucesivamente por los elementos primarios siguientes hasta que se alcance el elemento raíz de la aplicación.
Desde la perspectiva del elemento raíz en una ruta de propagación, si el control de clases o cualquier elemento situado más cerca del origen del evento enrutado invoca controladores que marcan los argumentos del evento como controlados, no se invocan los controladores de los elementos raíz y la ruta del evento se acorta de hecho antes de alcanzar ese elemento raíz. Sin embargo, la ruta no se detiene por completo, ya que se pueden agregar controladores utilizando una condicional especial que indica que se deberían seguir invocando, incluso si un controlador de clases o un controlador de instancias ha marcado el evento enrutado como controlado. Esto se explica en Agregar controladores de instancia que se desencadenan incluso si los eventos están marcados como controlados, más adelante en este tema.
En un nivel más profundo que la ruta del evento, también hay varios controladores de clases que podrían actuar sobre cualquier instancia determinada de una clase. Esto se debe a que el modelo de control de clases para los eventos enrutados permite que cada una de las clases posibles de una jerarquía de clases registre su propio controlador de clases para cada evento enrutado. Cada controlador de clases se agrega a un almacén interno y cuando se construye la ruta de eventos para una aplicación, se agregan todos los controladores de clases a la ruta de eventos. Los controladores de clases se agregan a la ruta de forma tal que el controlador de la clase más derivada se invoca el primero y los controladores de clases de cada clase base sucesiva se invocan después. Generalmente, los controladores de clases no se registran para que también respondan a los eventos enrutados que ya se marcaron como controlados. Por consiguiente, este mecanismo de control de clases habilita una de estas dos opciones:
Las clases derivadas pueden complementar el control de clases heredado 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 que el controlador de la clase derivada.
Las clases derivadas pueden reemplazar el control de clases de la clase base agregando un controlador de clases que marca el evento enrutado como controlado. Debería 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 estados, el control de entrada y el control de comandos.
Control de clases de los eventos enrutados por las clases base de los controles
En el nodo de cada uno de los elementos de una ruta de eventos, los agentes de escucha de la clase tienen la oportunidad de responder al evento enrutado antes de que lo haga cualquier agente de escucha de la instancia del elemento. Por esta razón, a veces se utilizan los controladores de clases para suprimir eventos enrutados que una implementación de una clase de control determinada no desea 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 desencadenar su propio evento específico de la clase que contiene características más concretas sobre el significado de alguna condición de los datos proporcionados por el usuario en el contexto de esa clase en particular. La implementación de la clase podría marcar el evento enrutado más general como controlado. Los controladores de clases normalmente se agregan de forma que no se invoquen para los eventos enrutados cuyos datos de evento compartidos ya se han marcado como controlados, pero para casos atípicos también hay una firma RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) que registra los controladores de clases para que también se invoquen cuando los eventos enrutados se marquen como controlados.
Elementos virtuales de controlador de clases
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 reemplazar para implementar un controlador de clases para ese evento enrutado. Las clases de elementos base registran estos métodos virtuales como su controlador de clases para cada uno de esos eventos enrutados utilizando RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) tal como se explicó anteriormente. Los métodos virtuales de On*Event hacen que sea mucho más fácil implementar el control de clases para los eventos enrutados pertinentes, sin que sea necesaria una inicialización especial en constructores estáticos para cada tipo. Por ejemplo, puede agregar el control de clases para el evento DragEnter en cualquier clase derivada UIElement reemplazando el método virtual OnDragEnter. Dentro del reemplazo, podría controlar el evento enrutado, desencadenar otros eventos, iniciar lógica específica de la clase que podría cambiar las propiedades de elementos en instancias, o cualquier combinación de esas acciones. En general, debería llamar a la implementación base en dichos reemplazos aun cuando marque el evento como controlado. Se recomienda encarecidamente llamar a la implementación base porque el método virtual está en la clase base. El modelo virtual protegido estándar que consiste en llamar a las implementaciones base desde cada elemento virtual básicamente reemplaza y es semejante a un mecanismo similar que es nativo al control de clases de eventos enrutados, según el cual se llama a los controladores de clases para todas las clases de una jerarquía de clases en cualquier instancia determinada, comenzando por el controlador de la clase más derivada y continuando hasta el controlador de la clase base. Sólo debería omitir la llamada a la implementación base si su clase tiene un requisito deliberado para cambiar la clase base que controla la lógica. La decisión de llamar a la implementación base antes o después de su código de reemplazo dependerá de la naturaleza de su implementación.
Control de clases de los eventos de entrada
Todos los métodos virtuales de los controladores de clases se registran de forma que sólo se invocan en los casos en que no haya ningún tipo de datos de evento compartidos que ya se hayan marcado como controlados. Por otra parte, sólo para los eventos de entrada, las versiones de túnel y de propagación normalmente se desencadenan de forma secuencial y comparten los datos de los eventos. Esto supone que para un par determinado de controladores de clases de eventos de entrada donde uno es la versión de túnel y el otro es la versión de propagación, puede que no desee marcar inmediatamente el evento como controlado. Si implementa el método virtual de control de clases de túnel para marcar el evento como controlado, evitará que se invoque el controlador de clases de propagación (y también evitará que se invoque cualquier controlador de instancias normalmente registrado desde el evento de túnel o de propagación).
Una vez completado el control de clases en un nodo, se tienen en cuenta los agentes de escucha de la instancia.
Agregar controladores de instancia que se desencadenan incluso si los eventos están marcados como controlados
El método AddHandler proporciona una sobrecarga determinada que le permite agregar controladores que serán invocados por el sistema de eventos cada vez que un evento alcance el elemento de control en la ruta, incluso si algún otro controlador ya ha ajustado los datos del evento para marcarlo como controlado. Esto no se es lo que se hace normalmente. En general, es posible escribir controladores para ajustar todas las áreas de código de la aplicación que podrían verse influenciadas por un evento, sin tener en cuenta donde se controló en un árbol de elementos, incluso si se desean varios resultados finales. Por otra parte, lo normal es que sólo haya un elemento que necesite responder a ese evento y que la lógica de la aplicación adecuada ya se haya ejecutado. Pero la sobrecarga handledEventsToo está disponible para los casos excepcionales en que algún otro elemento de un árbol de elementos o de una composición de controles ya haya marcado un evento como controlado, pero otros elementos situados más arriba o más abajo en el árbol de elementos (dependiendo de la ruta) todavía deseen que sean invocados sus propios controladores.
Cuándo deben marcarse como no controlados los eventos controlados
Generalmente, los eventos enrutados que se marcan como controlados no se deberían marcar como no controlados (volver a establecer Handled en false) incluso por los controladores que actúan sobre handledEventsToo. Sin embargo, algunos eventos de entrada tienen representaciones de eventos de alto nivel y de nivel más bajo 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. Por ejemplo, considere el caso en que un elemento secundario escucha 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 más alto se puede suprimir incluso en el elemento secundario que intuitivamente debería tener oportunidad de controlar el evento en primer lugar.
En estas situaciones puede ser necesario agregar controladores a los elementos principales y a los elementos secundarios para el evento de bajo nivel. La implementación de controlador del elemento secundario puede marcar el evento de bajo nivel como controlado, pero la implementación de controlador del elemento primario lo establecería de nuevo como no controlado para que los siguientes elementos situados más arriba en el árbol (así como el evento de alto nivel) puedan tener la oportunidad de responder. Esta situación debería ser bastante rara.
Supresión deliberada de eventos de entrada para la composición de controles
El escenario principal donde se utiliza el control de clases de eventos enrutados es para los eventos de entrada y los controles compuestos. Por definición, un control compuesto consta de varios controles prácticos o clases base de controles. A menudo el autor del control desea amalgamar todos los posibles eventos de entrada que podrían desencadenar cada uno de los subcomponentes para informar sobre el control completo como el único origen de los eventos. En algunos casos el autor del control podría desear suprimir por completo los eventos de los componentes o sustituir un evento definido por 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 un control Windows Presentation Foundation (WPF)Button controla cualquier evento del mouse que al final se resolverá 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 de UIElement, y la mayoría de la infraestructura de eventos necesaria para el procesamiento de la entrada de los controles está disponible en el nivel UIElement. En concreto, UIElement procesa eventos Mouse generales que controlan la prueba de posicionamiento para el cursor del mouse dentro de sus límites, y proporciona eventos distintos para las acciones de botones más comunes, como MouseLeftButtonDown. UIElement también proporciona un método virtual vacío OnMouseLeftButtonDown como controlador de clases prerregistrado para MouseLeftButtonDown y ButtonBase lo invalida. De igual forma, ButtonBase utiliza controladores de clases para MouseLeftButtonUp. En los reemplazos, a los que se les pasan los datos de los eventos, las implementaciones marcan esa instancia de RoutedEventArgs como controlada estableciendo Handled en true, y esos mismos datos de evento son lo que continúa a lo largo del resto de la ruta hasta otros controladores de clases y también hasta los controladores de instancias o los establecedores de eventos. Por otra parte, el reemplazo de OnMouseLeftButtonUp desencadenará el evento Click a continuación. El resultado final para la mayoría de los agentes de escucha será que los eventos MouseLeftButtonDown y MouseLeftButtonUp "desaparecen" y son reemplazados por Click, un evento que tiene más significado porque se sabe que este evento tuvo su origen en un verdadero botón y no en alguna parte compuesta del botón o en algún otro elemento en su totalidad.
Evitar la supresión de eventos por parte de los controles
A veces este comportamiento de supresión de eventos dentro de los controles individuales puede interferir con intenciones un poco más generales de lógica de control de eventos para una aplicación. Por ejemplo, si por alguna razón su aplicación tenía un controlador para MouseLeftButtonDown situado en el elemento raíz de la aplicación, observaría que cualquier clic en un botón del mouse no invocaría los controladores MouseLeftButtonDown ni MouseLeftButtonUp en el nivel raíz. El evento en sí realmente 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 controladores después de marcarlo como controlado). Cuando el evento enrutado llegó al botón, el control de clases ButtonBase marcó MouseLeftButtonDown como controlado porque deseaba sustituir el evento Click con más significado. Por tanto, cualquier controlador MouseLeftButtonDown estándar situado más arriba en la ruta no sería invocado. En este caso, hay dos técnicas que puede utilizar para asegurarse de que sus controladores serían invocados.
La primera técnica consiste en agregar deliberadamente el controlador mediante la firma handledEventsToo de AddHandler(RoutedEvent, Delegate, Boolean). Una limitación de este enfoque es que esta técnica para asociar un controlador de eventos sólo puede utilizarse desde el código, no desde el marcado. La sintaxis simple consistente en especificar el nombre del controlador de eventos como un valor de atributo de evento a través de Extensible Application Markup Language (XAML) no permite ese comportamiento.
La segunda técnica sólo funciona para los eventos de entrada, cuyas versiones de túnel y de propagación del evento enrutado están emparejadas. Para estos eventos enrutados, puede agregar controladores al evento enrutado equivalente de preview/túnel. Ese evento enrutado descenderá por la ruta partiendo de la raíz, por lo que el código de control de la clase del botón no lo interceptaría, presumiendo que asoció el controlador preview en algún nivel de elemento antecesor en el árbol de elementos de la aplicación. Si utiliza este enfoque, tenga cuidado al marcar cualquier evento Preview como controlado. Para el ejemplo proporcionado con PreviewMouseLeftButtonDown que se controla en el elemento raíz, si marcara el evento como Handled en la implementación del controlador, realmente suprimiría el evento Click. Este no suele ser el comportamiento deseable.
Vea también
Tareas
Cómo: Crear un evento enrutado personalizado