Marquage des événements routés comme gérés et gestion des classes (WPF .NET)
Bien qu’il n’existe aucune règle absolue pour marquer un événement routé comme géré, envisagez de marquer un événement comme géré si votre code répond à l’événement de manière significative. Un événement routé marqué comme géré continue le long de son itinéraire, mais seuls les gestionnaires configurés pour répondre aux événements gérés sont appelés. En fait, le marquage d’un événement routé comme géré limite sa visibilité aux écouteurs le long de la route d’événement.
Les gestionnaires d’événements routés peuvent être des gestionnaires d’instances ou des gestionnaires de classes. Les gestionnaires d’instances gèrent les événements routés sur des objets ou des éléments XAML. Les gestionnaires de classes gèrent un événement routé au niveau de la classe et sont appelés avant tout gestionnaire d’instance répondant au même événement sur une instance de la classe. Lorsque les événements routés sont marqués comme gérés, ils sont souvent marqués comme tels dans les gestionnaires de classes. Cet article décrit les avantages et les pièges potentiels liés au fait de marquer les événements routés comme gérés, les différents types d’événements routés et de gestionnaires d’événements routés, ainsi que la suppression d’événements au sein des contrôles composites.
Conditions préalables
L’article suppose une connaissance de base des événements routés et que vous avez lu Vue d’ensemble des événements routés. Pour suivre les exemples de cet article, il vous aide à connaître le langage XAML (Extensible Application Markup Language) et savoir comment écrire des applications Windows Presentation Foundation (WPF).
Quand marquer les événements routés comme gérés
En règle générale, un seul gestionnaire doit fournir une réponse significative pour chaque événement routé. Évitez d’utiliser le système d’événements routé pour fournir une réponse significative sur plusieurs gestionnaires. La définition de ce qui constitue une réponse significative est subjective et dépend de votre application. En guise d’aide générale :
- Les réponses significatives incluent la définition du focus, la modification de l’état public, la définition des propriétés qui affectent la représentation visuelle, l’déclenchement de nouveaux événements et la gestion complète d’un événement.
- Les réponses non négligeables incluent la modification de l’état privé sans impact visuel ou programmatique, la journalisation des événements et l’examen des données d’événement sans répondre à l’événement.
Certains contrôles WPF suppriment les événements au niveau du composant qui n’ont pas besoin d’une gestion supplémentaire en les marquant comme gérés. Si vous souhaitez gérer un événement indiqué comme traité par un contrôle, consultez Contournement de la suppression d'événements par les contrôles.
Pour marquer un événement comme géré, définissez la valeur de la propriété Handled dans ses données d’événement sur true
. Bien qu’il soit possible de rétablir cette valeur à false
, la nécessité de le faire doit être rare.
Paires d’événements routées d’aperçu et de propagation
Les paires d’événements routés d’aperçu et de propagation sont spécifiques aux événements d’entrée. Plusieurs événements d’entrée implémentent une paire d’événements routés de tunneling et de propagation, comme PreviewKeyDown et KeyDown. Le préfixe Preview
signifie que l'événement de propagation commence une fois l'événement d’aperçu terminé. Chaque paire d’événements d’aperçu et de propagation partage la même instance de données d’événement.
Les gestionnaires d’événements routés sont appelés dans un ordre qui correspond à la stratégie de routage d’un événement :
- L’événement d’aperçu passe de l’élément racine de l’application vers l’élément qui a déclenché l’événement routé. Les gestionnaires d’événements en préversion attachés à l’élément racine de l’application sont appelés en premier, suivis de gestionnaires attachés à des éléments imbriqués successifs.
- Une fois l’événement d’aperçu terminé, l’événement de propagation associé passe de l’élément qui a déclenché l’événement routé à l’élément racine de l’application. Les gestionnaires d’événements de propagation attachés au même élément qui a déclenché l’événement routé sont appelés en premier, suivis de gestionnaires attachés à des éléments parents successifs.
Les événements d’aperçu et de propagation jumelés font partie de l’implémentation interne de plusieurs classes WPF qui déclarent et déclenchent leurs propres événements routés. Sans cette implémentation interne au niveau de la classe, les événements routés d’aperçu et de propagation sont entièrement distincts et ne partagent pas les données d’événement, quel que soit le nommage des événements. Pour plus d’informations sur l’implémentation d'événements routés d'entrée de type bubbling ou tunneling dans une classe personnalisée, consultez créer un événement routé personnalisé.
Étant donné que chaque paire d’événements d’aperçu et de propagation partage la même instance de données d’événement, si un événement routé en préversion est marqué comme géré, son événement de propagation jumelé sera également géré. Si un événement routé de propagation est marqué comme géré, il n’affecte pas l’événement d’aperçu jumelé, car l’événement d’aperçu est terminé. Faites attention lors du marquage des paires d’événements d’entrée d’aperçu et de propagation en tant qu’événements gérés. Un événement d'aperçu d'entrée géré n'appellera pas les gestionnaires d'événements normalement inscrits pour le reste de l’itinéraire de tunneling, et l'événement de propagation associé ne sera pas déclenché. Un événement d’entrée de propagation géré n’appelle pas de gestionnaires d’événements normalement inscrits pour le reste de l’itinéraire de propagation.
Gestionnaires d’événements routés d’instance et de classe
Les gestionnaires d’événements routés peuvent être des gestionnaires d’instances ou des gestionnaires de classes. Les gestionnaires de classes d’une classe donnée sont appelés avant tout gestionnaire d’instance répondant au même événement sur une instance de cette classe. En raison de ce comportement, lorsque les événements routés sont marqués comme gérés, ils sont souvent marqués comme tels dans les gestionnaires de classes. Il existe deux types de gestionnaires de classes :
- Gestionnaires d’événements de classe statique, qui sont enregistrés au sein d’un constructeur de classe statique en appelant la méthode RegisterClassHandler.
- Remplacer les gestionnaires d’événements de classe, qui sont enregistrés en remplaçant les méthodes d'événements virtuels de la classe de base. Les méthodes virtuelles d'événements des classes de base existent principalement pour les événements d’entrée et ont des noms qui commencent par <nom d’événement> et OnPreview<nom d’événement>.
Gestionnaires d’événements d’instance
Vous pouvez attacher des gestionnaires d’instances à des objets ou des éléments XAML en appelant directement la méthode AddHandler. Les événements routés WPF implémentent un wrapper d’événements CLR (Common Language Runtime) qui utilise la méthode AddHandler
pour attacher des gestionnaires d’événements. Étant donné que la syntaxe d’attribut XAML pour ajouter des gestionnaires d’événements se traduit par un appel au wrapper d’événements CLR, même l’ajout de gestionnaires dans XAML se résout par un appel AddHandler
. Pour les événements gérés :
- Les gestionnaires attachés à l’aide de la syntaxe d’attribut XAML ou de la signature commune de
AddHandler
ne sont pas appelés. - Les gestionnaires attachés par la surcharge AddHandler(RoutedEvent, Delegate, Boolean) avec le paramètre
handledEventsToo
défini surtrue
sont appelés. Cette surcharge est disponible dans les rares cas où il est nécessaire de répondre aux événements gérés. Par exemple, certains éléments d’une arborescence d’éléments ont marqué un événement comme géré, mais d’autres éléments plus loin le long de l’itinéraire d’événement doivent répondre à l’événement géré.
L’exemple XAML suivant ajoute un contrôle personnalisé nommé componentWrapper
, qui encapsule un TextBox nommé componentTextBox
, à un StackPanel nommé outerStackPanel
. Un gestionnaire d’événements d’instance pour l’événement PreviewKeyDown est attaché au componentWrapper
à l’aide de la syntaxe d’attribut XAML. Par conséquent, le gestionnaire d’instances répond uniquement aux événements de tunneling non gérés PreviewKeyDown
déclenchés par le componentTextBox
.
<StackPanel Name="outerStackPanel" VerticalAlignment="Center">
<custom:ComponentWrapper
x:Name="componentWrapper"
TextBox.PreviewKeyDown="HandlerInstanceEventInfo"
HorizontalAlignment="Center">
<TextBox Name="componentTextBox" Width="200" />
</custom:ComponentWrapper>
</StackPanel>
Le constructeur MainWindow
attache un gestionnaire d'instance pour l'événement de propagation KeyDown
au componentWrapper
à l’aide de la surcharge UIElement.AddHandler(RoutedEvent, Delegate, Boolean), avec le paramètre handledEventsToo
défini sur true
. Par conséquent, le gestionnaire d’événements d’instance répond aux événements non gérés et gérés.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Attach an instance handler on componentWrapper that will be invoked by handled KeyDown events.
componentWrapper.AddHandler(KeyDownEvent, new RoutedEventHandler(Handler.InstanceEventInfo),
handledEventsToo: true);
}
// The handler attached to componentWrapper in XAML.
public void HandlerInstanceEventInfo(object sender, KeyEventArgs e) =>
Handler.InstanceEventInfo(sender, e);
}
Partial Public Class MainWindow
Inherits Window
Public Sub New()
InitializeComponent()
' Attach an instance handler on componentWrapper that will be invoked by handled KeyDown events.
componentWrapper.[AddHandler](KeyDownEvent, New RoutedEventHandler(AddressOf InstanceEventInfo),
handledEventsToo:=True)
End Sub
' The handler attached to componentWrapper in XAML.
Public Sub HandlerInstanceEventInfo(sender As Object, e As KeyEventArgs)
InstanceEventInfo(sender, e)
End Sub
End Class
L’implémentation code-behind de ComponentWrapper
est indiquée dans la section suivante.
Gestionnaires d’événements de classe statique
Vous pouvez attacher des gestionnaires d’événements de classe statique en appelant la méthode RegisterClassHandler dans le constructeur statique d’une classe. Chaque classe d’une hiérarchie de classes peut inscrire son propre gestionnaire de classes statique pour chaque événement routé. Par conséquent, il peut y avoir plusieurs gestionnaires de classes statiques appelés pour le même événement sur n’importe quel nœud donné dans l’itinéraire d’événement. Lorsque l’itinéraire d’événement de l’événement est construit, tous les gestionnaires de classes statiques pour chaque nœud sont ajoutés à l’itinéraire d’événement. L’ordre d’appel des gestionnaires de classes statiques sur un nœud commence par le gestionnaire de classes statiques le plus dérivé, suivi de gestionnaires de classes statiques de chaque classe de base successive.
Les gestionnaires d’événements de classe statique inscrits à l’aide de la surcharge de RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean), avec le paramètre handledEventsToo
défini sur true
, répondront aux événements routés, qu'ils soient gérés ou non.
Les gestionnaires de classes statiques sont généralement inscrits pour répondre aux événements non gérés uniquement. Dans ce cas, si un gestionnaire de classes dérivé sur un nœud marque un événement comme géré, les gestionnaires de classes de base pour cet événement ne seront pas appelés. Dans ce scénario, le gestionnaire de classes de base est remplacé par le gestionnaire de classes dérivée. Les gestionnaires de classes de base contribuent souvent à la conception de contrôle dans des zones telles que l’apparence visuelle, la logique d’état, la gestion des entrées et la gestion des commandes. Soyez donc prudent sur leur remplacement. Les gestionnaires de classes dérivés qui ne marquent pas un événement comme étant gérés complètent les gestionnaires de classes de base au lieu de les remplacer.
L’exemple de code suivant montre la hiérarchie de classes pour le contrôle personnalisé ComponentWrapper
qui a été référencé dans le code XAML précédent. La classe ComponentWrapper
dérive de la classe ComponentWrapperBase
, qui dérive à son tour de la classe StackPanel. La méthode RegisterClassHandler
, utilisée dans le constructeur statique des classes ComponentWrapper
et ComponentWrapperBase
, inscrit un gestionnaire d’événements de classe statique pour chacune de ces classes. Le système d’événements WPF appelle le gestionnaire de classes statiques ComponentWrapper
avant le gestionnaire de classes statiques ComponentWrapperBase
.
public class ComponentWrapper : ComponentWrapperBase
{
static ComponentWrapper()
{
// Class event handler implemented in the static constructor.
EventManager.RegisterClassHandler(typeof(ComponentWrapper), KeyDownEvent,
new RoutedEventHandler(Handler.ClassEventInfo_Static));
}
// Class event handler that overrides a base class virtual method.
protected override void OnKeyDown(KeyEventArgs e)
{
Handler.ClassEventInfo_Override(this, e);
// Call the base OnKeyDown implementation on ComponentWrapperBase.
base.OnKeyDown(e);
}
}
public class ComponentWrapperBase : StackPanel
{
// Class event handler implemented in the static constructor.
static ComponentWrapperBase()
{
EventManager.RegisterClassHandler(typeof(ComponentWrapperBase), KeyDownEvent,
new RoutedEventHandler(Handler.ClassEventInfoBase_Static));
}
// Class event handler that overrides a base class virtual method.
protected override void OnKeyDown(KeyEventArgs e)
{
Handler.ClassEventInfoBase_Override(this, e);
e.Handled = true;
Debug.WriteLine("The KeyDown routed event is marked as handled.");
// Call the base OnKeyDown implementation on StackPanel.
base.OnKeyDown(e);
}
}
Public Class ComponentWrapper
Inherits ComponentWrapperBase
Shared Sub New()
' Class event handler implemented in the static constructor.
EventManager.RegisterClassHandler(GetType(ComponentWrapper), KeyDownEvent,
New RoutedEventHandler(AddressOf ClassEventInfo_Static))
End Sub
' Class event handler that overrides a base class virtual method.
Protected Overrides Sub OnKeyDown(e As KeyEventArgs)
ClassEventInfo_Override(Me, e)
' Call the base OnKeyDown implementation on ComponentWrapperBase.
MyBase.OnKeyDown(e)
End Sub
End Class
Public Class ComponentWrapperBase
Inherits StackPanel
Shared Sub New()
' Class event handler implemented in the static constructor.
EventManager.RegisterClassHandler(GetType(ComponentWrapperBase), KeyDownEvent,
New RoutedEventHandler(AddressOf ClassEventInfoBase_Static))
End Sub
' Class event handler that overrides a base class virtual method.
Protected Overrides Sub OnKeyDown(e As KeyEventArgs)
ClassEventInfoBase_Override(Me, e)
e.Handled = True
Debug.WriteLine("The KeyDown event is marked as handled.")
' Call the base OnKeyDown implementation on StackPanel.
MyBase.OnKeyDown(e)
End Sub
End Class
L’implémentation code-behind des gestionnaires d’événements de classe de remplacement dans cet exemple de code est décrite dans la section suivante.
Remplacer les gestionnaires d’événements de classe
Certaines classes de base des éléments visuels exposent des méthodes virtuelles vides On<nom d'événement> et OnPreview<nom d'événement> pour chacun de leurs événements d'entrée routés publics. Par exemple, UIElement implémente les gestionnaires d’événements virtuels OnKeyDown et OnPreviewKeyDown, et bien d’autres. Vous pouvez remplacer les gestionnaires d’événements virtuels de classe de base pour implémenter des gestionnaires d’événements de classe de remplacement pour vos classes dérivées. Par exemple, vous pouvez ajouter un gestionnaire de classes de remplacement pour l’événement DragEnter dans n’importe quelle classe dérivée UIElement
en substituant la méthode virtuelle OnDragEnter. La substitution de méthodes virtuelles de classe de base est un moyen plus simple d’implémenter des gestionnaires de classes que d’inscrire des gestionnaires de classes dans un constructeur statique. Dans le remplacement, vous pouvez déclencher des événements, lancer une logique spécifique à la classe pour modifier les propriétés d’élément sur les instances, marquer l’événement comme géré ou effectuer d’autres actions de gestion d’événements.
Contrairement aux gestionnaires d’événements de classe statique, le système d’événements WPF appelle uniquement les gestionnaires d’événements de classe de remplacement pour la classe la plus dérivée dans une hiérarchie de classes. La classe la plus dérivée d’une hiérarchie de classes peut ensuite utiliser le mot clé de base
Dans l’exemple de code précédent, la méthode virtuelle de la classe de base OnKeyDown
est redéfinie dans les classes ComponentWrapper
et ComponentWrapperBase
. Étant donné que le système d’événements WPF appelle uniquement le gestionnaire d’événements de classe de remplacement ComponentWrapper.OnKeyDown
, ce gestionnaire utilise base.OnKeyDown(e)
pour appeler le gestionnaire d’événements de classe de remplacement ComponentWrapperBase.OnKeyDown
, qui utilise à son tour base.OnKeyDown(e)
pour appeler la méthode virtuelle StackPanel.OnKeyDown
. L’ordre des événements dans l’exemple de code précédent est le suivant :
- Le gestionnaire d’instances attaché à
componentWrapper
est déclenché par l’événement routéPreviewKeyDown
. - Le gestionnaire de classes statiques attaché à
componentWrapper
est déclenché par l’événement routéKeyDown
. - Le gestionnaire de classes statiques attaché à
componentWrapperBase
est déclenché par l’événement routéKeyDown
. - Le gestionnaire de classes de substitution attaché à
componentWrapper
est déclenché par l’événement routéKeyDown
. - Le gestionnaire de classes de substitution attaché à
componentWrapperBase
est déclenché par l’événement routéKeyDown
. - L’événement routé
KeyDown
est marqué comme géré. - Le gestionnaire d’instances attaché à
componentWrapper
est déclenché par l’événement routéKeyDown
. Le gestionnaire a été enregistré avec le paramètrehandledEventsToo
défini surtrue
.
Suppression d’événements d’entrée dans les contrôles composites
Certains contrôles composites suppriment des événements d’entrée au niveau du composant afin de les remplacer par un événement de haut niveau personnalisé qui contient plus d’informations ou implique un comportement plus spécifique. Un contrôle composite est par définition composé de plusieurs contrôles pratiques ou classes de base de contrôle. Un exemple classique est le contrôle Button, qui transforme différents événements de souris en un événement routé Click. La classe de base Button
est ButtonBase, qui dérive indirectement de UIElement. Une grande partie de l’infrastructure d’événements nécessaire pour contrôler le traitement des entrées est disponible au niveau UIElement
. UIElement
expose plusieurs événements Mouse tels que MouseLeftButtonDown et MouseRightButtonDown. UIElement
implémente également les méthodes virtuelles vides OnMouseLeftButtonDown et OnMouseRightButtonDown en tant que gestionnaires de classes préinscrirés. ButtonBase
remplace ces gestionnaires de classes et, dans le gestionnaire de substitution, définit la propriété Handled sur true
et déclenche un événement Click
. Le résultat final pour la plupart des auditeurs est que les événements MouseLeftButtonDown
et MouseRightButtonDown
sont masqués, et que l’événement Click
de niveau supérieur est visible.
Contourner la suppression d’événements d’entrée
Parfois, la suppression d’événements au sein de contrôles individuels peut interférer avec la logique de gestion des événements dans votre application. Par exemple, si votre application a utilisé la syntaxe d’attribut XAML pour attacher un gestionnaire pour l’événement MouseLeftButtonDown sur l’élément racine XAML, ce gestionnaire n’est pas appelé, car le contrôle Button marque l’événement MouseLeftButtonDown
comme géré. Si vous souhaitez que les éléments vers la racine de votre application soient appelés pour un événement routé géré, vous pouvez :
Attachez des gestionnaires en appelant la méthode UIElement.AddHandler(RoutedEvent, Delegate, Boolean) avec le paramètre
handledEventsToo
défini surtrue
. Cette approche nécessite l’attachement du gestionnaire d’événements dans code-behind, après avoir obtenu une référence d’objet pour l’élément auquel il sera attaché.Si l’événement marqué comme géré est un événement d’entrée de propagation, attachez des gestionnaires pour l’événement d’aperçu jumelé, le cas échéant. Par exemple, si un contrôle supprime l’événement
MouseLeftButtonDown
, vous pouvez attacher un gestionnaire pour l’événement PreviewMouseLeftButtonDown à la place. Cette approche fonctionne uniquement pour les paires d’événements d’entrée d’aperçu et de propagation, qui partagent des données d’événement. Veillez à ne pas marquer lePreviewMouseLeftButtonDown
comme géré, car cela supprimerait complètement l’événement Click.
Pour un exemple de contournement de la suppression des événements d’entrée, consultez Contournement de la suppression d’événements par les contrôles.
Voir aussi
.NET Desktop feedback