Introduction
Mon quotidien Allemand est principalement axé sur la technologie WPF, ces temps.
Au programme d'aujourd'hui : Un outil permettant la localisation de ressources :
Charger un fichier XAML contenant un ResourceDictionnary, en extraire toutes les ressources de type sys:String, et les afficher à l'écran. L'idée est de pouvoir éditer lesdites chaines, nous allons donc les charger dans une TextBox.
Et puisqu'il est intéressant de pouvoir se rappeller quelle était la valeur par défaut : Nous voulons afficher dans le ToolTip, la valeur originale.
Implémentation
En admettant que nous ayons un XmlDataSource déclaré dans le XAML ( ne pas oublier de déclarer aussi les différents namespaces XML !! ) nommé xmlTranslationProvider.
Pour utiliser un contrôle visuel léger, utilisons ItemsControl : Puisque nous n'utilisons pas les mécanismes de sélection d'éléments, nous pouvons nous passer de l'overhead d'une ListBox.
Nous devrons ensuite lier l'ItemSource de ItemsControl à notre XmlDataProvider, afin de définir quels seront les Items à afficher : Les éléments de type sys:String.
<ItemsControl ItemsSource="{Binding Source={StaticResource xmlTranslationProvider}, XPath=sys:String }" >
Pour définir le rendu de chacun de nos Items : Définissons le template de ItemsControl.ItemTemplate, où nous insèrerons la TextBox qui servira à la traduction.
Intuitivement, les Bindings appliqués devraient ressembler à celà :
<TextBox Name="tbTranslationField" Text="{Binding XPath=.}" Grid.Column="0" ToolTip="{Binding XPath=.,Mode=OneTime}" />
Le binding de ItemsControl.ItemsSource définissant quels seront les items, nous définissons ici comment les représenter : La propriété Text affichera la valeur du noeud, de même pour le ToolTip, à l'exception près que nous voulons que le ToolTip garde sa valeur d'origine, d'où le Mode OneTime. OneTime permet de ne recharger les données que lorsque l'application démarre, où que le DataContext est changé.
Les ennuis commencent
Malheureusement, dans les faits, à chaque fois que le ToolTip est affiché, il contient les dernières modifications apportées à la chaîne à traduire.
Pour quelle raison ?
Analysons la démarche :
Créons un nouveau TextBox dont la propriété Text possèdera un Binding identique au ToolTip ci-dessus : Le comportement est celui que nous attendions : son contenu est mis à jour une seule fois, et n'est jamais changé, en dépit du fait que la donnée liée, elle, change : Le mode OneTime fonctionne : Que se passe-t-il ?
Le TextBox est généré au lancement de l'application. il va chercher les données auxquelles il est lié, l'affiche à l'écran, et ne les touche plus.
Explications
Qu'y a-t-il de différent avec un ToolTip ?
Le ToolTip attend que le pointeur de la souris passe au dessus de la zone appropriée, puis à ce moment là, et uniquement à ce moment là, il est construit : Il va donc chercher les données auxquelles il est lié, et ne reçoit plus jamais de notification en cas de modification des données auxquelles il est lié.
Si l'utilisateur déplace la souris, le ToolTip est détruit.
Si l'utilisateur déplace la souris à nouveau au dessus de la zone sensible, le ToolTip est à nouveau reconstruit de A à Z. Il va donc rechercher les informations auxquelles il est lié, les affiche à l'écran, et ne reçoit plus jamais de notification en cas de modification des données liées.
Que se passe-t-il si l'utilisateur, entre la destruction du ToolTip et sa nouvelle création, modifie les données auxquelles le ToolTip est lié ?
Le ToolTip est créé, puis il va rechercher les données auxquelles il est lié. Mais attention, nous avons spécifié le mode OneTime, WPF possède donc un mécanisme pour s'assurer que les données liées ne doivent être chargées qu'au démarrage de l'application ou lors d'un chargement de DataContext....
Mais alors pourquoi diable, mon ToolTip change-t-il tout de même de valeur ??
La réponse est simple : Les données liées n'ont pas changé !!!
Et pourtant le ToolTip, lui, ne possède pas le même contenu ! Pourquoi ?
Tout ceci est dû au fait que nous avons fait une erreur de sémantique dans notre Binding : Nous avons en effet lié un Noeud XML à notre TextBox et à notre ToolTip, et non pas une chaîne de caractère. L'objet XmlNode en question, qui est lié à nos propriétés ToolTip et Text, n'a absolument pas été changé. Il a seulement été altéré !
Les ValueConverters très puissants de WPF ont simplement converti un noeud XML en représentation sous forme de string à notre place.
Ainsi, lorsque le ToolTip est créé après que le Noeud XML auquel il est lié ait été altéré, le mécanisme de WPF s'assure qu'il n'ait pas été changé, et le ToolTip peut sans autre lire le texte se trouvant dans le XmlNode, cependant, celui-ci a été changé.
C'est de par la nature même du ToolTip que l'on peut observer ces différences de comportement entre un TextBox qui est rendu à l'écran une fois pour toutes, et un ToolTip qui est recréé à partir de zéro plusieurs fois au cours du cycle de vie de l'application.
Résoudre le problème :
L'idée est donc d'indiquer au mécanisme du mode OneTime de s'assurer non-pas que l'objet XmlNode reste inchangé, mais bien que le texte qu'il contient reste le même ! Le texte en question est représenté par la propriété InnerText de l'objet XmlNode.
Par conséquent, nous allons modifier les Bindings de notre TextBox comme suit :
<TextBox Name="tbTranslationField" Text="{Binding XPath=., Path=InnerText}" Grid.Column="0" ToolTip="{Binding XPath=.,Mode=OneTime, Path=InnerText}" />
A noter que le même genre de subtilités survient lorsqu'une DependencyProperty de type List<T> (n'implémentant pas INotifyCollectionChanged) est altérée : Si l'on ajoute un élément à une List<T>, la propriété référencant cette liste n'est pas modifiée, car l'adresse mémoire de l'objet pointé par la propriété est resté le même, en dépit du fait que l'objet lui même ait été altéré.
En eséprant que cet article puisse aider quelques personnes à mieux comprendre les subtilités du binding.
c39d5c3e-1788-443a-95f0-a2261e59e8f3|0|.0