• 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

Gérer l'orientation

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

Jusqu’à présent, nous avons toujours utilisé notre téléphone (enfin, surtout l'émulateur) dans sa position portrait, où les boutons sont orientés vers le bas, le téléphone ayant son côté le plus long dans le sens vertical, droit devant nous. Il peut se tenir aussi horizontalement, en mode paysage. Suivant les cas, les applications sont figées et ne peuvent se tenir que dans un sens. Dans d’autres cas, elles offrent la possibilité de tenir son téléphone dans plusieurs modes, révélant au passage une interface légèrement différente.
Si on tient le téléphone en mode paysage, il y a plus de place en largeur, il peut être pertinent d’afficher plus d’informations…

Les différentes orientations

L’orientation portrait est particulièrement adaptée à la visualisation des listes déroulantes pouvant contenir beaucoup d’éléments. Il devient également naturel de faire défiler les éléments vers le bas.
Par contre, en mode paysage, il y a plus de place en largeur. Si on garde le même type d’affichage, il risque d’y avoir un gros trou sur la droite, et la liste d’éléments deviendra sans doute un peu moins visible. De plus, dans ce mode-là, il semble plus naturel de faire défiler les éléments de gauche à droite. Et quel mode portrait adopter quand on tourne son téléphone vers la droite, pour avoir les boutons à gauche ou quand on tourne le téléphone vers la gauche et ainsi avoir les boutons à droite ?

Un téléphone Windows Phone sait détecter quand il change d’orientation. Il est capable de lever un événement nous permettant de réagir à ce changement d’orientation… mais n’anticipons pas et revenons à la base, la page. Si nous regardons en haut dans les propriétés de la page, nous pouvons voir que nous avons régulièrement créé des pages qui possèdent ces propriétés :

SupportedOrientations="Portrait" Orientation="Portrait"

Cela indique que l’application supporte le mode portrait et que la page démarre en mode portrait. Il est possible de changer les valeurs de ces propriétés. On peut par exemple affecter les valeurs suivantes à la propriété SupportedOrientation :

  • Portrait

  • Landscape

  • PortraitOrLandscape

qui signifient respectivement portrait, paysage et portrait-ou-paysage. Ainsi, si l’on positionne cette dernière valeur, l’application va automatiquement réagir au changement d’orientation. Modifions donc cette propriété pour utiliser le mode portrait et paysage :

SupportedOrientations="PortraitOrLandscape" Orientation="Portrait"

et utilisons par exemple le code XAML suivant :

<Grid x:Name="LayoutRoot" Background="Transparent">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
        <TextBlock x:Name="ApplicationTitle" Text="MON APPLICATION" Style="{StaticResource PhoneTextNormalStyle}"/>
        <TextBlock x:Name="PageTitle" Text="Hello World" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
    </StackPanel>

    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
        <StackPanel>
            <TextBlock Text="Bonjour à tous" />
            <Button Content="Cliquez-moi" />
        </StackPanel>
    </Grid>
</Grid>

Si nous démarrons l’application nous obtenons la figure suivante.

Emulateur en mode portrait avec les boutons de l'émulateur pour le faire pivoter
Emulateur en mode portrait avec les boutons de l'émulateur pour le faire pivoter

Il est possible de simuler un changement d’orientation avec l’émulateur, il suffit de cliquer sur les boutons disponibles dans la barre en haut à droite de l’émulateur, comme indiqué sur l’image du dessus. L’écran est retourné et l’interface est automatiquement adaptée à la nouvelle orientation, comme vous pouvez le voir à la figure suivante.

L'émulateur en mode paysage
L'émulateur en mode paysage

Ceci est possible grâce à la propriété que nous avons ajoutée. Les contrôles se redessinent automatiquement lors du changement d’orientation.
Remarquez que lorsqu’on retourne le téléphone dans l’autre mode paysage, le principe reste le même.

Ici, le changement d’orientation reste globalement élégant. Mais si nous avions utilisé un contrôle en positionnement absolu grâce à un Canvas par exemple, il est fort possible qu’il se retrouve à un emplacement non voulu lors du changement d’orientation. C’est la même chose avec un StackPanel, peut être que tous les éléments tiennent dans la hauteur en mode portrait, mais il faudra sûrement rajouter un ScrollViewer en mode paysage…
Par exemple, le XAML suivant en mode portrait semble avoir ses composants centrés :

<Grid x:Name="LayoutRoot" Background="Transparent">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
        <TextBlock x:Name="ApplicationTitle" Text="MON APPLICATION" Style="{StaticResource PhoneTextNormalStyle}"/>
        <TextBlock x:Name="PageTitle" Text="Hello World" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
    </StackPanel>

    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
        <Canvas>
            <TextBlock x:Name="MonTextBlock" Text="Bonjour à tous" Canvas.Left="160" />
            <Button x:Name="MonBouton" Content="Cliquez-moi" Canvas.Left="140" Canvas.Top="40" />
        </Canvas>
    </Grid>
</Grid>

Alors que si on le retourne, on peut voir un beau trou à droite (voir la figure suivante).

Positionnement décalé suivant l'orientation
Positionnement décalé suivant l'orientation

Détecter les changements d'orientation

Il est possible de détecter le type d’orientation d’un téléphone en consultant la propriété Orientation d’une page.

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

        switch (Orientation)
        {
            case PageOrientation.Landscape:
            case PageOrientation.LandscapeLeft:
            case PageOrientation.LandscapeRight:
                MessageBox.Show("Mode paysage");
                break;
            case PageOrientation.Portrait:
            case PageOrientation.PortraitDown:
            case PageOrientation.PortraitUp:
                MessageBox.Show("Mode portrait");
                break;
        }
    }
}

Que l’on peut également simplifier de cette façon :

if ((Orientation & PageOrientation.Landscape) == PageOrientation.Landscape)
{
    MessageBox.Show("Mode paysage");
}
else
{
    MessageBox.Show("Mode portrait");
}

Et, bien souvent plus utile, il est possible d’être notifié des changements d’orientation. Cela pourra permettre par exemple de réaliser des ajustements. Pour être prévenu, il suffit de substituer la méthode OnOrientationChanged :

protected override void OnOrientationChanged(OrientationChangedEventArgs e)
{
    switch (e.Orientation)
    {
        case PageOrientation.Landscape:
        case PageOrientation.LandscapeLeft:
        case PageOrientation.LandscapeRight:
            // faire des choses pour le mode paysage
            break;
        case PageOrientation.Portrait:
        case PageOrientation.PortraitDown:
        case PageOrientation.PortraitUp:
            // faire des choses pour le mode portrait 
            break;
    }
    base.OnOrientationChanged(e);
}

que l'on peut à nouveau simplifier en :

protected override void OnOrientationChanged(OrientationChangedEventArgs e)
{
    if ((e.Orientation & PageOrientation.Landscape) == PageOrientation.Landscape)
    {
        // faire des choses pour le mode paysage
    }
    else
    {
        //faire des choses pour le mode portrait
    }
    base.OnOrientationChanged(e);
}

Prenons notre précédent exemple. Nous pouvons modifier les propriétés de dépendance Canvas.Left pour avoir :

protected override void OnOrientationChanged(OrientationChangedEventArgs e)
{
    if ((e.Orientation & PageOrientation.Landscape) == PageOrientation.Landscape)
    {
        MonTextBlock.SetValue(Canvas.LeftProperty, 320.0);
        MonBouton.SetValue(Canvas.LeftProperty, 300.0);
    }
    else
    {
        MonTextBlock.SetValue(Canvas.LeftProperty, 160.0);
        MonBouton.SetValue(Canvas.LeftProperty, 140.0);
    }
    base.OnOrientationChanged(e);
}

Ainsi, nous « recentrons » les contrôles à chaque changement d’orientation.

Stratégies de gestion d'orientation

Bien sûr, l’exemple précédent est un mauvais exemple, vous l’aurez compris. La bonne solution est d’utiliser correctement le système de placement du XAML pour centrer nos composants, avec uniquement du XAML :

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <StackPanel>
        <TextBlock x:Name="MonTextBlock" Text="Bonjour à tous" HorizontalAlignment="Center" />
        <Button x:Name="MonBouton" Content="Cliquez-moi" HorizontalAlignment="Center" />
    </StackPanel>
</Grid>

Mais modifier des propriétés de contrôles permet quand même de pouvoir faire des choses intéressantes. Prenez l’exemple suivant où nous positionnons des contrôles dans une grille :

<Grid x:Name="LayoutRoot" Background="Transparent">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
        <TextBlock x:Name="ApplicationTitle" Text="MON APPLICATION" Style="{StaticResource PhoneTextNormalStyle}"/>
        <TextBlock x:Name="PageTitle" Text="Hello World" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
    </StackPanel>

    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Button x:Name="Accueil" Content="Accueil" />
        <Button Grid.Column="1" x:Name="Cours" Content="Cours" />
        <Button Grid.Column="2" x:Name="MesMPs" Content="Mes MPs" />
        <Image x:Name="ImageSdz" Grid.Row="1" Grid.RowSpan="2" Grid.ColumnSpan="3" Source="http://open-e-education-2013.openclassrooms.com/img/logos/logo-openclassrooms.png" />
    </Grid>
</Grid>

Observez la figure suivante pour le rendu.

La grille en mode portrait
La grille en mode portrait

Nous pouvons utiliser la technique précédente pour changer la disposition des contrôles dans la grille afin de produire un autre rendu en mode paysage. Pour cela, voyez le code-behind suivant :

protected override void OnOrientationChanged(OrientationChangedEventArgs e)
{
    if ((e.Orientation & PageOrientation.Landscape) == PageOrientation.Landscape)
    {
        Grid.SetRow(ImageSdz, 0);
        Grid.SetRow(Accueil, 0);
        Grid.SetRow(Cours, 1);
        Grid.SetRow(MesMPs, 2);

        Grid.SetColumn(ImageSdz, 0);
        Grid.SetColumn(Accueil, 2);
        Grid.SetColumn(Cours, 2);
        Grid.SetColumn(MesMPs, 2);

        Grid.SetRowSpan(ImageSdz, 3);
        Grid.SetColumnSpan(ImageSdz, 2);
    }
    else
    {
        Grid.SetRow(ImageSdz, 1);
        Grid.SetRow(Accueil, 0);
        Grid.SetRow(Cours, 0);
        Grid.SetRow(MesMPs, 0);

        Grid.SetColumn(ImageSdz, 0);
        Grid.SetColumn(Accueil, 0);
        Grid.SetColumn(Cours, 1);
        Grid.SetColumn(MesMPs, 2);

        Grid.SetRowSpan(ImageSdz, 2);
        Grid.SetColumnSpan(ImageSdz, 3);
    }
    base.OnOrientationChanged(e);
}

Ainsi, on déplace les contrôles d’une colonne à l’autre, on arrange la fusion de certaines lignes, etc. Regardez la figure suivante pour le rendu.

La grille réordonnée en mode paysage
La grille réordonnée en mode paysage

Et voilà un positionnement complètement différent, plutôt sympathique. N’oubliez bien sûr pas de repositionner les contrôles en mode portrait également, sinon ils garderont la disposition précédente.

Cependant, il est parfois judicieux de ne pas gérer le changement d’orientation et de forcer l’application à une unique orientation. Beaucoup de jeux sont bloqués en mode paysage et beaucoup d’applications, notamment dès qu’il y a un panorama ou un pivot forcent l’orientation en portrait. C’est un choix qui peut se justifier et il vaut parfois mieux proposer une expérience utilisateur optimale dans un mode, qu’une expérience bancale dans les deux modes. Nous avons vu qu’il suffisait de positionner la propriété SupportedOrientations à Portrait ou Landscape. Sachant que ceci peut également se faire via le code-behind, si par exemple toute l’application gère la double orientation, mais qu’un écran en particulier peut uniquement être utilisé en mode portrait.

Si vous le pouvez, c’est également très pratique que vos applications gèrent différentes orientations. La solution la plus pratique est d’utiliser la mise en page automatique du XAML, comme ce que nous avons fait en centrant le bouton et la zone de texte grâce à la propriété HorizontalAlignement. La première chose à faire est de toujours utiliser des conteneurs qui peuvent se redimensionner automatiquement, d’éviter de fixer des largeurs ou des hauteurs et de penser à des contrôles pouvant faire défiler leur contenu (ListBox, ScrollViewer, etc.). Cela peut par contre parfois donner des rendus pas toujours esthétiques, avec un bouton qui prend toute la longueur de l’écran par exemple. Pensez-bien au design de vos écrans et n’oubliez pas de tester dans toutes les orientations possibles.
Comme on l’a déjà vu, vous pouvez également faire vos ajustements lors de la détection du changement d’orientation. Vous pouvez modifier la hauteur/largeur d’un contrôle, changer son positionnement, en ajouter un nouveau ou au contraire, supprimer un contrôle qui ne rentre plus. L’inconvénient de cette technique est qu’elle requiert plus de code et qu’elle est plus compliquée à mettre en place avec une approche MVVM.

Vous pouvez également rediriger l’utilisateur sur une page adaptée à un mode précis lorsqu’il y a un changement d’orientation. Par exemple sur la PagePortrait.xaml :

protected override void OnOrientationChanged(OrientationChangedEventArgs e)
{
    if ((e.Orientation & PageOrientation.Landscape) == PageOrientation.Landscape)
    {
        NavigationService.Navigate(new Uri("/PagePaysage.xaml", UriKind.Relative));
    }
    base.OnOrientationChanged(e);
}

et sur la PagePaysage.xaml :

protected override void OnOrientationChanged(OrientationChangedEventArgs e)
{
    if (!((e.Orientation & PageOrientation.Landscape) == PageOrientation.Landscape))
    {
        NavigationService.Navigate(new Uri("/PagePortrait.xaml", UriKind.Relative));
    }
    base.OnOrientationChanged(e);
}

L’inconvénient est que cela fait empiler beaucoup de pages dans la navigation. Il faut alors retirer la page précédente de la pile de navigation. Il suffit de redéfinir la méthode OnNavigatedTo et d’utiliser la méthode RemoveBackEntry :

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    NavigationService.RemoveBackEntry();
    base.OnNavigatedTo(e);
}

Dans le même style, nous pouvons créer des contrôles utilisateurs dédiés à une orientation, que nous affichons et masquons en fonction de l’orientation. Un contrôle utilisateur est un bout de page que nous pouvons réutiliser dans n’importe quelle page. Pour créer un nouveau contrôle utilisateur, on utilise le modèle « Contrôle utilisateur Windows Phone » lors d’un clic droit sur le projet, ajouter, nouvel élément (voir la figure suivante).

Ajout d'un contrôle utilisateur
Ajout d'un contrôle utilisateur

Le contrôle utilisateur portrait pourra contenir le XAML suivant :

<Grid x:Name="LayoutRoot">
    <StackPanel>
        <TextBlock Text="Je suis en mode portrait" HorizontalAlignment="Center" />
    </StackPanel>
</Grid>

Alors que le contrôle utilisateur paysage contiendra :

<Grid x:Name="LayoutRoot">
    <StackPanel>
        <TextBlock Text="Je suis en mode paysage" HorizontalAlignment="Center" />
    </StackPanel>
</Grid>

Nous pourrons alors avoir le code-behind suivant dans la page principale :

public partial class MainPage : PhoneApplicationPage
{
    private ControlePortrait controlePortrait;
    private ControlePaysage controlePaysage;

    public MainPage()
    {
        InitializeComponent();
        controlePortrait = new ControlePortrait();
        controlePaysage = new ControlePaysage();

        ContentPanel.Children.Add(controlePortrait);
    }

    protected override void OnOrientationChanged(OrientationChangedEventArgs e)
    {
        if ((e.Orientation & PageOrientation.Landscape) == PageOrientation.Landscape)
        {
            ContentPanel.Children.Remove(controlePortrait);
            ContentPanel.Children.Add(controlePaysage);
        }
        else
        {
            ContentPanel.Children.Remove(controlePaysage);
            ContentPanel.Children.Add(controlePortrait);
        }
        base.OnOrientationChanged(e);
    }
}

Le principe est d’instancier les deux contrôles et de les ajouter ou de les retirer à la grille en fonction de l’orientation. L’inconvénient, comme pour la précédente stratégie, est qu’il faut potentiellement dupliquer pas mal de code dans chacun des contrôles.

Enfin, nous pouvons utiliser le gestionnaire d’état. De la même façon que nous l’avons vu dans la partie précédente pour les contrôles, une page peut avoir plusieurs états. Il suffit de considérer que l’état normal est celui en mode portrait et que nous allons créer un état pour le mode paysage. Pour cela, le plus simple est d’utiliser Blend. Démarrons-le et commençons à créer notre page (voir la figure suivante).

Page portrait dans Blend
Page portrait dans Blend

qui correspond au XAML suivant :

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <TextBlock Text="Mode Portrait" HorizontalAlignment="Center" />
    <Button Content="Cliquez-moi" Width="200" VerticalAlignment="Bottom" HorizontalAlignment="Right" />
</Grid>

Maintenant, nous allons créer un nouvel état. Sélectionnez la page dans l’arbre visuel (en bas à gauche) puis dans l’onglet état, cliquez sur le petit bouton à droite permettant d’ajouter un groupe d’état, comme indiqué sur la figure suivante.

Ajout d'un groupe d'état dans Blend
Ajout d'un groupe d'état dans Blend

Nous pouvons le nommer par exemple EtatsOrientations.

Avant de pouvoir créer un état propre au mode paysage, nous allons positionner le designer en mode paysage. Pour cela il faut aller dans l’onglet Appareil qui est un peu plus à droite que l’onglet états et basculer en landscape (voir la figure suivante).

Passer en mode paysage dans Blend
Passer en mode paysage dans Blend

Revenez dans l’onglet état, et cliquez à présent à droite sur « ajouter un état », comme indiqué à la figure suivante.

Ajout d'un état dans Blend
Ajout d'un état dans Blend

Nous pouvons appeler ce nouvel état ModePaysage (voir la figure suivante).

Ajout d'un état dans Blend
Ajout d'un état dans Blend

Nous pouvons voir que l’état est en mode enregistrement, c’est-à-dire que nous allons pouvoir maintenant modifier la position des contrôles pour créer notre écran en mode paysage. Changez la disposition et changez la propriété Text du TextBlock pour afficher Mode paysage, comme indiqué à la figure suivante.

Modification de la disposition des contrôles en mode paysage
Modification de la disposition des contrôles en mode paysage

Vous pouvez maintenant sauvegarder vos modifications, fermer Blend et revenir dans Visual Studio. Remarquons que Blend nous a modifié notre XAML, notamment pour rajouter de quoi faire une animation des contrôles :

<TextBlock x:Name="textBlock" Text="Mode Portrait" HorizontalAlignment="Center" RenderTransformOrigin="0.5,0.5" >
    <TextBlock.RenderTransform>
        <CompositeTransform/>
    </TextBlock.RenderTransform>
</TextBlock>
<Button x:Name="button" Content="Cliquez-moi" Width="200" VerticalAlignment="Bottom" HorizontalAlignment="Right" RenderTransformOrigin="0.5,0.5" >
    <Button.RenderTransform>
        <CompositeTransform/>
    </Button.RenderTransform>
</Button>

De même, il a rajouté de quoi gérer les états :

<VisualStateManager.VisualStateGroups>
    <VisualStateGroup x:Name="EtatsOrientations">
    	<VisualState x:Name="ModePaysage">
    		<Storyboard>
    			<DoubleAnimation Duration="0" To="-254" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="textBlock" d:IsOptimized="True"/>
    			<DoubleAnimation Duration="0" To="182" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)" Storyboard.TargetName="textBlock" d:IsOptimized="True"/>
    			<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(TextBlock.Text)" Storyboard.TargetName="textBlock">
    				<DiscreteObjectKeyFrame KeyTime="0" Value="Mode Paysage"/>
    			</ObjectAnimationUsingKeyFrames>
    			<DoubleAnimation Duration="0" To="-300" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="button" d:IsOptimized="True"/>
    			<DoubleAnimation Duration="0" To="-208" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)" Storyboard.TargetName="button" d:IsOptimized="True"/>
    		</Storyboard>
    	</VisualState>
    </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

Nous pouvons constater qu’il s’opère des changements de coordonnées via translations, ainsi que la modification du texte du TextBlock. À cet état, ajoutons un état ModePortrait vide pour signifier l’état de départ, le plus simple ici est de le faire dans le XAML, avec :

<VisualStateGroup x:Name="EtatsOrientations">
    <VisualState x:Name="ModePortrait" />
    <VisualState x:Name="ModePaysage">
    	<Storyboard>
    		…
    	</Storyboard>
    </VisualState>
</VisualStateGroup>

Enfin, vous allez avoir besoin d’indiquer que vous gérez le multi-orientation et que vous démarrez en mode portrait avec :

SupportedOrientations="PortraitOrLandscape" Orientation="Portrait"

Puis maintenant, il faudra réagir à un changement d’orientation et modifier l’état de la page grâce au VisualStateManager :

protected override void OnOrientationChanged(OrientationChangedEventArgs e)
{
    if ((e.Orientation & PageOrientation.Landscape) == PageOrientation.Landscape)
    {
        VisualStateManager.GoToState(this, "ModePaysage", true);
    }
    else
    {
        VisualStateManager.GoToState(this, "ModePortrait", true);
    }

    base.OnOrientationChanged(e);
}

Maintenant, vous pouvez changer l’orientation de l’émulateur ; vous pouvez voir le rendu à la figure suivante.

Changement d'état de la page lors du changement d'orientation
Changement d'état de la page lors du changement d'orientation

Et voilà ! :)

  • Un téléphone sait détecter les changements d’orientation, ainsi une application peut s’exécuter en mode portrait ou en mode paysage.

  • Pour être averti d’un changement d’orientation, il suffit de redéfinir la méthode OnOrientationChanged.

  • Il faut toujours tester à fond notre application avec différentes orientations si l’on souhaite gérer le changement d’orientation.

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