[WP7] Alternative tiles avec arrière-plan généré dynamiquement
Au cours d’un de mes précédents articles sur Mango, j’ai déclaré qu’il n’était pas possible de générer dynamiquement l’arrière-plan d’une tile sans passer par un serveur tiers pour la stocker. C’était une erreur, et je m’en excuse. Il est en effet possible de référencer une image stockée dans l’isolated storage en utilisant la syntaxe “isostore:/chemin”. A partir de là, toutes les fantaisies sont possibles.
Pour illustrer ça, reprenons l’application de gestion de tâches de mes précédents billets. Petit rappel, il s’agit d’une application qui permet d’épingler des tâches sur la page d’accueil sous la forme de tiles. Ces tiles se colorent en fonction du temps restant jusqu’à échéance de la tâche : vert, orange, puis rouge. Nous allons ajouter plus de nuances au système, en permettant aux tiles de se colorer progressivement de gauche à droite.
Commençons par rajouter une image “Task.png” au projet. Cette image reprend l’icône des tâches, mais avec un arrière-plan transparent :

Le “Build action” de cette image est réglé sur “Content”.
Au chargement de la page, dans l’évènement “OnNavigatedTo”, nous chargeons cette image dans un objet BitmapImage, et nous l’assignons à une propriété (lignes 1 à 10) :
1: protected BitmapImage BitmapSource { get; set; }
2:
3: protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
4: {
5: base.OnNavigatedTo(e);
6:
7: this.BitmapSource = new BitmapImage();
8: this.BitmapSource.CreateOptions = BitmapCreateOptions.None;
9:
10: this.BitmapSource.UriSource = new Uri("Images/Task.png", UriKind.Relative);
11:
12:
13: if (this.TaskDataContext == null)
14: {
15: this.TaskDataContext = new TaskDataContext();
16: }
17:
18: if (this.NavigationContext.QueryString.ContainsKey("TaskId"))
19: {
20: this.PanelCreateTask.Visibility = Visibility.Collapsed;
21: this.PanelEditTask.Visibility = Visibility.Visible;
22:
23: int taskId = int.Parse(this.NavigationContext.QueryString["TaskId"]);
24:
25: if (this.Task == null || this.Task.Id != taskId)
26: {
27: var task = this.TaskDataContext.Tasks.First(t => t.Id == taskId);
28:
29: this.TaskTitle = task.Title;
30: this.Description = task.Description;
31: this.DueDate = task.DueDate;
32: this.DueTime = task.DueDate;
33:
34: this.Task = task;
35: }
36: }
37: else
38: {
39: this.PanelCreateTask.Visibility = Visibility.Visible;
40: this.PanelEditTask.Visibility = Visibility.Collapsed;
41: }
42: }
Le chargement de l’image dans un BitmapImage se fait de manière asynchrone. Pour être correct, il aurait donc fallu s’attacher à l’event “ImageOpened” pour détecter la fin du chargement. Dans notre cas et par mesure de simplicité, nous allons considérer que vu que le chargement de l’image est commencé dès le chargement de la page, la probabilité qu’elle n’ait pas terminé de se charger lorsqu’on en aura besoin est extrêmement faible.
Maintenant, dans l’évènement ButtonCreate_Click, au moment de créer la tile alternative, nous instancions un WriteableBitmap et créons dynamiquement l’image à afficher. Pour cela, nous parcourons les pixels de notre image “modèle”. Si un pixel a pour valeur “0”, il s’agit d’un des pixels transparents de l’arrière-plan, et nous le remplaçons par un pixel de la valeur de notre choix. S’il s’agit d’un pixel de toute autre valeur, il s’agit du dessin et nous le recopions tel quel.
1: var wb = new WriteableBitmap(173, 173);
2:
3: const int greenPixelValue = 0
4: | 127 << 8
5: | 14 << 16
6: | 0 << 24;
7:
8: const int redPixelValue = 19
9: | 16 << 8
10: | 206 << 16
11: | 0 << 24;
12:
13: const int orangePixelValue = 32
14: | 136 << 8
15: | 216 << 16
16: | 0 << 24;
17:
18: int leftPixelValue = greenPixelValue;
19: int rightPixelValue = greenPixelValue;
20:
21: int limit = 0;
22:
23: var timeRemaining = task.DueDate - DateTime.Now;
24:
25: if (timeRemaining.TotalHours >= 24 && timeRemaining.TotalHours < 48)
26: {
27: leftPixelValue = orangePixelValue;
28: rightPixelValue = greenPixelValue;
29:
30: limit = (int)(173 * ((48 - timeRemaining.TotalHours) / 24));
31: }
32: else if (timeRemaining.TotalHours < 24 && timeRemaining.TotalHours >= 0)
33: {
34: leftPixelValue = redPixelValue;
35: rightPixelValue = orangePixelValue;
36:
37: limit = (int)(173 * ((24 - timeRemaining.TotalHours) / 24));
38: }
39: else if (timeRemaining.TotalSeconds < 0)
40: {
41: leftPixelValue = redPixelValue;
42: rightPixelValue = redPixelValue;
43: }
44:
45: for (int coordY = 0; coordY < source.PixelHeight; coordY++)
46: {
47: for (int coordX = 0; coordX < source.PixelWidth; coordX++)
48: {
49: int index = coordY * source.PixelWidth + coordX;
50:
51: if (source.Pixels[index] != 0)
52: {
53: wb.Pixels[index] = source.Pixels[index];
54: }
55: else
56: {
57: if (coordX < limit)
58: {
59: wb.Pixels[index] = leftPixelValue;
60: }
61: else
62: {
63: wb.Pixels[index] = rightPixelValue;
64: }
65: }
66: }
67: }
Notre image créée, nous la stockons dans l’isolated storage, en se servant de l’id de la tâche comme nom de fichier, afin de le retrouver facilement :
1: using (var isolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication())
2: {
3: if (!isolatedStorageFile.DirectoryExists("Images"))
4: {
5: isolatedStorageFile.CreateDirectory("Images");
6: }
7:
8: using (var file = isolatedStorageFile.CreateFile(string.Format("Images/{0}.jpg", task.Id)))
9: {
10: wb.SaveJpeg(file, 173, 173, 0, 100);
11: }
12: }
Et il n’y a plus qu’à créer la tile alternative, en lui indiquant d’utiliser comme arrière-plan l’image que nous venons de générer. Pour cela la syntaxe est “isostore:/chemin/vers/image” :
1: var tile = new StandardTileData
2: {
3: Title = task.Title,
4: BackgroundImage = new Uri(string.Format("isostore:/Images/{0}.jpg", task.Id)),
5: };
6:
7: ShellTile.Create(new Uri(string.Format("/MainPage.xaml?TaskId={0}&DueDate={1}", task.Id, task.DueDate), UriKind.Relative),tile);
Et voilà le travail !

Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :