Définition
Séparation de la Vue et du Modèle afin d'améliorer:
- la maintenabilité: tout est rangé au bon endroit, on sait où trouver ce que l'on cherche
- les tests: simuler l'utilisation de l'application en manipulant le Vue-Modèle (Unit Tests + Mock)
- l'extension du code: le code est cloisonné, on peut ajouter ou remplacer des éléments (Design Time Data pour le Designer, Plugins)
Adaptation du modèle MVC à WPF.
Model |
les données clientes.
- Notification de changement: INotifyPropertyChanged.PropertyChanged
- Validation: INotifyDataErrorInfo / IDataErrorInfo
|
View |
les éléments visuels.
- le code-behind ne doit contenir que les traitements entre éléments visuels.
|
ViewModel |
le code logique de la vue (vérification de la saisie)
- adapte les données de la couche Model pour la couche View.
|
- échanges bi-directionnels entre View et ViewModel grâce au DataBinding.
- View n'a pas accès à Model.
- la View utilise des commandes appeler des méthodes du ViewModel.
Framework
|
<!-- Définition d'un DataContext pour le design time et d'un autre pour le run time -->
<UserControl xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DataContext="{Binding ...}"
DataContext="{Binding ...}">
<UserControl.Resources>
<CollectionViewSource x:Key="MyCollection" Source="{Binding ...}" d:Source="{Binding ...}" />
</UserControl.Resources>
<ItemsControl ItemsSource="{Binding Source={StaticResource MyCollection}}" />
|
|
Le Designer n’exécute pas le code-behind: le DataContext doit être définit dans le XAML. |
Specify the datacontext to use during design time.
|
<Window xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MyNamespace"
d:DataContext="{d:DesignInstance local:MainWindowViewModel}">
|
ViewModelBase
|
/// <summary>
/// A method to wrap the INotifyPropertyChanged interface.
/// </summary>
public interface IViewModel : INotifyPropertyChanged
{
/// <summary>
/// A method to wrap the call of the INotifyPropertyChanged.PropertyChanged event.
/// </summary>
/// <param name="propertyName">The name of the property which changed.</param>
void OnPropertyChanged(string propertyName);
}
/// <summary>
/// An implementation of a basis ViewModel with a useful method OnPropertyChanged
/// to warn that a property has changed.
/// </summary>
public class ViewModelBase : IViewModel
{
/// <summary>
/// A method to wrap the call of the PropertyChanged event.
/// </summary>
/// <param name="propertyName">The name of the property which changed.</param>
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
/// <summary>
/// Se produit lorsqu'une valeur de propriété est modifiée.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
}
|
ViewModel first
En fonction du type de vue-modèle, le DataTemplate permet de choisir la vue.
|
<Window.Resources>
<DataTemplate DataType="{x:Type ViewModel1}">
<View1 />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModel2}">
<View2 />
</DataTemplate>
</Window.Resources>
<!-- en fonction du type passé à CurrentViewModel (ViewModel1 ou ViewModel2)
un DataTemplate (une vue) sera appliqué aux données de CurrentViewModel (respectivement View1 ou View2) -->
<ContentControl Content="{Binding CurrentViewModel}"></ContentControl>
|
|
public class MainWindowViewModel
{
public object CurrentViewModel { get; set; }
}
|
Refactoriser du code pour MVVM
- Rendre le Model Observable (INotifyPropertyChanged)
- Créer un ViewModel
- EventHandler → Command ou Behavior
- Injecter le DataService
- Injecter les ViewServices (DisplayMessage, Navigation)
- Binder View et ViewModel
- Ajouter des Design Time Data
- DesignDataService : IDataService
- Créer des Unit Tests: simuler (Mock) les services
- TestDataService : IDataService
- ViewService : IViewService