Développer un client Silverlight pour Sharepoint 2010 en appliquant le pattern MVVM (1/2)

Cet article décrit comment réaliser une application Silverlight qui présente la liste externe “Wines” (Sharepoint 2010 Dev Partie 1 : Création d’une liste externe basée sur des données stockées dans SqlServer) dans une forme plus ludique, et en appliquant le pattern Model-View-ViewModel. Nous verrons notamment un moyen d’intégrer les requêtes asynchrones du Client Object Model dans un modèle en couche, grâce aux événements C#.
            Vous pouvez suivre cet article en utilisant n’importe quelle liste Sharepoint, du moment qu’elle contient une colonne de type texte et une colonne de type numérique
            Je précise que le pattern MVVM n’a aucun rapport avec le rendu visuel de la liste…vous pouvez donc sauter directement à la deuxième partie de l’article pour la partie xaml

Téléchargez les sources complètes :

Sujets abordés dans l’article:

Ce premier article, abordera les notions suivantes:
Création d’une application Silverlight
Mise en place de l’architecture MVVM
Utilisation du Client Object Model de Sharepoint 2010
Gestion de notifications asynchrones dans une architecture MVVM

Dans le deuxième article ici , nous verrons:
l’implémentation de INotifyPropertyChanged par les classes du ViewModel pour que le binding fonctionne correctement
l’écueil classique lors d’un développement Silverlight/WPF dans un contexte multithread
comment passer d’un rendu habituel de liste à quelque chose de plus…original :) grâce à Silverlight et xaml

Pour rappel, voici notre liste “Wines” dans Sharepoint:

image

Et voici maintenant le rendu de notre liste Silverlight à la fin de l’article. Chaque vin est représenté par un cercle dont le diamètre est fonction de la quantité de bouteilles. Je conviens que ce n’est pas forcément très joli et je suis sûre que vous ferez beaucoup mieux…mais ça a le mérite d’être très différent de la version “Liste” de Sharepoint :)

image 

Pourquoi Silverlight, pourquoi MVVM ?

Grâce à un client Silverlight pour Sharepoint 2010, on pourra facilement :
    obtenir un rendu graphique élaboré
    accéder à des ressources externes pour intégrer d’autres sources d‘information
    accéder aux données de Sharepoint grâce au Client Object Model    
    choisir d’héberger l’application Silverlight comme une WebPart dans Sharepoint, ou l’héberger directement dans IIS pour l’utiliser directement

Pour plus d’information sur le développement et la technologie Silverlight : Introduction à Silverlight

Grâce à MVVM on pourra facilement : 
    changer la provenance et les moyens d’accès à la source de données sans remettre en question le code métier ou de présentation
    ajouter de nouvelles fonctionnalités facilement 
    changer la partie visuelle sans toucher au code métier
    faciliter la mise en oeuvre des tests  

Pour plus d’informations sur MVVM :WPF Apps With The Model-View-ViewModel Design Pattern et aussi https://japf.developpez.com/tutoriels/dotnet/mvvm-pour-des-applications-wpf-bien-architecturees-et-testables/

Sommaire de l’article 1

  1. Création de la solution dans Visual Studio 2010
  2. Mise en place des éléments pour l’architecture MVVM
  3. Rappels concernant MVVM
  4. Accès à la liste Wines dans Sharepoint
  5. Création du ViewModel
  6. Création de la View

 

Création de la solution dans Visual Studio 2010

Lancez Visual Studio 2010 en mode Administrateur. Créer un nouveau projet de type Application Silverlight et appelez-le Wines.SL

image

Laissez les valeurs par défaut dans l’écran suivant :

image

La solution contient 2 projets :

Wines.SL : qui contient le code métier et les pages Silverlight
Wines.SL.Web : qui contient les éléments nécessaires au chargement et au déploiement de l’application   

 image 

Déploiement et communication cross-domain de Silverlight:

Pour pouvoir exécuter les applications Silverlight durant la phase de codage et de débuggage, Visual Studio utilise un hébergement custom. Nous pourrons donc voir directement le résultat dans un navigateur, comme si l’application était hébergée dans IIS. On pourra ensuite uploader le .xap dans Sharepoint, pour pouvoir l’afficher dans une WebPart ou la déployer dans IIS. Ce point fera l’objet d’un article ultérieur.

Quand l’application Silverlight s’exécute dans une WebPart de Sharepoint, il n’y a pas de problématique Cross-Domain car l’hébergement est identique. Dans notre cas par-contre, il faut autoriser l’application Silverlight à utiliser les services Web du serveur Sharepoint en déployant le fichier “clientaccesspolicy.xml” suivant à la racine du serveur Sharepoint (C:\inetpub\wwwroot\wss\VirtualDirectories\2828) :

 <?xml version="1.0" encoding="utf-8" ?>
 <!-- This file must be put on the Server from where a Silverlight client gets infomration (e.g. SharePoint Site) -->
 <access-policy>
   <cross-domain-access>
     <policy>
       <allow-from http-request-headers="*">
         <domain uri="*"/>
       </allow-from>
       <grant-to>
         <resource include-subpaths="true" path="/"/>
       </grant-to>
     </policy>
   </cross-domain-access>
 </access-policy>

Plus d’informations : Communications inter-domaines

Mise en place des éléments pour l’architecture MVVM

Rappels concernant MVVM

MVVM (Model-View-ViewModel) est une architecture multi-tiers qui permet de découpler la couche Vue (View) de la couche Métier (Model) à travers la couche Cas d’utilisation (ViewModel).

Le ViewModel remplit 2 rôles :

il présente des éléments provenant du model et d’autres ViewModel, pour présenter un ensemble d’informations pertinentes pour un cas d’utilisation fonctionnel de l’application – et donc pour une ou plusieurs Views.

il implémente les interfaces et propriétés adaptées à l’usage d’une technologie de binding comme Silverlight ou WPF  

[La dénomination “Cas d’utilisation” pour le View Model est un terme qui m’est propre et n’engage donc que moi, mais je trouve qu’il correspond bien à la fonction de cette couche]. 

En pratique, la couche View accède à la couche ViewModel par l’intermédiaire du binding.

 

image

 

 

Un des intérêts de ce modèle est de localiser tout le code d’accès à la logique métier par la vue dans les classes du ViewModel, et ainsi de ne pas avoir de code behind dans les fichiers .cs des vues. Ainsi, toute la logique d’accès aux métier est située dans le ViewModel et seul le XAML (et les converters) perdurent côté View. Un autre avantage indéniable est de faciliter les tests qui sont alors pratiqués sur la couche ViewModel. Et bien sûr, comme toutes les architectures multi-tiers, il permet de découpler la couche d’accès aux données, le modèle, les cas d’utilisation et l’UI.

Je ne vais pas m’étendre davantage sur ce pattern dans cet article. Pour plus d’informations sur l’architecture MVVM, je vous recommande l’article du MSDN magazine suivant, avec les sources d’un cas pratique :WPF Apps With The Model-View-ViewModel Design Pattern et aussi https://japf.developpez.com/tutoriels/dotnet/mvvm-pour-des-applications-wpf-bien-architecturees-et-testables/ (en français :) )

C’est Parti:

En règle générale, on prépare un répertoire pour chaque couche dans la solution. Créez 3 répertoires View, Model et ViewModel dans le projet Wines.SL (click droit sur le projet –> Ajouter –> Nouveau Répertoire).

image

 

Accès à la liste “Wines” dans Sharepoint

Définition de la couche Model

Nous allons définir la classe qui représente 1 élément de la liste des vins. Elle proposera autant de propriétés que de colonnes de la liste Sharepoint. Ajoutez une classe “Wine.cs” dans le répertoire “Model”et définissez les attributs suivants: Name, Count et ToString()

 using System;
 using System.Net;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Documents;
 using System.Windows.Ink;
 using System.Windows.Input;
 using System.Windows.Media;
 using System.Windows.Media.Animation;
 using System.Windows.Shapes;
  
 namespace Wines.SL.Model
 {
     public class Wine
     {
         // = la colonne Name de la liste SP
         public string Name { get; set; }
  
         // = la colonne Count dans la liste SP
         public UInt16 Count { get; set; }
  
         // Affichage par défaut de l'élément
         public override string ToString()
         {
             return Name;
         }
     }
 }

 

Définition de la couche d’accès aux données

L’accès aux données dans Sharepoint fait l’objet d’une nouvelle couche “Accès aux données” (Data Access Layer) dans notre solution. En effet, le fait d’accéder aux informations à travers Sharepoint n’est en aucun cas une contrainte du modèle lui-même (qui n’est finalement qu’un IEnumerable<Wine>), mais de la manière dont on a décidé de stocker les données. On trouvera dans la répertoire DAL, les classes qui constituent les différentes manières d’accéder aux informations métier (une seule dans cet article : une liste SharePoint via le Client Object Model).

Créez un répertoire DAL dans le projet Wines.SL. Ajoutez une classe “WinesFromSP.cs” dans le répertoire “DAL” et ajoutez les références suivantes dans le fichier .cs :

 using Wines.SL.Model;               // Pour utilisation de la couche Model
 using Microsoft.SharePoint.Client;  // Référence au ClientObjectModel
 using System.Collections.Generic;   // Pour les Generics
 using System.Linq;                  // Pour Linq

Ajoutez une méthode “RequestFromServer” qui se chargera de récupérer les données depuis Sharepoint et les présentera comme un ensemble d’éléments de type Wine dans une propriété WineItems.

 public class WinesFromSP
     {
         public void RequestFromServer()
         {
  
         }
  
         public IEnumerable<Wine> WineItems { get; set; }
     }

Utilisation du Client Object Model de Sharepoint 2010

Le Client Object Model (CSOM) est une nouveauté de Sharepoint 2010. C’est une librairie de classes qui permet facilement d’accéder aux objets Sharepoint côté client et évite ainsi d’avoir directement recours aux Web Services de Sharepoint. Le contexte du CSOM va retenir vos opérations de requêtage pour les envoyer en une seule fois au serveur et ainsi optimiser la communication entre le client Silverlight et le serveur Sharepoint. Par défaut, le CSOM fonctionne dans le contexte de l’utilisateur de la session Windows. Assurez-vous donc que les droits sur la liste soient attribués à l’utilisateur avec lequel vous avez ouvert la session Windows pour faire fonctionner l’application.

Pour pouvoir utiliser le CSOM, il faut référencer les assemblies suivantes qui se trouvent dans "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\ClientBin":

image

Ce code permet de récupérer la liste des vins. Complétez la classe “WinesFromSP” comme ci-dessous:

 public class WinesFromSP
     {
         // Contexte de connexion au serveur
         ClientContext _ctx = new ClientContext(@"https://stephe:2828/MySite");
         // La liste des vins résultante
         ListItemCollection _wines;
                 
         public void RequestFromServer()
         {
             // Récupère la liste "Wines"
             var wineList = _ctx.Web.Lists.GetByTitle("Wines");
             // Récupère les éléments de la liste
             _wines = wineList.GetItems(new CamlQuery());
             // Charge les éléments de la liste
             _ctx.Load(_wines);
             // Exécute la requête asynchrone
             _ctx.ExecuteQueryAsync(onWineElemsLoaded, onWineElemsError);
         }
  
         private void onWineElemsLoaded(object sender, ClientRequestSucceededEventArgs args)
         {
             // Succes
         }
  
         private void onWineElemsError(object sender, ClientRequestFailedEventArgs args)
         {
             // Traitement sur erreur
         }
     }

On commence par instancier le contexte client en spécifiant le site concerné. Ce contexte sera enrichi par nos requêtes et demandes de chargement jusqu’à ce que l’on envoie effectivement la demande au serveur.

         ClientContext _ctx = new ClientContext(@https://stephe:2828/MySite);

 

On spécifie dans le contexte que l’on souhaite utiliser la liste “Wines”.

             var wineList = ctx.Web.Lists.GetByTitle("Wines");

 

On demande ensuite à récupérer tous les éléments de la liste wines. SP Linq (Linq To Sharepoint) ne fonctionne que côté serveur, il restera donc des instructions CAML dans le code client.

             _wines = wineList.GetItems(new CamlQuery());

Il faut ensuite charger les éléments que vous souhaitez obtenir en réponse du serveur par Load.

             _ctx.Load(_wines);

La requête est ensuite envoyée et exécutée côté serveur de manière asynchrone par l'appel de ExecuteQueryAsync. Ceci implique que les éléments récupérés dans _wines seront exploitables dans la callback onWineElemsLoaded. Il n’y a donc pas de communication avec le serveur avant l’appel de ExecuteQueryAsync.

             _ctx.ExecuteQueryAsync(onWineElemsLoaded, onWineElemsError);

 

Je vous recommande la lecture de l’article Introduction au Client Object Model qui détaille les modalités de requêtage et l’utilisation du Client Object Model ainsi que Sharepoint Guidance 2010 qui contient tout un chapitre très détaillé sur le CSOM.

Le schéma suivant qui synthétise les étapes du requêtage est extrait de SP2010 Guidance:

image

A présent, nous ajoutons un mécanisme de notification accessible facilement de l’extérieur de la classe :

    un événement WineItemsUpdated qui renverra la liste des vins et auquel le ViewModel pourra s’abonner pour être notifié du rafraichissement des données

    une propriété WineItems qui représente la dernière liste des vins chargée

Nous verrons tout à l’heure comment l’événement sera utilisé par la couche ViewModel pour être notifiée et permettre ainsi à la vue d’être actualisée via le binding.

Pour rester dans les règles de l’art concernant l’événement WineItemsUpdated :

    la signature doit être de type EventHandler<T>

    le 1er paramètre du delegate est l’émetteur de l’événement

   le second paramètre est l’information <T> à faire passer, qui doit hériter de la classe système EventArgs 

Ajoutez le fichier “WineItemsUpdatedEventArgs.cs” dans le répertoire DAL:

 using System;
 using System.Collections.Generic;
 using Wines.SL.Model;
  
 namespace Wines.SL.DAL
 {
     public class WineItemsUpdatedEventArgs : EventArgs
     {
         public WineItemsUpdatedEventArgs(IEnumerable<Wine> wineItems)
         {
             WineItems = wineItems;
         }
  
         public IEnumerable<Wine> WineItems 
         { 
             get; 
             private set; 
         }
     }
 }

Intéressons-nous au code de la callback onWineElemsLoaded :

 public class WinesFromSP
     {
         // Contexte de connexion au serveur
         ClientContext _ctx = new ClientContext(@"https://stephe:2828/MySite");
         // La liste des vins résultante
         ListItemCollection _wines;
         // Pour notifier l'update des éléments
         public event EventHandler<WineItemsUpdatedEventArgs> WineItemsUpdated;
                 
         public void RequestFromServer()
         {
             // Récupère la liste "Wines"
             var wineList = _ctx.Web.Lists.GetByTitle("Wines");
             // Récupère les éléments de la liste
             _wines = wineList.GetItems(new CamlQuery());
             // Charge les éléments de la liste
             _ctx.Load(_wines);
             // Exécute la requête asynchrone
             _ctx.ExecuteQueryAsync(onWineElemsLoaded, onWineElemsError);
         }
  
         public IEnumerable<Wine> WineItems { get; set; }
  
         private void onWineElemsLoaded(object sender, ClientRequestSucceededEventArgs args)
         {
             // Succes
             // wines.AsEnumerable() pour repasser en Linq To Object [using System.Linq]
             WineItems = _wines.AsEnumerable().Select(w => new Wine()
             {
                 Name = w["Name"].ToString(),
                 Count = Convert.ToUInt16(w["Count"])
             });
  
             if(WineItemsUpdated != null)
             {
                 WineItemsUpdated(this, new WineItemsUpdatedEventArgs(WineItems));
             }
         }
  
         private void onWineElemsError(object sender, ClientRequestFailedEventArgs args)
         {
             // Traitement sur erreur
         }
     }

On récupère la liste d’éléments renvoyés par le serveur. Puis pour chaque élément w de “wines”, on projette une instance de la classe métier “Wine”. La clause Select renvoit un type IEnumerable<Wine> (car dans notre cas, le corps du Select renvoit un type “Wine”) que l’on affecte à la variable WineItems.

 WineItems = _wines.AsEnumerable().Select(w => new Wine()
            {
                Name = w["Name"].ToString(),
                Count = Convert.ToUInt16(w["Count"])
            });

Plutôt que d’expliquer longuement la projection à l’aide de Select, je vais l’écrire sans la syntaxe Linq, et tout sera dit ou presque :

 WineItems = new List<Wine>();
  
 foreach (var w in wines)
 {
     WineItems.Add(new Wine() 
         { Name = w["Name"].ToString(), 
           Count = Convert.ToUInt16(w["Count"]) });
 }

 

Remarquez que dans la version longue “non Linq”, on instancie directement la liste des vins avant d’y ajouter des éléments :

WineItems = new List<Wine>();

Comme on ajoute les éléments les uns après les autres, WineItems doit être une “liste” alors que dans la version Linq, un “enumerable” est suffisant puisqu’il ne sera plus modifié après sa création. De plus, la version Linq permet d’avoir une exécution différée de ce code de projection. Ce dernier sera évalué immédiatement mais ne sera exécuté que lorsque l’on utilisera la variable WineItems. Attention, dans la version avec Linq, on force l’utilisation de Linq To Objects en appelant .AsEnumerable(). En effet, le type ListItemCollection du CSOM ne supporte pas la méthode d’extension .Select.

Les dernières lignes de la callback déclenchent un événement qui notifiera ceux qui y sont abonnés que la liste des vins a été actualisée.

Création du ViewModel

Le ViewModel va s’abonner à l’événement WineItemsUpdated du modèle et va présenter la liste des vins à la couche vue.

Pour cela, créons une classe WinesListVM.cs dans le répertoire ViewModel du projet. Pour s’abonner à l’évenement du modèle, cette classe doit connaître son type. Or, quand on y pense, tout ce qui intéresse le ViewModel, c’est d’être notifié chaque fois que le liste des vins est réactualisée et de récupérer cette liste à jour comme un IEnumerable<Wine>. Mais le fait que les données proviennent de Sharepoint, cela ne la concerne pas : pour être plus précis, WinesListVM n’a pas besoin de connaître le type concrêt WinesFromSP défini dans notre DAL. Elle pourrait très bien s’abonner à une classe qui récupère la liste des vins en provenance de SqlServer, ou d’un fichier : le code du ViewModel serait le même.

Pour apporter plus de souplesse, nous allons définir la signature de ce qu’est un modèle, au regard du ViewModel : une interface qui présente un événement dont le delegate a pour argument un WineItemsUpdatedEventArgs.

Nous retournons donc dans le répertoire DAL et y ajoutons un fichier IWinesDAL.cs contenant une interface qui comporte un événement : le même que celui défini dans WinesFromSP

 using System;
 using System.Collections.Generic;
 using Wines.SL.Model;
  
 namespace Wines.SL.DAL
 {
     public interface IWinesDAL
     {
         event EventHandler<WineItemsUpdatedEventArgs> WineItemsUpdated;
     }
 }

Notre classe WinesFromSP respecte cette signature et elle implémente donc cette interface comme ceci:

 namespace Wines.SL.DAL
 {
     public class WinesFromSP : IWinesDAL
     { …

A présent, nous pouvons retourner dans le code du ViewModel, dans la classe WinesListVM. Le constructeur de cette classe prendra en paramètre un type IWinesDAL, c’est à dire n’importe quelle classe qui implémente cette interface, et donc qui respecte la signature de l’événement WinesItemsUpdated. Le viewModel peut ensuite s’abonner à cet événement pour être notifié des changements et récupérer la liste à jour.

Dans le ViewModel, on ajoute également la propriété WineItems ainsi que SelectedItems pour récupérer l’élément sélectionné dans la vue.

 using System;
 using Wines.SL.DAL;
 using System.Collections.Generic;
 using Wines.SL.Model;
  
 namespace Wines.SL.ViewModel
 {
     public class WinesListVM
     {
         public WinesListVM(IWinesDAL winesDal)
         {
             winesDal.WineItemsUpdated += ((s, e) => WineItems = e.WineItems);
         }
         
         public IEnumerable<Wine> WineItems
         {
             get;
             set;
         }
  
         public Wine SelectedItem
         {
             get;
             set;
         }
     }
 }

Le code d’abonnement à l’événement

 public WinesListVM(IWinesDAL winesDal)
 {
     winesDal.WineItemsUpdated += ((s, e) => WineItems = e.WineItems);
 }

est comparable au code ci-dessous. Il a été simplifié à l’aide d’une expression lambda pour le delegate:

 public WinesListVM(IWinesDAL winesDal)
 {
     winesDal.WineItemsUpdated += new EventHandler<WineItemsUpdatedEventArgs>(winesDal_WineItemsUpdated);
 }
  
 void winesDal_WineItemsUpdated(object sender, WineItemsUpdatedEventArgs e)
 {
     WineItems = e.WineItems;
 }

 

Création de la View

Nous allons présenter les vins sous forme de cercles dont la taille dépend du nombre de bouteilles. Les cercles seront placés aléatoirement dans la fenêtre. Dans le répertoire “View”, ajoutez une nouvelle Page Silverlight et appelez-le WinesListView.xaml. Nous allons commencer par un rendu “normal de liste” et nous changerons l’aspect visuel par la suite. Copiez le code suivant dans votre fichier WinesListView.xaml :

 <navigation:Page x:Class="Wines.SL.View.WinesListView" 
            xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
            xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" 
            xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
            mc:Ignorable="d"
            xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
            d:DesignWidth="640" d:DesignHeight="480"
            Title="MainView Page">
     <Grid x:Name="LayoutRoot">
         <ListBox ItemsSource="{Binding WineItems}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
         </ListBox>
     </Grid>
 </navigation:Page>

Remarquez que l’on binde la propriété WineItems du ViewModel à l’ItemsSource de la ListBox et le SelectedItem du ViewModel à la propriété SelectedItem de la ListBox…sauf que pour l’instant, rien ne relie le ViewModel à la vue et c’est ce à quoi nous allons remédier maintenant.

Ouvrez le fichier App.xaml.cs, et placez-vous sur Application_Startup(). Voici le code qui a été auto-généré pour ce fichier, lors de la création du projet.

 public partial class App : Application
     {
  
         public App()
         {
             this.Startup += this.Application_Startup;
             this.Exit += this.Application_Exit;
             this.UnhandledException += this.Application_UnhandledException;
  
             InitializeComponent();
         }
  
         private void Application_Startup(object sender, StartupEventArgs e)
         {
             this.RootVisual = new MainPage();
         }
  
         private void Application_Exit(object sender, EventArgs e)
         {

Ajoutez les références aux namespaces :

 using Wines.SL.View;
 using Wines.SL.DAL;
 using Wines.SL.ViewModel;

Modifiez le code de Application_Startup de la manière suivante, pour instancier notre vue WinesListView plutôt que la page par défaut qui est créée lors de la création d’un projet Silverlight:

 private void Application_Startup(object sender, StartupEventArgs e)
 {
     WinesDALFromSP dal = new WinesFromSP();
     WinesListVM vm = new WinesListVM(dal);
     dal.RequestFromServer();
  
     this.RootVisual = new WinesListView() { DataContext = vm };
 }

Reprenons le code de Application_Startup ligne par ligne. C’est dans cette méthode que l’on va lier les différentes couches de notre architecture:

    La première ligne permet d’instancier la classe qui communique avec SP côté serveur

L’instance dal est ensuite passée en paramètre du ViewModel dans la 2ème ligne (relie le ViewModel au Model)
La 3ème ligne permet de déclencher le chargement de la liste des vins.

La 4ème ligne permet d’instancier la View et de binder le DataContext de cette vue au ViewModel. Nous verrons dans l’étape suivante comment le DataContext se comportera dans la vue. (relie la View au ViewModel)

Il est intéressant de connecter nos couches de manière “externe” (dans du code qui ne fait partie d’aucune des couches, comme App.xaml.cs) c’est à dire qu’aucune couche n’instancie explicitement des classes appartenant à d’autres couches. Ainsi, non seulement nous avons l’avantage de centraliser le code de rattachement entre les couches, mais les couches sont découplées au maximum (on pourrait aller plus loin et faire “sauter” ce code de liaison entre les couches en utilisant un mécanisme d’injection de dépendance comme MEF ou Unity…mais c’est un autre sujet). Il existe également des frameworks MVVM qui effectueront ces opérations pour vous : instanciation des ViewModels et liaison entre les couches.

Revenons sur le code de la View:

 <navigation:Page x:Class="Wines.SL.View.WinesListView" 
            xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
            xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" 
            xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
            mc:Ignorable="d"
            xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
            d:DesignWidth="640" d:DesignHeight="480"
            Title="MainView Page">
     <Grid x:Name="LayoutRoot">
         <ListBox ItemsSource="{Binding WineItems}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
         </ListBox>
     </Grid>
 </navigation:Page>

Plus haut, nous avons initialisé la propriété DataContext de la Page avec notre ViewModel par le code:

     this.RootVisual = new MainView() { DataContext = vm };

En Silverlight (et en WPF), les DataContext des contrôles héritent de celui de leur parent. Ainsi, notre Grid et notre ListBox utilisent le même DataContext que celui qui a été affecté à MainView, à savoir “vm”. Lorsque l’on binde une propriété, le binding s’effectue dans le contexte du DataContext. En clair : dans notre cas lorsque l’on binde la propriété “ItemsSource” de la ListBox sur “WineItems”, cela revient à binder “ListBox.ItemsSource” sur “vm.WineItems”. Lorsque l’on binde , la propriété “SelectedItem” de la ListBox sur “SelectedItem” cela revient à binder “ListBox.SelectedItem” sur “vm.SelectedItem”.

Le binding permettra le rafraichissement automatique des propriétés des contrôles, chaque fois que les éléments bindés sont modifiés, et inversement pour les binding 2 ways. Le but de cet article n’étant pas de réexpliquer le fonctionnement du binding et la syntaxe xaml, je vous encourage à suivre les tutoriaux du MSDN ou de voir des Webcasts sur le sujet si cela vous semble flou.

Pour récapituler, voici un schéma simpliste de la communication entre les différents éléments, pour pouvoir afficher la liste des vins:

image 

 

Les différentes couches sont reliées, pourquoi ne pourrait-on faire fonctionner l’application dès maintenant ? Tapez F5 pour lancer l’application et vous obtiendrez…une jolie page blanche. Pas de panique c’est normal…il reste quelques éléments à mettre en place…

Deuxième partie de l’article ici….