Introduction
Le MVVM est un pattern utilisé pour la création d’application WPF. Cette architecture désigne les trois couches Model, View et ViewModel. Ce pattern est largement inspiré du MVC que l’on retrouve dans le monde du web. Il permet de mieux séparer le code selon trois fonctionnements distincts :
- La couche Model contient toutes les entités utilisées dans votre application, aucun traitement n’est réalisé ici,
- La couche View contient les vues de notre application (elle ne réalise pas de traitement sur les données), c’est ici que l’on retrouve l‘affichage et la mise en forme des données,
- La couche ViewModel permet de faire l’interfaçage entre les vues et les modèles, elle permet de mettre en forme les données ou de réaliser des traitements ou des vérifications particulières.
Cette méthode facilite les tests unitaires (notamment sur les ViewModel) et permet aussi de limiter les liens forts entre les Views, les Models et les ViewModels.
Avec cet article, je vais vous proposer une implémentation de base d’un ViewModel, implémentant les principales fonctionnalités d’un ViewModel.
INotifyPropertyChanged
Un ViewModel étant destiné à être connecté à une vue, ce dernier doit impérativement implémenter l’interface INotifyPropertyChanged, afin de permettre la mise à jour des données du ViewModel vers la vue. En effet, cette interface oblige d’implémenter un évènement (PropertyChanged) qui doit être lancé à chaque modification de la valeur d’une propriété. La vue s’abonne à cette évènement pour pouvoir se mettre à jour.
Cette implémentation doit être présente dans tout vos ViewModel. Voila le détail de celle ci :
public interface INotifyPropertyChanged
{
// Summary:
// Occurs when a property value changes.
event PropertyChangedEventHandler PropertyChanged;
}
Implémentation
Passons à l’implémentation de cette interface. En plus de l’évènement, je vais créer une méthode qui facilite l’utilisation de cet évènement :
public void Notify([CallerMemberName]string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
Cette méthode prend en paramètre le nom de la propriété qui viens d’être mise à jour. L’utilisation de CallerMemberName (apparue dans la version 4.5 du Framework .NET) raccourcie encore l’appel de cette méthode. Cela permet de récupérer le nom de la propriété ou méthode appelante.
Set ()
J’implémente aussi une méthode Set comme suit :
protected bool Set<TProperty>(ref TProperty attrib, TProperty value, [CallerMemberName]string propertyName = null)
{
if (!object.Equals(attrib, value))
{
attrib = value;
Notify(propertyName);
return true;
}
return false;
}
Cette méthode permet de centraliser les actions à mener lors d’un changement de valeur mais aussi de simplifier le développement d’accesseur de propriété. Elle prend trois paramètres :
- le premier est l’objet passé par référence pour pouvoir effectuer l’affectation
- le second est la nouvelle valeur de la propriété
- le troisième est le nom de la propriété résolu avec CallerMemberName
Dorénavant, on peut définir une propriété comme cela :
private string _title;
public string Title
{
get { return _title; }
set { Set(ref _title, value); }
}
OnPropertyChanging & OnPropertyChanged
Maintenant que nous avons une méthode exécutée à chaque changement de valeur d’une propriété, nous pouvons aisément créer une méthode que l’on appel avant et après chaque modifications :
protected virtual void OnPropertyChanged(string propertyName) { }
protected virtual void OnPropertyChanging(string propertyName, object oldValue, object newValue) { }
Ces deux méthodes sont vides dans l’implémentation du ViewModel de base. Elle sont virtual, ce qui permet de les ré-implémenter dans un ViewModel enfant.
Enfin, il faut aussi modifier la méthode Set pour qu’elle appelle ces deux méthodes :
protected bool Set<TProperty>(ref TProperty attrib, TProperty value, [CallerMemberName]string propertyName = null)
{
if (!object.Equals(attrib, value))
{
OnPropertyChanging(propertyName, attrib, value);
attrib = value;
Notify(propertyName);
OnPropertyChanged(propertyName);
return true;
}
return false;
}
ICommand
Les commandes sont une partie très importante du modèle MVVM. Elles permettent le déclenchement d’action depuis la vue. Pour cela, une interface existe dans le Framework .NET : ICommand. Cette interface expose deux méthodes :
bool CanExecute(object parameter);
void Execute(object parameter);
La méthode Execute est celle qui est appelée lorsque la commande est exécutée. La méthode CanExecute permet d’activer ou de désactiver l’utilisation de la commande. Dans de très nombreux cas, les commandes sont utilisées sur des boutons. La classe Button expose des propriétés de dépendance :
- Command
- CommandParameter
L’appel de CanExecute va directement mettre à jour le booléen IsEnabled du bouton.
L’interface ICommand expose aussi un évènement qui permet de rafraîchir l’état de la commande. Quand cette évènement est déclenché, la vue (qui est abonné à cette évènement) va appeler la méthode CanExecute pour mettre à jour l’état de la commande.
Pour un développement plus simple, je vous déconseille de créer une classe pour chaque commande que vous devez implémenter. A la place de cela, je vous conseille de créer une classe de base qui implémente ICommand et dont le constructeur prend en paramètre les méthodes correspondantes aux méthodes CanExecute et Execute.
Voilà une implémentation de base pour vos commandes :
public class CommandBase : ICommand
{
private Action<object> _executeMethod;
private Func<object, bool> _canExecuteMethod;
public object _parameter { get; set; }
public string Libelle { get; set; }
public event EventHandler CanExecuteChanged;
public CommandBase(Action<object> executeMethod)
: this(executeMethod, null, null, null)
{
}
public CommandBase(Action<object> executeMethod, string libelle, object parameter)
: this(executeMethod, null, libelle, parameter)
{
}
public CommandBase(Action<object> executeMethod, Func<object, bool> canExecuteMethod)
: this(executeMethod, canExecuteMethod, null, null)
{
}
public CommandBase(Action<object> executeMethod, string libelle)
: this(executeMethod, null, libelle, null)
{
}
public CommandBase(Action<object> executeMethod, Func<object, bool> canExecuteMethod, string libelle, object parameter)
{
_executeMethod = executeMethod;
_canExecuteMethod = canExecuteMethod;
Libelle = libelle;
_parameter = parameter;
}
public bool CanExecute(object parameter)
{
if (_canExecuteMethod != null)
{
if (parameter != null)
return _canExecuteMethod(parameter);
else
return _canExecuteMethod(_parameter);
}
return true;
}
public void Execute(object parameter)
{
if (parameter != null)
_executeMethod(parameter);
else
_executeMethod(_parameter);
}
public void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
}
C’est terminé pour cette première partie sur les ViewModel de base, nous verrons la suite dans un prochain article.