• Facile

Ce cours est visible gratuitement en ligne.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Mis à jour le 29/04/2014

Panorama et Pivot

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Nous avons vu dans la partie précédente que nous pouvions naviguer entre les pages, c’est bien ! Mais sachez que nous pouvons également naviguer entre les données. C’est encore mieux. :)
C’est là qu’interviennent deux contrôles très utiles qui permettent de naviguer naturellement entre des données : le contrôle Panorama et le contrôle Pivot.
Le contrôle Panorama sert en général à voir un petit bout d’un plus gros écran, qui ne rentre pas dans l’écran du téléphone. Le principe est qu’on peut mettre beaucoup d’informations sur une grosse page et la mécanique du contrôle Panorama incite l’utilisateur à se déplacer avec le doigt sur le reste du plus gros écran.
Le contrôle Pivot quant à lui permet plutôt de voir la même donnée sur plusieurs pages. La navigation entre les pages se fait en faisant glisser le doigt, comme si l’on tournait une page. Par exemple pour une application météo, la première page permet d’afficher la météo du jour, la page suivante permet d’afficher la météo de demain, etc.

Découvrons à présent ces deux contrôles.

Panorama

Le panorama est donc un contrôle qui sert à voir un petit bout d’un plus gros écran dont la taille dépasse celle de l’écran du téléphone. On l’illustre souvent avec une image de ce genre (voir la figure suivante).

Représentation du contrôle Panorama
Représentation du contrôle Panorama

Vous vous rappelez l’introduction du cours et le passage sur les hubs ? Ce contrôle est exactement le même. Nous pouvons l’intégrer dans nos applications et tirer parti de son élégance et de ses fonctionnalités.
Pour découvrir le panorama, le plus simple est de créer un nouveau projet. Vous avez sûrement constaté que Visual Studio nous proposait de créer différents modèles de projet, dont un projet s’appelant « Application Panorama Windows Phone » et un autre s’appelant « Application Pivot Windows Phone ». Choisissons le projet « Application Panorama Windows Phone », ainsi qu'indiqué à la figure suivante.

Création d'un projet Panorama
Création d'un projet Panorama

Si nous démarrons immédiatement l’application, nous pouvons voir qu’elle contient un panorama existant. Wahou… bon ok, passons sur la relative traduction des divers éléments. :p

Ce qu’il faut remarquer ici, c’est qu’il est possible de faire glisser l’écran en cours de gauche à droite, affichant trois éléments en tout, et en boucle, sachant que le troisième élément occupe plus d’espace qu’un écran (voir la figure suivante).

La panorama du projet exemple, composé de 3 écrans
La panorama du projet exemple, composé de 3 écrans

Il faut également remarquer que l’affichage de chaque écran incite l’utilisateur à aller voir ce qu’il y a à droite. En effet, on peut voir que le titre n’est pas complet. Pareil pour le carré jaune, on se doute qu’il doit y avoir quelque chose à côté… Bref, tout est fait pour donner envie d’aller voir ce qu’il y a plus loin. C’est le principe du panorama.

Voyons à présent le XAML qui a été généré pour obtenir cet écran :

<phone:Panorama Title="mon application">
    <phone:Panorama.Background>
        <ImageBrush ImageSource="/DemoPanorama;component/Assets/PanoramaBackground.png"/>
    </phone:Panorama.Background>

    <!--Élément un de panorama-->
    <phone:PanoramaItem Header="first item">
        <!--Liste simple trait avec habillage du texte-->
        <phone:LongListSelector Margin="0,0,-22,0" ItemsSource="{Binding Items}">
            […]
        </phone:LongListSelector>
    </phone:PanoramaItem>

    <!--Élément deux de panorama-->
    <phone:PanoramaItem>
        <!--Liste double trait avec espace réservé pour une image et habillage du texte utilisant un en-tête flottant qui défile avec le contenu-->
        <phone:LongListSelector Margin="0,-38,-22,2" ItemsSource="{Binding Items}">
            […]
        </phone:LongListSelector>
    </phone:PanoramaItem>

    <!--Élément trois de panorama-->
    <phone:PanoramaItem Header="third item" Orientation="Horizontal">
        <!--Double largeur de panorama avec espaces réservés pour grandes images-->
        <Grid>
            […]
        </Grid>
    </phone:PanoramaItem>
</phone:Panorama>

Ce qu’on peut constater déjà c’est qu’il est composé de trois parties qui sont toutes les trois des PanoramaItem. Un PanoramaItem correspond donc à une « vue » de la totalité du Panorama. La navigation se passe entre ces trois éléments.
Nous pouvons d’ailleurs voir dans le designer le rendu du premier PanoramaItem. Vous pouvez également voir le second en allant vous positionner dans le XAML au niveau du second PanoramaItem, et de même pour le troisième. Plutôt pas mal, le rendu est assez fidèle.

Nous pouvons également constater que le contrôle Panorama est défini dans un espace de noms différent de ceux que nous avons déjà utilisés, on voit notamment qu’il est préfixé par phone qui correspond à :

xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"

Ce contrôle se situe donc dans l’espace de noms Microsoft.Phone.Controls et dans l’assembly Microsoft.Phone.

Le panorama possède un titre qui est affiché tout en haut du contrôle, ici, en haut de la page. On le remplit via la propriété Title. Vous pouvez d’ailleurs constater que ce titre n’est pas affiché en entier et que cela nous incite encore à aller voir plus à droite s’il n’y a pas autre chose. Ce titre est une propriété de contenu que nous pouvons remplacer par n’importe quoi, comme avec le bouton. À titre d’exemple, remplacez la propriété Title par :

<phone:Panorama>
    <phone:Panorama.Title>
        <Image Source="/Assets/ApplicationIcon.png" Margin="150 60 0 -30" />
    </phone:Panorama.Title>
    …

Vous pouvez voir le résultat à la figure suivante.

Le titre est un contrôle de contenu
Le titre est un contrôle de contenu

De la même façon, vous pouvez mettre un fond d’écran au panorama via la propriété BackGround. Notez que l’image doit absolument avoir son action de génération à Resource, sinon elle risque de ne pas apparaître immédiatement et d’être chargée de manière asynchrone.

Chaque élément du panorama a un titre, représenté par la propriété Header. Dans le deuxième, on peut remarquer que le titre commence par un « s » et qu’il dépasse du premier élément. Tout ceci est fait automatiquement sans que l’on ait à faire quoi que ce soit de supplémentaire.

Nous pouvons créer autant de PanoramaItem que nous le voulons et y mettre ce que nous voulons. Ici, il a été mis des listes de type LongListSelector, et dans le troisième élément une grille mais cela pourrait être n’importe quoi d’autre vu que le Panorama fait office de conteneur.
Soyez vigilant quant à l’utilisation du contrôle Panorama. Il doit être utilisé à des emplacements judicieux afin de ne pas perturber l’utilisateur. Bien souvent, il est utilisé comme page d’accueil d’une application.
Vous vous doutez bien qu’on peut faire beaucoup de choses avec ce panorama. Il est par exemple possible de s’abonner à l’événement de changement de PanoramaItem, ou se positionner directement sur un élément précis du panorama. Illustrons ceci avec le XAML suivant :

<phone:Panorama x:Name="MonPanorama" Title="Mes tâches" Loaded="Panorama_Loaded" SelectionChanged="Panorama_SelectionChanged">
    <phone:PanoramaItem Header="Accueil">
        <StackPanel>
            <TextBlock Text="Blablabla" HorizontalAlignment="Center" />
            <Button Content="Allez à aujourd'hui" Tap="Button_Tap" Margin="0 50 0 0" />
        </StackPanel>
    </phone:PanoramaItem>
    <phone:PanoramaItem Header="Aujourd'hui">
        <ListBox>
            <ListBoxItem>Tondre la pelouse</ListBoxItem>
            <ListBoxItem>Arroser les plantes</ListBoxItem>
        </ListBox>
    </phone:PanoramaItem>
    <phone:PanoramaItem Header="Demain">
        <StackPanel>
            <TextBlock Text="Passer l'aspirateur" Margin="30 50 0 60" />
            <TextBlock Text="Laver la voiture" Margin="30 50 0 60" />
        </StackPanel>
    </phone:PanoramaItem>
</phone:Panorama>

Mon panorama contient trois éléments, un accueil, des tâches pour aujourd’hui et des tâches pour demain. Je me suis abonné à l’événement de chargement du panorama ainsi qu’à l’événement de changement de sélection. Ici, cela fonctionne un peu comme une ListBox. Notons également que l’écran d’accueil possède un bouton avec un événement de clic.
Passons au code-behind à présent :

public partial class MainPage : PhoneApplicationPage
{
    public MainPage()
    {
        InitializeComponent();
    }

    private void Panorama_Loaded(object sender, RoutedEventArgs e)
    {
        if (IsolatedStorageSettings.ApplicationSettings.Contains("PageCourante"))
        {
            MonPanorama.DefaultItem = MonPanorama.Items[(int)IsolatedStorageSettings.ApplicationSettings["PageCourante"]];
        }
    }

    private void Button_Tap(object sender, System.Windows.Input.GestureEventArgs e)
    {
        MonPanorama.DefaultItem = MonPanorama.Items[1];
    }

    private void Panorama_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        IsolatedStorageSettings.ApplicationSettings["PageCourante"] = MonPanorama.SelectedIndex;
    }
}

La méthode Panorama_SelectionChanged est appelée à chaque changement de sélection. Dans cette méthode, je stocke dans le répertoire local l’index de la page en cours, obtenu comme pour la ListBox avec la propriété SelectedIndex. Ce qui me permet, au chargement du panorama, de me repositionner sur la dernière page visitée s’il y en a une. Cela se fait grâce à la propriété DefaultItem que je renseigne avec le PanoramaItem trouvé à l’indice de la propriété Items, indice qui est celui stocké dans le répertoire local. De la même façon, je peux me positionner sur un PanoramaItem choisi lorsque je clique sur le bouton.
Même si c’est un peu plus rare, il est possible d’utiliser le binding avec le contrôle Panorama. Reproduisons plus ou moins notre exemple précédent en utilisant un contexte de données (je retire la page accueil et le bouton, ce sera plus simple). Tout d’abord le XAML :

<phone:Panorama x:Name="MonPanorama" Title="Mes tâches" Loaded="Panorama_Loaded" SelectionChanged="Panorama_SelectionChanged" ItemsSource="{Binding ListeEcrans}">
    <phone:Panorama.HeaderTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Titre}" />
        </DataTemplate>
    </phone:Panorama.HeaderTemplate>
    <phone:Panorama.ItemTemplate>
        <DataTemplate>
            <ListBox ItemsSource="{Binding ListeDesTaches}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding}" Margin="0 20 0 0" />
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </DataTemplate>
    </controls:Panorama.ItemTemplate>
</controls:Panorama>

Ici, c’est comme pour la ListBox. Le contrôle Panorama possède aussi des modèles, que nous pouvons utiliser. Il y a le modèle HeaderTemplate qui nous permet de définir un titre et le modèle ItemTemplate qui nous permet de gérer le contenu. Le contrôle Panorama a sa propriété ItemsSource qui est liée à la propriété ListeEcrans que nous retrouvons dans le code-behind :

public partial class MainPage : PhoneApplicationPage, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(String propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (null != handler)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    private bool NotifyPropertyChanged<T>(ref T variable, T valeur, [CallerMemberName] string nomPropriete = null)
    {
        if (object.Equals(variable, valeur)) return false;

        variable = valeur;
        NotifyPropertyChanged(nomPropriete);
        return true;
    }

    private List<Ecran> _listeEcrans;
    public List<Ecran> ListeEcrans
    {
        get { return _listeEcrans; }
        set { NotifyPropertyChanged(ref _listeEcrans, value); }
    }

    public MainPage()
    {
        InitializeComponent();

        ListeEcrans = new List<Ecran>
            {
                new Ecran 
                { 
                    Titre = "Aujourd'hui",
                    ListeDesTaches = new List<string> { "Tondre la pelouse", "Arroser les plantes"}
                },
                new Ecran 
                { 
                    Titre = "Demain",
                    ListeDesTaches = new List<string> { "Passer l'aspirateur", "Laver la voiture"}
                }
            };

        DataContext = this;
    }

    private void Panorama_Loaded(object sender, RoutedEventArgs e)
    {
        if (IsolatedStorageSettings.ApplicationSettings.Contains("PageCourante"))
        {
            MonPanorama.DefaultItem = MonPanorama.Items[(int)IsolatedStorageSettings.ApplicationSettings["PageCourante"]];
        }
    }

    private void Panorama_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        IsolatedStorageSettings.ApplicationSettings["PageCourante"] = MonPanorama.SelectedIndex;
    }
}

Avec la classe Ecran suivante :

public class Ecran
{
    public string Titre { get; set; }
    public List<string> ListeDesTaches { get; set; }
}

Et le tour est joué. Vous n’avez plus qu’à démarrer l’application pour obtenir ce résultat (voir la figure suivante).

Contrôle Panorama et Binding
Contrôle Panorama et Binding

Pivot

Passons maintenant à l’autre contrôle très pratique, le Pivot, qui est un peu le petit frère du panorama. Il permet plutôt de voir la même donnée sur plusieurs pages. La navigation entre les pages se fait en faisant glisser le doigt, comme si l’on tournait une page. On pourrait le comparer à un contrôle de gestion d’onglets. On passe à l’onglet suivant en faisant glisser son doigt…

Voyons à présent comme fonctionne le contrôle Pivot. Pour cela, créez le deuxième type de projet que nous avons vu, à savoir « Application Pivot Windows Phone » et démarrons l’application exemple (voir la figure suivante). On constate qu’on peut également naviguer en faisant glisser la page sur la droite ou sur la gauche avec le doigt (ou la souris :p ).

Rendu du projet Pivot exemple
Rendu du projet Pivot exemple

Ici, visuellement, il y a seulement le titre des pages qui nous renseigne sur la présence d’un autre élément. Voyons à présent le code XAML :

<phone:Pivot Title="MON APPLICATION">
    <!--Élément un de tableau croisé dynamique-->
    <phone:PivotItem Header="first">
        <!--Liste double trait avec habillage du texte-->
        <phone:LongListSelector Margin="0,0,-12,0" ItemsSource="{Binding Items}">
            [… code supprimé pour plus de clarté…]
        </phone:LongListSelector>
    </phone:PivotItem>

    <!--Élément deux de tableau croisé dynamique-->
    <phone:PivotItem Header="second">
        <!--Liste double trait, aucun habillage du texte-->
        <phone:LongListSelector Margin="0,0,-12,0" ItemsSource="{Binding Items}">
            [… code supprimé pour plus de clarté…]
        </phone:LongListSelector>
    </phone:PivotItem>
</phone:Pivot>

Ici, le principe est le même que pour le Panorama. Le contrôle Pivot est composé de deux PivotItem, chacun faisant office de container. Dedans il y a un LongListSelector mais tout autre contrôle y trouve sa place. Encore une fois, c’est la propriété Header qui va permettre de donner un titre à la page.
Vous pouvez également voir dans le designer les différents rendus des PivotItem en vous positionnant dans le XAML à leurs niveaux.
Tout comme pour le panorama, vous pouvez allégrement modifier les différentes propriétés, Title, Background… afin de personnaliser ce contrôle. De même, il possède des événements bien pratiques pour être notifié d’un changement de vue et également de quoi se positionner sur celle que l’on veut.
Il est également possible d’utiliser le binding avec ce contrôle, et c’est d’ailleurs ce que vous aurez tendance à souvent faire. Reprenons l’exemple de la liste des tâches qui est particulièrement adapté au contrôle Pivot et améliorons cet exemple. Voici dans un premier temps le XAML :

<Grid x:Name="LayoutRoot" Background="Transparent">
    <phone:Pivot Title="Mes tâches" SelectedIndex="{Binding Index, Mode=TwoWay}" Loaded="Pivot_Loaded" ItemsSource="{Binding ListeEcrans}">
        <phone:Pivot.HeaderTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Titre}" />
            </DataTemplate>
        </phone:Pivot.HeaderTemplate>
        <phone:Pivot.ItemTemplate>
            <DataTemplate>
                <ListBox ItemsSource="{Binding ListeDesTaches}">
                    <ListBox.ItemTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding}" Margin="0 20 0 0" />
                        </DataTemplate>
                    </ListBox.ItemTemplate>
                </ListBox>
            </DataTemplate>
        </phone:Pivot.ItemTemplate>
    </phone:Pivot>
</Grid>

Cela ressemble beaucoup à ce que nous avons fait pour le panorama. Une des premières différences vient de la propriété SelectedIndex du Pivot, qui fonctionne comme pour la ListBox. Je l’ai liée à une propriété Index en mode TwoWay afin que la mise à jour de la propriété depuis le code-behind affecte le contrôle mais qu’inversement, un changement de valeur depuis le contrôle mette à jour la propriété.
Du coup, je n’ai plus besoin de l’événement de changement de sélection qui existe également sur le contrôle Pivot. Le reste du XAML est semblable au Panorama.
Passons au code-behind :

public partial class MainPage : PhoneApplicationPage, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(String propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (null != handler)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    private bool NotifyPropertyChanged<T>(ref T variable, T valeur, [CallerMemberName] string nomPropriete = null)
    {
        if (object.Equals(variable, valeur)) return false;

        variable = valeur;
        NotifyPropertyChanged(nomPropriete);
        return true;
    }

    private List<Ecran> _listeEcrans;
    public List<Ecran> ListeEcrans
    {
        get { return _listeEcrans; }
        set { NotifyPropertyChanged(ref _listeEcrans, value); }
    }

    private int _index;
    public int Index
    {
        get { return _index; }
        set
        {
            IsolatedStorageSettings.ApplicationSettings["PageCourante"] = value;
            NotifyPropertyChanged(ref _index, value);
        }
    }

    public MainPage()
    {
        InitializeComponent();

        ListeEcrans = new List<Ecran>
        {
            new Ecran 
            { 
                Titre = "Aujourd'hui",
                ListeDesTaches = new List<string> { "Tondre la pelouse", "Arroser les plantes"}
            },
            new Ecran 
            { 
                Titre = "Demain",
                ListeDesTaches = new List<string> { "Passer l'aspirateur", "Laver la voiture"}
            }
        };

        DataContext = this;
    }

    private void Pivot_Loaded(object sender, RoutedEventArgs e)
    {
        if (IsolatedStorageSettings.ApplicationSettings.Contains("PageCourante"))
        {
            Index = (int)IsolatedStorageSettings.ApplicationSettings["PageCourante"];
        }
    }
}

Nous voyons que j’ai rajouté une propriété Index, et qu’à l’intérieur de son modificateur, j’enregistre dans le répertoire local la valeur de la sélection. Ensuite, au chargement du pivot et lorsque la valeur existe, je positionne la propriété Index à la valeur enregistrée lors d’une visite précédente.
Et voilà, vous pouvez admirer le rendu à la figure suivante.

Le binding du contrôle Pivot
Le binding du contrôle Pivot

Vous pourriez trouver que les deux contrôles se ressemblent, et ce n’est pas complètement faux. Je vous rappelle juste que le contrôle Panorama permet d’afficher plusieurs données sur une seule grosse page alors que le contrôle Pivot est utilisé pour présenter la même donnée sur plusieurs pages. Vous appréhenderez au fur et à mesure la subtile différence entre ces deux contrôles. ^^

  • Le Panorama et le Pivot permettent de « naviguer » à l’intérieur des données.

  • Le Panorama sert à voir un petit bout d’un plus gros écran dont la taille dépasse celle de l’écran du téléphone.

  • Le Pivot permet plutôt de voir la même donnée sur plusieurs pages.

  • Chaque changement de vue se fait grâce à un glissement de doigt.

  • Le contrôle Pivot est particulièrement bien adapté au binding.

Exemple de certificat de réussite
Exemple de certificat de réussite