Création de nouvelles pages et navigation sur Windows Phone 7

Ceci est le sixième billet d’une série sur Windows Phone 7 dans laquelle je construis une application permettant de surveiller la disponibilité de vélos des stations Vélib’ à Paris. Pour rappel, voici la table de matières:

  1. Introduction et installation des outils
  2. Création d’une application et utilisation du contrôle Bing Maps
  3. Le GPS et les services de géolocalisation
  4. Consommation d’un service OData
  5. Ajout de punaises sur une carte Bing Maps
  6. Création de nouvelles pages et navigation
  7. Création de vignettes sur la page d’accueil

Un modèle de programmation Web

Screenshot 2

Vous avez surement remarqué l’absence de fenêtres dans notre application Windows Phone 7. Cela aurait pu vous marquer, notamment si vous êtes habituées au développement sur environnements desktop, voire aussi sur des anciennes versions de Windows Mobile.

Si je devais faire un parallèle entre le développement sur Windows Phone 7 et une autre plateforme, je me verrais obligé de le comparer au développement Web—en tout cas pour ce qui est des applications Silverlight ; les application XNA sont une toute autre histoire.

En effet, si vous examinez le code généré dans le fichier App.xaml.cs vous trouverez qu’au démarrage de l’application on commence par déclarer comme élément racine un objet de type PhoneApplicationFrame. C’est dans ce frame que vont être affichées par la suite vos pages, qui dérivent de la classe PhoneApplicationPage. Le similitudes ne se limitent pas au frame et aux pages. Vous verrez qu’on retrouvera des concepts tels que les URIs, les hyperliens, etc.

Une nouvelle page

Hier nous avons permis à l’utilisateur d’afficher des stations sur sa carte. Il est même capable d’appuyer sur l’une de ces dernières et, de notre côté, nous savons identifier la station qu’il a sélectionnée, mais il nous manque quelque chose pour en afficher les détails.

Ajoutez à votre projet un nouvel élément. Sélectionnez une nouvelle Windows Phone Portrait Page parmi les templates Silverlight for Windows Phone.

Add New Item - UnVelo.Wp7

La page créée ressemble à la page principale lorsque nous avons démarré le projet.

Cette fois-ci, nous allons travailler sur le modèle par défaut en remplaçant certaines valeurs et en le complétant avec les contrôles nécessaires pour obtenir un affichage comme celui sur la capture au début de ce billet.

Commençons par modifier la partie du haut :

<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
    <TextBlock x:Name="ApplicationTitle" Text="UN VELO.FR"
               Style="{StaticResource PhoneTextNormalStyle}"/>
    <TextBlock x:Name="PageTitle" Text="{Binding Number, StringFormat='Station {0}'}"
               Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
```Le nom de l’application sera une valeur constante, en revanche, le titre devra afficher le numéro de station. Nous utilisons donc une expression de _binding_ pour afficher la propriété `Number` de la classe `Station`. Petite nouveauté “Mango” : comme on utilise enfin Silverlight 4, nous avons accès à la propriété `StringFormat` de l’expression de _binding_, ce qui nous permet de personnaliser un peu le texte affiché sans passer par des convertisseurs ou des piles de `TextBlock`s.

En raison de ce qui semblerait être un bug dans le contrôle Bing Maps, je n’ai pas pu placer tous mes contrôles dans le **ContentPanel** fourni pour cet effet. J’ai donc modifié le **LayoutRoot** :
<Grid.Resources>
    <local:StationConverter x:Key="StationConverter" />
</Grid.Resources>

En y ajoutant une ligne, j’ai pu placer la carte juste au dessus du **ContentPanel** : <map:Map Margin=“12,0,12,0” Grid.Row=“1” Grid.ColumnSpan=“2” Height=“250” CredentialsProvider="{StaticResource BingMapsKey}" ZoomLevel=“16” Center="{Binding Converter={StaticResource StationConverter}}"> <map:MapItemsControl x:Name=“pins” ItemsSource="{Binding Converter={StaticResource StationConverter}}"> map:MapItemsControl.ItemTemplate <map:Pushpin Location="{Binding Location}" /> </map:MapItemsControl.ItemTemplate> </map:MapItemsControl> </map:Map>


[![ViewHolder2](/2011/06/viewholder2_thumb.png "ViewHolder2")](/2011/06/viewholder2.png)

Tous les _bindings_ ont été faits en supposant qu’un objet de type `Station` se trouve dans le `DataContext` de la page. Or nous n’avons pas encore défini de station, donc rien ne s’affiche.

On a besoin d’une station !

Naviguer entre les pages
------------------------

Revenons sur la page principale ; plus spécifiquement, dans le gestionnaire de l’évènement `Tap` des punaises :```
private void OnStationTap(object sender, GestureEventArgs e)
{
    var station = (sender as Pushpin).DataContext;
}
```Nous avons une station dont nous voulons afficher les détails.

Pas question ici de créer une nouvelle instance de la classe que nous venons de créer. Je vous rappelle que nous sommes dans un modèle type Web et nous allons tout simplement naviguer entre les pages comme nous l’aurions fait avec des documents HTML.

Pour cela, nous avons à notre disposition un `[NavigationService](http://msdn.microsoft.com/library/system.windows.navigation.navigationservice(v=VS.92).aspx)` qui est accessible par la propriété homonyme de vos pages. Ce service propose une méthode `Navigate` qui prend en paramètre un URI relatif vers la destination :

private void OnStationTap(object sender, GestureEventArgs e) { var station = (sender as Pushpin).DataContext;

NavigationService.Navigate(new Uri("/DetailsPage.xaml", UriKind.Relative));

// Dans la version ci-dessous on spécifie l'assembly (UnVelo.Wp7)
// Cela peut-être utile si vos pages se trouvent dans un assembly
// différent de celui en cours d'exécution
// NavigationService.Navigate(new Uri("/UnVelo.Wp7;component/DetailsPage.xaml", UriKind.Relative));

}


En revanche, le code ne fait que ça : passer la page ; et comme nous n’instancions pas la page nous-mêmes, il n’y a, a priori pas de moyen de passer des données via des propriétés de la page comme on a l’habitude de le faire sous d’autres plateformes.

Transmission de données entre les pages
---------------------------------------

Il y a deux manières de passer des données en mémoire d’une page à une autre : la _query string_ (comme dans le Web) et l’état courant du `PhoneApplicationService`.

Comme dans une page web, on peut passer des informations dans l’URI de la page appelée, par exemple, dans la page principale on écrit :

private void OnStationTap(object sender, GestureEventArgs e) { var pin = ((sender as Pushpin).DataContext as Pin);

var uri = string.Format("/DetailsPage.xaml?id={0}", pin.Station.Number);
NavigationService.Navigate(new Uri(uri, UriKind.Relative));

}

Et dans la page détails on peut récupérer la valeur avec le code suivant : public override void OnNavigatedTo(NavigationEventArgs e) { var number = NavigationContext.QueryString[“id”];

// ...

base.OnNavigatedTo(e);

}


Toutefois, dans l’exemple qui nous concerne, nous voulons passer bien plus d’informations que juste le numéro de station, et nous avons toutes ces informations encapsulées dans une instance de la classe `Station`. De plus, il peut exister des cas où les données qui doivent être partagées entre les pages ne peuvent pas être transformées en chaine de caractères pour être concaténées à l’URI de la page. Pour ces cas-là, la classe `PhoneApplicationService` mantient un état courant dans lequel on peut stocker des objets pour les partager entre plusieurs pages :

private void OnStationTap(object sender, GestureEventArgs e) { var pin = ((sender as Pushpin).DataContext as Pin);

PhoneApplicationService.Current.State\["currentStation"\] = pin.Station;

NavigationService.Navigate(new Uri("/DetailsPage.xaml", UriKind.Relative));

}

Les lignes ci-dessus enregistrent dans un emplacement de l’état courant de l’application, que l’on appelle arbitrairement _currentStation_, la station contenue dans le DataContext de la punaise qui a été appuyée par l’utilisateur. Du côté de la page de détails, quelques lignes suffiront à récupérer cet objet pour afficher les informations qu’il contient : protected override void OnNavigatedTo(NavigationEventArgs e) { object station;

if (PhoneApplicationService.Current.State.TryGetValue("currentStation", out station))
{
    this.DataContext = station;
}

base.OnNavigatedTo(e);

}

On aurait pu se contenter de faire tout simplement l’assignation : this.DataContext = PhoneApplicationService.Current.State[“currentStation”];


Grâce aux bindings que nous avions déclarés lorsque nous avons créé la page de détails, le fait d’assigner un objet de type **Station** à son DataContext suffit à afficher la page comme on le voulait.

[![Details_page](/2011/06/details_page_thumb.png "Details_page")](/2011/06/details_page.png)

Si vous essayez d’exécuter le code il vous manquera cependant la classe **StationConverter** que nous avons utilisée pour simplifier certains bindings :

public class StationConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { var station = value as Station;

    if (null == station)
    {
        return value;
    }

    if (typeof(GeoCoordinate) == targetType)
    {
        return new GeoCoordinate((double)station.Latitude, (double)station.Longitude);
    }
    else if (typeof(IEnumerable) == targetType)
    {
        return new object\[\] { new Pin { Station = station } }.AsEnumerable();
    }

    return value;
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
    throw new NotImplementedException();
}

}


---
### Comments:

[Jérémie Joachim](http://twitter.com/j_joachim "") - <time datetime="2012-03-22 02:56:00">Mar 4, 2012</time>

Très bon tutoriel. J'ai dû changer 2, 3 p'tites choses mais ça m'a bien fait avancé :)

---

[madd0](http://blog.madd0.com "mauricio.diazorlich@gmail.com") - <time datetime="2012-03-22 09:27:00">Mar 4, 2012</time>

Si les 2-3 petites choses étaient des bugs dans mon code, je veux bien que vous nous les partagiez, pour corriger l'article. Bon code ;)

---

[Jérémie Corpinot](http://twitter.com/jeremiecorpinot "") - <time datetime="2012-12-14 01:34:00">Dec 5, 2012</time>

Non, je devais juste adapté mon code, pas de bug :)

---