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 l’itinéraire de l’é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 de marquage des événements routés comme gérés, les différents types d’événements routés et les gestionnaires d’événements routés et la suppression d’événements dans les contrôles composites.

Important

La documentation du Guide du bureau pour .NET 7 et .NET 6 est en cours de construction.

Prérequis

L’article suppose une connaissance de base des événements routés et que vous avez lu la 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 règle 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 marqué comme géré par un contrôle, consultez Working around event suppression by controls.

Pour marquer un événement comme géré, définissez la valeur de la Handled propriété dans ses données trued’événement sur . Bien qu’il soit possible de rétablir cette valeur false, la nécessité de le faire doit être rare.

Aperçu et combinaisons d’événements routées

Les paires d’événements routées de préversion et de bulle sont spécifiques aux événements d’entrée. Plusieurs événements d’entrée implémentent une paire d’événements routée de tunneling et de boublage , comme PreviewKeyDown et KeyDown. Le Preview préfixe signifie que l’événement de basculement démarre une fois l’événement d’aperçu terminé. Chaque paire d’événements d’aperçu et de bulle 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 :

  1. 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.
  2. Une fois l’événement d’aperçu terminé, l’événement de bubbling jumelé passe de l’élément qui a déclenché l’événement routé vers l’élément racine de l’application. Les gestionnaires d’événements bubbling 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 de préversion et de bulle 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 en préversion et en bulle 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 ou de 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 bulle 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 bubbling jumelé sera également géré. Si un événement routé 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é. Soyez prudent lors du marquage des paires d’événements d’aperçu et de boublage des événements d’entrée comme gérées. Un événement d’entrée en préversion géré n’appelle pas de gestionnaires d’événements normalement inscrits pour le reste de l’itinéraire de tunneling, et l’événement de bubbling jumelé ne sera pas déclenché. Un événement d’entrée de boublage géré n’appelle pas de gestionnaires d’événements normalement inscrits pour le reste de l’itinéraire de boublage.

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 inscrits en appelant la RegisterClassHandler méthode dans un constructeur de classe statique.
  • Remplacer les gestionnaires d’événements de classe, qui sont inscrits en remplaçant les méthodes d’événements virtuels de classe de base. Les méthodes d’événements virtuels de classe de base existent principalement pour les événements d’entrée et ont des noms qui commencent par le nom> de l’événement On<et le nom> de l’événement OnPreview<.

Gestionnaires d’événements d’instance

Vous pouvez attacher des gestionnaires d’instances à des objets ou des éléments XAML en appelant directement la AddHandler méthode. Les événements routés WPF implémentent un wrapper d’événements CLR (Common Language Runtime) qui utilise la AddHandler méthode pour attacher des gestionnaires d’événements. Étant donné que la syntaxe d’attribut XAML pour l’attachement de gestionnaires d’événements entraîne un appel au wrapper d’événements CLR, même l’attachement de gestionnaires dans XAML se résout par un AddHandler appel. 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 à l’aide de la AddHandler(RoutedEvent, Delegate, Boolean) surcharge avec le jeu de handledEventsToo paramètres à true appeler. 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 nom componentTextBox, à un StackPanel nommé outerStackPanel. Un gestionnaire d’événements d’instance pour l’événement PreviewKeyDown est attaché à la componentWrapper syntaxe d’attribut XAML. Par conséquent, le gestionnaire d’instances répond uniquement aux événements de tunneling non gérés déclenchés PreviewKeyDown 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 MainWindow constructeur attache un gestionnaire d’instances pour l’événement KeyDown de boublage à l’aide componentWrapper de la UIElement.AddHandler(RoutedEvent, Delegate, Boolean) surcharge, avec le handledEventsToo paramètre 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 RegisterClassHandler méthode 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 RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) surcharge avec le jeu de handledEventsToo paramètres pour true répondre aux événements routés non gérés et gérés.

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 ComponentWrapper contrôle personnalisé référencé dans le code XAML précédent. La classe ComponentWrapper dérive de la classe ComponentWrapperBase, elle-même dérivée de la classe StackPanel. La RegisterClassHandler méthode, utilisée dans le constructeur statique des ComponentWrapper classes et ComponentWrapperBase des classes, inscrit un gestionnaire d’événements de classe statique pour chacune de ces classes. Le système d’événements WPF appelle le ComponentWrapper gestionnaire de classes statiques devant le ComponentWrapperBase gestionnaire de classes statiques.

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 d’éléments visuels exposent des méthodes virtuelles vides Sur le nom> d’événement et OnPreview><< pour chacun de leurs événements d’entrée routés publics. Par exemple, UIElement implémente les OnKeyDown gestionnaires d’événements virtuels et OnPreviewKeyDown 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 UIElement classe dérivée en remplaçant la OnDragEnter méthode virtuelle. 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 logiques 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 l’mot clé de base pour appeler l’implémentation de base de la méthode virtuelle. Dans la plupart des cas, vous devez appeler l’implémentation de base, que vous marquez un événement comme géré. Vous devez uniquement omettre d’appeler l’implémentation de base si votre classe a besoin de remplacer la logique d’implémentation de base, le cas échéant. 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.

Dans l’exemple de code précédent, la méthode virtuelle de classe OnKeyDown de base est substituée dans les classes et ComponentWrapperBase les ComponentWrapper classes. Étant donné que le système d’événements WPF appelle uniquement le ComponentWrapper.OnKeyDown gestionnaire d’événements de classe de remplacement, ce gestionnaire utilise base.OnKeyDown(e) pour appeler le ComponentWrapperBase.OnKeyDown gestionnaire d’événements de classe de substitution, qui à son tour utilise base.OnKeyDown(e) pour appeler la StackPanel.OnKeyDown méthode virtuelle. L’ordre des événements dans l’exemple de code précédent est le suivant :

  1. Le gestionnaire d’instances auquel il est attaché componentWrapper est déclenché par l’événement PreviewKeyDown routé.
  2. Le gestionnaire de classes statiques componentWrapper auquel il est attaché est déclenché par l’événement KeyDown routé.
  3. Le gestionnaire de classes statiques componentWrapperBase auquel il est attaché est déclenché par l’événement KeyDown routé.
  4. Le gestionnaire de classes de remplacement auquel il est attaché componentWrapper est déclenché par l’événement KeyDown routé.
  5. Le gestionnaire de classes de remplacement auquel il est attaché componentWrapperBase est déclenché par l’événement KeyDown routé.
  6. L’événement KeyDown routé est marqué comme géré.
  7. Le gestionnaire d’instances auquel il est attaché componentWrapper est déclenché par l’événement KeyDown routé. Le gestionnaire a été inscrit auprès du handledEventsToo paramètre défini sur true.

Suppression d’événements d’entrée dans les contrôles composites

Certains contrôles composites suppriment les é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 Button contrôle, qui transforme différents événements de souris en événement Click routé. La Button classe de base est ButtonBase, qui dérive indirectement de UIElement. Une grande partie de l’infrastructure d’événements nécessaire pour contrôler le UIElement traitement des entrées est disponible au niveau. UIElement expose plusieurs Mouse événements tels que MouseLeftButtonDown et MouseRightButtonDown. UIElement implémente également les méthodes OnMouseLeftButtonDown virtuelles vides 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 Handled propriété true et déclenche un Click événement. Le résultat final de la plupart des écouteurs est que les événements et MouseRightButtonDown les MouseLeftButtonDown événements sont masqués et que l’événement de haut niveau Click 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 sur l’élément MouseLeftButtonDown racine XAML, ce gestionnaire ne sera pas appelé, car le Button contrôle 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 UIElement.AddHandler(RoutedEvent, Delegate, Boolean) méthode avec le handledEventsToo paramètre défini sur true. 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 bulle, 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 à la PreviewMouseLeftButtonDown place. Cette approche fonctionne uniquement pour les paires d’événements d’entrée en préversion et de bouclage, qui partagent des données d’événement. Veillez à ne pas marquer le PreviewMouseLeftButtonDown tel qu’il est géré, car cela supprimerait complètement l’événement Click .

Pour obtenir un exemple de fonctionnement de la suppression d’événements d’entrée, consultez Working around event suppression by controls.

Voir aussi