[Edit 31/05/2012] Le sujet du portage d’application depuis Windows Phone vers Windows 8 (et donc indirectement du partage de code entre plates-formes) a aussi été traité de manière très détaillée par Pierre Cauchois dans sa suite d’articles Stratégies et techniques de partage de code C# et XAML entre Windows Phone et Windows 8

 

Voici le troisième 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 Windows Phone

La version actuelle de VS11 ne permet pas de créer des applications Windows Phone et il faut donc se résoudre à créer une solution sous Visual Studio 2010 pour la version WP7 de notre démo.

Le projet commun est déjà codé et prêt à être utilisé dans notre application WP, reste à créer le projet de type Windows Phone 7. Encore une fois, je choisis le template le plus simple pour ne pas surcharger la démo :

image_thumb19

On ajoute le projet CaveAVins.PortableLib dans notre solution ainsi qu’une référence à celui-ci dans le projet WP7.

image_thumb21

 

La couche d’accès aux données (DAL)

Cette fois, c’est plus simple puisqu’il suffit d’ajouter une référence au service OData dans notre projet WP7 :

image_thumb24

Les classes proxy correspondantes seront créées automatiquement, et nous aurons donc une implémentation toute prête de notre classe Wine. Vous pouvez accéder au fichier  References.cs contenant les classes proxy en cliquant sur le bouton “Show All Files” permettant d’afficher les fichiers caché de la solution :

image_thumb29

Vous y trouverez une classe Wine avec les propriétés qui vont bien, dont Name, PictureUrl et Year:

image_thumb28 

Le souci c’est qu’elle n’impléménte pas notre interface inter-plateforme IWine. En fait ce n’est pas vraiment un souci puisque cette classe est définie en partial et peut donc être complétée facilement comme ceci dans un fichier Wine.cs que je place dans un nouveau répertoire “Model”:

// Même namespace que les classes proxy générées en faisant le Add Service reference vers mon service OData
namespace CaveAVins.WP.ServiceReference1
{
    public partial class Wine : IWine
    {
        // Mes classes proxy servent d'implémentation à mon interface portable IWine
    }
}

Le chargement de la collection de vins (notre implémentation côté DAL pour Windows Phone) sera faite grâce à WCF Data Services, de manière très classique :

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

namespace CaveAVins.WP.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);
        }

    }
}

On n’oublie pas de déclencher l’évènement de fin de chargement.

La couche ViewModel

Rien à faire ici : notre implémentation disponible dans la Portable Lib nous suffit.

 

La couche Vue

Encore une fois, on utilise une page fournie dans le template : MainPage.xaml que l’on complète de la manière suivante.

Chaque vin est représenté par le DataTemplate suivant dont les éléments sont bindés aux propriétés Name, Year et PictureUrl de notre classe WineVM:

<Grid Margin="5">
   <Grid.ColumnDefinitions>
      <ColumnDefinition Width="Auto"></ColumnDefinition>
      <ColumnDefinition Width="*"></ColumnDefinition>
   </Grid.ColumnDefinitions>
   <Image Width="120" Height="120" Grid.Column="0" Source="{Binding PictureUrl}" Stretch="Uniform"></Image>
   <StackPanel Grid.Column="1">
      <TextBlock Text="{Binding Name}" TextWrapping="Wrap" Style="{StaticResource PhoneTextLargeStyle}"/>
      <TextBlock Text="{Binding Year}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
   </StackPanel>
</Grid>

Comme le téléphone a un écran plus long que large, on choisit plutôt un défilement vertical. C’est le fonctionnement par défaut de la ListBox:

<ListBox x:Name="MainListBox" Margin="0,0,-12,0" ItemsSource="{Binding Wines}">

 

Remarquez que les bindings sont similaires à ceux de l’application Windows 8 : en effet, les ViewModels sont communs (sauf pour l’image)!

 

Voici le code complet de la page:

<phone:PhoneApplicationPage 
    x:Class="CaveAVins.WP.MainPage"
    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"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True"
    >

    <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="PORTABLE LIBRARY DEMO" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="Wines" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <ListBox x:Name="MainListBox" Margin="0,0,-12,0" ItemsSource="{Binding Wines}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <Grid Margin="5">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto"></ColumnDefinition>
                                <ColumnDefinition Width="*"></ColumnDefinition>
                            </Grid.ColumnDefinitions>
                            <Image Width="120" Height="120" Grid.Column="0" Source="{Binding PictureUrl}" Stretch="Uniform"></Image>
                            <StackPanel Grid.Column="1">
                                <TextBlock Text="{Binding Name}" TextWrapping="Wrap" Style="{StaticResource PhoneTextLargeStyle}"/>
                                <TextBlock Text="{Binding Year}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
                            </StackPanel>
                        </Grid>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </Grid>
    </Grid>

</phone:PhoneApplicationPage>

Initialisation de l’application

La chargement des données se fera au démarrage de l’application, dans l’évènement Application_Launching.

// Code to execute when the application is launching (eg, from Start)
// This code will not execute when the application is reactivated
private void Application_Launching(object sender, LaunchingEventArgs e)
{
    CaveAVinsDataProvider dp = new CaveAVinsDataProvider();
    dp.WinesLoaded += (wines =>
    {
        _vm = new CaveAVinsVM(wines);
        RootFrame.DataContext = _vm;
    });

    dp.LoadDataAsync();
}

Cette fois, nous pouvons utiliser le ViewModel par défaut pour les vins (celui défini dans la Portable Lib) et donc passer directement la collection wines récupérée dans l’évènement WinesLoaded.
Tout comme dans l’application Windows 8, on affecte le viewModel principal (CaveAVinsVM) au DataContext de notre page principale dont tous les contrôles hériteront.

Go !

Au démarrage de l’application, nous obtenons le résultat suivant :

image_thumb36

 

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