Des aides comme Mvvm Light sont précieuses pour mettre en place des logiciels bien conçus suivant le pattern MVVM mais il y a toujours de la place à amélioration. Le cas de l’INPC pour les champs calculés en fait partie. Grâce à AutoInpc Dot.Blog vous offre une extension qui règle la question avec un simple Attribut…
INPC
INPC c’est le petit nom d’un évènement parmi tant d’autres sous .NET mais si essentiel au pattern MVVM car c’est lui qui permet au Binding XAML d’être averti d’un changement de valeur et de mettre à jour son affichage. INotifyPropertyChanged n’est d’ailleurs pas un évènement mais une interface qui définit le support d’un tel évènement mais ce n’est qu’un détail rendant son implémentation et son utilisation plus souple.
Il est donc ici question de INPC.
Mvvm Light et INPC
Mvvm Light propose tout ce qu’il faut, ou presque, pour gérer le système de notification INPC. Notamment la librairie se base sur ObservableObject qui possède déjà les méthodes implémentées pour gérer cet évènement comme par exemple les différentes surcharges de RaisePropertyChanged.
Tout objet basé sur ObservableObject est donc potentiellement intéressé par DotBlog.AutoInpc !
Et parmi ces classes on trouve souvent des classes du Modèle mais aussi la super-star de MVVM : le ViewModel. La classe ViewModelBase de Mvvm Light descend en droite ligne de ObservableObject.
Le problème des champs calculés
Entrons tout de suite dans le vif du sujet et prenons pour exemples le ViewModel le plus simple possible : Il définit deux champs, FirstName et LastName (nom et prénom) de type string. Grâce aux méthodes de ObservableObject dont hérite ViewModelBase ces deux propriétés savent lever INPC lorsque leur contenu change.
Ajoutons juste un troisième champ, FullName qui est une propriété string aussi en lecture seule (pas de Set) et dont le Getter se résume à retourner le prénom et le nom séparés par un espace.
Ecrivons la Vue et ajoutons deux TextBox pour la saisie du nom et du prénom et un TextBlock pour l’affichage de FullName. On compile, on lance et là …
On a beau modifié le nom et le prénom, nos bindings ont beau être parfaits, le champ “FullName” ne s’affiche tout simplement pas !
Le coupable : INPC.
Les champs FirstName et LastName disposent d’un Setter et c’est dans celui-ci qu’est levé INPC. Or, FullName ne possède pas de Setter (et pour cause sa valeur n’est que déduite d’autres valeurs). Mais sans Setter pas de INPC …
Comment faire alors ?
La seule solution qui reste consiste à modifier les deux Setters des deux propriétés “vraies” et en fin de leurs Setters ajouter un RaisePropertyChanged(()=>Fullname);
Si demain on gère à l’américaine un MiddleName, qu’on ajoute un titre (ou formule de politesse) et que tout cela doit apparaitre dans FullName il faudra se rappeler de l’existence de cette propriété et ajouter dans chaque nouvelle propriété le fameux RaisePropertyChanged sur FullName. Et si demain nous créons une propriété FullNameWithAge il faudra ajouter encore et encore un RaisePropertyChanged adapté à FirstName et LastName mais aussi à la propriété Age. Etc… C’est un enfer.
Et je ne vous parle ici que d’un cas ultra simple, presque idéal, juste valable pour une démo… Dans la réalité les dépendances sont souvent plus nombreux, plus complexes, et la maintenance devient vite aléatoire. Il n’est pas rare de voir de tels champs calculés ne pas se mettre à jour correctement dans certaines circonstances à cause d’un oubli. Car tout le problème est que ce n’est pas dans le champ calculé qu’il faut agir mais sur un nombre variables d’autres champs qui n’ont pas à connaitre le ou les champs calculés qui en dépendent !
La solution DotBlog.AutoInpc
Il m’est apparu qu’il y avait un manque dans Mvvm Light, INPC n’y est pas traité totalement car il ne prévoit rien pour les champs calculés qui sont pourtant monnaie courante. Mais Mvvm Light est Light et c’est aussi son gros avantage. Donc plutôt que de pleurer sur ce manque, ne serait-il pas plus simple de le régler par une mini-librairie ?
C’est en tout cas ce que je me suis dit et DotBlog.AutoInpc en est le résultat.
Comment ça marche ?
De la façon la plus simple qu’il soit : un simple et unique attribut vous permet de déclarer sur le champ calculé lui-même quels sont les champs “triggers” (déclencheurs) dont il dépend.
Dans notre exemple plus haut il suffit donc de décorer le champ FullName par cet attribut en indiquant les champs FirstName et LastName et voilà c’est tout !
Chaque champ calculé sait de quoi il a besoin, et les autres champs n’ont pas à connaitre FullName.
Exemple
Prenons un ViewModel qui suit l’exemple cité plus haut du nom et du prénom, il déclarera donc deux propriétés de ce type :
public string FirstName { .... }
public string LastName { .... }
Puis pour le nom complet qui est le champ calculé nous écrirons le code suivant :
[ComputedField(new [FirstName","LastName)]
public string FullName => FirstName " " LastName;
l’écriture de la propriété elle-même ne doit pas vous étonner, c’est du C# 6. Ce qui compte c’est l’attribut ComputedField qui décore la propriété FullName. Sa syntaxe est on ne peut plus simple puisque qu’il suffit de passer en paramètre la liste des champs dont dépend celui qui est calculé. Ici FullName dépend de FirstName et de LastName et lorsqu’un de ces deux champs sera modifié INPC sera levé automatiquement. Rien d’autre à écrire !
Un petit dessin valant mieux que…
L’animation ci-dessus nous montre comment les modifications des champs FirstName et LastName sont répercutées sur FullName grâce à la seule présence de notre Attribut !
Mais la démo va un peu plus loin car vous apercevez deux boutons, “Suspend AutoInpc” et “Resume AutoInpc”. A quoi cela sert-il ?
Le mode Suspendu
Arrivé à ce stade j’avais rempli le contrat que je m’étais fixé : régler de façon élégante et simple le problème des champs calculés dans les ViewModel et les objets dérivés de ObservableObject.
Mais il manquait quelque chose.
Dans la réalité il n’est pas rare que le code lui-même modifie simultanément plusieurs champs qui peuvent avoir un impact sur un ou plusieurs champs calculés. Il est alors un peu idiot de lever autant de fois INPC alors qu’une seule fois en fin de manipulation de tous les champs serait bien plus propre et efficace.
Il arrive aussi que des champs calculés ne puissent être correctement rendus qu’une fois plusieurs champs ont été initialisés correctement. Tous les INPC intermédiaires causent une faute dans le ou les champs calculés jusqu’à temps qu’ils puissent être rendus correctement. Il serait plus simple de couper l’INPC automatique sur les champs calculés jusqu’à ce que tous les champs maitres soient correctement renseignés. Mais lorsque l’état suspendu est stoppé il faut que toutes les notifications qui n’ont pas été rendues le soient !
Mieux, il faudrait que les INPC soient regroupées car si un même champ a été notifié 10 fois durant la suspension, il n’est nécessaire de le notifier qu’une seule fois lorsque l’état suspendu est stoppé…
C’est exactement à ces problèmes que répond la propriété IsSuspended de AutoInpc.
Une fois passée à True tous les INPC sont stockés dans une liste d’attente sans doublon. Et lorsqu’on le repasse à False, sont état de base, tous les INPC en attente sont rendus (sans le doublons donc).
Maintenant vous pouvez mieux comprendre l’animation plus haut et le jeu des deux boutons de suspension de l’INPC…
Gratuit et Open Source
DotBlog.AutoInpc est une librairie gratuite, comme tout ce que j’offre ici, mais mieux, elle est Open Source sur CodePlex !
Rendez-vous sur http://autoinpc.codeplex.com/ pour télécharger la version 1.0 considérée comme Beta tant qu’elle n’aura pas été testée par suffisamment de personnes.
Participer ?
Oui c’est possible ! AutoInpc est pour l’instant une simple DLL UWP contenant deux fichiers sources. On peut les intégrer tel quel dans tout projet ce n’est pas compliqué. Mais ça serait bien d’en faire un paquet Nuget par exemple, voire d’améliorer ou étendre les fonctionnalités. On peut aussi penser à des adaptations pour d’autres framework MVVM.
Si vous souhaitez m’aider dans ces petites tâches signalez-vous sur le site d’AutoInpc sur Codeplex pour entrer dans l’équipe de dev …
Comment ça marche AutoInpc ?
Je pourrais vous répondre qu’il n’y a qu’à télécharger le source sur CodePlex. Mais ce n’est pas mon habitude. Tout cela n’a d’ailleurs d’intérêt que parce que je peux écrire quelque chose sur le sujet ici. C’est une occasion de voir du code, d’en parler et de partager notre passion commune.
Donc ne vous inquiétez pas un prochain article fera l’étude de code de AutoInpc car il y a malgré tout deux ou trois petites choses intéressantes je pense.
Conclusion
Mvvm Light est une librairie MVVM dont j’ai toujours loué la simplicité et l’efficacité, j’y ai consacré des pages et des pages. AutoInpc règle une petite lacune de Mvvm Light et cette solution est transposable à d’autres framework car ce problème est récurrent.
Gratuit, Open Source et en plus documenté et bientôt même commenté ici, que rêver de plus ! … Que mes chers lecteurs fassent un peu de pub à ce petit projet car il intéressera tous ceux qui utilisent Mvvm Light !
Merci d’avance et plus que jamais …
… Stay Tuned !