1. Introduction

Windows Phone 7 (WP7) est la nouvelle plateforme de développement de Microsoft destinée aux smartphones.

Si vous découvrez la programmation WP7, n'hésitez pas à consulter l'article précédent : tutoriel d'introduction au développement d'applications Windows Phone 7.

Dans ce deuxième tutoriel, vous découvrirez la puissance du contrôle ListBox WP7 et comment il s'adapte au développement sur téléphone.
Vous verrez aussi comment traiter des événements simples, comme un click sur un bouton par exemple.
Vous verrez enfin comment faire persister de l'information entre les différents démarrages du téléphone et lorsque l'application est suspendue.

2. Utiliser la ListBox WP7

Je vous ai indiqué dans le tutoriel précédent que les contrôles standards ont été réécrits pour tirer parti des fonctionnalités du téléphone.
C'est le cas de la ListBox qui prend en charge automatiquement un effet classique dans les téléphones : le défilement vertical.
Vous allez voir que le rendu est plutôt élégant et qu'il n'y a rien à faire...

Commençons par créer une nouvelle application comme on l'a déjà fait dans le tutoriel précédent : Fichier -> Nouveau projet -> Application Windows Phone. J'appelle cette application : ListBoxDemo.

Nous allons développer une application toute simple de "todo list".

Dans le Xaml, il s'agit de rajouter un TextBox pour saisir une valeur et un bouton pour ajouter la valeur saisie.
Ensuite, nous pouvons ajouter une ListBox qui contiendra un TextBlock pour afficher les valeurs de la liste de TODO :

MainPage.xaml
Sélectionnez

<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="Demo ListBox" Style="{StaticResource PhoneTextNormalStyle}"/>
  </StackPanel>

  <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <!--<TextBlock Text="Nom : " Margin="0 20 43 0" FontSize="35" />-->
    <Grid>
      <TextBlock Text="TODO : " Margin="0 20" FontSize="45" />
      <TextBox Margin="150 15 0 0" Height="80" VerticalAlignment="Top" FontSize="30" 
	    Name="todoTb" Width="320" HorizontalAlignment="Left" />
      <Button Height="70" Width="250" VerticalAlignment="Top" Margin="0 100 0 0" 
		Click="Button_Click" Content="Ajouter" />
      <ListBox Margin="0 190 0 0" Name="listbox">
        <ListBox.ItemTemplate>
          <DataTemplate>
            <Border Background="#FFDEDEDE" CornerRadius="10" Margin="20">
              <StackPanel Width="400">
                <TextBlock HorizontalAlignment="Center" Text="{Binding Todo}" 
				  FontSize="45" Foreground="Blue" Margin="20" />
              </StackPanel>
            </Border>
          </DataTemplate>
        </ListBox.ItemTemplate>
      </ListBox>
    </Grid>
  </Grid>
</Grid>

Si vous démarrez l'application, vous pourrez noter que la TextBox prend automatiquement en charge l'affichage d'un clavier virtuel et qu'il n'y a rien à faire de particulier.

Image non disponible
Figure 1 : Le clavier virtuel.



En regardant ce Xaml, on observe notamment le TextBox "todoTb" ainsi qu'un bouton qui possède une propriété Click : Click="Button_Click".
On peut ainsi constater que l'appui sur un bouton (le "tap") peut être géré par le même handler que pour une application Silverlight pilotée à la souris.

Enfin, nous pouvons voir la ListBox "listbox" qui possède un TextBlock dans sa propriété ItemTemplate avec une valeur liée à la propriété Todo : Text="{Binding Todo}".

Pour assurer le binding, créons une classe TodoElement qui implémente INotifyPropertyChanged :

TodoElement.cs
Sélectionnez

public class TodoElement : INotifyPropertyChanged
{
	private string todoElement;

	public event PropertyChangedEventHandler PropertyChanged;

	public void NotitfyPropertyChanged(string propertyName)
	{
		if (PropertyChanged != null)
		{
			PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
		}
	}

	public string Todo
	{ 
		get { return todoElement; }
		set 
		{
			todoElement = value;
			NotitfyPropertyChanged("Todo");
		}
	}
}

Et assurons ce binding dans le code behind via la propriété ItemsSource de la ListBox grâce à une ObservableCollection de TodoElement :

MainPage.xaml.cs
Sélectionnez

public partial class MainPage : PhoneApplicationPage
{
	ObservableCollection<TodoElement> listTodo = new ObservableCollection<TodoElement>();

	public MainPage()
	{
		InitializeComponent();

		listbox.ItemsSource = listTodo;
	}

	private void Button_Click(object sender, RoutedEventArgs e)
	{
		listTodo.Add(new TodoElement { Todo = todoTb.Text });
	}
}

3. Gestion de l'événement d'appui sur le bouton


Nous voyons également la méthode Button_Click qui permettra de gérer l'événement d'appui sur le bouton. Ici, nous rajouterons un élément à la liste.

On peut voir que la gestion de l'événement d'appui sur une touche se passe de la même façon que la gestion du clic sur une application Silverlight classique. On verra lors de prochains tutoriels qu'il est possible de gérer les événements utilisateurs d'une autre façon, pour reconnaitre par exemple les "glisser" (swipe) ou les gestes avec une structure plus complexe.

Pour des applications simples de gestion, comme ici, on aura tout à fait intérêt à utiliser cette méthode, en utilisant les handler de clic sur un bouton par exemple.

4. Démarrage de l'application dans l'émulateur

Démarrons l'application dans l'émulateur (F5) et ajoutons quelques éléments...
Ce qui donne :

Image non disponible
Figure 2 : Listbox avant défilement vertical.



Et si on fait défiler vers le bas en maintenant la pression sur la souris et en faisant glisser vers le haut, on voit le défilement attendu ; que vous pouvez retrouver sur la vidéo ci-dessous.



Vous conviendrez que l'effet est plutôt réussi. Et tout ça sans code supplémentaire. La Listbox WP7 intègre par défaut ce comportement, ce qui est bien pratique.

Cliquons sur "back", qui a pour effet de terminer l'application, et relançons-la.
Horreur ! Tout a disparu. Ceci est plutôt dérangeant pour une liste de TODO. Si on doit se rappeler des tâches à faire et les ressaisir à chaque fois, l'application perd grandement de son intérêt...

Forcément me diriez-vous, nous n'avons rien sauvegardé. Voyons comme faire.

5. Persistance dans l'Isolated Storage

Pour faire persister de l'information, nous avons à notre disposition l'Isolated Storage. Le principe sera de sérialiser notre liste de TODO dans l'Isolated Storage au moment où l'on souhaite la sauvegarder. De même, nous pourrons la désérialiser quand l'on souhaitera recharger les informations.

Pour ce faire, on va utiliser une classe helper qui est fournie dans le training kit de WP7 qui permet de sérialiser et désérialiser en JSON.
Nous aurons besoin pour ce faire de rajouter une référence à System.Servicemodel.Web.

Image non disponible
Figure 3 : Ajout de la référence à System.Servicemodel.Web.dll.



On ajoute la classe IsolatedStorageHelper dont le code ci-dessous est assez simple à comprendre :

IsolatedStorageHelper.cs
Sélectionnez

// ----------------------------------------------------------------------------------
// Microsoft Developer & Platform Evangelism
// 
// Copyright (c) Microsoft Corporation. All rights reserved.
// 
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES 
// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
// ----------------------------------------------------------------------------------
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
// ----------------------------------------------------------------------------------

using System.IO;
using System.IO.IsolatedStorage;
using System.Runtime.Serialization.Json;
using System.Text;

namespace ListBoxDemo
{
    public static class IsolatedStorageHelper
    {
        public static T GetObject<T>(string key)
        {
            if (IsolatedStorageSettings.ApplicationSettings.Contains(key))
            {
                string serializedObject = IsolatedStorageSettings.ApplicationSettings[key].ToString();
                return Deserialize<T>(serializedObject);
            }

            return default(T);
        }

        public static void SaveObject<T>(string key, T objectToSave)
        {
            string serializedObject = Serialize(objectToSave);
            IsolatedStorageSettings.ApplicationSettings[key] = serializedObject;
        }

        public static void DeleteObject(string key)
        {
            IsolatedStorageSettings.ApplicationSettings.Remove(key);
        }

        private static string Serialize(object objectToSerialize)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                DataContractJsonSerializer serializer = new DataContractJsonSerializer(objectToSerialize.GetType());
                serializer.WriteObject(ms, objectToSerialize);
                ms.Position = 0;

                using (StreamReader reader = new StreamReader(ms))
                {
                    return reader.ReadToEnd();
                }
            }
        }

        private static T Deserialize<T>(string jsonString)
        {
            using (MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(jsonString)))
            {
                DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T));
                return (T)serializer.ReadObject(ms);
            }
        }
    }
}

J'ai choisi de sauvegarder les informations après chaque ajout à la liste de TODO, mais il pourrait tout à fait être envisageable de faire les sauvegardes lors de l'événement de fermeture de l'application.

Ce qui nous donne :

MainPage.xaml.cs
Sélectionnez

private void Button_Click(object sender, RoutedEventArgs e)
{
	listTodo.Add(new TodoElement { Todo = todoTb.Text });

	IsolatedStorageHelper.SaveObject("ListeTodo", listTodo);
}

Pour recharger les informations, on peut modifier le constructeur de MainPage pour avoir :

MainPage.xaml.cs
Sélectionnez

List<TodoElement> todoElementList;
ObservableCollection<TodoElement> listTodo = new ObservableCollection<TodoElement>();

public MainPage()
{
	InitializeComponent();

	todoElementList = IsolatedStorageHelper.GetObject<List<TodoElement>>("ListeTodo");
	if (todoElementList == null)
		todoElementList = new List<TodoElement>();

	foreach (TodoElement todoElement in todoElementList)
	{
		listTodo.Add(todoElement);
	}
	listbox.ItemsSource = listTodo;
}

Redémarrons l'application et saisissons quelques valeurs.

Cliquons sur back, le débogueur se ferme. Relançons l'application ainsi que le débogueur (F5) et ô miracle, notre Listbox est remplie avec les informations déjà saisies.
Merci la sérialisation.

Il ne manquera plus à notre application qu'à savoir supprimer des éléments de la liste pour qu'elle soit vraiment fonctionnelle.
Ceci est plutôt simple à faire, il faudrait rajouter un bouton dans l'ItemTemplate de la Listbox, brancher un événement click dessus et supprimer de la liste le todo sélectionné.
Comme ce n'est pas l'objet du tutoriel, nous n'allons pas le faire. Je vous encourage chaudement à essayer afin de vous entrainer.

Juste pour le plaisir, n'hésitez pas à passer en debug dans l'IsolatedStorageHelper, vous pourrez voir notamment comment est sérialisée notre liste de Todo. Par exemple :

MainPage.xaml.cs
Sélectionnez

[{"Todo":"aaa"},{"Todo":"bbb"}]

est la représentation JSON d'une liste contenant deux todo : "aaa" et "bbb".

6. Réactivation d'une application

Disons que notre application est fonctionnelle et que je suis en train de noter un élément à faire et que je reçois un appel. Ou alors, j'ai besoin d'aller chercher une information sur Internet pour compléter ma saisie. Ce qui peut se résumer par le scénario suivant :

Je lance mon application. Je commence à saisir. J'appuie sur start pour aller dans le navigateur, je fais ma recherche, je ferme la recherche et je reviens sur mon application.
Malheur, ce que j'étais en train de saisir a disparu.

C'est le principe de ce qu'on appelle le Tombstone, qui correspond au passage en mode "désactivé" de l'application.
Ce qui se passe en fait c'est que lorsque l'application passe en mode désactivé puis repasse en mode réactivé, alors l'application est relancée depuis le début (ce qui explique que l'on perde nos données saisies) mais à la différence près que l'on passe dans un événement particulier, signe de la réactivation de l'application.

Le modèle de développement de Windows Phone 7 nous offre l'opportunité de stocker des informations temporaires pendant le temps où l'application est désactivée et où elle se réactive.

Pour bien comprendre comment cela fonctionne, ouvrons le fichier App.Xaml.cs et mettons un point d'arrêt dans les méthodes suivantes :

App.xaml.cs
Sélectionnez

// Code à exécuter lorsque l'application démarre (par exemple, à partir de Démarrer)
// Ce code ne s'exécute pas lorsque l'application est réactivée
private void Application_Launching(object sender, LaunchingEventArgs e)
{
}

// Code à exécuter lorsque l'application est activée (affichée au premier plan)
// Ce code ne s'exécute pas lorsque l'application est démarrée pour la première fois
private void Application_Activated(object sender, ActivatedEventArgs e)
{
}

// Code à exécuter lorsque l'application est désactivée (envoyée à l'arrière-plan)
// Ce code ne s'exécute pas lors de la fermeture de l'application
private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
}

// Code à exécuter lors de la fermeture de l'application (par exemple, lorsque l'utilisateur clique sur Précédent)
// Ce code ne s'exécute pas lorsque l'application est désactivée
private void Application_Closing(object sender, ClosingEventArgs e)
{
}

Relancez l'application en debug. On passe dans un premier temps dans l'événement Application_Launching.
Cliquons sur Start et l'événement Application_Deactivated est levé. Cliquons sur back et nous pouvons observer l'affichage d'un texte "reprise" dans l'émulateur :

Image non disponible
Figure 4 : Reprise de l'application avec désactivation.



Puis nous passons dans l'événement Application_Activated.
Cliquons sur back et nous passons dans l'événement Application_Closing.


Pour faire persister temporairement la valeur de la textbox, nous pourrons utiliser le dictionnaire State du PhoneApplicationService.
Pour ce faire, rajoutons un événement de changement de valeur pour la textbox :

MainPage.xaml
Sélectionnez

<TextBox Name="todoTb"  [...] TextChanged="todoTb_TextChanged" />

et dans le code behind :

MainPage.xaml.cs
Sélectionnez

private void todoTb_TextChanged(object sender, TextChangedEventArgs e)
{
    PhoneApplicationService.Current.State["CurrentTodo"] = todoTb.Text;
}

Cela nous permet de stocker les informations dans le dictionnaire d'états temporaires.

Pour récupérer les informations, on peut le faire dans le constructeur de la page par exemple :

MainPage.xaml.cs
Sélectionnez

public MainPage()
{
    InitializeComponent();

    if (PhoneApplicationService.Current.State.ContainsKey("CurrentTodo"))
        todoTb.Text = (string)PhoneApplicationService.Current.State["CurrentTodo"];
       
    ...
}

Recommençons l'opération.
On lance l'application. On clique sur Start, on clique sur Back et on peut constater que la valeur de la TextBox est bien restaurée.

Parfait et cela évitera à nos utilisateurs d'être frustrés si jamais ils ont saisi un long texte...

Notez que nous avons utilisé la persistance au niveau de l'application. Il est également possible d'utiliser la persistance au niveau de la page ; en effet, la PhoneApplicationPage dispose aussi d'un dictionnaire State. Celui-ci sera plutôt utilisé via les événements de navigation (OnNavigatedFrom et OnNavigatedTo) pour faire persister des informations d'affichage (focus, etc.) lors de la navigation.

7. Remarques

La ListBox est un contrôle très puissant, mais elle peut souffrir de lenteur, surtout si elle est très volumineuse.
On pourra avantageusement tirer parti du contrôle écrit par David Anson, le DeferredLoadListBox qui pallie ces problèmes de lenteur en se basant sur des StackPanel.

Accéder à l'article.

8. Téléchargements

Vous pouvez télécharger ici les sources du projet : version rar (75 Ko) , version zip (81 Ko).

9. Conclusion

Ce tutoriel a donc présenté comment la ListBox WP7 fonctionnait sur les téléphones. Nous avons également vu comment réagir à un appui sur un bouton. Enfin, nous avons eu un aperçu de comment faire persister des informations entre les lancements d'applications et lorsque celles-ci passent en mode désactivé.

J'espère que ce tutoriel a pu vous être utile et vous a donné envie de vous lancer dans la création d'applications sur Windows Phone 7.

Remerciements

Je remercie les membres de l'équipe Dotnet pour leurs relectures attentives du document et leurs remarques ainsi que Mahefasoa et jacques_jean pour leurs relectures orthographique.

Contact

Si vous constatez une erreur dans le tutoriel, dans le code source, dans la programmation ou pour toute information, n'hésitez pas à me contacter par le forum.