Stratégies de partage de code XAML dans les applications universelles (1/2)

[Please find the EN version here]

Grâce aux applications universelles (Universal apps), les applications multi-plateformes conçues pour Windows 8.1 et Windows Phone 8.1 peuvent accéder à de nouveaux scénarios de partage de code.

Dans Visual Studio, une solution “universelle” contient au minimum 3 projets : un pour Windows 8.1 (application du store), un pour Windows Phone 8.1 (version WinRT), et un autre appelée “Shared” pour les éléments communs.

Dans le projet shared, on ne partage pas les binaires comme c’est le cas pour les projets de type PCL, mais des fichiers comme des ressources, des images, du xml, du XAML, du code, etc, qui seront réutilisés dans chaque projet spécifique à une plateforme.

Dans cet article, je me focaliserai sur le partage de code XAML. C’est une possibilité attrayante même si l’idée n’est pas de partager 100% du code XAML entre les plateformes. En effet, il faudra toujours veiller à adapter l’ergonomie des pages en fonction du format du device et des différences d’usage de l’application, pour garantir la meilleure expérience possible à l’utilisateur.

A l’inverse, nous verrons les principales stratégies qui permettront d’obtenir une ergonomie d’application spécifique aux plateformes tout en gardant une partie du code XAML partagé.

Ne vous forcez pas non plus à mettre en commun le maximum de XAML possible dans vos projets : cela peut rapidement rendre l’architecture du projet très compliquée et donc difficile de comprendre quels styles, datatemplates et user controls sont partagés ou non et comment ils interagissent les uns avec les autres. Cet article est un aperçu des principales possibilités techniques qui pourra vous aider à faire vos propres choix, dans le contexte de votre application.

L’article est divisé en 2 parties:

  • Partie 1 : Création d’un nouvelle application universelle dans Visual Studio et mise en place des éléments communs sur la page (cet article).
  • Partie 2 : Personnalisation de l’ergonomie selon la plateforme : au-delà des contrôles standards, styles, etc… qui peuvent être partagés et dont le comportement dépendra de l’implémentation sur chaque plateforme, comment peut-on personnaliser notre interface utilisateur sur Windows 8 et Windows Phone alors que notre page XAML est partagée ?

 

Vous pouvez télécharger le code source de cette application de démo ici:

 

Création d’un projet de type application universelle

De nouveaux templates de projets sont disponibles pour créer des applications universelles dans Visual Studio 2013 Update 2. On peut utiliser différents langages : C#, C++ et une grande première pour Windows Phone 8.1 : HTML/Javascript. Vous pouvez aussi partir d’un projet Windows 8.1 existant et ajouter un nouveau projet Windows Phone 8.1 (ou l’inverse) dans la même solution.

Pour notre exemple, nous partirons d’un projet vierge:

image_thumb3

Voici à quoi ressemble la structure de la solution :

  • 1 projet Windows 8.1
  • 1 projet Windows Phone 8.1 (basé sur WinRT et non Silverlight)
  • 1 projet Shared

 

image_thumb8

Seul le fichier app.xaml et son code-behind sont partagés.

 <Application
    x:Class="BuildMemories.App"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:BuildMemories">

</Application>

Partage de code XAML

Il est aussi possible de partager le fichier MainPage.xaml créé par défaut par le Template et qui est quasi vierge pour le moment : glissez-déplacez celui du projet Windows 8.1 vers le projet Shared et supprimez-le ensuite des projets Windows 8.1 et Windows Phone 8.1.

image_thumb10

Commençons par ajouter des contrôles à cette MainPage partagée !

Comme Tim Heuer l’a expliqué lors de sa session Build, une grande partie des API UI sont maintenant communes aux applications du store sur Windows 8.1 et Windows Phone 8.1 (la plupart des différences sont liées à l’Automation et les contrôles de type Picker).

 

Classes

Structs

Interfaces

Windows 8.1 SDK

566

119

59

Windows Phone 8.1 SDK

624

131

57

 

+58

+12

-2

 

Une API commune n’implique pas forcément d’avoir une implémentation similaire sur Windows et Windows Phone. Ainsi la plupart des contrôles bénéficieront d’une ergonomie voire d’un comportement différent, pour adapter l’expérience utilisateur à la plateforme. Certains sont très similaires, d’autres complètement différents. Tim couvre ces différences dans ses slides:

image_thumb12 image_thumb14

D’autres restent dédiés à une plateforme donnée:

image_thumb22

Je ne rentrerai pas dans le détail des différents contrôles dans cet article, mais je vais en choisir quelques-uns pour illustrer les possibilités de partage de code dans une application d’exemple.

Ajoutons quelques contrôles à notre MainPage.xaml:

 <Page
    x:Class="BuildMemories.MainPage"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:BuildMemories"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel>
            <TextBlock TextWrapping="Wrap" Text="TextBlock" Margin="30" FontSize="28"/>           
            <DatePicker Margin="30"/>
            <CheckBox Content="CheckBox" Margin="30"/>
            <Button Content="Button" Margin="30"/>
        </StackPanel> 
    </Grid>
</Page>

Les fonctionnalités de l’éditeur graphique de Visual Studio ont été améliorées dans l’update 2 de Visual Studio 2013: vous pouvez maintenant personnaliser l’aperçu de la page, suivant le format de votre device, sa résolution, l’orientation, le contraste élevé, le thème, …

image46_thumb8

Alors que notre fichier MainPage.xaml est partagée dans le projet Shared, les contrôles auront un aspect et un comportement différent entre Windows et Windows Phone, suivant leur implémentation sur chacune des plateformes (le DatePicker en est un bon exemple):

image_thumb31[1]

image_thumb34[1]image_thumb33

Continuons en ajoutant une FlipView, pour y montrer des photos que j’ai prises à la conférence //build:

     <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <FlipView Margin="0,9,0,-9" ItemsSource="{Binding Items}">
            <FlipView.ItemTemplate>
                <DataTemplate >
                    <Grid>
                        <Image Source="{Binding Path}" VerticalAlignment="Top" />                   
                        <StackPanel>
                            <TextBlock TextWrapping="Wrap" Text="{Binding Title}" Margin="30" FontSize="28"/>
                            <DatePicker Margin="30"/>
                            <CheckBox Content="I like that" Margin="30"/>
                            <Button Content="Share" Margin="30"/>
                        </StackPanel>
                    </Grid>
                </DataTemplate>
            </FlipView.ItemTemplate>
        </FlipView>
    </Grid>

 

Je vais également créer un répertoire ViewModels avec des classes MainViewModel et un ItemViewModel, pour faire un binding entre la source de l’image et la propriété Path de l’ItemViewModel.

 

Création d’un mécanisme basique de ViewModel

Vous pouvez passer ce chapitre si seule la partie XAML vous intéresse, c’est juste un peu de plomberie qui me permettra de faire le binding entre ma vue et mes données.

Je n’ai pas implémenté le mécanisme de binding de bout en bout dans les propriétés de mes ViewModels car je préfère alléger le code pour qu’il n’adresse que le sujet de l’article. Si vous n’avez pas encore de framework MVVM favori ou que le sujet est nouveau pour vous, je vous recommande l’utilisation de MVVMLight. Il est disponible sous forme de PCL et vous permet de bénéficier d’une implémentation du INotifyPropertyChanged dans une classe ViewModelBase, d’une implémentation de RelayCommand, etc…

image_thumb7

Laurent Bugnion a écrit un petit article pour démarrer un projet avec MVVMLight pour une application universelle.

MainPageVM.cs

 using System;
using System.Collections.Generic;
using System.Text;
using GalaSoft.MvvmLight;
 namespace BuildMemories.ViewModels
{
    // Making it partial can help sharing code too...
    public partial class MainPageVM : ViewModelBase
    {
        ItemVM[] _items = {
            new ItemVM() { Path = "Assets/WP_1.jpg", Title="SF From the bay"},
            new ItemVM() { Path = "Assets/WP_2.jpg", Title="I <3 Xamarin"},
            new ItemVM() { Path = "Assets/WP_3.jpg", Title="XBox session"},
            new ItemVM() { Path = "Assets/WP_4.jpg", Title="Which was that one ?"},
            new ItemVM() { Path = "Assets/WP_5.jpg", Title="Sunny, lucky me !"},
            };

        public MainPageVM()
        {
        }

        public ItemVM[] Items
        {
            get
            {
                return _items;
            }
        }
    }
}
 ItemVM.cs
 using System;
using System.Collections.Generic;
using System.Text;using GalaSoft.MvvmLight;

namespace BuildMemories.ViewModels
{
// Using a partial class will help sharing VM accross shared and platform specific projects
public partial class ItemVM: ViewModelBase
{
public string Path { get; set; }
public string Title { get; set; }
}

Dans le fichier xaml, on instancie MainPageVM que l’on bind au DataContext:

 <Page
    x:Class="BuildMemories.MainPage"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:BuildMemories"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:vm="using:BuildMemories.ViewModels"
    mc:Ignorable="d"
    >

    <Page.Resources>
        <vm:MainPageVM x:Key="dc"/>
    </Page.Resources>

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
          DataContext="{StaticResource dc}">
 </Page>

Mon projet Shared ressemble maintenant à cela:

image_thumb36

Vous pouvez télécharger le code source complet à la fin de l’article.

L’application à ce stade

Voici à quoi ressemblent les deux versions de l’application lorsque je les exécute :

image_thumb41 image_thumb43

La FlipView fonctionne correctement, dans les deux versions, y compris à la souris pour Windows 8.1.

Pour améliorer un peu le résultat, je souhaite regrouper les contrôles dans ce que l’on pourrait appeler un bandeau de commandes, aligné au bas de la page, organisés de manière horizontale pour Windows 8 et verticale pour Windows Phone (pour profiter de l’espace vide sous la photo).

Ajuster la vue en fonction du ratio largeur/hauteur sur l’évènement SizeChanged

Une première approche pourrait être de conserver le code XAML dans le projet partagé et adapter le positionnement des contrôles en fonction du mode portrait ou paysage de la page, que ce soit sur Windows 8 ou Windows Phone. Une manière ultra simple de réaliser cela serait d’évaluer le ratio largeur/hauteur de la page sur l’évènement SizeChanged:

La manière simple

Dans mon cas, je vais simplement changer l’orientation de mon StackPanel.

 private void Page_SizeChanged(object sender, SizeChangedEventArgs e)
{
    if(e.NewSize.Width < e.NewSize.Height)
    {
        spActions.Orientation = Orientation.Vertical;
    }
    else
    {
        spActions.Orientation = Orientation.Horizontal;
    }
}

image81_thumb image84_thumb

image87_thumb image124_thumb

Le bénéfice intéressant c’est que la vue narrow (fenêtre réduite en largeur) sur Windows 8.1 gagne aussi ce comportement et profite donc également de l’espace vide disponible sous la photo pour afficher les commandes:

image120_thumb

Avec des Visual States

Vous pouvez bénéficier de plus de flexibilité avec des Visual States comme cela est suggéré dans les guidelines depuis Windows 8.1. Voici un exemple du MSDN:

 /// If the page is resized to less than 500 pixels, use the layout for narrow widths. 
/// If the page is resized so that the width is less than the height, use the tall (portrait) layout. 
/// Otherwise, use the default layout. 
void Page_SizeChanged(object sender, SizeChangedEventArgs e) 
{ 
    if (e.NewSize.Width < 500) 
    { 
        VisualStateManager.GoToState(this, "MinimalLayout", true); 
    } 
    else if (e.NewSize.Width < e.NewSize.Height) 
    { 
        VisualStateManager.GoToState(this, "PortraitLayout", true); 
    } 
    else 
    { 
        VisualStateManager.GoToState(this, "DefaultLayout", true); } 
} 

Partager la ressource de couleur d’accentuation

Vous pouvez utiliser un ThemeResource commun à Windows et Windows Phone pour la couleur d’accentuation grâce à SystemColorControlAccentBrush. Pour la démo, je vais ajouter un fond au StackPanel qui sert de bandeau, en lui attribuant la couleur d’accentuation.

 <StackPanel x:Name="spActions" VerticalAlignment="Bottom" Orientation="Horizontal" 
            Background="{ThemeResource SystemColorControlAccentBrush}" Opacity="0.8">

 

image_thumb4 image_thumb5

Et bien plus encore…

Il est possible de partager d’autres choses comme les animations, l’appbar, mais pour cet article, je voudrais me focaliser sur les possibilités de personnalisation de la page sur chacune des plateformes, en partant de notre XAML qui est partagé,

C’est ce que nous verrons dans la seconde partie de l’article.