MVVM Light sous Windows Phone 7

Développant depuis quelques mois sous Windows Phone 7,  je me suis lancé dans un projet « fou ». Celui-ci va me mener à la réalisation d’une application de a à Z expliquée sur mon blog.  Je me repose donc sur des technologies Microsoft et sur le SDK RTW de Windows Phone 7 Mango pour Visual Studio 2010.
Nous démarrons donc cette aventure avec un post expliquant la structure « vide » ainsi que l’architecture utilisée pour mener à bien la mission.

1) Choix d'utilisation du pattern MVVM

Nous allons utiliser le pattern MVVM pour réaliser cette application. L’objectif de ce pattern (inventé par John Gossman) est de simplifier le code « behind » des vues et de permettre de tester son code plus facilement. Nous pouvons pousser plus loin en couplant ce pattern avec le pattern IoC pour totalement découper certaines couches mais, dans cette session ci, nous nous limiterons à l’utilisation de MVVM. Vous pouvez trouver plus d’informations sur MVVM via ce lien.

2) Enregistrement du "Locator" dans l'application

La première des manipulations est de créer puis d'enregistrer en ressource notre classe "Locator". Notre classe est en réalité une facade. Dans notre projet, nous n'avons qu'un seul "view model".

public class ViewModelLocator
{
    private static MainViewModel _main;
    /// <summary>
    /// Initializes a new instance of the ViewModelLocator class.
    /// </summary>
    public ViewModelLocator()
    {
        _main = new MainViewModel();
    }
    /// <summary>
    /// Gets the Main property which defines the main viewmodel.
    /// </summary>
    public MainViewModel Main
    {
        get { return _main; }
    }
}

Nous pouvons enregistrer dans notre XAML notre class en tant que ressource. Nous pourrons par après accéder à notre classe via une clé depuis n'importe quel écran.

<?xml version="1.0" encoding="utf-8"?>
<Application x:Class="WorkCountdown7.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone" xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="clr-namespace:WorkCountdown7.ViewModel" mc:Ignorable="d">
  <!--Application Resources-->
  <Application.Resources>
    <vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
  </Application.Resources>
  <Application.ApplicationLifetimeObjects>
    <!--Required object that handles lifetime events for the application-->
    <shell:PhoneApplicationService Launching="Application_Launching" Closing="Application_Closing" Activated="Application_Activated" Deactivated="Application_Deactivated" />
  </Application.ApplicationLifetimeObjects>
</Application>

3) Implémentation du ViewModel

Nous définissons notre logique business dans cette classe. Notre but est donc d'avoir un bouton permettant de démarrer / arrêter un "chronomètre". Nous devons donc diffuser une API avec une collection d'items ainsi qu'un ICommand permettant de lancer l'action. Nous ne définissons pas de droits sur le bouton(Ceci aurait pu se faire via la classe RelayCommand ...). Nous implémentons notre logique business dans la méthode exécutée lors d'une action "Execute". Cela suffit pour avoir une classe testable à souhait. Nous pouvons passer à l'implémentation de l'interface.

public class MainViewModel : ViewModelBase
{
    private ObservableCollection<TimerItem> items = new ObservableCollection<TimerItem>();
    public ObservableCollection<TimerItem> Items { get { return this.items; } }
    public MainViewModel()
    {
        this.Execute = new RelayCommand(ExecuteAction);
    }
    public ICommand Execute { getprivate set; }
    private void ExecuteAction()
    {
        if (this.currentItem == null)
        {
            // no value
            this.currentItem = new TimerItem { StartTime = DateTime.Now };
            this.items.Add(this.currentItem);
        }
        else if (!this.currentItem.StopTime.HasValue)
        {
            this.currentItem.StopTime = DateTime.Now;
        }
        else
        {
            this.currentItem = new TimerItem { StartTime = DateTime.Now };
            this.items.Add(this.currentItem);
        }
    }
    private TimerItem currentItem;
}

4) Implémentation de l'interface

Nous allons réutiliser notre Locator (expliqué en point 2) en l'enregistrant en tant que DataContext de notre UI.

DataContext="{Binding Source={StaticResource Locator},  Path=Main}"

Nous pouvons par après utiliser un contrôle pivot pour avoir deux "onglets". Le premier contiendra un bouton. Le second contiendra l'historique des actions.

<controls:Pivot>
    <controls:Pivot.Title>
        <TextBlock Text="Hello World !"/>
    </controls:Pivot.Title>
    <!--Pivot item one-->
    <controls:PivotItem Header="Timer">
        <local:Timer/>
    </controls:PivotItem>
    <!--Pivot item two-->
    <controls:PivotItem Header="History">
        <local:History/>
    </controls:PivotItem>
</controls:Pivot>

Dans notre premier onglet, nous avons un bouton pour démarrer/arrêter notre "compteur". Nous pouvons simplement lier notre bouton sur un Command de notre ViewModel ... en l'occurence ici notre Execute.

<Button Content="Button" Command="{Binding Execute}" Height="72" HorizontalAlignment="Left" Name="button1" VerticalAlignment="Top" Width="160" />

Dans notre second onglet, nous décidons que nos items rempliront la listbox.

<ListBox DataContext="{Binding Path=Items}" ItemsSource="{Binding}">
</ListBox>

4) Ensuite ? 

Nous pouvons exécuter notre code. Celui-ci fonctionnera parfaitement ... Il est testable et surtout il est possible sans problème de repenser l'interface graphique via Expression Blend sans toucher à une ligne de notre code business. Vous pouvez télécharger le code complet via ce lien. Le prochain post abordera la persistence des données dans une base SQL Compact afin d'historiser / requêter simplement nos "timers".

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.