Dot.Blog

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

Windows 10 / UWP, MVVM Light et un peu de magie !

[new:30/09/2015]Dans un article précédent j’ai présenté le développement sous Windows 10 et UWP avec le célèbre Hello world incontournable. Aujourd’hui on recommence l’exercice mais en MVVM !

MVVM et Windows 10 / UWP

Les applications pour Windows 10 et UWP ne sont pas si différentes de celles en WinRT, en dehors du côté universel de UWP, on pourrait même dire que c’est du WinRT. Et WinRT lui-même si on ne regarde que son API c’est un peu la même chose que du .NET en plus étendu. Même logique, espaces de noms et classes similaires sauf exceptions, on passe de l’un à l’autre dans une continuité déroutante. Quant à XAML même s’il en a existé et existe plusieurs saveurs avec de belles différences cela reste du XAML. Quant on a compris une fois on a compris pour toujours, en tout cas jusqu’à maintenant et l’avenir semble tout aussi limpide. Enfin C# lui-même en dehors de ses améliorations constantes reste un choix de roi – roi un peu fainéant puisque là aussi quand à compris C# 1.0 on a compris pour toujours...

De fait en UWP on se retrouve à faire du C#/XAML exactement comme on en faisait en WinRT, exactement comme on en fait avec WPF et comme on en faisait avec Silverlight.

Depuis Windows 8 et WinRT il y a quelques petites choses à savoir en plus mais elles sont finalement peu nombreuses et grâce à UWP qui harmonise vraiment tout les différences sont à apprendre un fois pour toutes les cibles.

Il résulte de cette magnifique continuité rare en informatique et malgré les ruptures d’importance dans les OS et les plateformes physiques supportées que bien évidemment tout ce qu’on a appris sur les bonnes façons de développer restent vrai dans l’essentiel.

Et en toute logique il en va ainsi pour MVVM qui s’applique à merveille à UWP.

Quels outils ?

Prism

J’ai fait partie du groupe qui a travaillé sur Prism pour WinRT au sein du “Developer Guidance Advisory Council Microsoft” il serait donc mal venu de ne pas en parler en premier… Et comme je suis honnête je vais même dire que Prism pour WinRT ne m’a pas plus convaincu que WinRT lui-même qui, dans les faits meurt avec UWP (même si derrière on reste en WinRT).

La raison est que le modèle d’application plein écran de WinRT ne m’a jamais séduit, j’ai d’ailleurs été parmi ceux qui ont publiquement demander le retour des fenêtres pour donner un avenir aux Apps WinRT (“Ne jetez pas WinRT par les fenêtres : offrez-lui en ! (la solution pour un WinRT adapté au PC)”), ce que fait avec bonheur Windows 10.

UWP m’oblige à envisager un nouvel avenir plus prometteur à Prism pour WinRT une fois celui-ci adapté à UWP.

Brian Noyes, l’un des membres de l’équipe Prism a fait un portage de Prism WinRT vers UWP, il l’a annoncé sur son blog le  6 juillet dernier. Même s’il s’agit d’un portage “initial” selon ses termes donc imparfaitement adapté à UWP, le code est déjà utilisable et publié sur GitHub (PrismLibrary/Prism).

On notera que Prism est passé en open-source et que la version WinRT ne connaitra plus d’évolution sauf débogue. Quand je parlais de la “petite mort” de WinRT en septembre 2014 à la suite des premiers leaks de Windows 9 qui deviendrait 10 j’avais là encore eu le nez creux… WinRT et ce qui tourne autour comme les outils de développement et les librairies type Prism n’évolueront plus jamais, on passe à UWP qui est proche mais tellement différent de WinRT. Une “petite mort” le terme était donc bien choisi à l’époque.

Pour en revenir à Prism pour UWP il faudra certainement attendre la prochaine version qui est en préparation et qui sera elle parfaitement adaptée à la plateforme. L’intérêt du portage de Brian se résume à celui des rares développeurs qui ont un code WinRT à porter rapidement en UWP sans trop entrer dans les subtilités et différences entre les deux approches.

Mais gardez un œil sur Prism (et sur Dot.Blog !) car dans quelques temps la nouvelle version sera prête et là viendra le temps d’en parler plus longuement !

MVVM Light

J’ai tellement parlé de MVVM sur Dot.Blog … Et de MVVM Light aussi. Je ne peux que renvoyer le lecteur qui se poserait (encore) des questions sur cette approche de développement vers la liste de tous les articles tagués “MVVM”, il découvrira une galaxie d’articles et livres qui occuperont largement ses longues soirées d’automne !

Je vais donc faire court et juste rappeler que MVVM Light est une librairie que j’ai soutenue depuis ses premières versions car j’aime la façon dont son auteur, Laurent Bugnion, a su aborder avec simplicité ce qui chez les autres était sac de nœuds (Caliburn est un bon exemple d’un tel enfer et même les premières versions de Prism). Plus loin, dès le départ Laurent a fait en sorte que MVVM Light soit “Blendable”, utilisable au design time notamment pour fournir des données adaptées à la mise en page XAML sous Blend (ou sous VS depuis que celui-ci supporte un designer visuel digne de ce nom pour XAML). C’est essentiel quand on créé une application et trop de frameworks MVVM ignorent superbement (et à tort) cet aspect.

MVVM Light est aussi une librairie qui a su évoluer sans trop changer et on la retrouve disponible pour toutes les cibles, depuis Silverlight et WPF jusqu’à Windows Phone 7 de la première heure et donc tout naturellement jusqu’à UWP aujourd’hui.

Au moment où j’écrit ces lignes MVVM Light pour UWP n’existe pas vraiment mais il se trouve que les PCL’s pour Windows 8.1 peuvent s’ajouter dans un projet UWP sans aucun problème… La version 5.1.1 actuellement disponible est donc utilisable directement (ce qui montre le niveau de compatibilité et la proximité des plateformes Windows 8.x et Windows 10 au moins au niveau API et l’utilisation sous UWP de WinRT).

C’est donc MVVM Light que j’utiliserai aujourd’hui pour les besoins de cet article. Créer un (gros) Hello World MVVM.

Rapide comparatif Prism / MVVM Light

Lorsque Prism pour UWP sera disponible il sera possible de véritablement faire un tableau des différences d’autant que des breaking changes sont annoncés, raisonner sur une adaptation rapide UWP de Prism pour WinRT ne serait pas tout à fait loyal.

Mais les différences d’approches sont assez nombreuses même si Prism pour WinRT qui est totalement différent de Prism pour WPF a été conçu presque dans l’esprit de MVVM Light à un point tel qu’au tout début du projet quand nous parlions des fonctionnalités avec l’équipe Prism j’en étais arrivé à me demander l’intérêt de créer un MVVM Light bis… Mais le projet avançant Prism WinRT a proposé des fonctionnalités véritablement propres à la plateforme, navigation, asynchronisme, et gestion des évènements particulier du cycle de vie des Apps (notamment pour sauvegarder à temps l’état de l’App quand elle passe en fond ou qu’elle se fait décharger de la mémoire par l’OS).

De fait MVVM Light est une librairie cross-plateforme ce qui permet même de réutiliser du code Silverlight ou Windows Phone 8 en Xamarin sous UWP, c’est un gros avantage, mais qui se paye un peu par un manque d’adaptation aux spécificités de telle ou telle plateforme. Je pense qu’avec UWP Laurent pourra se permettre de personnaliser un peu plus la librairie, mais Prism pour WinRT porté rapidement pour UWP répondra mieux à certaines problématiques comme le cycle de vie des applications si cela est essentiel dans une App donnée (ce qui n’est pas toujours le cas et qui peut malgré tout se gérer avec Mvvm Light).

Et d’autres librairies ?

Il existe d’autres librairies MVVM mais pour l’instant elles restent mal ou pas adaptées du tout à UWP. On pourra citer Okra qui n’est pas mal par exemple, mais de ce que j’en vois qui reste pour l’instant coincé dans le mode “universal app” de Windows 8.1 qui n’a rien à voir en réalité avec les Universal Apps de UWP. Je n’ai pas encore eu le temps de faire l’essai, mais on peut supposer que comme MVVM Light il est possible d’ajouter le paquet Nuget W8.1 dans une appli UWP. Je vous tiendrais au courant de mes essais à moins qu’un lecteur plus rapide que moi ne l’ai déjà fait, dans ce cas qu’il n’hésite pas nous laisser ses commentaires sous cet article ! Une annonce récente sur le blog de Okra laisse clairement entendre qu’une version UWP sera “bientôt” releasée, je garde donc un œil sur tout ça !

Caliburn.Micro qui a succédé depuis un moment au pachydermique Caliburn au point d’en devenir finalement le remplaçant n’a pas à ma connaissance encore été adapté à UWP mais lorsqu’il le sera c’est un framework qu’il faudra prendre en considération. Je ne suis pas fan de l’approche même simplifiée de Caliburn en général, micro ou pas, mais c’est l’un des frameworks qui compte dans le paysage MVVM.

En dehors de cela il existe des embryons de librairies pour UWP mais rien de concret, mais il est vrai que cette plateforme est à peine distribuée depuis quelques jours au travers de la mise à jour Windows 10.

Bref laissons le temps au temps… tout finira par venir prochainement et pour l’instant nous en resterons à MVVM Light !

Exemple

Je vais reproduire ici “à ma sauce” une application exemple publiée par Laurent Bugnion lui-même car elle contient un condensé de ce que permet Mvvm Light. Toutefois expliquer ce code pourtant simple s’avère un exercice délicat car il y a beaucoup à dire et il ne faut pas transformer l’article en roman (ce que je fais à chaque fois ou presque malgré tout… et en me relisant je vous le dis maintenant que je connais la fin, c’est un roman !).

C’est parti !

Installer Mvvm Light

Pour commencer nous créons un projet via Template / Visual C# / Windows / Universal puis nous choisissons un projet vide (Blank App) comme le montre la capture ci-dessous.

image

Clic droit sur Référence du projet dans l’explorateur de solution puis gestion des paquets Nuget. On remarque au passage le tout nouveau look de cet écran totalement intégré à VS.

image

On tape dans la zone de recherche “mvvmlight” et le premier élément dans la liste de gauche est le bon. A droite les détails du paquet avec le choix de la version. La 5.1.1 est la dernière stable au moment où j’écris ces lignes, c’est elle que je vais ajouter en cliquant sur le bouton “install” à droite.

On passe alors un premier dialogue de confirmation pour l’installation des paquets :

image

puis un second pour la validation de la licence :

image

On accepte tout cela et on revient à notre application…

On s’aperçoit que si le paquet s’est bien installé le projet n’a pas été modifié comme d’habitude pour y intégrer les petites choses dont on a besoin généralement comme le ViewModel Locator par exemple. Donc il va falloir ajouter tout cela “à la main” mais ce n’est pas grand chose. Laurent est en train de travailler à compléter les templates et il est possible que lorsque vous lirez cet article ces manipulations ne soient plus nécessaires. Mais sinon, suivez le guide !

Modifier App.Xaml.cs

Mvvm Light met à la disposition du développeur un Dispatcher qui permet de s’assurer qu’une action sera réalisée sur le thread de l’UI. Cela est utile lorsqu’on veut modifier un élément d’UI depuis un autre thread. Ce sont des choses que j’ai déjà présentées et je n’oublie pas que nous ne réalisons qu’une sorte de Hellow World pour MVVM. Restons simples, même dans les explications !

Ce Dispatcher doit être initialisé sur le thread principal pour fonctionner correctement, c’est donc naturellement dans la classe App qu’on va ajouté en fin de méthode OnLaunched ce petit bout de code :

DispatcherHelper.Initialize();

 

Les explications sont plus longues que le code… Mais puisque nous sommes dans App et sa méthode OnLaunched profitons-en pour ajouter à la suite du code précédent cela :

Messenger.Default.Register<NotificationMessageAction<string>>(
                this,
                handleNotificationMessage);

 

Il s’agit ici de démontrer le fonctionnement de la messagerie et pour ce faire nous allons enregistrer un gestionnaire de message pour le type NotificationMessageAction<string>. De ce fait les messages de ce type qui seront envoyés de n’importe où dans le code aboutirons tous dans App et sa méthode handleNotificationMessage qu’il nous reste à ajouter à la suite de la méthode OnLaunched par exemple pour rester grouper …

private static void handleNotificationMessage(NotificationMessageAction<string> message)
{
  message.Execute("Reçu et Traité par App.xaml.cs!");
}

 

Ici rien de bien intelligent il faut dire puisqu’on se limite à exécuter le delegate passé dans le message et à retourner un texte de confirmation (pur exemple sans grand intérêt fonctionnel dans la démo).

C’est tout ce qu’il y a à faire mais c’est important, au moins pour le Dispatcher, la gestion des messages est là pour la démo mais n’est pas obligatoire.

Model

Dans MVVM, il y a Model… C’est donc tout naturellement que nous allons créer un réperoire Model dans l’application et y placer les … modèles.

La “blendabilité” de Mvvm Light s’exprime notamment par une construction utilisant une interface d’accès aux données. C’est le fonctionnement de base qui est là plutôt pour montrer le principe. Dans un programme réel où les modèles sont nombreux et complexes on utilisera d’autres approches ou une version plus sophistiquées que celle que nous allons voir. Mais cet article n’est ni un cours sur MVVM, ni sur Mvvm Light. Alors passons à l’action !

Créons tout d’abord les “données” que nous allons manipuler dans la démo. Il s’agira ici uniquement d’une instance de classe véhiculant une chaine de caractère, un titre qui sera affiché par la vue. Bien entendu la réalité est bien plus sophistiquée et les données proviendront d’un réseau, d’une base de données, d’une couche DAL ou Entity Framework etc.

namespace MvvmLightDemo.Model
{
    public class DataItem
    {
        public string Title
        {
            get;
            private set;
        }

        public DataItem(string title)
        {
            Title = title;
        }
    }

 

Créons l’interface de gestion des données :

using System.Threading.Tasks;

namespace MvvmLightDemo.Model
{
    public interface IDataService
    {
        Task<DataItem> GetData();
    }
}

 

Cette interface n’expose qu’une méthode, GetData, qui programmation moderne fluide et réactive oblige est implémentée sous la forme d’une méthode asynchrone. Cela est raisonnable pour un accès à des données qui dans les faits vont solliciter disques, réseaux, middlewares, etc.

Il nous faut maintenant deux implémentations de cette interface, la première pour fournir les données “réelles” et la seconde pour le design-time afin de disposer d’une simulation des données pour nous aider à faire la mise en page (la fameuse “blendabilité” donc).

Voici la première implémentation, le service d’accès aux données réelles :

using System.Threading.Tasks;

namespace MvvmLightDemo.Model
{
    public class DataService : IDataService
    {
        public Task<DataItem> GetData()
        {
            return Task.FromResult(new DataItem("Hello World - Données réelles !"));
        }
    }
}

 

Si vous avez en tête la petite série sur Task que je publie en ce moment vous noterez j’en suis certain l’utilisation du FromResult ici, mais je n’en dis pas plus ce n’est pas le sujet, juste un petit signal …

Bien entendu la classe ci-dessus retourne des données immédiates, c’est une démo. Il n’y a pas d’accès à une base de données ni rien d’autre. Donc si nous utilisions directement une instance pour nourrir notre ViewModel nous aurions des données de design affichées. Mais ce n’est qu’une conséquence de l’effet réducteur de la démo.

C’est pourquoi pour le design time nous allons implémenter une nouvelle fois l’interface :

using System.Threading.Tasks;
using MvvmLightDemo.Model;

namespace MvvmLightDemo.Design
{
    public class DesignDataService : IDataService
    {
        public Task<DataItem> GetData()
        {
            return Task.FromResult(new DataItem("Hello World - Données de Design !"));
        }
    }
}

 

Pour rendre lisible la structure du projet cette classe est créée dans un sous-répertoire appelé Design. C’est là qu’on mettra dans la réalité toutes les implémentations de données de design pour toutes les interfaces que nous aurons définies.

Nous avons initialisé le Dispatcher, ajouter un mécanisme de réception pour un certain type de message, nous avons créé les modèles, une interface et deux implémentations, l’une réelle, l’autre pour le Design, passons au ViewModel !

ViewModel

Pour pasticher une célèbre pub on pourrait dire “Le ViewModel c’est le cœur de la maison !”. En effet c’est là que se joue le lien entre les modèles que nous avons définis et la vue que nous créerons ensuite. Sans le ViewModel pas de données adaptées, pas de commandes répondant à l’utilisateur, rien.

Pour les besoins de la démonstration nous allons créer un ViewModel démontrant plusieurs facettes de MVVM et de Mvvm Light. C’est un peu plus long que le code d’un Hello World pour la console en C#…

namespace MvvmLightDemo.ViewModel
{
    public class MainViewModel : ViewModelBase
    {
        public const string ClockPropertyName = "Clock";
        public const string WelcomeTitlePropertyName = "WelcomeTitle";
        private string clock = "Démarrage";

        private readonly IDataService dataService;
        private readonly INavigationService navigationService;


        private int counter;
        private RelayCommand incrementCommand;
        private RelayCommand<string> navigateCommand;
        private bool clockRunning;
        private RelayCommand sendMessageCommand;
        private RelayCommand showDialogCommand;
        private string welcomeTitle = string.Empty;

        public string Clock
        {
            get
            {
                return clock;
            }
            set
            {
                Set(ClockPropertyName, ref clock, value);
            }
        }

        public RelayCommand IncrementCommand
        {
            get
            {
                return incrementCommand
                    jQuery15207097273420076817_1439148280690 (incrementCommand = new RelayCommand(
                    () =>
                    {
                        WelcomeTitle = string.Format("Compteur de clic = {0}", ++counter);
                    }));
            }
        }

        public RelayCommand<string> NavigateCommand
        {
            get
            {
                return navigateCommand
                       jQuery15208317112296354026_1439148465592 (navigateCommand = new RelayCommand<string>(
                           p => navigationService.NavigateTo(ViewModelLocator.SecondPageKey, p),
                           p => !string.IsNullOrEmpty(p)));
            }
        }

        public RelayCommand SendMessageCommand
        {
            get
            {
                return sendMessageCommand
                    ?? (sendMessageCommand = new RelayCommand(
                    () =>
                    {
                        Messenger.Default.Send(
                            new NotificationMessageAction<string>(
                                "Test",
                                reply =>
                                {
                                    WelcomeTitle = reply;
                                }));
                    }));
            }
        }

        public RelayCommand ShowDialogCommand
        {
            get
            {
                return showDialogCommand
                       ?? (showDialogCommand = new RelayCommand(
                           async () =>
                           {
                               var dialog = ServiceLocator.Current.GetInstance<IDialogService>();
                               await dialog.ShowMessage("Hello World depuis une Universal App !", "Ca marche !");
                           }));
            }
        }

        public string WelcomeTitle
        {
            get
            {
                return welcomeTitle;
            }

            set
            {
                Set(ref welcomeTitle, value);
            }
        }

        public MainViewModel(
            IDataService dataService,
            INavigationService navigationService)
        {
            this.dataService = dataService;
            this.navigationService = navigationService;
            Task.Run(() => initialize()).Wait();
        }

        public void RunClock()
        {
            clockRunning = true;

            Task.Run(async () =>
            {
                while (clockRunning)
                {
                    DispatcherHelper.CheckBeginInvokeOnUI(() =>
                    {
                        Clock = DateTime.Now.ToString("HH:mm:ss");
                    });

                    await Task.Delay(1000);
                }
            });
        }

        public void StopClock()
        {
            clockRunning = false;
        }

        private async Task initialize()
        {
            try
            {
                var item = await dataService.GetData();
                WelcomeTitle = item.Title;
            }
            catch (Exception ex)
            {
                WelcomeTitle = ex.Message;
            }
        }
    }
}

 

Sans trop entrer dans les détails je vais pointer toutes les petites choses intéressantes de ce code :

ViewModelBase

D’abord vous remarquerez que le ViewModel descend de ViewModelBase, une classe fournie par Mvvm Light et qui contient tout ce qu’il faut pour simplifier l’écriture d’un ViewModel, notamment l’indispensable support de INPC (petit nom qu’on donne pour aller plus vite à l’interface INotifyPropertyChanged).

Clock

La prorpriété Clock contient une chaine qui indique l’heure courante. Plusieurs choses intéressantes sont à noter ici. La première est que cette horloge n’est pas gérée par un timer mais par une tâche possédant un délai. Il s’agit d’une Promise Task et non d’une Delegate Task, c’est à dire une tâche évènement n’exécutant aucun code. Task.Delay n’est pas identique à un Thread.Sleep de ce point de vue. Mais cela vous le savez si vous suivez la série sur les Task publier en ce moment…

L’horloge est démarrée par la méthode StartClock() et arrêtée par StopClock(). C’est dans la première que se trouve la tâche responsable de son fonctionnement.

L’utilisation d’une tâche plutôt que d’un timer n’est pas que stylistique, la programmation asynchrone apporte des bénéfices différents. Ici encore je ne fais que pointer les choses à voir, développer à chaque fois nous entrainerait trop loin dans le cadre du présent article (d’autant que ce sont des choses que j’explique ou ai expliqué dans de nombreux billets).

Enfin on peut se demander pourquoi StartClock() n’est pas appelé par le constructeur du ViewModel et StopClock() par un évènement de sortie. D’ailleurs où est utilisé StartClock ? Nulle part ! En effet pas dans le code du ViewModel. Mais où alors ? Je triche un peu mais nous le verrons dans le code-behind de la MainPage… On peut déjà dire qu’une des raisons est l’absence dans le ViewModel de moyens simples de savoir s’il y a “sortie” ou non alors que cette information est de la connaissance du système de navigation dont la Vue est elle au courant. Mais nous pouvons si nécessaire contourner ce problème. L’autre raison est qu’il est intéressant de montrer comment la Vue peut dialoguer avec son ViewModel sans briser les principes de MVVM. Exercice de style donc.

IncrementCommand

Il s’agit d’une commande. En MVVM on utilise l’interface ICommand pour gérer les commandes que l’utilisateur peut donner au ViewModel. implémenter ICommand est fastidieux. C’est pourquoi Mvvm Light nous propose la classe RelayCommand qui simplifie la création de commandes. Elle supporte déjà ICommand et c’est donc comme cela que sont vues les RelayCommand par la Vue. Le développeur lui voit le côté RelayCommand ce qui est plus pratique.

Sinon IncrementCommand est un exemple de commande tout bête montrant comment à chaque appel un compteur est incrémenté. Le résultat est passé pour simplifier à la même zone que celle qui contient le titre au lancement. C’est un peu “bordélique” je l’accorde, mais la démo ne peut pas être un code réel. Mais ne faites pas cela dans une application de production ! Les noms et intentions doivent être clairs. La propriété WelcomeTitile ne peut contenir qu’un titre de bienvenue, rien d’autre. Soit il faut renommer la propriété soit il faut en créer une autre pour gérer l’affichage du compteur ce qui serait bien plus propre.

NavigateCommand

Cette commande nous permet de voir comment le service de navigation est utilisé par le ViewModel. C’est une problématique typique de MVVM, qui de la poule ou de l’œuf… qui du ViewModel ou de la Vue est en charge de la navigation ? S’agissant d’une action sur les vues ce sont ces dernières qui souvent possèdent un système de navigation proposé par la plateforme. Et c’est le cas dans UWP. Hélas ce n’est pas le code-behind de la Vue qui doit s’occuper de cela, c’est la responsabilité du ViewModel en  MVVM que de répondre aux commandes de l’utilisateur et de décider d’activer telle ou telle Vue.

MvvmLight utilise depuis quelques versions un conteneur IoC qui permet de faire de l’injection de dépendances. C’est ainsi que le constructeur du ViewModel s’attend à recevoir une instance du système de navigation qui lui sera passé par le conteneur IoC au moment de son instanciation.

La navigation mise en œuvre ici active donc l’affichage d’une seconde page. Elle n’utilise pas de ViewModel (parfois cela n’est pas nécessaire) et nous l’étudierons plus loin.

SendMessageCommand

Cette commande sert à montrer l’utilisation de la messagerie MVVM proposée par Mvvm Light. Un message de type NotificationMessageAction est envoyé par le ViewModel sans qu’il sache qui le traitera. Le découplage est donc très fort entre demandeur et exécuteur.

Le type de message envoyé est particulier, c’est un message de notification possédant une action, un delegate. Le récepteur récupère la chaine passée dans le message (c’est l’objet principal d’un message de notification, un bout de texte envoyé comme une bouteille à la mer) mais il a aussi la responsabilité d’exécuter l’action qui est ajoutée au texte. Cela permet à l’envoyeur du message de prévoir un traitement à réaliser une fois le message traité. Celui qui reçoit le message ne sait rien du code à exécuter, il n’y a pas non plus de callback qui pourraient poser des problèmes de libération de mémoire, ni d’évènement. C’est une façon originale de programmer dans un mode totalement découplé… et surtout de répondre à des problèmes bien concrets qui se posent lorsqu’on applique MVVM !

Si vous revenez beaucoup plus haut dans cet article au paragraphe traitant les modification de App.Xaml.cs vous retrouver… L’enregistrement du récepteur des notifications du même type. Mvvm Light collectera le message et le distribuera à tous les récepteurs inscrits pour le type de message envoyé. Dans notre application il n’y a qu’un point de réception et c’est la classe App.

Dans notre implémentation ultra simple App ne s’occupe en réalité pas de la chaine de notification passée dans le message. En revanche elle exécute bien le delegate passée dans ce dernier. Et que fait-il ? Il modifie la valeur de WelcomeTitle en lui affectant “reply”. D’où vient ce dernier ? C’est le paramètre chaine de l’action. Et qui donne une valeur à ce paramètre ? Celui qui appelle l’action bien entendu, donc le code de App, et c’est donc le texte “Reçu et traité par App.xaml.cs !” qui sera passé au delegate qui modifiera WelcomeTitle …

Ne vous inquiétez pas, au début ce n’est pas facile à piger, c’est normal. Il faut avoir en tête la totalité de touts ces mécanismes pour s’en faire une image correcte et cela ne vient pas d’un coup d’un seul comme une révélation ésotérique. On mesure d’ailleurs ici le fossé entre l’apparente simplicité d’un framework comme Mvvm Light et la complexité de son utilisation correcte…

ShowDialogCommand

Encore une autre facette de Mvvm Light et une autre problématique typique de MVVM : les dialogues.

En effet en MVVM le code se trouve dans le ViewModel, en tout cas celui qui peut être amené à réagir à une action de l’utilisateur pouvant éventuellement déboucher sur l’affichage d’un message. Dans de rares cas cela peut être aussi le Model qui initie un dialogue, d’une part parce qu’il peut rencontrer par exemple un problème dans le traitement des données et qu’il peut être plus simple d’envoyer le message là où le problème se pose plutôt que de le faire dans tous les ViewModel qui exploitent la même source et puis parce que MVVM autorise dans les cas simple à ce que la Vue soit connectée directement au Model ce qui fait que le code utile piloté par l’utilisateur se trouve alors dans celui-ci et non dans un ViewModel.

Or les ViewModel et plus encore les Model sont des classes qui n’ont rien à voir avec l’UI et qui surtout ne doivent rien à voir avec elle !

Dès lors comment afficher un dialogue depuis un code qui n’a pas accès aux fonctions de l’UI qui le permettent ?

De même qu’il existe un service de navigation que les ViewModel peuvent manipuler alors que la navigation implique de manipuler des vues le tout en conservant un découplage fort entre Vue et ViewModel, il existe un service de dialogue qui permet à tout code, mais principalement les ViewModel, d’afficher des boites de dialogue (qu’elles attendent une réponse ou non).

La commande ShowDialogCommand utilise donc ce fameux service de dialogue qui est ici obtenu non pas par injection de dépendances mais en interrogeant directement le ServiceLocator ce qui met en lumière une autre façon d’obtenir des services. Le service de dialogue offre un moyen simple d’afficher un dialogue et d’attendre la réponse le tout sans bloquer l’application puisque tout cela est réalisé en asynchrone (vous comprenez maintenant pourquoi ces derniers temps j’insiste sur Task et sur l’asynchronisme, c’est “la” façon de programmer sous UWP (pas que) !).

WelcomeTitle

C’est une propriété texte dont le contenu sert un peu à tout dans cette démo. Au départ elle contient un texte de bienvenu de type Hello World mais au fil des actions elle affichera d’autres informations. Comme déjà dis une zone fourre-tout n’est jamais une bonne idée surtout quand elle porte un nom précis. Programmer en faisant en sorte que l’intention du développeur soit claire est un impératif totalement violé ici. Mais ce n’est qu’une démo.

MainViewModel() et Initialize()

Le constructeur de notre ViewModel accepte plusieurs paramètres de type interface. Les interfaces rappelons-le sont les premières pierres qui ont été posées pour construire l’édifice de la programmation découplée. Bien qu’étant un concept très ancien (Delphi, Java proposaient déjà cette notion d’interface il y a bien une vingtaine d’années !) il est toujours aussi moderne et on l’utilise le plus souvent possible dans le cadre d’une programmation moderne.

Comme dit un peu avant le constructeur récupère les instances de ces interfaces via l’injection de dépendances effectuée par le conteneur IoC proposé par Mvvm Light. Par défaut ce conteneur est très simple (mais bien suffisant pour une majorité d’applications) et il est possible d’en changer à l’initialisation de l’application (notamment grâce à l’utilisation des interfaces).

En dehors de stocker les valeurs reçues, le constructeur appelle une méthode d’initialisation qui va chercher les données dans le Model. S’agissant d’une méthode async il est nécessaire de l’attendre, c’est plus clair, même si ici cela ne joue aucun rôle puisque quand les données arriveront les propriétés ad hoc seront modifiées et que cela entrainera par le jeu de INPC la notification des contrôles XAML bindés qui se mettront alors à jour. On pourrait donc se passer du “.Wait()” sur l’appel surtout qu’on est en fin de méthode (donc l’intérêt d’un point de retour vers le contexte est sans intérêt). En revanche s’agissant d’un constructeur on ne pourrait pas faire un await car il n’est possible d’avoir un constructeur async…

StartClock et StopClock

Déjà évoqué plus haut ces deux méthodes servent à démarrer et arrêter l’affichage d’une horloge. On remarque donc que l’horloge est gérée par du code asynchrone et non pas par un timer et que c’est un Task.Delay et non un Thread.Sleep qui est utilisé. Ces subtilités sont décrites dans la série sur Task que je publie en parallèle de cet article.

Ces méthodes ne sont pas appelées dans le constructeur, elle le seront dans le code-behind de la Mainpage pour montrer comment sans briser le découplage une Vue peut éventuellement faire appel à du code du ViewModel. Ce qui est une situation rare mais d’une grande utilité dans certains cas. MVVM ayant à la base une rigidité posant certains problèmes que justement des librairies comme Mvvm Light solutionnent.

La Vue

Nous avons décrit le Model, le ViewModel, reste la Vue.

Voici d’abord à quoi la vue ressemble (sa mise en page est en mode téléphone, je reparlerai plus tard des stratégies de la Reactive UI qui permettent avec un même code XAML de s’adapter à tous les form factors) :

image

 

Rien de bien exotique, une grille, un stackpanel et des bouton ou des zones de texte et des bindings comme on sait le faire en XAML depuis longtemps ce qui donne le code XAML suivant :

<Page
    x:Class="MvvmLightDemo.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MvvmLightDemo"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    DataContext="{Binding Main, Source={StaticResource Locator}}">

    <Grid>
        <Grid.Background>
            <LinearGradientBrush EndPoint="0.906,1.164"
                                 StartPoint="0.244,-0.159">
                <GradientStop Color="#FFA20000"
                              Offset="0" />
                <GradientStop Color="#FFCD7C10"
                              Offset="1" />
            </LinearGradientBrush>
        </Grid.Background>

        <StackPanel Background="#FF1F1F1F"
                    Width="300"
                    HorizontalAlignment="Center"
                    Height="450"
                    VerticalAlignment="Center">
            <Button Content="Incrémenter compteur"
                    HorizontalAlignment="Stretch"
                    VerticalAlignment="Stretch"
                    Margin="0,0,0,20"
                    FontSize="24"
                    Command="{Binding IncrementCommand, Mode=OneWay}"
                    Foreground="White" />
            
            <Button Content="naviguer vers page 2"
                    HorizontalAlignment="Stretch"
                    VerticalAlignment="Stretch"
                    Margin="0,0,0,20"
                    FontSize="24"
                    Command="{Binding NavigateCommand, Mode=OneWay}"
                    CommandParameter="{Binding Text, ElementName=NavigationParameterText}"
                    Foreground="White" />

            <TextBox x:Name="NavigationParameterText"
                     Text="Texte pour naviguer..."
                     Margin="0,0,0,20"
                     FontSize="24"
                     Foreground="White" />

            <Button Content="Afficher un dialogue"
                    HorizontalAlignment="Stretch"
                    VerticalAlignment="Stretch"
                    Margin="0,0,0,20"
                    FontSize="24"
                    Command="{Binding ShowDialogCommand, Mode=OneWay}"
                    Foreground="White" />

            <Button Content="Envoyer message MVVM"
                    HorizontalAlignment="Stretch"
                    VerticalAlignment="Stretch"
                    Margin="0,0,0,20"
                    FontSize="24"
                    Command="{Binding SendMessageCommand, Mode=OneWay}"
                    Foreground="White" />

            <TextBlock TextWrapping="Wrap"
                       Text="{Binding WelcomeTitle}"
                       FontFamily="Segoe UI Light"
                       FontSize="24"
                       HorizontalAlignment="Center"
                       TextAlignment="Center"
                       Foreground="White" />

            <TextBlock FontFamily="Segoe UI Light"
                       FontSize="18.667"
                       HorizontalAlignment="Center"
                       TextAlignment="Center"
                       Foreground="White"
                       Margin="23.82,20,23.18,0"
                       Text="{Binding Clock}" />
        </StackPanel>
    </Grid>
</Page>

 

Il n’y a donc rien à dire sur ce code en dehors des bindings qui implique un DataContext qu’on peut voir en haut. Ce morceau là est intéressant car si on le regarde bien on s’aperçoit qu’il utilise une ressource statique “Locator” pour se lier à sa propriété “Main”. D’où cela vient-il ?

Mvvm Light utilise une stratégie de centralisation des ViewModel via un ViewModel Locator. C’est ce locator qui est utilisé ici. Mais comment est-il défini et où est créée la ressource ?

Le ViewModelLocator

Son rôle est de créer un point d’accès centralisé pour les ViewModel ce qui permet au passage de fournir des données de Design. Le découplage reste fort puisque la vue ne connait l’instance qu’au travers de son DataContext lié à une propriété du locator. On pourrait aller encore plus loin et créer des interfaces pour chaque ViewModel et n’exposer dans le Locator que ces dernières. C’est beaucoup de travail pour un découplage qui dans la pratique s’avère sans grand intérêt.

Le code du locator de notre application est le suivant :

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Ioc;
using GalaSoft.MvvmLight.Views;
using Microsoft.Practices.ServiceLocation;
using MvvmLightDemo.Model;

namespace MvvmLightDemo.ViewModel
{
    public class ViewModelLocator
    {
        public const string SecondPageKey = "SecondPage";

        static ViewModelLocator()
        {
            ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);

            var nav = new NavigationService();
            nav.Configure(SecondPageKey, typeof(SecondPage));
            SimpleIoc.Default.Register<INavigationService>(() => nav);

            SimpleIoc.Default.Register<IDialogService, DialogService>();

            if (ViewModelBase.IsInDesignModeStatic)
            {
                SimpleIoc.Default.Register<IDataService, Design.DesignDataService>();
            }
            else
            {
                SimpleIoc.Default.Register<IDataService, DataService>();
            }

            SimpleIoc.Default.Register<MainViewModel>();
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
            "CA1822:MarkMembersAsStatic",
            Justification = "This non-static member is needed for data binding purposes.")]
        public MainViewModel Main
        {
            get
            {
                return ServiceLocator.Current.GetInstance<MainViewModel>();
            }
        }
    }
}

 

On remarque l’utilisation du conteneur IoC qui permet de faire de l’injection de dépendances et qui joue aussi le rôle de cache pour les instances. Il y a beaucoup à dire sur le sujet que j’ai déjà abordé à plusieurs reprises alors passons à la suite : où est instancié ce locator ?

Pour qu’il soit visible de partout il faut l’instancier dans un code accessible comme celui de App. Mais il est plus intelligent de le créer en tant que ressource, donc ce sera dans App.Xaml à l’intérieur d’un dictionnaire de ressources XAML :

<Application
    x:Class="MvvmLightDemo.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:viewModel="using:MvvmLightDemo.ViewModel"
    RequestedTheme="Light">

    <Application.Resources>
    <viewModel:ViewModelLocator x:Key="Locator"  />
    </Application.Resources>
    
</Application>

 

Dans les ressources de l’application, donc bien un niveau central, la ressource “Locator” est créée par instanciation de la classe ViewModelLocator se trouvant dans l’alias “viewModel” lui-même défini plus haut comme “using:MvvmLightDemo.ViewModel” c’est à dire l’espace de noms du répertoire contenant nos ViewModel et le locator (il est logique de le placer à cet endroit mais on pourrait faire autrement).

Ces déclarations permettent à la Vue de lier son DataContext au ViewModel principal tout en respectant le découplage fort typique de MVVM.

Le code-behind de la Vue

On entend souvent dire qu’en MVVM il n’y a aucun code dans le code-behind de la Vue. C’est faux. En tout cas c’est partiellement faux. S’il est vrai qu’aucun code fonctionnel ne doit se trouver à cet endroit il est en revanche parfaitement licite d’y faire apparaitre du code purement UI, c’est même une obligation car on voit mal un tel code être placé dans les ViewModel qui ont interdiction de connaitre l’UI et ses namespaces.

Pour illustrer cette possibilité même si ici l’exemple n’est pas le plus parlant, le code-behind de la vue principal contient ceci :

using Windows.Foundation.Metadata;
using Windows.Phone.UI.Input;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
using MvvmLightDemo.ViewModel;

namespace MvvmLightDemo
{
    public sealed partial class MainPage : Page
    {
        public MainViewModel Vm => (MainViewModel)DataContext;

        public MainPage()
        {
            InitializeComponent();

            if (ApiInformation.IsTypePresent("Windows.Phone.UI.Input.HardwareButtons"))
            {
                HardwareButtons.BackPressed += onBackPressed;
            }

            Loaded += (s, e) =>
            {
                Vm.StartClock();
            };
        }

        protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
        {
            Vm.StopClock();
            base.OnNavigatingFrom(e);
        }

        private void onBackPressed(object sender, BackPressedEventArgs e)
        {
            if (!Frame.CanGoBack) return;
            e.Handled = true;
            Frame.GoBack();
        }

    }
}

La propriété MainViewModel est définie en utilisant une syntaxe très moderne, décryptée là pour la comprendre Sourire

Le fameux code placé dans ici est celui qui initialise l’horloge affichée en bas de page principale et celui qui stoppe cette horloge. Il n’y a pas de boutons pour ces fonctions, l’horloge démarre quand la page s’affiche et doit s’arrêter dès que la page est désactivée, des informations que la Vue possède et pas le ViewModel.

C’est donc sur le Loaded de la Vue qu’on programme un gestionnaire qui déclenchera l’horloge. Et c’est naturellement par une surcharge de OnNavigatingFrom qu’on arrêtera l’horloge avant de laisser la navigation s’effectuer.

De même on gère un bouton “Back” dont le gestionnaire onBackPressed utilise les mécanismes de navigation propres aux Vues pour réaliser l’action. Cela ne pourrait pas se trouver dans le ViewModel (qui montre par ailleurs comment lui est capable de naviguer aussi via le service de navigation).

Mais le code le plus étrange ici et celui que vous ne reconnaitrez certainement pas c’est celui du test :

 if (ApiInformation.IsTypePresent("Windows.Phone.UI.Input.HardwareButtons"))
            {
                HardwareButtons.BackPressed += onBackPressed;
            }

 

Quel est ce type que nous testons en passant un namespace complet ? Et d’où vient-il ?

On touche aussi à la façon dont UWP sur une base totalement commune à tous les form factors permet malgré tout d’utiliser des spécificités de l’une ou de l’autre. Sur un PC il n’y a pas de bouton physique “retour”. En revanche sur un smartphone si (physique ou tactile c’est pareil pour nous).

Par souci d’ergonomie et de Reactive Design, un Design adapté à toutes les cibles qui s’adapte tout seul au runtime, nous souhaitons prendre en compte la présence d’un tel bouton quand il existe. Mais quand il existe seulement…

Cela s’effectue en deux temps.

Dans un premier temps nous allons ajouter aux référence de notre projet les extensions pour unités mobiles :

image

Grâce à ses extensions, qu’elles soient implémenter ou non en réalité sur une cible donnée, nous avons la possibilité dans notre code “Universel” de nous servir de ce qu’elles offrent, comme ici l’accès au bouton “retour”.

J’ai déjà longuement expliqué l’injection de code natif notamment dans les vidéos sur MvvmCross. Microsoft utilise ici une méthode similaire.

En revanche ce n’est pas parce que nous rendons un code spécifique à un form factor disponible à notre App que cela veut dire qu’on peut s’en servir sur n’importe quelle machine !

Bien entendu sur un PC par exemple l’utilisation directe du bouton “back” ne donnera pas “rien” mais un beau plantage ! Il faut donc tester la disponibilité de l’API avant de s’en servir.

Et c’est ainsi qu’on comprend mieux le test en question, il s’assure que l’API “Windows.Phone.UI.Input.HardwareButtons” existe avant de programmer un évènement sur le bouton physique de retour. Si l’API n’existe pas, comme sur un PC, l’évènement ne sera pas connecté au bouton. Dans ce cas c’est plutôt au niveau du Design que nous ferons peut-être apparaitre une flèche de retour en haut à gauche (qui sera cachée pour un smartphone). Le code du test peut donc s’occuper de gérer ses différents aspects et c’est bien dans le code-behind de la Vue qu’il doit se trouver car tout cela est inaccessible au ViewModel, cela ne concerne que l’UI.

La vue secondaire

Pour montrer le service de navigation il fallait bien une seconde Vue… Mais elle sera simple et sans ViewModel, pourtant elle va nous permettre de voir plusieurs choses intéressantes…

Côté XAML rien de spécial, un bouton, un texte… passons rapidement :

<Page x:Class="MvvmLightDemo.SecondPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:MvvmLightDemo"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

    <Grid>
        <Grid.Background>
            <LinearGradientBrush EndPoint="0.5,1"
                                 StartPoint="0.5,0">
                <GradientStop Color="#FF120DE2"
                              Offset="0" />
                <GradientStop Color="#FF86FBF6"
                              Offset="1" />
            </LinearGradientBrush>
        </Grid.Background>
        
        <StackPanel HorizontalAlignment="Center"
                    VerticalAlignment="Center">
            <Button x:Name="GoBackButton"
                    Content="Retour"
                    HorizontalAlignment="Stretch"
                    VerticalAlignment="Stretch"
                    Click="GoBackButton_Click"
                    FontSize="32"
                    Width="150"
                    Height="60" />
        
            <TextBlock x:Name="DisplayText"
                       TextWrapping="Wrap"
                       FontFamily="Segoe UI"
                       FontSize="24"
                       Margin="0,20,0,0"
                       HorizontalAlignment="Center"
                       TextAlignment="Center" />
        </StackPanel>

    </Grid>
</Page>

 

Ici aussi le bouton de navigation est géré par du code-behind, exactement de la même façon que dans la Vue principale :

namespace MvvmLightDemo
{
    public sealed partial class SecondPage
    {
        public SecondPage()
        {
            InitializeComponent();
        }

        private void GoBackButton_Click(object sender, RoutedEventArgs e)
        {
            var nav = ServiceLocator.Current.GetInstance<INavigationService>();
            nav.GoBack();
        }

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            DisplayText.Text = e.Parameter.ToString();
            base.OnNavigatedTo(e);
        }

    }

 

En revanche la surcharge de OnNavigateTo montre une astuce intéressante pour passer des informations d’une Vue à l’autre. Le paramètre de navigation passé à la méthode contient en effet l’objet envoyé par la page principale, ici une simple chaine qui est récupérée et affichée. Mais cela pourrait être un objet complexe autant qu’un simple ID d’une fiche dont il faudrait montrer le détail par exemple.

Pour rappel nous avons sur la page principale une TextBox appelée NavigationParamaterText qui peut recevoir un texte tapé par l’utilisateur avant que celui-ci n’appelle la navigation vers la page 2. Mais il n’y a pas de propriété équivalente dans le MainViewModel. Comment ce paramètre est-il passé à la seconde Vue alors ?

D’abord disons que ce cas n’est qu’une possibilité, le paramètre pourrait fort bien être fourni par le ViewModel bien entendu. Mais regardons le code XAML du bouton de navigation :

                    <Button Content="naviguer vers page 2"
                    HorizontalAlignment="Stretch"
                    VerticalAlignment="Stretch"
                    Margin="0,0,0,20"
                    FontSize="24"
                    Command="{Binding NavigateCommand, Mode=OneWay}"
                    CommandParameter="{Binding Text, ElementName=NavigationParameterText}"
                    Foreground="White" />

 

Le bouton est bindé à la commande NavigateCommande du ViewModel mais le paramètre de cette commande est lui bindé par un Element Binding directement au contenu de la zone de saisie. De fait quand l’utilisateur clic sur le bouton la commande du ViewModel est invoquée et elle reçoit le texte en paramètre. Revisitons le bout de code concerné :

public RelayCommand<string> NavigateCommand
        {
            get
            {
                return navigateCommand
                       ?? (navigateCommand = new RelayCommand<string>(
                           p => navigationService.NavigateTo(ViewModelLocator.SecondPageKey, p),
                           p => !string.IsNullOrEmpty(p)));
            }
        }

 

La RelayCommand qui est créée est une commande générique typée avec un paramètre string, c’est pour cela que le paramètre peut directement être utilisé dans ce format. Ensuite le code ci-dessus invoque la navigation vers la page 2 dans son delegate d’action et fixe en second paramètre la valeur du paramètre de navigation, la fameuse chaîne reçue depuis l’UI…

Ce n’est pas très compliqué mais arriver à penser tout un code en l’architecturant avec ces petites choses là en tête demande un peu d’entrainement il est vrai.

Il y a un temps pour expliquer et un autre pour voir…

Le temps des explications est terminé, non pas que j’ai tout dit, mais je pense en avoir “assez” dit pour cette petite prise en main de Mvvm Light en UWP sous Windows 10.

N’ayez crainte je reviendrais forcément sur de nombreux aspects dans les jours et semaines à venir…

Mais maintenant il faut que nous coutions le fruit de nos efforts en exécutant l’application…

Une App, Un code, Une UI, Tous les form factors !

C’est ici que la magie annoncée dans le titre de cet article fleuve s’opère… Dans un premier temps je vais choisir d’exécuter l’application dans l’émulateur Windows 10 :

image

Voici la page principale, et maintenant la page secondaire :

image

Le bouton “retour” fonctionnera sur toutes les machines mais sur le smartphone on pourra aussi utiliser le bouton en bas à gauche. Le “texte pour naviguer” n’est qu’un bout de texte qu’on peut taper sur l’écran principal et qui est passé à la page 2 pour montrer les mécanismes de passage d’information entre les écrans.

On peut retourner l’écran ça marche de base sans rien faire…

image

je vous fais grâce de toutes les manipulations (envoi de message, etc) car le code de l’application est joint à l’article et il sera bien plus intéressant de le faire tourner et de l’expérimenter vous-même…

Passons plutôt au côté magique, je vais maintenant lancer l’application en mode “machine locale” (c’est à dire déploiement sur ma machine, un PC donc) :

image

Comme le Design de l’application n’est pas réactif cela donne un truc moche centré. D’où la nécessite de faire un petit effort côté XAML pour faire du Reactive Design. Mais ça marche !

Conclusion

Imaginez-vous deux secondes ce que tout cela signifie ? Un seul et unique code pour TOUS les form factors ? UN SEUL exécutable qui tourne sur une XBox, un PC, Surface, une phablet ou un smartphone !

Certes tout cela dans le monde Microsoft, nous sommes bien en cross-form-factor plutôt qu’en cross-plateforme, la fameuse universalité verticale qui a remplacé le fantasme d’une universalité horizontale. Mais quelle bonheur ! Microsoft répond ici à nos vrais besoins : celui de couvrir tous les form factors de façon cohérente et avec le minimum d’effort. On attendait cela avec Windows 8 et ce n’était pas réussi, il fallait certainement un peu plus de temps. Mais avec Windows 10 le pari est tenu et gagné !

C’est une bénédiction pour tout le monde, un soulagement on revient à l’époque glorieuse d’un Windows qui fait tout et qui couvre tous les besoins, d’un seul code… Fini les tracas du cross-plateforme ! Windows et Microsoft sont de nouveau dans la course. Encore va-t-il falloir reconquérir tout le terrain perdu ces dernières années et occupé par des sociétés comme Google ou Apple qui ne vont pas se laisser balayer de la carte comme ça… Microsoft a eu un trou d’air, et des gros poissons sont tombés dedans et ils occupent le terrain. Difficile de refermer la nasse d’un geste et de revenir à l’hégémonie d’autant. Pourtant c’est ce qu’il faut à notre métier, de la stabilité, de la cohérence. On se moque des tablettes ou smartphones de Google ou Android, franchement. Tout ça fait la même chose… Et les produits Microsoft ne téléphonent pas moins bien ni ont de moins bons écrans. Ils proposent tous la même chose. Mais seul Microsoft nous offre un avenir aussi radieux que notre passé, le temps où un seul OS, Windows, permettait de couvrir 100% des besoins, de planifier sereinement l’avenir. C’est ça qui a fait gagner de l’argent à tout le monde et a permis à la “micro informatique” d’être ce qu’elle est, terme devenu vieux mais ô combien contemporain et d’avenir…

Windows 10 et surtout UWP offrent enfin une réponse intelligente à un vrai problème, celui de couvrir plusieurs form factors avec un cout minimum. Et quand on raisonne en terme de cout et de rentabilité, c’est à dire comme une entreprise, il n’y a pas photo, l’avenir c’est Windows 10 et UWP.

Avec C#, XAML et MVVM …

Stay Tuned !

 

Le code source du projet : 

MvvmLightDemo.zip (74,89 kb)

blog comments powered by Disqus