Dot.Blog

C#, XAML, WinUI, WPF, Android, MAUI, IoT, IA, ChatGPT, Prompt Engineering

Prism pour WinRT– Partie 2 – Premier exemple de code

[new:15/07/2013]je vous ai présenté Prism pour WinRT le mois dernier (partie 1), il est temps d’aller un peu plus loin pour comprendre cette nouvelle approche proposée par Patterns & Practices de Microsoft.

Prism pour le Windows Runtime

Sans faire de redite et juste pour replacer le sujet dans son contexte je rappellerais ici que PRISM for Windows Runtime (ou Prism WinRT) est une guideline de programmation sous WinRT concoctée par le groupe Patterns & Pratices de Microsoft. L’équipe qui a créé PRISM pour WinRT n’est pas exactement la même que celle qui a conçu PRISM pour WPF/Silverlight mais elle a travaillé dans le même esprit : créer la meilleure guidance pour un environnement précis, ici WinRT.

Il n’y a aucune compatibilité entre ces deux PRISM, ni aucune volonté de les faire se rejoindre. WPF/SL sous .NET réclament une approche propre, WinRT aussi. Malgré ses ressemblances (si on utilise le couple C#/XAML ce qui n’est qu’une option possible) WinRT est fondamentalement différent de .NET. PRISM pour WinRT l’est donc aussi.

A quoi sert Prism ?

Prism est une boîte à outils, un ensemble de guidelines destiné à facilité la création d’applications composites (couplage faible entre les parties, extensibilité…) respectant au mieux les contraintes de la plateforme.

Sous WPF/SL nous disposons de nombreuses bibliothèques ayant un but proche notamment dans la mouvance MVVM : Mvvm Light, Caliburn, Jounce, Prism 4, etc.

Pour WinRT certaines de ces bibliothèques sont aussi disponibles comme Mvvm Light ou d’autres pour aborder le cross-plateforme comme MvvmCross.

Prism WinRT n’est donc qu’une bibliothèque parmi d’autres offrant sa propre logique et s’adaptant mieux à certaines applications qu’à d’autres.

Sous WPF/SL, je n’ai rencontré Prism que chez des clients ayant des équipes de haut niveau et fortement soudées. PRISM 4 est en effet plus difficile semble-t-il à comprendre que d’autres bibliothèques comme Mvvm Light et cela réclame des équipes stables où il ne faut pas réexpliquer régulièrement tout à des membres qui entrent dans l’équipe et en ressortent rapidement.

De plus PRISM WPF/SL a mis du temps à accepter l’idée de MVVM dont le terme n’est apparu que tardivement dans la documentation et plutôt comme une sorte “d’obligation” que comme véritable but à atteindre. Prism 4 propose une approche différente même si elle revient au même (séparation des modules et de l’UI). Toutefois elle s’avère moins pratique et moins efficace de les bibliothèques directement MVVM, notamment lorsque ces dernières comme Mvvm Light assurent la “blendabilité” simplifiant grandement la mise en place du visuel.

Ainsi Prism WPF/SL a toujours été hautement estimé pour sa qualité mais n’a que rarement été utilisé par des petites équipes et encore moins des développeurs seuls.

Prism pour WinRT a été conçu différemment. D’abord l’évidence même de MVVM s’est imposé à tous depuis et l’équipe a pris ce paradigme comme base. Ensuite, l’environnement WinRT, par les contraintes nouvelles qu’il impose, a amené l’équipe de Prism, aidée par les membre du Developer Guidance Advisory Council (dont je fais partie, notamment sur ce projet Prism), à trouver des solutions originales adaptées au contexte.

Les buts visés par Prism WinRT sont les mêmes que ceux de Prism 4, seule la façon d’y arriver est différente :

  • La modularité : Consiste à définir et charger dynamiquement des fonctionnalité faiblement couplés dans une instance d'application en cours d'exécution.
  • La composition de l’UI : Consiste à brancher des vues dans des conteneurs parents dans un mode à couplage lâche où le parent et l'enfant n'ont pas besoin de se connaître explicitement (pas de références d'objet directes).
  • Les communications : Consiste à gérer notamment les commandes dans un mode à couplage lâche et en l’utilisation d’un pattern pub / sub pour les événements entre les composants de l’application.
  • La navigation : Consiste à gérer les changements de vue dans un conteneur de lorsque l'utilisateur interagit avec l'application sans jamais que les vues et leur parent n’aient besoin de se connaître.
  • La Structure : Consiste à offrir le support du pattern Model-View-ViewModel (MVVM), ainsi qu'un conteneur d'injection de dépendances, et d'autres patterns d’implémentation pour aider à mettre en place la solution avec la meilleure séparation possible entre les tiers la constituant.

 

Une chose importante à comprendre au sujet de Prism est que ce n'est pas un cadre “tout-ou-rien”. Vous pouvez utiliser une ou plusieurs de ses caractéristiques et ignorer les autres parties si elles n'ont pas de sens pour votre application ou vos besoins. En plus de ces principales fonctionnalités Prism offre beaucoup de petites petites classes utilitaires qui peuvent être utilisées seules en dehors du contexte Prism. Le code source étant disponible il n’est pas même indispensable d’intégrer les bibliothèques Prism en entier pour juste emprunter telle ou telle partie qui les constituent.

Créer une application WinRT avec Prism

Le meilleur moyen de comprendre les services offerts par Prism pour WinRT est de bâtir un exemple. Nous nous limiterons dans un premier temps aux bases essentielles. Prism est un “gros morceau” même sous WinRT. Bien entendu il existe des modèles de solution qui permettent d’éviter une partie de la mise en place que nous allons voir maintenant, mais tout l’intérêt est justement de le faire “à la main” pour comprendre le fonctionnement de Prism…

Créer le projet

Pour commencer, ouvrez Visual Studio et créez un nouveau projet Windows Store :

image

Utilisez le modèle “Blank” qui met en place une structure basique sans fioriture.

Ajouter des Pages et leurs ViewModels

Dans une application Windows Store, vous concevez votre application autour de la notion de Page. L'utilisateur navigue de page en page pour accéder à toutes les fonctionnalités de votre application. Vos pages sont votre niveau de vue le plus élevé suivant une logique MVVM, et vous devrez donc mettre en place les ViewModels correspondants. Vous pourriez avoir des vues enfant de certaines pages - par exemple on peut concevoir l’idée d’un ContentControl dans une page dans lequel vous changeriez les vues enfant. Mais ce genre de mise en page est beaucoup moins fréquente que sous WPF et Silverlight en raison des directives de style de l'interface Modern UI. Avec WinRT vous devez concevoir des écrans moins denses que ce qu’il se pratique en LOB classique sous WPF/SL. Dans le modèle d’UX Modern UI les fonctionnalités de l'application sont réparties sur plusieurs pages parmi lesquelles l'utilisateur navigue au lieu de tout faire dans une grande vue compliquée.

Il s’agit là de la façon “positive” de présenter les restrictions bien réelles de WinRT. Pour dire la vérité et comme nous l’avons vu dans la Partie 1 Modern UI ne s’adapte qu’à un certain type d’application d’entreprise. Celles qui peuvent se satisfaire de pages pauvres en contenu et d’une navigation incessante entre les pages. La plupart des applications LOB ne se plient pas à ces contraintes et ce n’est pas par manque de créativité ou d’imagination mais parce que le fonctionnel l’oblige. L’efficacité veut en général au contraire que l’utilisateur puisse accéder au maximum de choses sans trop avoir à naviguer.

Je ne redirais pas ce que j’ai argumenté dans la Partie 1 mais il faut se rappeler que si Modern UI est parfaitement utilisable en entreprise cela ne peut s’entendre que pour certains types d’applications, vouloir tout programmer en WinRT serait une erreur. Pour simplifier je dirais que tout ce qui était éventuellement réalisé sous la forme de sites Web (Intranet par exemple) peut être facilement porté en WinRT car les contraintes de mise en page et de navigation sont assez semblables au final. D’ailleurs même l’exemple fourni par l’équipe Prism avec les guidances ressemble au final à un gros site marchand, mais pas à une application LOB, et si WinRT peut se programmer en HTML 5 ce n’est pas rien non plus.

En revanche tout ce qui réclame un travail long de l’utilisateur, avec concentration ou des tâches répétitives ne pourra que créer une UX médiocre avec le modèle Modern UI : l’utilisateur ne comprendra pas pourquoi il doit sans cesse naviguer alors qu’il y a plein de place à l’écran non (mal ?) utilisée. N’oubliez pas non plus qu’en entreprise les machines Windows 8 et futures versions ne seront pas majoritairement équipées d’écrans tactiles.
C’est toute l’ambigüité de WinRT qui a été jusqu’à ce jour présenté comme le seul et unique remplaçant de toutes les autres technologies Microsoft, notamment de WPF et SL, alors qu’en pratique il faut le voir comme une possibilité supplémentaire pour des parties spécifiques d’applications uniquement et avec le gain d’un exécutable tournant sur PC et Surface.
Les avantages sont donc bien réels et dans ce cadre WinRT est une superbe plateforme qui aurait pu être valorisée mille fois mieux, c’est à vouloir faire la grenouille plus grosse que le bœuf qu’on la fait éclater... Comme quoi le savoir-vendre est parfois plus important que la technologie !

On notera que par souci de simplification Prism WinRT utilise plutôt des conventions que des fichiers de configuration. Il y a une classe ViewModelLocator qui rend l’accès aux VM très simple, un peu comme sous Mvvm Light. La blendabilité s’en trouve améliorée par rapport à Prism WPF/SL. Ce ViewModelLocator utilise d’ailleurs l’une des conventions évoquées plus haut, elle suppose que vous stockez vos pages dans un sous-dossier du projet intitulé Views et vos ViewModels dans un sous-dossier nommé - vous l'aurez deviné - ViewModels. Si cette convention ne vous va pas dans un projet donné il existe des hooks pour placer les fichiers où on veut en le précisant. Mais une grande partie du code réutilisable de Prism est conçu autour d’un modèle commun de guidelines et si vous suivez ces dernières vous aurez moins de travail à fournir.

Donc, pour commencer il faut structurer l’application avec les sous-répertoire Views et ViewModels. Ensuite supprimez le MainPage.xaml qui a été ajouté à la racine du projet par Visual Studio. Puis faites un clic droit sur le dossier Views et sélectionnez Add>New item dans le menu contextuel. Dans la catégorie Windows Store, sélectionnez le modèle de page "Basic" et nommer-le "MainPage".

image

Lorsque vous cliquez sur Ajouter, vous serez invité à ajouter des fichiers communs aux applications Windows Store à votre projet. Acceptez la proposition de VS, même si ici nous n'utiliserons pas la plupart de ces classes mais les équivalents de Prism qui correspondent mieux avec le modèle MVVM que nous allons utiliser.

image

Allez dans le code XAML de MainPage et changer "My Application" en "Hello Prism" dans la section des ressources (ou mettez ce qu’il vous plaira, cela n’a aucune incidence).

Répétez Add>new Item pour ajouter une deuxième page Basic nommé AddSalePage dans le dossier Views.

Maintenant vous pouvez voir que nous avons des ressources dupliquées dans les différentes pages. Le mieux étant de les supprimer de ces dernières pour les ajouter à App.Xaml et ainsi les centraliser :

image

Ensuite, ajoutez deux classes dans le dossier ViewModels: MainPageViewModel et AddSalePageViewModel. À ce stade, votre projet doit ressembler à ceci dans l'Explorateur de solutions :

image

Ajouter Prism et les références à la DLL

La version finale de Prism pour WinRT peut être téléchargée ici : http://code.msdn.microsoft.com/windowsapps/Prism-for-the-Windows-86b8fb72

Pour ajouter Microsoft.Practices.Prism.StoreApps à votre application il suffit d’ajouter à la solution le projet “Prism.StoreApps” se trouvant dans le zip (que vous aurez décompressé en totalité quelque part sur l’un de vos disques).

Il est fort probable (je n’ai pas vérifié) qu’au moment où j’écris ces lignes le package Nuget soit disponible. Dans ce dernier cas il sera plus simple d’utiliser l’installation automatique par ce biais que d’ajouter les sources du projet Prism à votre solution.

Une fois Prism installé il faut bien entendu ajouter la référence à Microsoft.Practices.Prism.StoreApps évoquée plus haut (sauf si l’installation passe par un package Nuget qui est censé le faire automatiquement).

Modifier la classe de base de l’application

La classe application définit dans App.Xaml.cs dérive de la classe du framework WinRT et contient du code pour gérer le cycle de vie de l’application, la création de la première vue etc… Tout cela est encapsulé dans une classe spécifique de Prism.

Il suffit de substituer la classe mère de App par MvvmAppBase. Cette dernière va fournir des comportements par défaut tout en offrant des hooks dans le cas où vous souhaiteriez reprendre la main sur certains des aspects pris en charge automatiquement.

On profite de cette substitution pour faire un grand coup de ménage, la quasi totalité du code dans App.Xaml.cs ne sert plus rien. On ne garde au final que le constructeur (avec InitializeComponent) ainsi que la méthode OnLaunchApplication dont on remplace le code présent par un appel au service de navigation de Prism :

image

Malgré la grande simplicité du code ci-dessus il y a un certain nombre de choses à prendre en considération car tout ne sera pas forcément si simple tout le temps.

Par exemple la substitution du code de OnLaunchApplication est vraiment minimaliste. Pour un "démarrage à froid" de votre application cela sera parfait. Mais vous devez garder à l'esprit que votre application Windows Store peut être lancée de plein façons différentes y compris comme une cible pour une opération de charme de recherche, une tuile secondaire sur l'écran de démarrage, ou encore d’autres voies ! Dans ces cas, vous pourriez avoir besoin de naviguer vers une page différente de “Main” sur la base des arguments de lancement qui entrent dans la méthode de lancement surchargée. Bien entendu MvvmAppBase a d'autres méthodes à surcharger pour manipuler recherche ou la gestion des paramètres. Mais pour l'instant dans notre exemple aller à la MainPage est un choix simple dont on se contentera pour ne pas rendre les choses trop compliquées.

Vous aurez certainement noté que nous naviguons sur «Main», et non sur MainPage, le type. Il s'agit d'une décision de conception : les demandes de navigation ne doivent pas être couplés à la nature spécifique de la vue ou de la page. Gardez à l'esprit que si vous voulez concevoir des applications WinRT efficaces et utilisables facilement vous devrez d'abord écrire des storyboards ou des wireframes pour définir avec précision quelles sont les pages, leur rôle et comment l’utilisation doit ou peut naviguer de l’une à l’autre. Vous devrez à ce moment là définir des noms logiques pour les vues même si vous n'avez encore aucune idée des noms définitifs des classes qui seront implémentées. La convention utilisée pour la navigation dans Prism par défaut est que vous accédez à un nom de vue logique, et elle suppose aussi que le nom du type sera nom logique de la vue + “Page”.

Dans le cas où vous souhaiteriez adopter une autre convention il existe un hook en surchargeant la méthode GetPageNameToTypeResolver de l’application. Cette méthode retourne un Func<string,Type> de fait vous êtes supposé retourner un délégué qui prend en paramètre une chaîne et renvoie un type. Par exemple si décidez de naviguer sur les vues en partant de leur nom de type et que ces vues résident dans l’espace de noms  de base plus “.Views” la méthode sera surchargée de cette façon :

image

Il est évident que dans l’exemple de code présenté ici nous utiliserons les conventions par défaut.

Il reste donc à faire le changement de classe application aussi dans le fichier App.Xaml (nous l’avons fait dans la partie C# mais pas encore dans la partie XAML) :

image

On notera, outre le changement de nom classe pour la racine, que nous avons ajouté l’espace de noms “prism” qui utilise la DLL de Prism WinRT.

Modifier la classe de base des pages

Prism a une classe spécifique pour les pages qui est semblable à LayoutAwarePage que Visual Studio propose par défaut, mais cette dernière ne prend en charge que l’affichage et non la gestion d'état et de navigation. Dans une application MVVM la navigation et le code de gestion d'état vivent dans le ViewModel, et la classe de base ViewModel de Prism le prend en charge. Nous verrons comment tout cela se connecte à l’étape suivante.

Il ne faut donc pas oublier de remplacer la classe de base des pages par celle fournie par Prism (en ajoutant l’espace de noms).

image

Bien entendu le ménage se poursuit dans la partie code-behind de chaque page où ne doit subsister que le contructeur avec l’appel à InitializeComponent (sans oublier de changer la classe de base comme dans le fichier XAML par VisualStateAwarePage).

Ces modifications très simples mais importantes doivent être effectuées dans les deux pages de l’exemple.

Une dernière chose : le modèle de page proposé par VS contient un bouton appelé “backButton” dont le clic appelle la méthode “GoBack” et le IsEnabled est bindé à des propriétés de la Frame. Tout cela ne sert plus, il faut conserver le bouton mais tous ses paramètres peuvent être supprimés, seuls le nom et le style subsistent :

image

Ajouter la classe de base aux ViewModels

Les ViewModels descendent bien entendu eux aussi d’une classe de Prism. Je dis “bien entendu” car c’est ce qu’on attend d’une toolbox de ce type : se charger des mécanismes cruciaux à notre place. Et les ViewModels sont d’une telle importance dans une architecture MVVM que c’est tout naturellement qu’ils sont pris en charge par Prism WinRT.

Les ViewModels ont désormais un code qui ressemblent à cela :

image

Dans cet exemple cela n’a pas beaucoup d’impact, voire aucun. Mais dans une application réelle et dans une prochaine partie nous verrons qu’il est important d’hériter de ViewModel qui prend en charge les subtilités de la navigation et du cycle de vie de l’application.

Connexion du ViewModelLocator aux vues

Prism WinRT fournit un ViewModelLocator comme le fait Mvvm Light mais avec une différence essentielle : par défaut son fonctionnement est totalement automatique et basé sur des conventions ce qui évite d’avoir à en compléter le code en permanence comme on doit le faire sous Mvvm Light.

Grâce à ces conventions, le ViewModelLocator de Prism sait comment connecter une Vue et son ViewModel ce qui évite le code répétitif et un peu stupide il faut le dire du ViewModelLocator de Mvvm Light (où on doit déclarer une propriété qui retourne une instance de la classe du ViewModel soit directement soit au travers d’un conteneur).

Toutefois les automatismes de Prism ne permettent pas de faire l’impasse sur la déclaration du lien au ViewModelLocator dans chaque Page (ce qui s’effectue via une propriété attachée proposée par Prism) :

image

Les conventions par défaut utilisées par le ViewModelLocator sont que pour un type de vue donné il assumera que la vue est définie dans espace de noms enfant ". Views" et que le ViewModel sera défini dans un espace de noms lui aussi enfant de l’espace de noms racine sous le nom de “.ViewModels” et que la classe portera le même nom que la Vue avec le suffixe “ViewModel”.

C’est une convention très simple qui ne fait que reprendre ce qu’on constate chez la plupart des développeurs utilisant une logique MVVM.

Une fois la propriété attachée ajoutée Prism saura fournir directement une instance du bon ViewModel à la Vue et initialisera au passage son DataContext convenablement.

A ce stade si vous exécutez l’application vous ne verrez pas grand chose se passer, mais si vous ajouter un contructeur par défaut au ViewModel de la vue principale et une trace à l’intérieur de ce dernier vous pourrez constater que tout ce passe comme prévu et que la page principale est bien créée et que la trace est bien suivie (qu’il s’agisse d’un log, d’un break point ou autre message de debug).

Bien entendu il serait logique d’ajouter un bouton dans la page principale pour appeler la page secondaire… Mais cela nécessiterait d’entrer dans les détails des commandes et de la navigation qui réclament malgré tout un peu d’explications. Faire entrer ces dernières dans ce billet déjà long ne serait pas une bonne idée. A chaque jour suffit sa peine, n’est-il pas… !

Conclusion

Prism pour WinRT simplifie beaucoup la programmation sous cette plateforme qui impose des contraintes particulières. Pour l’instant nous n’avons pas réellement pu voir ses avantages car il nous fallait poser le décor : comment construire une application avec Prism, ses vues, ses ViewModels, comment connecter le ViewModelLocator, modifier les classes de base des principaux intervenants (application, vues, VM), etc.

Ce que nous avons vu est donc peu, mais c’est déjà beaucoup !

Prism pour WinRT a été conçu “sur mesure” sans reprendre ou adapter un code existant (on verra qu’à la marge une ou deux classes de Prism 4 ont été reprises malgré tout). C’est en partant d’une page blanche et en réfléchissant à la meilleure façon de couvrir les besoins d’une application WinRT que Prism a été construit.

De très nombreuses réunions du Developer Guidance Advisory Council ont permis pendant des mois de discuter des meilleures voies à prendre, des choses à affiner, de celles qui pouvaient être ignorées, etc. A chaque fois et dans un véritable esprit d’équipe les membres du groupe de développement de Prism ont su écouter les remarques, ont su aussi convaincre lorsqu’ils pensaient tenir une bonne idée. C’est ce travail en profondeur qui fait la qualité de Prism WinRT.

Je suis assez fier d’avoir participer en tant que membre du Council à cette aventure qu’est la création d’un framework MVVM fonctionnant sur une plateforme aussi sophistiquée que WinRT.

Mes réserves concernant l’utilisation de WinRT pour des applications LOB ne changent pas pour autant dans l’état actuel de cette technologie (ce qui peut évoluer), et il en va de même de mon regard critique sur ce que je considère des erreurs d’approche faites par Microsoft dans la présentation et la diffusion de WinRT qui amène à une situation plus proche du rejet que de l’adoption massive, et c’est un euphémisme. Mais ces critiques concernent les décisions prises par la Direction de Microsoft, décisions concernant la façon de “vendre” WinRT et Windows 8, ce sont des erreurs de management, graves à mes yeux, mais ces critiques ne concernent en rien la plateforme WinRT qui offre de nouvelles possibilités réellement excitantes ni la qualité réelle de Windows 8 en tant qu’OS.

C’est pourquoi, jeu d’équilibrisme difficile je l’accorde, mais parfaitement cohérent, je continuerai à affirmer que l’UX de Windows 8 est un échec sur PC, que présenter WinRT comme seul moyen de créer des applications sous Windows est une hérésie et que toutes ces erreurs se payent aujourd’hui au prix fort par une faible adoption de ces produits, le tout en continuant d’affirmer que ces derniers ne sont pas en cause et qu’autant Windows 8 que WinRT sont des produits exceptionnels et bien ficelés techniquement.

J’aime toujours autant la technologie Microsoft, je soutiens toujours autant la qualité incroyable du travail des équipes Microsoft, mais, vous l’avez compris, je suis pressé que Microsoft change de Direction. Être clair et honnête avec ses lecteurs comme avec ses clients c’est dire ce qu’on croit être juste. C’est une question de sérieux et de crédibilité. Deux choses auxquelles je suis profondément attaché.

C’est pourquoi il y aura un partie 3 sur Prism pour WinRT dans quelques temps !

Stay Tuned !

PS: vous pouvez télécharger les exemples PrismForWindowsRuntimeQuickStarts.zip depuis CodePlex et consulter l’exemple “HelloWorld” dont est inspiré la structure de l’exemple de cet article.

blog comments powered by Disqus