MVVM

De Banane Atomic
Aller à la navigationAller à la recherche

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.

Mvvm.svg

Framework

Design Time Data

Xaml.svg
<!-- 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.

DesignInstance

Specify the datacontext to use during design time.

Xaml.svg
<Window xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:MyNamespace"
        d:DataContext="{d:DesignInstance local:MainWindowViewModel}">

ViewModelBase

Csharp.svg
/// <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;
}

RelayCommand

ViewModel first

En fonction du type de vue-modèle, le DataTemplate permet de choisir la vue.

Xaml.svg
<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>
Csharp.svg
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