Voici le dernier article de la série :

- Partager du code entre plate-formes grâce à la Portable Library (1/4)

- Partager du code entre plate-formes : l’application Windows 8 Metro (2/4)

- Partager du code entre plate-formes : l’application Windows Phone (3/4)

- Partager du code entre plate-formes : l’application Silverlight 5 (4/4)

 

Retrouvez le code source des démos :

 

L’application Silverlight 5

Nous profitons du contrôle Pivot Viewer qui est très facile à utiliser depuis Silverlight 5 puisque tout se passe côté client avec du binding.

Ajoutons un nouveau projet Silverlight 5 dans la solution CaveAVins:

image

Ajoutons une référence au projet CaveAVins.Portable.

 

La couche DAL

L’accès aux données est similaire à celui effectué sur Windows Phone. WCF Data Services nous permet de simplifier l’accès au service OData. La même implémentation de ICaveAVinsDataProvider  peut être utilisée dans cette application Silverlight, malgré tout elle ne peut pas être partagée dans la Portable Lib car WCF Data Services est implémenté par des assemblies différentes sur chaque plate-forme.

Tout comme pour Windows Phone, ajoutez une référence au service http://caveavins.cloudapp.net/CaveAVinsDataService.svc/.

Puis ajoutez la même implémentation de la classe CaveAVinsDataProvider que tout à l’heure pour Windows Phone:

using System;
using System.Collections.Generic;
using System.Data.Services.Client;
using System.Linq;
using CaveAVins.PivotViewer.ServiceReference1;
using CaveAVins.Portable.DAL;

namespace CaveAVins.PivotViewer.DAL
{
    public class CaveAVinsDataProvider : ICaveAVinsDataProvider
    {
        public event Action<IEnumerable<IWine>> WinesLoaded;
        DataServiceCollection<Wine> _results;

        public void LoadDataAsync()
        {
            CaveAVinsContext ct = new CaveAVinsContext(new Uri("http://caveavins.cloudapp.net/CaveAVinsDataService.svc/"));
            _results = new DataServiceCollection<Wine>(ct);
            _results.LoadCompleted += (o, e) =>
                {
                    if (e.Error == null)
                    {
                        // Handle a paged data feed.
                        if (_results.Continuation != null)
                        {
                            // Automatically load the next page.
                            _results.LoadNextPartialSetAsync();
                        }
                        else
                        {
                           WinesLoaded(_results.Cast<IWine>());
                        }
                    }
                };

            _results.LoadAsync(ct.Wines.Where(w => w.PictureUrl != null));
        }

    }
}

Ce code étant identique en dehors du nommage des namespaces locaux, il serait possible de partager directement ces fichiers entre les deux projets.

La couche ViewModel

Cette fois c’est le ViewModel de l’application Windows 8 que l’on peut reprendre, toujours pour obtenir une image de l’étiquette de la bouteille de vin:

namespace CaveAVins.PivotViewer.ViewModel
{
    public class SlWineVM : WineVM
    {
        public SlWineVM(IWine wine)
            : base(wine)
        {
        }

        ImageSource _picture;
        public ImageSource Picture
        {
            get
            {
                if (_picture == null)
                {
                    _picture = new BitmapImage(this.PictureUrl);
                }
                return _picture;
            }
        }
    }
}

Voici la solution incluant le projet Silverlight:

image

La couche Vue

Depuis la nouvelle version de Pivot Viewer disponible avec Silverlight 5, il est très facile de l’utiliser pour afficher, trier et filtrer une collection de données.

Nous allons le mettre en oeuvre de manière très basique pour cette démo. Ajoutez une nouvel élément de type Pivot Viewer dans la page de démarrage.

Associez le binding du Datacontext à la propriété Wines comme nous l’avons fait sur les plate-formes WP et Win8 dans les ListViews.

<UserControl xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit"
             x:Class="CaveAVins.PivotViewer.MainPage"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:pivot="clr-namespace:System.Windows.Controls.Pivot;assembly=System.Windows.Controls.Pivot"
             xmlns:cnv="clr-namespace:CaveAVins.PivotViewer.Converters"
             mc:Ignorable="d"
             d:DesignHeight="300"
             d:DesignWidth="400">

    <Grid x:Name="LayoutRoot"
          Background="White">
        <pivot:PivotViewer ItemsSource="{Binding Wines}">

            <pivot:PivotViewer.PivotProperties>
                <pivot:PivotViewerStringProperty Id="Name"
                                                 DisplayName="Name"
                                                 Binding="{Binding Name}" />

                <pivot:PivotViewerNumericProperty Id="Year"
                                                  Options="CanFilter"
                                                  DisplayName="Year"
                                                  Binding="{Binding Year}" />

            </pivot:PivotViewer.PivotProperties>

            <pivot:PivotViewer.ItemTemplates>
                
                <!-- Small-->
                <pivot:PivotViewerItemTemplate MaxWidth="100">
                    <Border BorderBrush="Black"
                            BorderThickness="1">
                        <Image Source="{Binding Path=Picture}"
                               Width="65"
                               Height="89" />
                    </Border>
                </pivot:PivotViewerItemTemplate>

                <!-- Medium-->
                <pivot:PivotViewerItemTemplate>
                    <Border BorderBrush="Black"
                            BorderThickness="0">
                        <StackPanel Orientation="Vertical">
                            <TextBlock Text="{Binding Name}"
                                       Width="176"
                                       FontSize="14"
                                       FontWeight="Bold"
                                       TextWrapping="Wrap"
                                       HorizontalAlignment="Center" />
                            <Image Source="{Binding Path=Picture}"
                                   Width="176"
                                   Height="240" />
                        </StackPanel>
                    </Border>
                </pivot:PivotViewerItemTemplate>

            </pivot:PivotViewer.ItemTemplates>

        </pivot:PivotViewer>
    </Grid>
</UserControl>

Nous définissons 2 templates qui correspondent à 2 manières d’afficher un vin, en fonction du niveau de zoom. Le premier est une simple image, le second est une image ainsi que le nom du vin. Il est ainsi possible d’afficher de plus en plus de détails sur l’éléments au fur et à mesure que l’on zoome sur celui-ci.

On utilise le binding vers les propriétés Name, Picture et Year de manière tout à fait classique, ce qui est une nouveauté depuis Silverlight 5 et la nouvelle version de Pivot Viewer.

La propriété Year est définie comme “CanFilter” et permet ainsi de filtrer les vins en fonction de leur année de production.

Pour plus d’informations sur Pivot Viewer et voir une version plus évoluée de cette application (plus de niveaux de zoom, chargement par lots, commandes, personnalisation de Pivot Viewer), je vous conseille la vidéo des Techdays 2012 à ce sujet : De A à Z : Utiliser PivotViewer dans une application orientée données

 

Initialisation de l’application

Le chargement des données se fait au démarrage de l’application:

private void Application_Startup(object sender, StartupEventArgs e)
{
   CaveAVinsDataProvider dp = new CaveAVinsDataProvider();
      dp.WinesLoaded += (wines =>
      {
         var vm = new CaveAVinsVM(wines.Select(w => new SlWineVM(w)));
            (this.RootVisual as UserControl).DataContext = vm;
      });

      dp.LoadDataAsync();

      this.RootVisual = new MainPage();
}

Go !

Et voici une version très basique d’application Silverlight 5 avec Pivot Viewer qui permet la manipulation de notre collection de vins.

image

image

Conclusion

Pour aller plus loin et profiter de ce partage de code, il faudrait implémenter les autres méthodes de la couche DAL, qui permettent d’éditer les données :

public interface ICaveAVinsDataProvider
{
    event Action<IEnumerable<IWine>> WinesLoaded;
    void LoadDataAsync();

    //void UpdateItem(IWine item);
    //void AddItem(IWine item);
    //void RemoveItem(IWine item);
}

Cela se fait comme pour le chargement : en fonction de la plate-forme. Dans le cas de Windows 8, il faut créer la requête REST à la main, suivant le formalisme OData. Pour Windows Phone et Silverlight, il suffit d’utiliser Linq et WCF Data Services.

 

Au final, cela peut paraitre contraignant de passer par la Portable Lib - et ça l’est effectivement – pour parfois peu de bénéfices. Mais en fonction du projet que vous développez cela peut être une vraie valeur ajoutée. Ce projet de démo n’est peut être pas le meilleur candidat au partage de code, mais il permet d’en montrer les principes et la mise en oeuvre de manière très simple.

Les principaux critères à prendre en compte pour décider de partir sur une base commune ou de développer des versions entièrement différentes sur chaque plate-forme sont les suivants:

- la taille des équipes de développement (il est plus simple pour les développeurs de la couche de présentation d’utiliser toujours les mêmes propriétés des mêmes classes/interfaces quelle que soit la plate-forme)

- le nombre de plate-formes à supporter (économie d’échelle)

- l’évolutivité potentielle de votre application (plus facile à maintenir une fois la base commune mise en place)

- la structure et le contenu du code existant (si tout est différent sur chaque plate-forme, ça ne vaut pas forcément le coup)

- le besoin de documentation d’architecture, de preuve de portabilité et de propreté de conception (si vous faites une petite appli tout seul dans votre coin, ce n’est pas comme si vous étiez en charge d’un projet, d’une vision à moyen/long terme de votre développement, et de la meilleure manière de répartir la charge entre les développeurs)

 

A vous de jouer !