Permet aux classes de la couche Modèle d'appeler RaisePropertyChanged
classData : ObservableObjet
{
privatestring_myProperty;
publicstring MyProperty
{
get { return_myProperty; }
set {
_myProperty = value;
RaisePropertyChanged("MyProperty"); // utilise un string pour nommer la propriétéRaisePropertyChanged(() => MyProperty); // utilise la réflexion pour nommer la propriété// set value + RaisePropertyChangedSet(() => MyProperty, ref _myProperty, value);
// exécute (set value + RaisePropertyChanged) seulement si (value ≠ _myProperty) et return true
}
}
ViewModelBase
Équivalant d'ObservableObjet pour la couche Vue-Modèle
<Windowxmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"xmlns:mvvm="http://www.galasoft.ch/mvvmlight"><GroupBoxHeader="My Header"><!-- Lance la commande ClickCmd lors du clique sur le Header du GroupBox --><i:Interaction.Triggers><i:EventTriggerEventName="MouseLeftButtonDown"><mvvm:EventToCommandCommand="{Binding ClickCmd}"CommandParameter="..." /></i:EventTrigger></i:Interaction.Triggers></Button>
Passer un argument de l’événement à la commande: IEventArgsConverter
Système de distribution de message (event bus) sans couplage entre le destinataire et l'expéditeur.
un objet envoie un message, sans savoir qui le recevra
un ou des objets s'inscrivent pour recevoir ces messages, sans savoir qui les envoie
Solution de dernier recourt, préférer un IoC container avec un Service.
Ce mécanisme permet:
d'appeler une méthode de la vue depuis le Vue-Modèle.
de communiquer entre Vue-Modèle.
MainWindow.cs
publicMainWindow() // ctor de la vue
{
// s'enregistre pour recevoir tous les messages du type NotificationMessage<MyType>
Messenger.Default.Register<NotificationMessage<MyType>>(this, message =>
{
MyTypemyObject = message.Content;
stringnotification = message.Notification;
objectsender = message.Sender;
objecttarget = message.Target;
});
// s'enregistre pour recevoir tous les messages du type NotificationMessage<MyType> avec un token
Messenger.Default.Register<NotificationMessage<MyType>>(this, token, message => { ... });
// s'enregistre pour recevoir tous les messages du type IMessage et de toutes les classes qui l'implémentent
Messenger.Default.Register<IMessage>(this, true, message => { ... });
/* Messenger utilise des weak references,
ainsi l'objet courant peut quand même être collecté par le GarbageCollector même si Unregister n'a pas été appelé */// désabonne l'objet courant de toutes les tous les types de messages auquel il était abonné
Messenger.Default.Unregister(this);
// désabonne l'objet courant des message de type NotificationMessage<MyType> auquel il était abonné
Messenger.Default.Unregister<NotificationMessage<MyType>>(this);
// désabonne l'objet courant des message de type NotificationMessage<MyType> pour l'action HandleMessage auquel il était abonné
Messenger.Default.Unregister<NotificationMessage<MyType>>(this, HandleMessage);
}
MainViewModel.cs
MyCmd = newRelayCommand(() =>
{
MyTypemyObject;
// créé un message du type NotificationMessage<MyType>varmessage = newNotificationMessage<MyType>(myObject, "text message");
// créé un message du type NotificationMessage<MyType> contenant le sendervarmessage = newNotificationMessage<MyType>(this, myObject, "text message");
// envoie le message
Messenger.Default.Send(message);
// envoie le message avec un token uniquepublicstaticreadonlyGuidtoken = Guid.NewGuid();
Messenger.Default.Send(message, token);
};
Messenger.Reset(); passe l'instance à null, au prochain accès l'instance sera recréé car elle est nulle. À appeler au début de chaque test unitaire.
DispatcherHelper
WPF dispatche automatiquement les événements PropertyChanged vers le thread principal.
Le Dispatcher permet à un sous-thread de modifier des éléments graphiques du thread principal.
il n'est accessible que depuis la couche vue.
accès aux éléments graphiques depuis un sous-thread sans le Dispatcher: InvalidOperationException: The calling thread cannot access this object because a different thread owns it.
// initialiser le DispatcherHelper, si possible au lancement de l'application (ctor static App.xaml.cs)
DispatcherHelper.Initialize();
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
// ce code sera exécuté dans le thread principal
});
ViewModelLocator
Cette classe est à créer.
Permet de cibler un Vue-Modèle depuis un dictionnaire de ressources.
Permet de contrôler plus facilement la création des Vue-Modèles et de leurs Services.
Par exemple, à la création de MainViewModel, on veut fournir un IDataService qui correspond soit:
aux véritables données de l'application: DataService
à des données statiques pour la visualisation dans un Designer: DesignerDataService
Sans SimpleIoc
ViewModelLocator.cs
publicclassViewModelLocator
{
publicstatic ViewModelLocator Instance
{
get
{
return Application.Current.Resources["Locator"] asViewModelLocator;
}
}
publicMainViewModel Main { get; privateset; }
staticViewModelLocator()
{
IDataServicedataService;
// choix du IDataService en fonction du DesignModeif (ViewModelBase.IsInDesignModeStatic)
{
dataService = newDesignDataService();
}
else
{
dataService = newDataService();
}
Main = newMainViewModel(dataService);
}
}
SimpleIoc permet de se passer de ViewModelLocator.
On peut appeler directement SimpleIoc.Default.GetInstance<MainViewModel>(); pour avoir une instance de MainViewModel.
ViewModelLocator reste utile si on veut obtenir MainViewModel depuis du code xaml pour les DesignTime Data.
ViewModelLocator.cs
publicclassViewModelLocator
{
publicstatic ViewModelLocator Instance
{
get
{
return Application.Current.Resources["Locator"] asViewModelLocator;
}
}
public MainViewModel Main
{
get
{
return SimpleIoc.Default.GetInstance<MainViewModel>();
}
}
staticViewModelLocator()
{
// choix du IDataService en fonction du DesignModeif (ViewModelBase.IsInDesignModeStatic)
{
SimpleIoc.Default.Register<IDataService, DesignDataService>();
}
else
{
SimpleIoc.Default.Register<IDataService, DataService>();
}
// MainViewModel a besoin d'un IDataService, il sera fournit par Dependency Composition car IDataService a été enregistré
SimpleIoc.Default.Register<MainViewModel>();
}
// dés-enregistre les classes et interfaces précédemment enregistréspublicstaticvoidCleanup()
{
SimpleIoc.Default.Unregister<IDataService, DesignDataService>();
SimpleIoc.Default.Unregister<IDataService, DataService>();
SimpleIoc.Default.Unregister<MainViewModel>();
}
}
SimpleIoc
Cache global d'objets à injecter.
Register
// enregistrement de la classe MainViewModel dans le conteneur, le constructeur par défaut est utilisé
SimpleIoc.Default.Register<MainViewModel>();
// enregistrement de l'interface IDataService en spécifiant l'implémentation à utiliser (ici le constructeur par défaut de DataService)
SimpleIoc.Default.Register<IDataService, DataService>();
SimpleIoc.Default.Register<IDataService>(() => newDataService(param));
// Factory delegate, exécuté à la demande// permet de passer des paramètres au constructeur
SimpleIoc.Default.Register<MainViewModel>(() => newMainViewModel(param));
// ou d'utiliser un objet existantvarmainViewModel = newMainViewModel();
SimpleIoc.Default.Register<MainViewModel>(() => mainViewModel);
// création immédiate de l'objet et mise en cache
SimpleIoc.Default.Register<MainViewModel>(true);
// utilisation d'une clé, ce qui permet d'avoir plusieurs instance de la même classe
SimpleIoc.Default.Register<MainViewModel>(() => newMainViewModel(), "Key1");
GetInstance
Création de l'instance et mise en cache ou récupération de l'instance dans le cache si elle a déjà été crée.
varinstance = SimpleIoc.Default.GetInstance<MainViewModel>();
varservice = SimpleIoc.Default.GetInstance<IDataService>();
// une exception est générée si la classe ou l'interface n'est pas ou plus enregistrée// avec la clévarspecificInstance = SimpleIoc.Default.GetInstance<MainViewModel>("Key1");
// si aucun clé n'a été utilisé pour Register, GetInstance avec une clé va créer une nouvelle instance pour chaque clé
Unregister
// dés-enregistrement de la classe MainViewModel du conteneur et suppression de toutes les instances du cache
SimpleIoc.Default.Unregister<MainViewModel>();
// suppression de l'instance du cache mais MainViewModel n'est pas dés-enregistré// si l'instance n'était pas enregistré il ne se passe rien
SimpleIoc.Default.Unregister<MainViewModel>(instance);
// suppression de l'instance associé à la clé du cache mais MainViewModel n'est pas dés-enregistré// si l'instance associé à la clé n'était pas enregistré il ne se passe rien
SimpleIoc.Default.Unregister<MainViewModel>("Key1");
Exemple
publicclassMainViewModel
{
// Property Injection, utile si IDialogService peut être amener à changerpublic IDialogService DialogService
{
get
{
return SimpleIoc.Default.GetInstance<IDialogService>();
}
}
// Constructor InjectionprivatereadonlyIDataService_dataService;
publicMainViewModel(IDataServicedataService)
{
_dataService = dataService;
}
Utility Methods
// vrai si IDialogService a été enregistrébooltest = SimpleIoc.Default.IsRegistered<IDialogService>();
// vrai si le cache contient une instance de IDialogServicebooltest = SimpleIoc.Default.ContainsCreated<IDialogService>();
// retourne toutes les instances de IDialogService qui se trouvent dans le cacheIEnumerable<IDialogService> allInstances = SimpleIoc.Default.GetAllCreatedInstances<IDialogService>();
// force la création des toutes les instances par défaut qui ont enregistrées, puis retourne ces instances IEnumerable<IDialogService> allInstances = SimpleIoc.Default.GetAllInstances<IDialogService>();
ServiceLocator
Modèle d'abstraction permettant de changer facilement d'IoC container.
// Définir SimpleIoc comme IoC container à utiliser
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
// utiliser l'IoC container courant pour récupérer une instance
ServiceLocator.Current.GetInstance<MainViewModel>();
[TestClass]
publicclassUnitTest
{
[TestMethod]
publicvoidTestMethod()
{
// reset le système le Message pour éviter les interactions entre méthodes de test
Messenger.Reset();
// création d'un IDataService pour nos testsvartestDataService = newTestDataService();
varmainVM = newMainViewModel(testDataService);
mainVM.MyCommand.Execute(null);
Assert.IsTrue(mainVM.MyProperty == "value");