Concevoir une application Windows WPF Multi-couche basée sur le MVVM Partie 1

windows

Dans ce post je vais vous présenter une application Windows  qui affiche un tableau de taux de change de plusieurs devises par rapport à une devise de base sélectionnée

currency1

Pour réaliser cette application j’utilise plusieurs couches séparées:

  • Couche modèle de données : ici je choisi d’utiliser : Entity FrameWork  pour gérer cette couche
  • Couche service : j’utilise WCF (Windows Communication Fondation)
  • Couche View : j’utilise  WPF (Windows Presentation Fondation)  et le design pattern MVVM

Cette organisation me permet de garantir une séparation entre les couches, et rend l’application facilement maintenable et réutilisable.

La structure finale de l’application sera comme suit :

currency2

 

Dans la  première partie je m’intéresse à la couche d’accès aux données et la couche service :

Pour cela j’utilise Entity FrameWork  pour l’accès aux données, et WCF comme couche de service pour lier la couche accès aux données  et l’application.

1.      Outils :

Pour réaliser cette application j’ai besoin des outils suivants :

-          Visual studio 2013

-          SQL SERVER 2012

2.      Couche DAO : EntityFrameWork

Je vais partir d’un projet blanc. Puis j’ajoute une librairie de classe qui  représentera la couche métier  et dans laquelle je définis le modèle de données.

currency3

A ce projet j’ajoute le modèle de données CurrencyModel qui se base sur la base de données Sql server CurrencyBase, j’utilise Entity FrameWork 6.0

currency4

Notre modèle vient d’être crée, la classe qui représente la table Currency est la suivante :

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated from a template.
//
//     Manual changes to this file may cause unexpected behavior in your application.
//     Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace Currency.Data
{
    using System;
    using System.Collections.Generic;

    public partial class Currency
    {
        public Currency()
        {
            this.Currency1 = new HashSet<Currency>();
        }

        public int Id { get; set; }
        public string CodeCurrency { get; set; }
        public string LibelleCurrency { get; set; }
        public Nullable<decimal> RateChange { get; set; }
        public int BaseCuurencyId { get; set; }
        public System.DateTime DateChange { get; set; }

        public virtual ICollection<Currency> Currency1 { get; set; }
        public virtual Currency Currency2 { get; set; }
    }
}

3.      Utilisation du WCF

Rappel WCF :

Windows Communication Foundation (WCF) est une infrastructure permettant de générer des applications orientées services. C’est une des nouveautés majeure de la version 3.0 du Framework .Net. Il fournit un modèle de programmation unifié pour construire des applications distribuées.

WCF va nous permettre de faire communiquer des composants applicatifs se trouvant sur une même machine, mais le plus souvent sur différentes machines reliées en réseau.

Vous devez penser que cela n'a rien de révolutionnaire et que cela existe depuis très longtemps déjà, et vous avez raison (Remoting, Web service). Ce qui change ici, c'est qu'avec WCF nous pouvons faire communiquer les applications que nous créons avec toutes ces technologies, sans pour autant devoir adapter nos développements de manière spécifique avec chacune. WCF va savoir interagir avec différentes technologies, le code n'aura pas à être adapté et modifié en fonction du moyen de communication que vous choisirez.

currency5

Afin que les applications puissent communiquer, il faut qu'un certain nombre de contraintes soient respectées :

-          définir les méthodes exposées par le serveur

-          définir les types de données transmissibles entre applications

-          définir l'ABC de la communication

Je commence par ajouter un projet de type WCF à la solution, qui jouera le rôle d’intermédiaire entre la partie data et le reste de l’application. Pour cela je dois ajouter la référence de Currency.Data à la liste des références du projet Currency.DataService .

currency6

Je supprime les fichiers services générés par défaut par Visual studio, puis je crée un nouveau service que j'appel CurrencyService.

Il faut aussi modifier le fichier web.config pour y ajouter  connectionString en dessous de la branche Configuration ,  la ConnectionString peut être copiée du fichier App.config du projet CurrencyData.

Voici à quoi ressemble notre classe CurrencyService :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using CurrencyData;
using System.Data.Entity;
namespace CurrencyDataService
{
     [ServiceContract]
    public class CurrencyService 
    {
         [OperationContract]

         public IEnumerable<Currency> GetCurrencys()
         {
             using (var context = new CurrencyEntities())
             {
                 var result = context.Currencies.ToList();
                 return result;
             }
         }

         [OperationContract]

         public IEnumerable<Currency> GetCurrencysFromBase(int baseCur)
         {
             using (var context = new CurrencyEntities())
             {
                 var result = context.Currencies.ToList();
                 if (baseCur!=0)
                   result = context.Currencies.Where(t=>t.BaseCuurencyId==baseCur).ToList();

                 return result;
             }
         }

          [OperationContract]
         public void SaveCurrency(Currency currencyToSave)
         {
using (var context = new CurrencyEntities())
            			 {
                 		   var cur = context.Currencies.Find(currencyToSave.Id);
                              context.Entry(cur).CurrentValues.SetValues(currencyToSave);
                              context.SaveChanges();
             }         }   

    }
   }

Dans cette classe service j’ai défini trois opérations :

  • GetCurrency qui récupère toutes les lignes de la table Currency et les convertit en IEnumerable.
  • GetCurrencyFromBase récupère toutes les lignes correspondantes à la devise de base  fournis en paramètre.
  • SaveCurrency  sauvegarde les modifications dans la base

Tous ces services seront consommés par la couche Vue qui sera totalement indépendante des couches inférieures. Cela signifie que si l'on change quelque choses  dans ces deux couches ça ne va pas impacter la couche vue.

Dans ce qui suit, je vais mettre l’accent sur la couche présentation (ou view) et sur le design pattern MVVM.

4- Couche Présentation : WPF et MVVM

Avant d’aller plus loin, définissons d’abord les notions WPF MVVM.

a.       WPF

Windows Presentation Foundation (WPF) est un système de présentation nouvelle génération qui génère des applications clientes Windows avec des expériences utilisateur visuellement surprenantes.

Le cœur de WPF est un moteur de rendu vectoriel et indépendant de toute résolution, créé pour tirer parti du matériel graphique moderne. WPF étend le cœur avec un jeu complet de fonctionnalités de développement d'applications qui incluent XAML (eXtensible Application Markup Language), des contrôles, la liaison de données et la disposition ainsi que des graphiques 2D et 3D, l'animation, les styles, les modèles, les documents, les médias, le texte et la typographie. WPF est inclus dans le Microsoft .NET Framework, vous pouvez donc générer des applications intégrant d'autres éléments de la bibliothèque de classes .NET Framework.

b.      MVVM

MVVM est un design pattern adapté pour le développement d'applications basées sur les technologies WPF. Il est 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 mets un schéma trouvé sur la MSDN qui explique de manière simple les échanges entre les différents éléments.

currency8

 

L’idée à retenir avec MVVM est simple :

-          la vue ne doit jamais traiter de données. Elle s’occupe uniquement de les afficher. Le View-Model aura en charge les conversions et les accès au modèle de données.

-          Le code behind de la vue ne doit pas contenir les informations de liaison  des données et les évènements des boutons comme se fait avec le Winform.

Je commence d’abord par ajouter un nouveau projet de type WPF, dans lequel  je crée un dossier ViewModel qui contiendra notre viewModel classe GestionnaireViewModel.cs.

Cette classe jouera le rôle d’intermédiaire entre la vue et le model, pour cela il doit implémenter l’interface INotifyPropertyChanged .

namespace System.ComponentModel
{
    // Summary:
    //     Notifies clients that a property value has changed.
    public interface INotifyPropertyChanged
    {
        // Summary:
        //     Occurs when a property value changes.
        event PropertyChangedEventHandler PropertyChanged;
    }
}

Cette interface informe le view model de tous les changements effectués sur les propriétés de la vue.

Il faut aussi ajouter la référence du service du projet WCF à notre projet courant pour qu’il puisse utiliser ces trois services.

Voici le code de notre classe GestionnaireViewModel

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CurrencyUI.CurrencyService;
using System.ComponentModel;
using System.Windows.Input;
namespace CurrencyUI.ViewModel
{
    public class GestionnaireViewModel : INotifyPropertyChanged
    {
        private CurrencyServiceClient clientService= new CurrencyServiceClient();
        private IEnumerable<Currency> currencys;
        private IEnumerable<Currency> currencyBases;
        private Currency currentCurrencyBase;
        private Currency currentCurrency;

        public Currency CurrentCurrency
        {
            get { return currentCurrency; }
            set { 
                   currentCurrency = value;
                   this.onPropertyChanged("CurrentCurrenc");
                }
        }
        public Action CloseAction { get; set; }
        public ICommand SaveCurrency { get; set; }
        public Currency CurrentCurrencyBase
        {
            get { return currentCurrencyBase; }
            set 
            {
                currentCurrencyBase = value;              
                this.onPropertyChanged("CurrentCurrencyBase");
                this.refreshCurrencyByBase();
            }
        }
        public IEnumerable<Currency> CurrencyBases
        {
            get { return currencyBases; }
            set {
                   currencyBases = value;
                   this.onPropertyChanged("CurrencyBases");
               }
        }

        public IEnumerable<Currency> Currencys
        {
            get { return this.currencys; }
            set {
                   currencys = value;
                   this.onPropertyChanged("Currencys");
                }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public GestionnaireViewModel()
        {
            this.refreshCurrency();

            SaveCurrency = new RelayCommand(SaveMethode);
        }

        private void SaveMethode()
        {
            if (CurrentCurrency != null)
            {
                this.clientService.SaveCurrencyAsync(CurrentCurrency);
                this.onPropertyChanged("CurrentCurrencyBase");
            }

        }

        private void refreshCurrency()
        {
            this.clientService.GetCurrencysCompleted += (s,e)=> {
                this.Currencys = e.Result;
            };
            this.clientService.GetCurrencysAsync();
        }

        private void refreshCurrencyByBase()
        {
            this.clientService.GetCurrencysFormBaseCompleted += (s, e) =>
            {
                this.CurrencyBases = e.Result;
            };
            this.clientService.GetCurrencysFormBaseAsync(this.currentCurrencyBase!=null ?this.currentCurrencyBase.Id:0);
        }

        private void onPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

 

Dans cette classe je définis les champs qui vont être liés aux composants graphiques de notre formulaire :

  • CurrencyBase sera lié au comboBox qui va nous permettre de sélectionner la devise de base de change.
  • Currencys: c'est une liste d’éléments de type Currency qui sera liée à notre grid et qui représente l’ensemble des devises définis face à la devise de base sélectionnée.
  • SaveCurrency de type ICommand qui va être lié au bouton save,  je vais présenter par la suite pourquoi ce champ est de type ICommand.
  • CurrentCurrencyBase :  grâce à ce champ on récupère la devise sélectionnée dans le Combobox, puis on déclenche ensuite le rafraîchissement de la Datagrid en passant la valeur du champ sélectionné.
  • CurrentCurrency ce champ sert à récupérer la valeur sélectionnée dans le DataGrid, qui sera utilisée pour la modification dans la base.

Je crée ensuite l’interface graphique  MainWindow.xaml

currency9

Dans cette fenêtre je vais rester simple. Je définis un combo box  pour sélectionner la devise de base,  un DataGrid pour afficher les taux des devises par rapport à la devise sélectionnée,  et un bouton pour sauvegarder  les changements.

voici le code XAML :

<Window x:Class="CurrencyFullClient.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Change de devises" Height="350" Width="525">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <ComboBox  Margin="222,5,5,5" ItemsSource="{Binding Path=Currencys}"  Text=""  SelectedItem="{Binding Path=CurrentCurrencyBase,Mode=TwoWay}"  >
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Path=LibelleCurrency,Mode=OneWay}"/>
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>
        <DataGrid  HorizontalAlignment="Left" Margin="25,38,0,0" Grid.Row="1" VerticalAlignment="Top" 
                  ItemsSource="{Binding Path=CurrencyBases}"  Width="457" Height="165" AutoGenerateColumns="False"  
                   SelectedItem="{Binding Path=CurrentCurrency, Mode=TwoWay}" CanUserAddRows="True" >
            <DataGrid.Columns>
                <DataGridTextColumn Header="Id" Binding="{Binding Id}" />
                <DataGridTextColumn Header="Currency" Binding="{Binding CodeCurrency}" />
                <DataGridTextColumn Header="Libelle" Binding="{Binding LibelleCurrency}" />
                <DataGridTextColumn Header="Taux Change" Binding="{Binding RateChange}" />
                <DataGridTextColumn Header="Devise de Base" Binding="{Binding BaseCuurencyId}" />
                <DataGridTextColumn Header="Date de change" Binding="{Binding DateChange}" />
            </DataGrid.Columns>

        </DataGrid>
        <Button Content="Save"  HorizontalAlignment="Left" Margin="168,218,0,0" Grid.Row="1" VerticalAlignment="Top" Width="160" Command="{Binding Path=SaveCurrency}"  IsEnabled="True" />
        <Label Content="Selectionner La devise de Base" HorizontalAlignment="Left" Margin="10,5,0,0" VerticalAlignment="Top" Width="205" Height="21"/>

    </Grid>
</Window>

Dans ce code certaines choses méritent des explications :

  • Liaison composant graphique avec le view model : on utilise ItemSource qu’on le lie avec la propriété qui lui correspond dans le View Model .
  • Liaison des éléments sélectionnés avec le model : On utilise SelectedItem qui sera lié aussi de la  même façon avec le view model
  • Liaison des Commandes : dans WPF MVVM on ne peut pas lier un bouton à une méthode, ni gérer son événement dans le code Behind de la vue. La solution à cette restriction et d’utiliser l’interface ICommand :

Cette interface possède deux méthodes CanExecute qui retourne Boolean , et  Execute qui se charge d’exécuter l’action, et un événement  CanExecuteChanged.

Il faut définir un élément de type ICommand  exemple (SaveCurrency) qui sera lié au bouton, et définir le comportement de cette command via un objet implémentant l’interface ICommand.

Cette classe est la suivante :

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

namespace CurrencyUI.ViewModel
{
   public class RelayCommand : ICommand 
    {
        private readonly Action actionAexecuter;

       public RelayCommand(Action action)
        {
            actionAexecuter = action;
        }
        public bool CanExecute(object parameter)
        {
            return parameter != null;
        }

        public event EventHandler CanExecuteChanged;

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

La classe  RelayCommande va me servir comme lanceur des commandes quelque soit leurs types. A cet effet j’ai déclaré  l’attribut actionAexecuter comme délégué de type Action , qui va représenter toutes les actions sans valeur de retour et sans paramètres. Evidemment la classe doit implémenter l’interface Icommand en mettant juste l’exécution de l’action du délégué  actionAexecuter.

Il ne reste plus qu’à initialiser notre attribut SaveCurrency définis dans le view model par l’injection de notre objet RelayCommand :

Dans le constructeur de la classe GestionnaireViewModel j’ai défini :     SaveCurrency = new RelayCommand(SaveMethode);

Ce qui veut dire que la méthode SaveMethode (définis dans le view model) sera lié à la commande SaveCurrency via notre classe de liaison RelayCommand.

Voici à quoi ressemble le Code behind :

using CurrencyUI.ViewModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace CurrencyFullClient
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        GestionnaireViewModel viewModel = new GestionnaireViewModel();
        public MainWindow()
        {
            InitializeComponent();
            this.Loaded += (s,e)=>{
                this.DataContext = this.viewModel;
                viewModel.CloseAction = new Action(() => this.Close());

            };

        }

    }
}

 

6- Conclusion

   Dans ce blog j'ai présenté de quelle façon nous pouvions créer une application Windows multicouche en respectant le pattern MVVM en utilisant WCF et Entity FrameWork.  Dans un autre post je vous présenterai comment utiliser les FameWorks MVVM pour faciliter l’utilisation de ce patterne.

8 commentaires

  1. Bon Traivail mon ami.
    Je manque beaucoups d’experiences en MVVM, j’ai quelque questions a vous posez:
    1- est ce que le WCF est necéssaire dans tout les applications commercial?
    2- est ce qu’on peut créer trois dossiers ‘ Mode l- View – ViewModel’ dans un seul projet à l’intérieur d’une seule solution? au lieu de creer un trois projets ‘ Mode l- View – ViewModel ‘.
    Merci

  2. WCF n’est pas nécessaire dans les toutes applications, je l’ai mis juste pour introduire Aussi le WCF, tu peux très bien utiliser Entity Frame Work avec le mvvm dans un seul projet en créant quatre dossiers view, modelView, service et data.

  3. Merci pour ce tutoriel.

    Cependant, il serait vraiment intéressant d’avoir le code source parce que je n’arrive pas à obtenir le résultat prévu.
    Diverses exceptions sont levées et je ne vois pas ce que j’ai de différent par rapport à ce que tu as fait.

    Merci d’avance.

  4. Merci beaucoup pour ce tutoriel.
    Mais pouvez-vous nous indiquer comment obtenir la base CurrencyBase? J’ai cherché en vain sur le web, je n’arrive pas à le télécharger.

  5. Bonjour,
    Merci pour ce tutoriel !!.
    Peux-tu en parallèle nous partager le code source de l’application ?
    Merci

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.