2. Sommaire
• Introduction
– Présentation
– MVVM Light Toolkit
– Comparaison rapide entre MVVM, MVC et MVP
• Notes pour cette présentation
• Architecture
• Les ViewModels
– Données
– Commandes
– Modes de binding
• Le ViewModelLocator
• Les Design ViewModels
• La Messagerie
3. Présentation
• MVVM est un patron de conception (design-pattern)
conçu à la base pour les applications .NET
• Model (Modèle) – View (Vue) – ViewModel (Vue-
Modèle)
• Permet une separation entre le traitement des données
et la façon dont elles sont affichées
• Utilisation du principe de “binding” au maximum
4. MVVM Light Toolkit
• Framework libre et gratuit facilitant l’implémentation du pattern
MVVM
• Crée par Laurent Bugnion (GalaSoft)
• Utilisable avec:
• WPF
• Windows 10
• Xamarin
• Silverlight
• Site officiel: http://www.galasoft.ch/mvvm/
• Téléchargeable via NuGet
7. Architecture
• DataService
• Classes d’accès à une ressource externe (base de données,
webservice, flux JSON, flux RSS, etc…)
• Model
• Des classes simples (POCO) représentant les données
• ViewModel
• Des fichiers .cs
• Vue
• Des fichier .xaml et .xaml.cs
8. Architecture
• Une Vue = un ViewModel
• Un ViewModel peut être lié à plusieurs vues (mais pas l’inverse)
• Le ViewModel est l’adaptation du modèle pour la vue. Son rôle est donc
autre que celui du Contrôleur MVC.
• Le ViewModel et la Vue sont liés en utilisant le binding
• Liaison des contrôles d’affichage (ListBox/ComboBox/TextBox…) à des
données
• Liaison des contrôles d’action (Button/Image/Slider…) à des commandes
9. Notes pour cette présentation
• Les exemples utilisés s’appuient sur un projet de
gestion de cave à vins réalisé avec WPF.
• Pour simplifier l’implémentation de MVVM, nous
avons utilisé le framework MVVM Light Toolkit.
10. Exemple de Model
public class Vin
{
public string Nom { get; set; }
public string Appellation { get; set; }
public Region Region { get; set; }
public int Annee { get; set; }
}
La classe la plus simple possible (POCO)
11. Exemple de ViewModel
public interface IMainViewModel
{
public string Titre { get; set; }
public ObservableCollection<Vin> Vins { get; }
public ICommand ChargerVinsCommand { get; }
}
Déclaration dans une interface
Données
Commandes
12. Exemple de ViewModel
public class MainViewModel : ViewModelBase, IMainViewModel
{
public MainViewModel()
{
Vins = new ObservableCollection<Vin>();
ChargerVinsCommand = new RelayCommand(ChargerVins);
}
private string _titre;
public string Titre
{
get { return _titre; }
set
{
_titre = value;
RaisePropertyChanged();
}
}
public ObservableCollection<Vin> Vins { get; private set; }
public ICommand ChargerVinsCommand { get; private set; }
private void ChargerVins()
{
WebserviceClient client = new WebServiceClient();
IList<Vin> vins = client.GetVins();
Vins.Clear();
foreach (var vin in vins)
Vins.Add(vin);
}
}
Implémentation
Données
Commandes
Initialisation
13. Composition du ViewModel
• Le ViewModel contient les données affichées dans la Vue
• Il permet de faire abstraction de la manière dont elles sont présentées
• On va agir directement sur les données et non plus sur les contrôles
graphiques
• Le ViewModel contient les commandes (actions métiers) de
la Vue
• Afficher ou modifier une donnée est une action métier
• Lancer une animation ou masquer un contrôle n’en est pas une
• Chaque classe ViewModel hérite de la classe
ViewModelBase, provenant de MVVM Light Toolkit
14. Focus : Binder une donnée
public class MainViewModel : ViewModelBase, IMainViewModel
{
private string _titre;
public string Titre
{
get { return _titre; }
set
{
_titre = value;
RaisePropertyChanged();
}
}
}
15. Focus : Binder une donnée
• Côté ViewModel:
• Pour une donnée, on crée un attribut privé et une propriété publique
• Dans le setter, on appelle la méthode RaisePropertyChanged. Cette méthode
permet d’avertir la Vue que la donnée a été modifiée
• RaisePropertyChanged est contenu dans la classe ObservableObject dont
ViewModelBase hérite. Cette classe implémente l’interface
INotifyPropertyChanged.
• Côté Vue:
• Veiller à ce que le mode de binding soit approprié pour le rafraîchissement
des données.
16. Focus : Binder une collection de données
public class MainViewModel : ViewModelBase, IMainViewModel
{
public MainViewModel()
{
Vins = new ObservableCollection<Vin>();
}
public ObservableCollection<Vin> Vins { get; private set; }
}
17. Focus : Binder une collection de données
• Côté ViewModel:
• On utilise le type ObservableCollection<T> (et non List<T> ou des T[])
• Cette classe implémente déjà INotifyPropertyChanged, ainsi que d’autres
interfaces pour le rafraîchissement automatique des données.
• Côté Vue:
• Veiller à ce que le mode de binding soit approprié pour le rafraîchissement
des données.
18. Modes de binding
• OneWay
• TwoWay
• OneTime
• OneWayToSource
• Default
La destination est mise à jour lorsque la source est mise à jour, mais pas l’inverse.
La destination est mise à jour lorsque la source est modifiée et vice-versa.
La destination prend en compte un seul changement de la source. Si la source est
de nouveau mise à jour, la destination n’est pas actualisée.
La source est mise à jour lorsque la destination est mise à jour mais pas l’inverse.
Pas toujours disponible en fonction de la technologie (ex: Windows Phone)
Utilise le comportement par défaut pour le contrôle concerné. (ex: OneWay pour
Label et TwoWay pour TextBox)
Source: donnée du ViewModel
Destination: valeur du contrôle graphique de la vue
19. public class MainViewModel : ViewModelBase, IMainViewModel
{
public MainViewModel()
{
ChargerVinsCommand = new RelayCommand(ChargerVins);
}
public ICommand ChargerVinsCommand { get; private set; }
private void ChargerVins()
{
Vins.Clear();
WebServiceClient client = new WebServiceClient();
foreach (Vin vin in client.GetVins())
{
Vins.Add(vin);
}
}
}
Focus : Binder une commande
20. • Côté ViewModel:
• Les actions métiers sont représentées par des méthodes (idéalement privées, mais il
se peut de devoir les rendre publiques pour nos tests unitaires)
• On lie ces méthodes à des commandes (objets de type ICommand)
• Le constructeur de RelayCommand prend 1 ou 2 paramètres
1. Obligatoire : La méthode qui sera appelée lors du déclenchement de la commande
2. Facultatif : La méthode qui détermine si la commande peut être exécutée ou non
• La méthode associée à la commande peut prendre un (et un seul) paramètre. On
utilise pour celà la classe RelayCommand<T>.
• Côté Vue:
• Les contrôles graphiques sont bindés aux commandes à l’aide de l’attribut Command
(et CommandParameter si un paramètre est passé)
Focus : Binder une commande
22. Info sur les commandes
• Tous les contrôles graphiques ne proposent pas d’attribut Command.
• Tous les évènements ne peuvent pas être traduits directement en
commande (ex: SelectionChanged, IsEnabled, etc…).
Heureusement, il existe deux alternatives.
• 1. Utiliser un EventTrigger dans le XAML :
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<command:EventToCommand Command="{Binding ChargerVinsCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
23. Info sur les commandes
private void lstVins_SelectionChanged(object sender, EventArgs e)
{
IMainViewModel vm = (IMainViewModel)DataContext;
vm.ChargerVinsCommand.Execute(null);
}
• Tous les contrôles graphiques ne proposent pas d’attribut Command.
• Tous les évènements ne peuvent pas être traduits directement en
commande (ex: SelectionChanged, IsEnabled, etc…).
Heureusement, il existe deux alternatives.
• 2. Créer un évènement et appeler la commande :
24. En bref sur les ViewModels…
• Le ViewModel est une adaptation du Modèle pour la Vue
• Un ViewModel comporte des données et des commandes
• Un ViewModel hérite de ViewModelBase
• ViewModelBase implémente INotifyPropertyChanged pour notifier la Vue
qu’une donnée a été modifiée
• Pour une donnée simple, on appelle RaisePropertyChanged
• Pour les collections, on utilise les ObservableCollection<T>
• Une commande peut prendre un (et un seul) paramètre.
• Si on veut faire des tests unitaires, ceux-ci peuvent être faits sur la
couche ViewModel.
25. ViewModelLocator
• Cette classe recense les ViewModels de notre
application
• Elle permet d’ailleurs un accès facile à nos ViewModels
• Elle permet de lier la couche Vue et ViewModel
• , et permet d’y accéder facilement.
• Elle recense également tous les Design ViewModels qui
seront utilisés par Visual Studio et Blend.
• Elle comporte également des méthodes pour décharger
les ViewModels de la mémoire vive lorsque ceux-ci ne
sont plus utilisés.
26. ViewModelLocator
public class ViewModelLocator
{
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<IMainViewModel, MainViewModel>();
}
public static IMainViewModel MainVM
{
get { return ServiceLocator.Current.GetInstance<IMainViewModel>(); }
}
public static void CleanMain()
{
SimpleIoc.Default.Unregister<IMainViewModel>();
SimpleIoc.Default.Register<IMainViewModel, MainViewModel>();
}
public static void Cleanup()
{
CleanMain();
}
}
27. ViewModelLocator
• On initialise le ServiceLocator dans le constructeur statique
• On référence ensuite les ViewModels de notre application à l’aide de la
classe SimpleIoC (fournie par MVVM Light Toolkit).
public class ViewModelLocator
{
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<IMainViewModel, MainViewModel>();
}
}
28. public class ViewModelLocator
{
public static IMainViewModel MainVM
{
get
{
return ServiceLocator.Current.GetInstance<IMainViewModel>();
}
}
}
ViewModelLocator
On déclare ensuite une propriété statique pour chaque ViewModel afin d’y
avoir un accès rapide depuis n’importe quelle autre classe, et notamment la
couche Vue.
Le ServiceLocator utilise des singletons. Si l’instance existe, elle est retournée
sinon elle est créée puis retournée.
Une exception est levée si le ViewModel n’a pas été enregistré au préalable
29. public class ViewModelLocator
{
public static void CleanMain()
{
SimpleIoc.Default.Unregister<IMainViewModel>();
SimpleIoc.Default.Register<IMainViewModel, MainViewModel>();
}
}
ViewModelLocator
Lorsqu’un ViewModel n’est plus nécessaire, il faut le retirer de la
mémoire.
On crée une méthode de nettoyage, qui va désallouer le ViewModel de
la mémoire, puis le re-préparer en cas de besoin. Il ne sera réinstancié
que si la propriété MainVM est appelée de nouveau.
30. public class ViewModelLocator
{
public static void Cleanup()
{
CleanMain();
}
}
ViewModelLocator
Enfin, on peut créer une méthode Cleanup qui appelle
toutes les autres méthodes Clean.
Elle peut être appelé lorsque on a besoin de réinitialiser
l’application par exemple.
31. Liaison couches Vue et ViewModel
• Pour lier les 2 couches, on utilise le ViewModelLocator. On le référence dans le
fichier App.xaml
• On déclare une ressource de type ViewModelLocator (nommée ici Locator)
• Exemple d’App.xaml dans un projet WPF:
<Application x:Class="CavesAVins.View.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModel="clr-namespace:CaveAVins.ViewModel;assembly=CaveAVins.ViewModel"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<viewModel:ViewModelLocator x:Key="Locator" />
</ResourceDictionary>
</Application.Resources>
</Application>
32. Liaison couches Vue et ViewModel
• Une fois la declaration du Locator faite, on peut aller chercher le
ViewModel de notre page.
• On lie le ViewModel à la propriété DataContext de la Vue
<Page x:Class="CaveAVins.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="1024" Width="1280"
Title="Accueil"
DataContext="{Binding MainVM, Source={StaticResource Locator}}">
33. Les Design ViewModels
• Ces classes contiennent des jeux de données fictives.
• Ces données fictives sont affichées directement dans Visual Studio et
Blend.
• Une classe Design ViewModel hérite d’un ViewModel. Pour chaque
ViewModel, on peut donc créer un Design ViewModel.
• Ainsi, il est possible de créer le rendu (les templates) de son application
avec des données fictives sans devoir lancer/debugger l’application.
34. Les Design ViewModels
On peut référencer nos Design ViewModels dans le ViewModelLocator
à l’aide de la propriété IsInDesignModeStatic de ViewModelBase.
Cette propriété est disponible partout et tout le temps.
public class ViewModelLocator
{
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
if (ViewModelBase.IsInDesignModeStatic)
{
// Mode Design pour Visual Studio en Blend
SimpleIoc.Default.Register<IMainViewModel, MainDesignViewModel>();
}
else
{
// Mode Debug / Run
SimpleIoc.Default.Register<IMainViewModel, MainViewModel>();
}
}
}
35. Les Design ViewModels
• Exemple
public class MainDesignViewModel : MainViewModel
{
public MainDesignViewModel()
{
Vins = new ObservableCollection
{
new Vin { Nom = “La Tour Carnet”,
Appellation = “Haut-Médoc”,
Region = Region.Bordeaux },
new Vin { Nom = “Merlot”,
Annee = 2011,
Region = Region.Rhone },
new Vin { Nom = “Cuvée-Silex”,
Appellation = “Pouilly-Fumé”,
Region = Region.Loire },
}
}
}
36. Messagerie
• La messagerie est fournie par MVVM Light Toolkit
• Elle permet d’envoyer des messages entre les classes
• Un message est un objet de n’importe quel type
• Elle permet, par exemple, d’envoyer des messages entre:
• Un ViewModel et une Vue
• Deux ViewModels
• Deux classes quelconques
• Ce mécanisme est une solution de contournement lorsqu’aucune autre
ne permet de transmettre un objet d’une classe à un autre
37. Messagerie
public class ExempleViewModel : ViewModelBase, IExempleViewModel
public ExempleViewModel()
{
EnvoyerPagesCommand = new RelayCommand<string>(EnvoyerMessage);
}
public Icommand ChangerPagesCommand { get; private set; }
public void EnvoyerMessage(string message)
{
MessengerInstance.Send(message);
}
}
• Côté ViewModel: Envoi du message
38. Messagerie
public class MainWindow
public MainWindow()
{
InitializeComponent();
Messenger.Default.Register<string>(this, ChangerPage);
}
private void AfficherMessage(string message)
{
Messenger.Show(message);
}
}
• Côté Vue: Abonnement à réception du message
39. Messagerie
• Pour qu’une classe reçoive un message, on utilise la méthode Register
• On utilise la méthode Unregister pour la désabonner de la messagerie
• N’importe quelle classe peut envoyer des messages, tout comme
n’importe quelle classe peut en recevoir
• Vous ne pouvez envoyer qu’un seul message à la fois, mais un message
peut être un objet de n’importe quel type
• Contrôlez-bien si votre message doit être reçu par une ou plusieurs
classes simultanément en utilisant :
• Correctement les méthodes Register et Unregister.
• Un token pour créer des canaux réservés et différencier les récepteurs
40. En résumé…
• MVVM = Model – View – ViewModel
• MVVM a été conçu pour faciliter la séparation entre la logique et l’affichage
• MVVM utilise la puissance du mécanisme de binding (OneWay/TwoWay)
• MVVM Light Toolkit facilite l’implémentation de MVVM dans une application
• Un ViewModel comporte des données et des commandes
• MVVM facilite les tests unitaires, puisque c’est la couche VM qui peut être testée
• Le ViewModelLocator permet l’accès facile aux ViewModels et gère leur cycle de vie
• Les Design ViewModels contiennent des données fictives qui sont directement
affichées dans Visual Studio et Blend
• La messagerie permet de transmettre des objets entre classes