Marquage des événements routés comme gérés et gestion de classe

Les gestionnaires d’un événement routé peuvent marquer l’événement comme étant géré dans les données d’événement. La gestion de l’événement a pour effet de raccourcir efficacement l’itinéraire. La gestion de classe est un concept de programmation pris en charge par les événements routés. Un gestionnaire de classe peut gérer un événement routé particulier au niveau d’une classe à l’aide d’un gestionnaire appelé avant tout gestionnaire d’instance sur une instance de la classe.

Prérequis

Cette rubrique décrit plus en détail les concepts introduits dans Vue d’ensemble des événements routés.

Quand marquer des événements comme étant gérés

Lorsque vous définissez la valeur de la Handled propriété true dans les données d’événement pour un événement routé, il s’agit de « marquage de l’événement géré ». Que vous soyez auteur d’une application ou auteur d’un contrôle qui répond à des événements routés existants ou qui implémente de nouveaux événements routés, il n’existe aucune règle absolue qui indique à quel moment marquer les événements routés comme étant gérés. Dans la plupart des cas, le concept de « handled » tel qu’il est effectué dans les données d’événement routés doit être utilisé comme protocole limité pour les réponses de votre propre application aux différents événements routés exposés dans les API WPF, ainsi que pour tous les événements routés personnalisés. Une autre façon d’envisager la question du marquage « géré » est que vous devez généralement marquer un événement routé comme étant géré si votre code a répondu à l’événement routé d’une manière significative et relativement complète. En règle générale, il ne doit pas exister plus d’une réponse significative à exiger des implémentations de gestionnaire distinctes pour une même occurrence d’événement routé. Si plusieurs réponses sont nécessaires, le code correspondant doit être implémenté à travers la logique d’application intégrée à un gestionnaire unique plutôt qu’en utilisant le système d’événement routé pour le transfert. L’idée de ce qui est « significatif » revêt aussi une dimension subjective et dépend de votre application ou de votre code. À titre d’indication générale, voici quelques exemples de « réponses significatives » : définition du focus, modification de l’état public, définition des propriétés qui affectent la représentation visuelle et déclenchement d’autres événements nouveaux. À l’inverse, la modification de l’état privé (sans impact visuel ni représentation de programmation), la journalisation d’événements ou la consultation des arguments d’un événement et la décision de ne pas y répondre sont des exemples de réponses non significatives.

Le comportement du système d’événements routé renforce ce modèle de « réponse significative » pour utiliser l’état géré d’un événement routé, car les gestionnaires ajoutés en XAML ou la signature commune de AddHandler ne sont pas appelés en réponse à un événement routé où les données d’événement sont déjà marquées comme gérées. Vous devez effectuer un effort supplémentaire d’ajout d’un gestionnaire avec la version de handledEventsToo paramètre (AddHandler(RoutedEvent, Delegate, Boolean)) afin de gérer les événements routés marqués par les participants antérieurs dans l’itinéraire d’événement.

Dans certains cas, les contrôles proprement dits marquent certains événements routés comme étant gérés. Un événement routé géré représente une décision par les auteurs de contrôle WPF que les actions du contrôle en réponse à l’événement routé sont significatives ou complètes dans le cadre de l’implémentation du contrôle, et que l’événement n’a pas besoin d’une autre gestion. Cela consiste généralement à ajouter un gestionnaire de classe pour un événement ou à remplacer l’un des virtuels de gestionnaire de classe qui existent dans une classe de base. Vous pouvez toujours contourner cette gestion d’événement, si nécessaire ; consultez la page Résolution des problèmes liés à la suppression d’événements par des contrôles plus loin dans cette rubrique.

Événements « Preview » (Tunneling) par rapport aux événements bubbling et gestion des événements

Les événements routés Preview sont des événements qui suivent un itinéraire de tunneling dans l’arborescence des éléments. « Preview », exprimé dans la convention de nommage, est révélateur du principe général des événements d’entrée selon lequel les événements routés Preview (tunneling) sont déclenchés avant l’événement routé de propagation équivalent. Par ailleurs, les événements routés d’entrée associés à une paire de tunneling et de propagation ont une logique de gestion distincte. Si l’événement routé de tunneling/preview est marqué comme étant géré par un écouteur d’événements, l’événement routé de propagation est alors marqué comme étant géré, avant même qu’un écouteur de l’événement routé de propagation l’ait reçu. Les événements routés de tunneling et de propagation sont des événements techniquement distincts, mais ils partagent délibérément la même instance de données d’événement pour autoriser ce comportement.

La connexion entre les événements routés de tunneling et de routage est effectuée par l’implémentation interne de la façon dont une classe WPF donnée déclenche ses propres événements routés déclarés, et cela est vrai des événements routés d’entrée jumelés. Cependant, à moins que cette implémentation au niveau de la classe existe, aucune connexion n’est établie entre un événement routé de tunneling et un événement routé de propagation qui partagent le modèle de nommage : sans cette implémentation, ils deviendraient deux événements routés totalement distincts et ne seraient pas déclenchés dans l’ordre, ni ne partageraient les données d’événement.

Pour plus d’informations sur l’implémentation des paires d’événements routés d’entrée de tunneling/propagation dans une classe personnalisée, consultez Créer un événement routé personnalisé.

Gestionnaires de classe et gestionnaires d’instance

Les événements routés reconnaissent deux types d’écouteur d’événement : les écouteurs de classe et les écouteurs d’instance. Les écouteurs de classe existent, car les types ont appelé une API particulière EventManager ,RegisterClassHandler dans leur constructeur statique ou ont substitué une méthode virtuelle de gestionnaire de classes à partir d’une classe de base d’élément. Les écouteurs d’instance sont des instances/éléments de classe particuliers dans lesquels un ou plusieurs gestionnaires ont été attachés pour cet événement routé par un appel à AddHandler. Les événements routés WPF existants effectuent des appels dans AddHandler le cadre du wrapper d’événements CLR (Common Language Runtime) pour ajouter{} et supprimer{} des implémentations de l’événement, ce qui est également la façon dont le mécanisme XAML simple d’attachement des gestionnaires d’événements via une syntaxe d’attribut est activé. Par conséquent, même l’utilisation XAML simple équivaut finalement à un AddHandler appel.

Les éléments de l’arborescence d’éléments visuels sont vérifiés pour déterminer s’ils comportent des implémentations de gestionnaire enregistré. Les gestionnaires sont potentiellement appelés tout au long de l’itinéraire, dans l’ordre inhérent au type de la stratégie de routage pour cet événement routé. Par exemple, les événements routés de propagation appellent d’abord les gestionnaires associés à l’élément qui a déclenché l’événement routé. Les événements routés sont ensuite « propagés » au prochain élément parent, et ainsi de suite jusqu’à ce que l’élément racine de l’application soit atteint.

Du point de vue de l’élément racine d’un itinéraire de propagation, si la gestion de classe ou tout élément plus proche de la source de l’événement routé appelle des gestionnaires qui marquent les arguments d’événement comme étant gérés, les gestionnaires des éléments racine ne sont pas appelés, et l’itinéraire de l’événement est raccourci efficacement avant d’atteindre l’élément racine en question. Cependant, l’itinéraire n’est pas complètement arrêté, car il est possible d’ajouter des gestionnaires à l’aide d’un conditionnel spécial qui exige qu’ils soient tout de même appelés, même si un gestionnaire de classe ou d’instance a marqué l’événement routé comme étant géré. Cet aspect est détaillé plus loin dans cette rubrique dans Ajout de gestionnaires d’instance déclenchés y compris quand les événements sont marqués comme étant gérés.

À un niveau plus approfondi que l’itinéraire d’événement se trouvent également des gestionnaires de classe potentiellement multiples qui agissent sur une instance donnée d’une classe. Cela est dû au fait que le modèle de gestion de classe des événements routés permet à toutes les classes possibles d’une hiérarchie de classes d’enregistrer chacune son propre gestionnaire de classe pour chaque événement routé. Chaque gestionnaire de classe est ajouté à un magasin interne, et quand l’itinéraire d’événement d’une application est construit, les gestionnaires de classe sont tous ajoutés à l’itinéraire d’événement. Les gestionnaires de classe sont ajoutés à l’itinéraire de sorte que le gestionnaire de classe le plus dérivé soit appelé en premier et que les gestionnaires de chaque classe de base consécutive soient appelés ensuite. En règle générale, les gestionnaires de classe ne sont pas enregistrés de sorte qu’ils répondent aussi aux événements routés qui étaient déjà marqués comme étant gérés. Par conséquent, ce mécanisme de gestion de classe permet d’opter pour l’un des deux choix suivants :

  • Les classes dérivées peuvent compléter la gestion de classe héritée de la classe de base en ajoutant un gestionnaire qui ne marque pas l’événement routé comme étant géré, car le gestionnaire de classe de base sera appelé peu de temps après le gestionnaire de classe dérivé.

  • Les classes dérivées peuvent remplacer la gestion de classe à partir de la classe de base en ajoutant un gestionnaire de classe qui marque l’événement routé comme étant géré. Vous devez être prudent avec cette approche, car elle est susceptible de changer la conception du contrôle de base prévu dans des domaines tels que l’apparence visuelle, la logique d’état, la gestion des entrées et la gestion des commandes.

Gestion de classe des événements routés par les classes de base de contrôle

Sur chaque nœud d’élément donné au sein d’un itinéraire d’événement, les écouteurs de classe peuvent répondre à l’événement routé avant n’importe quel écouteur d’instance de l’élément. C’est pourquoi les gestionnaires de classe sont parfois utilisés pour supprimer les événements routés qu’une implémentation de classe de contrôle particulière ne souhaite pas propager davantage, ou pour assurer une gestion spéciale de l’événement routé qui compte parmi les fonctionnalités de la classe. Par exemple, une classe peut déclencher son propre événement spécifique qui contient davantage de détails sur la signification de la condition d’entrée utilisateur dans le contexte de cette classe particulière. L’implémentation de classe peut ensuite marquer l’événement routé plus général comme étant géré. Les gestionnaires de classes sont généralement ajoutés afin qu’ils ne soient pas appelés pour les événements routés où les données d’événement partagées ont déjà été marquées comme gérées, mais pour les cas atypiques, il existe également une RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) signature qui enregistre les gestionnaires de classes à appeler même lorsque les événements routés sont marqués comme gérés.

Virtuels de gestionnaire de classe

Certains éléments, en particulier les éléments de base tels que UIElement, exposent des méthodes virtuelles vides « On*Event » et « OnPreview*Event » qui correspondent à leur liste d’événements routés publics. Ces méthodes virtuelles peuvent être remplacées de façon à implémenter un gestionnaire de classe pour l’événement routé en question. Les classes d’éléments de base inscrivent ces méthodes virtuelles comme gestionnaire de classes pour chaque événement RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) routé comme décrit précédemment. Les méthodes virtuelles On*Event simplifient considérablement l’implémentation de la gestion des classes pour les événements routés pertinents, sans nécessiter d’initialisation spéciale dans des constructeurs statiques pour chaque type. Par exemple, vous pouvez ajouter la gestion des classes pour l’événement DragEnter dans n’importe quelle UIElement classe dérivée en remplaçant la OnDragEnter méthode virtuelle. Au cours de ce remplacement, vous pouvez gérer l’événement routé, déclencher d’autres événements, initialiser la logique spécifique à la classe qui pourrait changer des propriétés d’élément dans les instances, ou toute combinaison de ces actions. En règle générale, dans ce type de modification, vous devez appeler l’implémentation de base même si vous marquez l’événement comme étant géré. Il est vivement recommandé d’appeler l’implémentation de base, car la méthode virtuelle se trouve au niveau de la classe de base. Dans l’absolu, le modèle virtuel protégé standard mis en œuvre pour appeler les implémentations de base à partir de chaque virtuel remplace et met en parallèle un mécanisme similaire natif de la gestion des classes d’événements routés, au moyen duquel sont appelés les gestionnaires de toutes les classes d’une hiérarchie de classes d’une instance donnée, en commençant par le gestionnaire de classe le plus dérivé pour ensuite passer au gestionnaire de classe de base. Vous ne devez omettre l’appel à l’implémentation de base que si votre classe exige délibérément de changer la logique de gestion de classe de base. C’est la nature de votre implémentation qui détermine si vous appelez l’implémentation de base avant ou après votre code de substitution.

Gestion des classes d’événements d’entrée

Les méthodes virtuelles des gestionnaires de classe sont toutes enregistrées pour faire en sorte qu’elles ne soient appelées que si les données d’événement partagées ne sont pas déjà marquées comme étant gérées. Par ailleurs, pour les événements d’entrée uniquement, les versions de tunneling et de propagation sont généralement déclenchées dans l’ordre et partagent les données d’événement. Par conséquent, pour une paire donnée de gestionnaires de classe d’événements d’entrée, où l’un correspond à la version de tunneling et l’autre à la version de propagation, vous pouvez ne pas marquer immédiatement l’événement comme étant géré. Si vous implémentez la méthode virtuelle de gestion de classe par tunneling pour marquer l’événement comme étant géré, le gestionnaire de classe de propagation ne pourra pas être appelé (ni même les gestionnaires d’instance enregistrés normalement pour l’événement de tunneling ou de propagation).

Une fois que la gestion de classe est terminée sur un nœud, les écouteurs d’instance sont pris en considération.

Ajout de gestionnaires d’instance déclenchés y compris quand les événements sont marqués comme étant gérés

La AddHandler méthode fournit une surcharge particulière qui vous permet d’ajouter des gestionnaires qui seront appelés par le système d’événements chaque fois qu’un événement atteint l’élément de gestion dans l’itinéraire, même si un autre gestionnaire a déjà ajusté les données d’événement pour marquer cet événement comme géré. Il ne s’agit pas de la procédure courante. En général, des gestionnaires peuvent être écrits pour ajuster toutes les zones de code d’application susceptibles d’être influencées par un événement, indépendamment de l’emplacement où il a été géré dans une arborescence d’éléments, même si plusieurs résultats finaux sont souhaités. De plus, seul un élément est généralement chargé véritablement de répondre à cet événement, et la logique d’application appropriée s’est déjà exécutée. Cependant, la surcharge handledEventsToo est disponible pour les cas exceptionnels où un autre élément d’une arborescence d’éléments ou une composition de contrôles a déjà marqué un événement comme étant géré, mais que d’autres éléments situés plus haut ou plus bas dans l’arborescence d’éléments (en fonction de l’itinéraire) demandent toujours à ce que leurs propres gestionnaires soient appelés.

Quand marquer des événements gérés comme étant non gérés

En règle générale, les événements routés marqués qui sont marqués ne doivent pas être marqués comme non gérés (Handled défini sur false) même par les gestionnaires qui agissent sur handledEventsToo. Cependant, certains événements d’entrée ont des représentations d’événement de haut niveau et de bas niveau qui peuvent se chevaucher quand l’événement de haut niveau et l’événement de bas niveau ne se trouvent pas à la même position dans l’arborescence. Par exemple, considérez le cas où un élément enfant écoute un événement clé de haut niveau, tel qu’un TextInput élément parent écoute un événement de bas niveau tel que KeyDown. Si l’élément parent gère l’événement de bas niveau, l’événement de haut niveau peut être supprimé même dans l’élément enfant qui logiquement devrait être le premier à pouvoir gérer l’événement.

Dans ce cas, il peut s’avérer nécessaire d’ajouter des gestionnaires aux éléments parents et enfants pour l’événement de bas niveau. L’implémentation du gestionnaire d’éléments enfants peut marquer l’événement de bas niveau comme étant géré, mais l’implémentation du gestionnaire d’éléments parents le redéfinirait comme étant non géré, si bien que les éléments de niveau supérieur de l’arborescence (ainsi que l’événement de haut niveau) peuvent avoir l’occasion de répondre. Ce cas de figure devrait être assez rare.

Suppression délibérée d’événements d’entrée pour la composition de contrôles

La gestion de classe d’événements routés est principalement utilisée pour les événements d’entrée et les contrôles composés. Un contrôle composé est par définition composé de plusieurs contrôles pratiques ou classes de base de contrôle. Souvent, l’intention de l’auteur du contrôle est d’amalgamer tous les événements d’entrée possibles que chaque sous-composant peut déclencher, de façon à signaler l’ensemble du contrôle comme étant la source singulière des événements. Dans certains cas, l’auteur du contrôle peut souhaiter supprimer totalement les événements des composants ou remplacer un événement défini par un composant qui contient davantage d’informations ou implique un comportement plus spécifique. L’exemple canonique qui est immédiatement visible par n’importe quel auteur de composant est la façon dont un Windows Presentation Foundation (WPF) Button gère n’importe quel événement de souris qui finira par se résoudre à l’événement intuitif que tous les boutons ont : un Click événement.

La Button classe de base (ButtonBase) dérive de Control laquelle dérive à son tour FrameworkElement et UIElement, et une grande partie de l’infrastructure d’événements nécessaire pour le traitement des entrées de contrôle est disponible au UIElement niveau. En particulier, UIElement traite les événements généraux Mouse qui gèrent les tests d’accès pour le curseur de la souris dans ses limites, et fournit des événements distincts pour les actions de bouton les plus courantes, telles que MouseLeftButtonDown. UIElement fournit également une machine virtuelle OnMouseLeftButtonDown vide en tant que gestionnaire de classes préinscrire pour MouseLeftButtonDown, et ButtonBase la remplace. De même, ButtonBase utilise des gestionnaires de classes pour MouseLeftButtonUp. Dans les substitutions, qui sont passées aux données d’événement, les implémentations marquent cette RoutedEventArgs instance comme gérée par la valeur Handledtrue, et les mêmes données d’événement se poursuivent le long du reste de l’itinéraire vers d’autres gestionnaires de classes et aussi vers des gestionnaires d’instances ou des setters d’événements. En outre, la OnMouseLeftButtonUp substitution déclenche ensuite l’événement Click . Le résultat final pour la plupart des écouteurs est que les MouseLeftButtonDown événements « MouseLeftButtonUp disparaissent » et sont remplacés à la place par Click, un événement qui contient plus de signification, car il est connu que cet événement provient d’un vrai bouton et non d’un morceau composite du bouton ou d’un autre élément entièrement.

Résolution des problèmes liés à la suppression d’événements par des contrôles

Ce comportement de suppression d’événements au sein des contrôles individuels peut parfois interférer avec certaines intentions plus générales de la logique de gestion des événements de votre application. Par exemple, si, pour une raison quelconque, votre application disposait d’un gestionnaire pour MouseLeftButtonDown se trouver à l’élément racine de l’application, vous remarqueriez que tout clic de souris sur un bouton n’appelle pas ou n’appelle MouseLeftButtonDown pas de MouseLeftButtonUp gestionnaires au niveau racine. L’événement proprement dit s’est bien propagé (encore une fois, les itinéraires d’événements ne se terminent pas véritablement, mais le système d’événements routés change leur comportement d’appel de gestionnaire après qu’ils ont été marqués comme étant gérés). Lorsque l’événement routé a atteint le bouton, la ButtonBase gestion des classes a marqué le MouseLeftButtonDown handled parce qu’elle souhaitait remplacer l’événement Click par plus de signification. Par conséquent, tout gestionnaire standard MouseLeftButtonDown plus haut dans la route n’est pas appelé. Vous avez le choix entre deux techniques pour faire en sorte que vos gestionnaires soient appelés dans ce cas.

La première technique consiste à ajouter délibérément le gestionnaire à l’aide de la handledEventsToo signature de AddHandler(RoutedEvent, Delegate, Boolean). La limite de cette approche vient du fait que le gestionnaire d’événements ne peut être joint qu’à partir du code et non du balisage. La syntaxe simple de la spécification du nom du gestionnaire d’événements en tant que valeur d’attribut d’événement par le biais du langage XAML (Extensible Application Markup Language) n’active pas ce comportement.

La deuxième technique vaut uniquement pour les événements d’entrée où les versions de tunneling et de propagation de l’événement routé sont associées. Pour ces événements routés, vous pouvez plutôt ajouter des gestionnaires à l’événement routé équivalent preview/tunneling équivalent. Cet événement routé crée un tunnel le long de l’itinéraire en partant de la racine, si bien que le code de gestion de classe Button ne l’intercepte pas, présumant que vous avez joint le gestionnaire Preview au niveau d’un élément ancêtre dans l’arborescence d’éléments de l’application. Si vous utilisez cette approche, faites attention quand il s’agit de marquer un événement Preview comment étant géré. Pour l’exemple donné avec PreviewMouseLeftButtonDown la gestion à l’élément racine, si vous avez marqué l’événement comme Handled dans l’implémentation du gestionnaire, vous supprimez réellement l’événement Click . Ce comportement n’est généralement pas souhaitable.

Voir aussi