Partager via


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

Les gestionnaires d’un événement routé peuvent marquer l’événement géré dans les données d’événement. La gestion de l’événement raccourcit efficacement l’itinéraire. La gestion de classes est un concept de programmation qui est pris en charge par les événements routés. Un gestionnaire de classes a la possibilité de gérer un événement routé particulier au niveau de la classe avec un gestionnaire appelé avant tout gestionnaire d’instance sur une instance de la classe.

Conditions préalables

Cette rubrique décrit les concepts présentés dans la 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 propriété Handled sur true dans les données d’événement pour un événement routé, il s’agit de « marquage de l’événement géré ». Il n’existe aucune règle absolue pour laquelle vous devez marquer les événements routés comme gérés, soit en tant qu’auteur d’application, soit en tant qu’auteur de contrôle qui répond aux événements routés existants ou implémente de nouveaux événements routés. Dans la plupart des cas, le concept de « handled » tel qu'il est porté dans les données des événements routés doit être utilisé comme un protocole restreint 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 de considérer le problème « géré » est que vous devez généralement marquer un événement routé géré si votre code a répondu à l’événement routé de manière significative et relativement complète. En règle générale, il ne doit pas y avoir plus d’une réponse significative qui nécessite des implémentations de gestionnaire distinctes pour toute occurrence d’événement routée unique. Si d’autres réponses sont nécessaires, le code nécessaire doit être implémenté par le biais de la logique d’application chaînée au sein d’un seul gestionnaire plutôt que d’utiliser le système d’événements routé pour le transfert. Le concept de ce qui est « significatif » est également subjectif et dépend de votre application ou de votre code. En guise d’aide générale, certains exemples de « réponse significative » incluent : 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 nouveaux événements. Voici quelques exemples de réponses non négligeables : modification de l’état privé (sans impact visuel ou représentation programmatique), journalisation des événements ou examen des arguments d’un événement et choix de ne pas y répondre.

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 en outre veiller à ajouter un gestionnaire avec la version du paramètre handledEventsToo (AddHandler(RoutedEvent, Delegate, Boolean)) de façon à gérer les événements routés marqués comme étant gérés par les premiers intervenants dans l’itinéraire de l’é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 se fait généralement en ajoutant un gestionnaire de classes pour un événement ou en remplaçant l’une des machines virtuelles de gestionnaire de classes qui existent sur une classe de base. Vous pouvez toujours contourner cette gestion des événements si nécessaire ; consultez Working Around Event Suppression by Controls plus loin dans cette rubrique.

Événements de « Préversion  » (Tunneling) par rapport aux événements de propagation et gestion des événements

Les événements routés de Préversion 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 propagation est établie par l’implémentation interne du mode de déclenchement des événements routés déclarés d’une classe WPF donnée, et cela s’applique aux événements routés d’entrée associés. Toutefois, sauf si cette implémentation au niveau de la classe existe, il n’existe aucune connexion entre un événement routé de tunneling et un événement routé de bubbling qui partagent le même schéma de nommage : sans cette implémentation, ils seraient deux événements routés entièrement distincts et ne seraient pas déclenchés en séquence, ni ne partageraient de données d’événement.

Pour plus d'informations sur la manière d'implémenter des paires d'événements routés de type tunnel/bulle 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 parce que des types ont appelé une API EventManager particulière, RegisterClassHandler, dans leur constructeur statique, ou ont remplacé la méthode virtuelle d’un gestionnaire de classe à partir d’une classe de base d’élément. Les écouteurs d’instance sont des instances de classe/éléments particuliers où un ou plusieurs gestionnaires ont été associés pour cet événement routé par un appel à AddHandler. Les événements routés WPF existants effectuent des appels à AddHandler dans le cadre du wrapper d’événements de l'environnement d'exécution du langage commun (CLR) pour ajouter{} et supprimer{} comme implémentations de l'événement. Cela permet également d'activer le mécanisme simple de XAML pour attacher des gestionnaires d'événements via une syntaxe d'attribut. Par conséquent, même l’utilisation XAML simple équivaut finalement à un appel AddHandler.

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é. Ensuite, l’événement routé se propage vers l’élément parent suivant, 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 profond que l’itinéraire d’événements, il existe également potentiellement plusieurs gestionnaires de classes agissant sur n’importe quelle instance donnée d’une classe. Cela est dû au fait que le modèle de gestion des classes pour les événements routés permet à toutes les classes possibles d’une hiérarchie de classes d’inscrire son propre gestionnaire de classes pour chaque événement routé. Chaque gestionnaire de classes est ajouté à un magasin interne et lorsque l’itinéraire d’événement d’une application est construit, les gestionnaires de classes sont tous ajoutés à l’itinéraire d’événements. Les gestionnaires de classes sont ajoutés à l’itinéraire afin que le gestionnaire de classes le plus dérivé soit appelé en premier, et les gestionnaires de classes de chaque classe de base successive sont 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 des classes permet l’un des deux choix suivants :

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

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

Gestion des classes d’événements routés par 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. Pour cette raison, les gestionnaires de classes 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 plus loin, ou pour fournir une gestion spéciale de cet événement routé qui est une fonctionnalité de la classe. Par exemple, une classe peut déclencher son propre événement spécifique à la classe qui contient plus de détails sur ce que signifie une 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énements partagées ont déjà été marquées comme gérées, mais pour les cas atypiques, il existe également une signature RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) qui inscrit 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 substituées pour implémenter un gestionnaire de classes pour cet événement routé. Les classes d’éléments de base inscrivent ces méthodes virtuelles comme gestionnaire de classes pour chaque événement routé à l’aide de RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) 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 classe dérivée UIElement en substituant la méthode virtuelle OnDragEnter. 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é. L’appel de l’implémentation de base est fortement recommandé, car la méthode virtuelle se trouve sur la classe de base. Le modèle virtuel protégé standard d’appel des implémentations de base de chaque machine virtuelle remplace et parallèle un mécanisme similaire natif à la gestion des classes d’événements routées, dans laquelle les gestionnaires de classes pour toutes les classes d’une hiérarchie de classes sont appelés sur n’importe quelle instance donnée, en commençant par le gestionnaire de classe la plus dérivée et en continuant vers le gestionnaire de classe de base. Vous ne devez omettre l’appel d’implémentation de base que si votre classe a une exigence délibérée pour modifier la logique de gestion des classes de base. Que vous appeliez l’implémentation de base avant ou après votre code de substitution dépend de la nature de votre implémentation.

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

Les méthodes virtuelles du gestionnaire de classes sont toutes inscrites de telle sorte qu’elles ne soient appelées que dans les cas où les données d’événement partagées ne sont pas déjà marquées comme 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 méthode AddHandler 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é. Cela n’est généralement pas fait. En règle générale, les gestionnaires peuvent être écrits pour ajuster toutes les zones de code d’application qui peuvent être influencées par un événement, quel que soit l’emplacement où il a été géré dans une arborescence d’éléments, même si plusieurs résultats finaux sont souhaités. En règle générale, il n’existe qu’un seul élément qui doit répondre à cet événement, et la logique d’application appropriée s’est déjà produite. 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 principe, les événements routés marqués comme étant gérés ne devraient pas être marqués comme étant non gérés (Handled reprend la valeur false) même par les gestionnaires qui agissent sur handledEventsToo. Toutefois, certains événements d’entrée ont des représentations d’événements de haut niveau et de bas niveau qui peuvent se chevaucher lorsque l’événement de haut niveau est vu à une position dans l’arborescence et l’événement de bas niveau à une autre position. Par exemple, considérez le cas où un élément enfant écoute un événement clé de haut niveau tel que TextInput tandis qu’un é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 niveau supérieur peut être supprimé même dans l’élément enfant qui doit avoir la première possibilité de gérer l’événement de manière intuitive.

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 géré, mais l’implémentation du gestionnaire d’éléments parent peut le redéfinir comme non géré afin que d’autres éléments plus hauts dans l’arborescence (ainsi que l’événement de haut niveau) puissent avoir la possibilité de répondre. Cette situation devrait être assez rare.

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

Le scénario principal où la gestion des classes des événements routés est utilisée pour les événements d’entrée et les contrôles composites. Un contrôle composite est par définition composé de plusieurs contrôles pratiques ou classes de base de contrôle. Souvent, l’auteur du contrôle souhaite fusionner tous les événements d’entrée possibles que chacun des sous-composants peut déclencher, afin de signaler l’intégralité du contrôle comme source d’événement unique. Dans certains cas, l’auteur du contrôle peut souhaiter supprimer entièrement les événements des composants, ou remplacer un événement défini par un composant qui contient plus d’informations ou implique un comportement plus spécifique. L'exemple canonique qui est immédiatement visible pour tout auteur de composant est la façon dont un Button Windows Presentation Foundation (WPF) gère tout événement de souris qui mène finalement à l'événement intuitif que possèdent tous les boutons : un événement Click.

La classe de base Button (ButtonBase) dérive de Control qui dérive à son tour de FrameworkElement et de 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 niveau UIElement. En particulier, UIElement traite les événements généraux Mouse qui gèrent les tests d’atteinte du 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 un OnMouseLeftButtonDown virtuel vide en tant que gestionnaire de classes préinscriré pour MouseLeftButtonDown, et ButtonBase le remplace. De même, ButtonBase utilise des gestionnaires de classes pour MouseLeftButtonUp. Dans les substitutions, qui reçoivent les données d’événement, les implémentations marquent l’instance RoutedEventArgs comme étant traitée en définissant Handled sur true, et ces mêmes données d’événement sont celles qui poursuivent le reste de l’itinéraire vers d’autres gestionnaires de classe et également vers des gestionnaires d’instance ou des accesseurs d’événement. De plus, la substitution OnMouseLeftButtonUp déclenchera ensuite l’événement Click. Le résultat final pour la plupart des auditeurs est que les événements MouseLeftButtonDown et MouseLeftButtonUp « disparaissent » et sont remplacés par Click, un événement qui a plus de sens, car il est connu que cet événement provient d’un vrai bouton et non d’une partie composite ou d’un autre élément.

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

Parfois, ce comportement de suppression d’événements au sein de contrôles individuels peut interférer avec certaines intentions plus générales de logique de gestion des événements pour votre application. Par exemple, si, pour une raison quelconque, votre application avait un gestionnaire pour MouseLeftButtonDown situé à l’élément racine de l’application, vous remarqueriez que tout clic de souris sur un bouton n’appelle pas MouseLeftButtonDown ou 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 gestion des classes ButtonBase a marqué le MouseLeftButtonDown comme géré, car elle souhaitait remplacer l’événement Click par une signification plus spécifique. Par conséquent, tout gestionnaire MouseLeftButtonDown standard situé plus haut dans le parcours ne serait pas appelé. Il existe deux techniques que vous pouvez utiliser pour vous assurer que vos gestionnaires seraient appelés dans ce cas.

La première technique consiste à ajouter délibérément le gestionnaire à l’aide de la signature handledEventsToo de AddHandler(RoutedEvent, Delegate, Boolean). Une limitation de cette approche est que cette technique pour attacher un gestionnaire d’événements n’est possible qu’à partir du code, et non à partir 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 de Préversion comment étant géré. Pour l’exemple donné avec PreviewMouseLeftButtonDown géré au niveau de 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