Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

WPF : Comprendre les Routed Events

WPF n’utilise pas le mécanisme standard d’évènement du Framework .NET. Il utilise une version améliorée : les Routed Events. Nous allons voir dans cet article comment ils fonctionnent.
Les Routed Events sont sous certains aspects assez similaires aux Dependency Properties (vous pouvez à ce propos consulter mon article sur les Dependency Properties).

Pour qu’un objet puisse bénéficier des fonctionnalités des Routed Events, il faut que cet objet dérive de UIElement, au même titre qu’un objet doit dériver de DependencyObject pour bénéficier du mécanisme des Dependency Properties.

Déclarer un Routed Event

Pour déclarer un Routed Event, il suffit d’ajouter une ligne de ce type dans une classe :

public static readonly RoutedEvent DocumentSelectedEvent = EventManager.RegisterRoutedEvent("DocumentSelected", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(DocumentSelector));

Le second argument est la stratégie de routage (Bubble, Tunnel ou Direct). Le 3e argument est le type de handler de l’évènement. Les premier et dernier paramètres sont le nom de l’évènement et le type qui est propriétaire de cet évènement, ils ne servent que lors de l’utilisation de l’évènement depuis le XAML.

L’appel à RegisterRoutedEvent renvoie une variable de type RoutedEvent qui est en fait un identificateur pour cet évènement.

Les conventions de nommage pour un Routed Event sont les suivantes :

public static readonly RoutedEvent [NomDeLEvènement]Event;

Utiliser un Routed Event depuis le code

Une fois que l’évènement a été déclaré de cette façon, il est utilisable depuis le code. Il y a 2 types d’utilisations pour un évènement :

Déclancher l’évèment

Cela se fait en appelant la méthode publique RaiseEvent de n’importe quelle instance de UIElement, et en passant en paramètre un RoutedEventArgs initialisé avec l’identificateur de l’évènement à déclencher. L’évènement que vous déclenchez sur un objet du type A n’a pas besoin d’être défini dans la classe A, ni d’avoir précisé A comme type propriétaire lors de l’appel à RegisterEvent. Vous pouvez par exemple déclencher un évènement ScrollBar.ScrollEvent (donc défini dans ScrollBar) sur un Button.

button.RaiseEvent(new RoutedEventArgs(ScrollBar.ScrollEvent));

Attacher et détacher des handlers à un évènement

On utilise pour cela les méthodes publiques AddHandler et RemoveHandler de UIElement. Avec un évènement CLR traditionnel, lorsque un objet A déclenchait un évènement, seul les handlers attachés à A étaient appelés. Pour un Routed Event, l’élément ayant déclenché le Routed Event (l’objet dont la méthode RaiseEvent a été appelée) ainsi que tous ses éléments parents vont appeler leurs handlers associés à cet évènement (pour les stratégies Bubble et Tunnel). C’est le principe du Routed Event.

Les parent peuvent être de n’importe quel type, il est donc logique que l’on puisse attacher n’importe quel évènement à n’importe quel type d’objet (dérivant de UIElement). Vous pouvez par exemple attacher un évènement de type Button.ClickEvent à un StackPanel.

stackPanel.AddHandler(Button.ClickEvent, new RoutedEventHandler(Button_Click));

Définir un wrapper CLR pour un Routed Event

Il est plus pratique de définir des wrappers CLR afin d’encapsuler ces appels à AddHandler et RemoveHandler. Il y a deux formes possibles pour ces wrappers. Vous pouvez définir un évènement CLR :

public event RoutedEventHandler DocumentSelected
{
    add {
AddHandler(DocumentSelector.DocumentSelectedEvent, value); }
    remove { RemoveHandler(DocumentSelector.DocumentSelectedEvent, value);
}
}

Ce type de wrapper permet uniquement d’attacher un handler à un objet du type qui définit ce wrapper. Mais nous avons vu que n’importe quel objet dérivant de UIElement peut attacher des handlers pour n’importe quel Routed Event. Il y a donc une seconde possibilité qui vous permet cette fois-ci de préciser à quel objet attacher le handler :

public static double AddDocumentSelectedHandler(DependencyObject target, RoutedEventHandler handler)
{
   
((UIElement)target).AddHandler(DocumentSelector.DocumentSelectedEvent, handler);
}

public static void RemoveDocumentSelectedHandler(DependencyObject target, RoutedEventHandler handler
)
{
    ((UIElement)target).RemoveHandler(DocumentSelector.DocumentSelectedEvent, handler);
}

Pour résumer, voici les conventions de nommage lorsque vous souhaitez encapsuler un Routed Event :

public event [TypeDuHandler] [NomDeLEvènement] { add; remove;}

ou

public static void Add[NomDeLEvènement]Handler(DependencyObject target, [TypeDuHandler] value);

public static void Remove[NomDeLEvènement]Handler(DependencyObject target, [TypeDuHandler] value);

Utiliser un Routed Event depuis le XAML

Un des avantages de WPF est de permettre de soulager le code en déclarant un maximum de choses dans le XAML. Tout ce qu’il est possible de faire depuis le code est donc possible également en XAML. Il est notamment possible d’attacher des handlers de Routed Events depuis le XAML. La syntaxe est la suivante : typename.event = "handler" (on appelle cela un nom qualifié). Par exemple :

<StackPanel Button.Click="Button_Click">

Mais si l’élément auquel vous attachez ce handler est du type typename, vous devez seulement écrire : event = "handler". Par exemple :

<Button Content="Button" Click="Button_Click"/>

Pour pouvoir les utiliser de cette façon, la déclaration faite plus haut ne suffit pas. Il m’a fallut beaucoup de temps pour comprendre la chose suivante, d’autant plus qu’elle est très peu documentée : ils ne sont pas gérés de la même façon lors de la compilation et lors de l’exécution.

Lors de la compilation

Le compilateur va vérifier si les Routed Events auxquels vous attachez des handlers existent. Il ne peut pas connaitre au moment de la compilation les arguments que vous passez à EventManager.RegisterRoutedEvent. Il va donc procéder de manière détournée en vérifiant l’existence d’un wrapper dans la classe typename. Par exemple, il devra obligatoirement exister un wrapper pour l’évènement DocumentSelected dans la classe DocumentSelector si vous écrivez la chose suivante :

<StackPanel local:DocumentSelector.DocumentSelected="OnDocumentSelected">

C’est pourquoi les conventions de nommage des wrappers sont très importantes. Vous êtes cependant libre d’utiliser la 1ere ou 2e forme de wrapper.

Lors de l’exécution

Lors de l’exécution, le moteur de WPF ne se préoccupe plus des wrappers, il ne va jamais les appeler (sauf aux endroits où vous les aurez appelés explicitement dans le code). Il utilise cette fois-ci sa base de données de Routed Events en mémoire, qui se constitue grâce aux appels à EventManager.RegisterRoutedEvent. Donc concrètement, le type propriétaire et le nom passé en 1er et 4e argument de EventManager.RegisterRoutedEvent devront correspondre respectivement aux parties avant et après le point dans le nom qualifié utilisé en XAML. Par exemple, vous devez avoir déclaré un Routed Event DocumentSelected comme dans l’exemple ci-dessus (paragraphe Déclarer un Routed Event) pour pouvoir utiliser la syntaxe XAML de l’exemple précédent.

Puisque WPF n’appellera pas les wrappers à l’exécution, mais appellera directement AddHandler et RemoveHandler, il est déconseillé d’ajouter de la logique aux wrappers en plus des appels à AddHandler et RemoveHandler, afin de conserver une symétrie entre le code et le XAML.

Conclusion

  • N’importe quel objet dérivant de UIElement peut déclencher n’importe quel Routed Event (quel que soit la classe qui le définit).
  • N’importe quel objet dérivant de UIElement peut attacher des handlers pour n’importe quel Routed Event (quel que soit la classe qui le définit).
  • Pour pouvoir utiliser la syntaxe XAML, assurez-vous que le type passé en 4e argument de l’appel à EventManager.RegisterRoutedEvent correspond bien à la classe dans laquelle vous avez défini le wrapper pour ce Routed Event (il est obligatoire de définir un wrapper, même si celui-ci ne fait rien). Cette classe est alors le type que vous devez utiliser dans le nom qualifié de l’évènement.
  • Le même principe s’applique pour utiliser la syntaxe XAML des Dependency Properties.
Publié mercredi 18 juillet 2007 14:30 par RaptorXP
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :

Commentaires

# re: WPF : Comprendre les Routed Events

excellent !

merci pour ce résumé ;-)

mercredi 18 juillet 2007 19:38 by gpommier

# re: WPF : Comprendre les Routed Events

Intéressant merci pour cet article !

Je cherchais pour ma part à appeler un routedEvent d'une classe x a partir du XAML, est-ce possible ?

Un peu comme cela :

<Button Content="Button" Click="MaClasse.MyEvent" />

jeudi 2 octobre 2008 13:25 by Antony
Les commentaires anonymes sont désactivés

Les 10 derniers blogs postés

- Merci par Blog de Jérémy Jeanson le 10-01-2019, 20:47

- Office 365: Script PowerShell pour auditer l’usage des Office Groups de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 11:02

- Office 365: Script PowerShell pour auditer l’usage de Microsoft Teams de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 10:39

- Office 365: Script PowerShell pour auditer l’usage de OneDrive for Business de votre tenant par Blog Technique de Romelard Fabrice le 04-25-2019, 15:13

- Office 365: Script PowerShell pour auditer l’usage de SharePoint Online de votre tenant par Blog Technique de Romelard Fabrice le 02-27-2019, 13:39

- Office 365: Script PowerShell pour auditer l’usage d’Exchange Online de votre tenant par Blog Technique de Romelard Fabrice le 02-25-2019, 15:07

- Office 365: Script PowerShell pour auditer le contenu de son Office 365 Stream Portal par Blog Technique de Romelard Fabrice le 02-21-2019, 17:56

- Office 365: Script PowerShell pour auditer le contenu de son Office 365 Video Portal par Blog Technique de Romelard Fabrice le 02-18-2019, 18:56

- Office 365: Script PowerShell pour extraire les Audit Log basés sur des filtres fournis par Blog Technique de Romelard Fabrice le 01-28-2019, 16:13

- SharePoint Online: Script PowerShell pour désactiver l’Option IRM des sites SPO non autorisés par Blog Technique de Romelard Fabrice le 12-14-2018, 13:01