Introduction à MVVM

Dans cet article, je vais présenter MVVM avec WPF (la problématique est identique avec Silverlight).
Je présenterai dans un premier temps le databinding, puis pour continuer une implémentation d’ICommand et enfin pour finir un exemple avec une propriété de visibilité dans le VueModel.

Introduction

MVVM est pattern intéressant à mettre en place dans les projets pour plusieurs raisons :

  • Le faible couplage entre la Vue et la VueModele permet de pouvoir modifier facilement la vue sans avoir d’impact sur la VueModele (et vice versa).
  • Il permet de tester de manière séparée les différents éléments de la solution.
  • Il permet une maintenance facilitée des projets
  • Le même code dans les VueModel et Modele peut être facilement réutilisé dans d’autres projets tout en utilisant des vues différentes.

Pour permettre de se faire une meilleure idée de l’organisation du pattern MVVM, je met un schéma trouvé sur la MSDN qui explique de manière simple les échanges entre les différents éléments.

source MSDN

La solution

La solution sera séparée en plusieurs fichiers, voici un exemple d’une possibilité de solution MVVM

Model

Le Model représente les données métier, il possède des champs et des propriétés. Il ne contiendra normalement pas de définition de fonction.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using ExempleMVVM.Outils;

namespace ExempleMVVM.Model
{
    public class Modele : NotifyPropertyChanged
    {
        private string _string1;
        private string _string2;
        private int _voteValue;

        public string String1
        {
            get { return _string1; }
            set
            {
                _string1 = value;
                OnPropertyChanged("String1");
            }
        }

        public string String2
        {
            get { return _string2; }
            set
            {
                _string2 = value;
                OnPropertyChanged("String2");
            }
        }

        public int VoteValue
        {
            get { return _voteValue; }
            set { _voteValue = value; OnPropertyChanged("VoteValue"); }
        }
    }
}

La Vue Model

C’est ici qu’il y aura un traitement sur les données, une mise en forme. Ce module fait le lien entre la Vue et le Model. C’est dans cette partie que les commandes provenant de la Vue seront traitées.

Pour le moment la VueModel qui est en place, ne fait pas grand-chose, mais au fur et à mesure de l’article elle deviendra plus complexe.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ExempleMVVM.Model;

namespace ExempleMVVM.VueModel
{
    public class VueModele : NotifyPropertyChanged
    {
        public Model.Modele _modele { get; set; }

        public VueModele()
        {
            _modele= new Modele();
        }
    }
}

La Vue

La Vue correspond quand à elle à la représentation graphique du Model. L’objectif de MVVM étant entre autre de décorréler la représentation graphique du modèle de donnée, il devient facile de pouvoir adapter rapidement n’importe quelle vue sur n’importe quel Modele.

Dans notre cas, la vue ne contient pas de CodeBehind, néanmoins il n’est pas incohérent de mettre du code dans le xaml.cs. Du code qui fera des modifications sur l’interface graphique sans avoir de lien avec le modèle pourra par exemple trouver sa place ici (une horloge, un label qui clignote, etc)

<Window x:Class="ExempleMVVM.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525"
        xmlns:VueModel="clr-namespace:ExempleMVVM.VueModel">
    <Window.Resources>
        <VueModel:VueModele x:Key="LeModele"/>        
    </Window.Resources>
    <Grid DataContext="{StaticResource ResourceKey=LeModele}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <TextBox Grid.Column="0"
                 Text="{Binding Path=_modele.String1}"/>
        <TextBox Grid.Column="1"
                 Text="{Binding Path=_modele.String2}"/>
    </Grid>
</Window>

Le databinding

Le databinding est le mécanisme qui permet de rafraichir la vue sans avoir à ajouter de code pour rafraichir l’IHM.
En effet, la manière « simple » consiste à setter la valeur d’un élément graphique chaque fois que la donnée qu’il représente évolue.
Avec le databinding, ce mécanisme de rafraichissement est automatique.
Sa mise en place ne requiert seulement qu’une implémentation de INotifyPropertyChanged. Une fois cette interface implémenter, on notifiera l’IHM du changement de la valeur via un event dans le setter de la propriété modifiée.

Implémentation de l’interface INotifyPropertyChanged

Pour éviter de réécrire dans chaque classe qui en aurait besoin l’implémentation de cette interface, je l’ai écrite dans une classe à part qui sera réutilisée à volonté.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Reflection;

namespace ExempleMVVM.Outils
{
    public class NotifyPropertyChanged :INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string PropertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
        }
    }
}

Chaque propriété qui devra avoir une représentation graphique devra lever un event à chaque fois qu’elle sera modifiée (en général l’event sera levé sur le setter de la propriété).

public string String1
        {
            get { return _string1; }
            set
            {
                _string1 = value;
                OnPropertyChanged("String1");
            }
        }

Mot clef dans le xaml

Au niveau de la vue pour permettre la mise en place du databinding, il y a des choses à mettre en place

  • Ajouter une référence de la VueModel
  • Définir un dictionnaire de ressource
  • Définir le datacontext

Référence de la VueModel

xmlns:VueModel="clr-namespace:ExempleMVVM.VueModel"

Dictionnaire de ressource

<Window.Resources>
        <VueModel:VueModele x:Key="LeModele"/>        
    </Window.Resources>

Définition du DataContext

<Grid DataContext="{StaticResource ResourceKey=LeModele}">

En faisant de la sorte, il n’y a pas de définition de la VueModel au niveau du CodeBehind.

Les Commandes

Les commandes permettent d’exécuter des actions lancées depuis la Vue dans la VueModel. Pour cela il faut implémenter l’interface ICommand.

Implémentation de ICommand

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;

namespace ExempleMVVM.Outils
{
    class Command : ICommand
    {
        public Command(Action<object> execute) : this(execute, null) { }

        public Command(Action<object> execute, Predicate<object> canExecute)
        {
            if (execute == null)
                throw new ArgumentNullException("execute");
            _execute = execute;
            _canExecute = canExecute;
        }
        public bool CanExecute(object parameter)
        {
            return _canExecute == null ? true : _canExecute(parameter);
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public void Execute(object parameter)
        {
            _execute(parameter);
        }

        private readonly Action<object> _execute;
        private readonly Predicate<object> _canExecute;
    }
}

Comme pour INotifyPropertyChanged, je n’implémente qu’une seule fois ICommand puis j’utilise ainsi la classe que j’ai créée.

Utilisation

On va dans la VueModel définir une propriété de type ICommand. Dans le constructeur on la typera comme « Command » (voir le nom de la classse au dessus).

On créera 2 fonctions, une de test d’exécution et une d’exécution.

La VueModel aura cette apparence:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
using ExempleMVVM.Model;
using ExempleMVVM.Outils;

namespace ExempleMVVM.VueModel
{
    public class VueModele 
    {
        public Model.Modele _modele { get; set; }
        public ICommand _myCommand { get; set; }

        public VueModele()
        {
            _modele= new Modele();
            _myCommand= new Command(o=>ActionExecute(), o => CanExecuteAction());
        }

        public void ActionExecute()
        {
            System.Windows.MessageBox.Show(_modele.String1 + "    " + _modele.String2);
        }

        public bool CanExecuteAction()
        {
            if (_modele.String1.Length != _modele.String2.Length)
                return true;
            else
            {
                return false;
            }
        }
    }
}

Il n’y a rien d’autre à faire sur la VueModel, maintenant il faut mettre en place un bouton côté Vue qui appellera la Command:

<Window x:Class="ExempleMVVM.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525"
        xmlns:VueModel="clr-namespace:ExempleMVVM.VueModel">
    <Window.Resources>
        <VueModel:VueModele x:Key="LeModele"/>        
    </Window.Resources>
    <Grid DataContext="{StaticResource ResourceKey=LeModele}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <TextBox Grid.Column="0"
                 Text="{Binding Path=_modele.String1}"/>
        <TextBox Grid.Column="1"
                 Text="{Binding Path=_modele.String2}"/>
        <Button Grid.Column="2"
                Command="{Binding _myCommand}"/>
    </Grid>
</Window>

Maintenant lors de chaque clic sur le bouton, la commande sera appelée et un test sera fait sur la véracité du lancement de la commande.

Les propriétés de visibilité

IsVisible étant une propriété qui raise OnNotifyPropertychanged.

Pour mon exemple, je souhaite que la visibilité de la propriété « String2 » dépende de la taille de la propriété « String1 ».

Comme on ne peut pas revenir faire ce genre de traitement dans le model, on va au niveau de la VueModel écouter l’évènement PropertyChanged. On le fera de la sorte :

public VueModele()
        {
            _modele= new Modele();
            _myCommand= new Command(o=>ActionExecute(), o => CanExecuteAction());
            _modele.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(_modele_PropertyChanged);
        }

        void _modele_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if(e.PropertyName=="String1")
                SetVisibilty();
        }

On est de cette façon notifié par le modèle des modifications sur ses propriétés.

De cette manière, on ne « casse » pas le pattern et on peut utiliser le même modèle avec différentes VueModèle sans difficultés.

En conclusion

Avec cet exemple, on a introduit un certain nombre d’outils et de pratiques qui permettent de mettre en place le Pattern MVVM. J’espère que cet article aura été utile et clair.

Répondre

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion /  Changer )

Photo Google

Vous commentez à l'aide de votre compte Google. Déconnexion /  Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion /  Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion /  Changer )

Connexion à %s