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

[Please find the EN version here ]

Vous trouverez la première partie de cet article ici : Stratégies de partage de code XAML dans les applications universelles 1/2.

Nous allons aborder différentes manières de spécialiser l’ergonomie et les fonctionnalités de l’application, sur chacune des plateformes Windows et Windows Phone, en partant de notre page XAML partagée:

  • Styles
  • DataTemplates
  • UserControl
  • Classes partielles
  • Partir d’un code partagé pour utiliser des éléments spécifiques à une plateforme, qui utilisent eux-même à nouveau du code partagé
  • les primitives de précompil #if WINDOWS_PHONE_APP / WINDOWS_APP

 

Vous pouvez télécharger le code source de l’application d’exemple ici:

Adapter l’ergonomie en fonction de la plateforme

Jusqu’ici tout le code XAML est partagé entre l’application Windows 8.1 et Windows Phone 8.1, par l’intermédiaire du projet Shared. Mais que se passe-t-il si l’on souhaite adapter notre application afin d’obtenir une ergonomie (voire  un comportement) différent sur Windows et Windows Phone ? Quelles sont les possibilités techniques à notre disposition sachant que l’on part d’un code XAML commun ?

Eh bien les possibilités sont nombreuses !

Styles

Pour illustrer la question des styles, je vais utiliser une couleur différente pour le titre de la photo, sur Windows et Windows Phone. Pas de soucis, je peux créer un style dans chaque projet spécifique et l’utiliser dans mon XAML partagé.

Pour cela, je vais créer un dictionnaire de ressources que j’appellerai CustomDictionary.xaml dans chacun des projets Windows et Windows Phone, et j’y fais référence dans mon app.xaml qui est commun.

 <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.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="CustomDictionary.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

image_thumb1

J’utilise du rose pour Windows et du jaune pour Windows Phone (attention les yeux Sourire).

BuildMemories.Windows/CustomDictionary.xaml:

 <ResourceDictionary
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:BuildMemories">
    
    <Style x:Key="MonTextblock" TargetType="TextBlock">
            <Setter Property="Foreground" Value="DeepPink"></Setter>
    </Style>
</ResourceDictionary>

BuildMemories.WindowsPhone/CustomDictionary.xaml

 <ResourceDictionary
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:BuildMemories">
    
    <Style x:Key="MonTextblock" TargetType="TextBlock">
            <Setter Property="Foreground" Value="Yellow"></Setter>
    </Style>
</ResourceDictionary>

J’utilise ensuite ce style dans ma page commune MainPage.xaml

BuildMemories.Shared/MainPage.xaml

 <TextBlock TextWrapping="Wrap" Text="{Binding ElementName=fv, Path=SelectedItem.Title}" Margin="10" FontSize="28"
            Style="{StaticResource MonTextblock}"/>

image_thumb24 image_thumb25

 

DataTemplate

Je peux également utiliser un DataTemplate différent sur chaque plateforme. C’est ce que je vais mettre en œuvre pour la FlipView, pour qu’elle affiche des informations différentes sur Windows et Windows Phone. Pour cela, je vais créer 2 DataTemplates différents appelés APhotoTemplate, que je vais utiliser dans l’ItemTemplate de ma FlipView.

BuildMemories.Shared/MainPage.xaml

 <FlipView Margin="0,9,0,-9" ItemsSource="{Binding Items}" 
           ItemTemplate="{StaticResource APhotoTemplate}">

Le Template APhotoTemplate sera défini dans le dictionnaire de ressources, tout comme le style.

Dans la version Windows, j’ajoute le titre de la photo sur l’image.

BuildMemories.Windows/CustomDictionary.xaml:

 <DataTemplate x:Name="APhotoTemplate">
    <Grid>
        <Image Source="{Binding Path}" VerticalAlignment="Top" />
        <TextBlock TextWrapping="Wrap" Text="{Binding Title}" FontSize="28" Margin="10"/>
    </Grid>
</DataTemplate>

BuildMemories.WindowsPhone/CustomDictionary.xaml:

     <DataTemplate x:Name="APhotoTemplate">
        <Grid>
            <Image Source="{Binding Path}" VerticalAlignment="Top" />
        </Grid>
    </DataTemplate>

 

image_thumb29

UserControl

L’utilisation d’un UserControl différent sur Windows et Windows Phone est également possible. Pour cet exemple, je souhaite ajouter une TextBox permettant d’ajouter un commentaire sur la photo, uniquement sur la version Windows 8.1. Je crée un UserControl regroupant les éléments du bandeau, pour chacune des plateformes et j’y fais référence dans la MainPage partagée:

 <local:BannerUserControl x:Name="spActions" 
                          DataContext="{Binding ElementName=fv, Path=SelectedItem}"/>

BuildMemories.Windows/BannerUserControl.xaml:

 <UserControl
    x:Class="BuildMemories.BannerUserControl"
    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"
    d:DesignHeight="300"
    d:DesignWidth="400"
    x:Name="uc">
    <StackPanel Orientation="{Binding ElementName=uc, Path=Orientation}" VerticalAlignment="Bottom" 
                Background="{ThemeResource SystemColorControlAccentBrush}" Opacity="0.8">

        <TextBlock TextWrapping="Wrap" Text="{Binding Title}" Margin="10" FontSize="28"
                   Style="{StaticResource MonTextblock}"/>
        <DatePicker Margin="10"/>
        <CheckBox Content="I like that" Margin="10"/>
        <TextBox Width="300" Margin="10"></TextBox>
        <Button Content="Share" Margin="0"/>
    </StackPanel>
</UserControl>

BuildMemories.WindowsPhone/BannerUserControl.xaml:

Idem que pour la version Windows 8 ci-dessus, sans la TextBox surlignée de jaune.

Classes partielles

Dans la version Windows 8.1, il ne suffit pas d’ajouter un contrôle TextBox pour permettre à l’utilisateur de saisir un commentaire sur la photo. En effet, il faut aussi faire un binding entre cette TextBox et le ViewModel. Or, le ViewModel de l’élément (ItemVM) est défini dans le projet Shared, et je ne souhaite pas surcharger cette classe avec des fonctionnalités qui ne seraient pas communes à toutes les versions de l’application.

C’est une situation pour laquelle le concept de classe partielle va nous être utile. Effectivement, si l’on déclare la classe ItemVM comme partielle, on pourra la définir de manière répartie, dans des fichiers différents et des projets différents. C’est une solution simple pour ajouter des fonctionnalités spécifiques à une plateforme dans le ViewModel.

Ajoutons un nouveau fichier ItemVM.cs dans le projet Windows 8.1, et ajoutons la propriété Comments dans la classe:

 using GalaSoft.MvvmLight;
using System;
using System.Collections.Generic;
using System.Text;

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

            set
            {
                Set(() => Comments, ref _comments, value);
            }
        }
    }
}

Il suffit de mettre en place le binding de la TextBox vers cette propriété, dans le fichier XAML de la version Windows 8.1:

 <TextBox Width="300" Margin="10" 
            Text="{Binding Comments, Mode=TwoWay}"/>

 

Voici la version Windows 8.1, avec la zone de commentaires:

image_thumb34

La version Windows Phone 8.1 n’a pas changé:

image_thumb35

Partir du code partagé pour aller vers du spécifique…qui réutilise du partagé

Notre UserControl est spécifique à chaque plateforme, mais il contient aussi des fonctionnalités communes. Prenons l’exemple du bouton “Share !” permettant de partager la photo avec ses amis. Peu importe comment il apparaît sur les 2 applications, la fonctionnalité métier est la même lorsque l’on clique sur le bouton, que ce soit sur Windows ou Windows Phone.

Pas de problème de ce côté là non plus : on peut parfaitement placer le RelayCommand associé au bouton, dans le ViewModel du projet Shared, alors qu’on le référence dans les fichiers XAML des projets spécifiques.

Ainsi on obtient une MainPage.xaml partagée, qui utilise une version de usercontrol différente selon que l’on soit sur Windows ou Windows Phone, qui lui-même utilise un RelayCommand de la classe ItemVM dans un fichier du projet Shared.

Ce n’est pas forcément le choix le plus judicieux en terme de lisibilité et de clarté du projet, mais dans certains cas, cela peut au contraire simplifier beaucoup de choses: à vous d’évaluer si cela peut être pertinent dans le contexte de votre projet.

Ajoutons la commande à l’ItemVM dans le projet Shared:

 // 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; }
        
    RelayCommand _shareItCmd;

    public RelayCommand ShareCmd
    {
        get
        {
            return _shareItCmd ?? (_shareItCmd = new RelayCommand(ShareIt));
        }
    }

    async void ShareIt()
    {
        var msg = new MessageDialog("Shared !");
        await msg.ShowAsync();
    }
}

Utilisons cette commande dans le BannerUserControl.xaml, dans les 2 projets Windows et Windows Phone:

 <Button Content="Share" Command="{Binding ShareCmd}"/>

Remarquez que pour la même fonction de partage utilisant la même classe MessageDialog, dans le ViewModel du projet Shared, on obtient automatiquement une expérience utilisateur différente et adaptée à Windows Phone 8.1 et Windows 8.1.

image_thumb42 image_thumb43

#if WINDOWS_PHONE_APP / WINDOWS_APP

Comme vous partagez du code et non des binaires, vous pouvez utiliser des primitives de pré-compilation dans votre code, dans le projet Shared. C’est un bon compromis pour éviter de complexifier l’architecture du projet en ajoutant du code dans les projets spécifiques, surtout quand les différences sont minimes.

Un bon exemple est la gestion du bouton (physique) Back sous Windows Phone:

 #if WINDOWS_PHONE_APP
            Windows.Phone.UI.Input.HardwareButtons.BackPressed += HardwareButtons_BackPressed;
#endif

Pour Windows 8.1, on utilise WINDOWS_APP.

Conclusion

Les possibilités de partage de code XAML dans les applications universelles nous offrent de nouvelles opportunités en terme d’architecture de projet et de code. Mais ce n’est pas une raison pour se forcer à les mettre en pratique !

En effet, cela peut vite devenir très compliqué de comprendre quels styles, datatemplates et usercontrols sont partagés ou non et comment ils interagissent les uns avec les autres.

Cet article vous donne un aperçu des principales possibilités de partage de code XAML, pour vous aider à faire vos propres choix.

Vous pouvez télécharger le code source de l’application d’exemple ici: