Plan du site  
français  English
pixel
pixel

Articles - Étudiants SUPINFO

Introduction

Dans ce chapitre, nous allons découvrir différentes interactions possibles entre le système d'exploitation et une application.

Cycle de vie d'une application

Une application vit par le système d'exploitation et s'exécute en son sein. Si l'utilisateur peut déclencher le lancement d'une application, c'est bien le système qui va régir l'ensemble des états par lesquels une application peut passer. On parle alors de cycle de vie quand on dresse la liste des états d'une application de son lancement à la fin de son exécution.

Pourquoi un cycle de vie ?

Le cycle de vie d'une application est conçu pour améliorer les performances de l'équipement. En effet, conserver l'ensemble des applications actives en permanence est très consommateur en ressources et peut offrir une expérience dégradée à l'utilisateur. Les systèmes d'exploitation implémentent donc un cycle pour chaque application, en tentant autant que possible d'offrir un maximum de performances pour les tâches que l'utilisateur effectue, tout en donnant l'illusion que chaque application est disponible avec un maximum de ressources allouées.

Dans le cadre des applications universelles, si le cycle de vie est le même, on peut constater de grandes différences de performance entre les différents équipements. Sa gestion par le système d'exploitation sera donc différente : si plusieurs applications sont affichées et actives en même temps sur PC, une seule application est en avant plan pour un mobile. Dans les deux cas, les applications non présentes à l'écran (minimisées, hors écran) sont suspendues ou terminées.

Dans tous les cas, le système d'exploitation est maître de la distribution des ressources de l'équipement.

Présentation du cycle de vie

Figure 1.1. Cycle de vie d'une application (Source: blogs.windows.com)

Cycle de vie d'une application (Source: blogs.windows.com)

Une application possède trois états :

  • Not Running : L'application n'est pas en cours d'exécution.

  • Running : L'application est en cours d'exécution.

  • Suspended : L'application a été suspendue dans son exécution et peut passer en état Not Running ou Running.

Au lancement d'une application, celle-ci à son activation déclenchera l'état Launched qu'il est possible de retrouver dans la méthode OnLaunched de la classe App, dans le fichier App.xaml.cs. Après cette méthode OnLaunched, l'état de l'application passe à Running.

Quand l'utilisateur n'interagit plus avec son application, celle-ci passe en état Suspended. Avant la suspension, l'événement Suspending est déclenché par la classe App, qu'il sera possible de retrouver dans la méthode OnSuspending. Concrètement, le système d'exploitation arrête l'exécution de votre application ainsi que tous les processus liés (méthodes asynchrones, événements, compteurs de temps...) et stocke l'état actuel de votre application.

[Note]

Sur ordinateur, une application passe en état Suspended quand l'application est minimisée ou quand le système d'exploitation passe en économie d'énergie. Sur téléphone ou tablette, une application passe en état Suspended dès qu'elle n'est plus en avant plan.

Si l'utilisateur revient sur l'application, alors l'état de l'application est chargé et tous les processus reprennent. L'application retrouve son état Running en déclenchant l'événement Resuming. Une nouvelle fois, cet événement est inscrit dans la classe App.

Mais si l'utilisateur ne revient pas sur l'application ou que l'équipement nécessite de la mémoire pour d'autres tâches, alors le système d'exploitation peut fermer votre application. Personne ne peut prévoir ce comportement, ni l'utilisateur, ni le développeur. Une application peut également être fermée par l'utilisateur en utilisant la croix sur un ordinateur/tablette ou le Task Switcher d'un mobile.

Quelle que soit la méthode par laquelle une application est fermée, elle passe d'abord dans l'état Suspended si elle n'y était pas déjà avant d'être terminée. En effet, pour une application terminée par l'utilisateur, celle-ci sera en état Suspended dix secondes avant d'être terminée.

[Warning]

Dans le cas d'une fermeture de l'application par le système, aucun événement n'est déclenché ! L'événement Suspended est donc le seul moment où il est possible de sauvegarder un état particulier dans votre application.

Figure 1.2. Task Switcher (Desktop et Mobile)

Task Switcher (Desktop et Mobile)

Application suspendue, que faire ?

Quand votre application est suspendue, la méthode OnSuspending sera déclenchée. Cette méthode est la dernière méthode exécutée par votre application et il n'est pas possible de déterminer si l'application sera reprise ou terminée par la suite ; cela dépendra de l'utilisateur ou de la mémoire nécessaire à l'équipement dans les heures suivantes.

Tout comme le système d'exploitation maintient l'illusion que toutes les applications sont actives en toutes circonstances, il appartient au développeur de maintenir l'illusion que son application a toujours été en cours d'exécution. A l'exception d'une application close par l'utilisateur, l'état d'une application doit se maintenir au cours du temps.

A ce titre, il faudra dans la méthode OnSuspending sauvegarder l'état de votre application. Selon le type de l'application, il sera nécessaire de stocker les données entrées par l'utilisateur, les champs en cours d'édition, la page sur laquelle l'utilisateur se situe, l'historique de navigation, ou tout autre variable jugée nécessaire à la conservation de l'expérience utilisateur.

La méthode OnSuspending se présente de la manière suivante :

// Classe App
private void OnSuspending(object sender, SuspendingEventArgs e)
{
  var deferral = e.SuspendingOperation.GetDeferral();
  //A faire : Sauvegarder l'état courant de l'application
  deferral.Complete();
}

On remarque une variable deferral. Celle-ci symbolise le temps nécessaire pour suspendre l'application. Ainsi, si votre application nécessite des méthodes asynchrones à la suspension (fermeture de fichier, stockage de données...), celles-ci pourront être exécutées. Une fois les méthodes terminées (via l'utilisation de l'opérateur await), la méthode deferral.Complete() symbolise la fin des opérations, permettant la suspension.

[Note]

C'est encore une fois le système d'exploitation qui aura le dernier mot. La méthode OnSuspending a une durée maximum fixée par le système d'exploitation à 5 secondes (10 dans le cadre d'une fermeture par l'utilisateur). Les opérations effectuées dans OnSuspending pourront être arrêtées en cours d'exécution.

Application reprise, que faire ?

L'événement Resuming est le seul non présent par défaut dans le fichier App.xaml.cs. Celui-ci est déclenché à la reprise de l'application, c'est-à-dire quand cette dernière a été suspendue puis affichée de nouveau sans que le système d'exploitation n'y mette fin. Dans ces conditions, l'application apparaît à l'utilisateur telle qu'elle était à sa suspension et tous les processus liés (méthodes asynchrones, événements, compteurs de temps...) reprennent.

Si la date exacte à laquelle la suspension a eu lieu n'est pas fournie, il est possible de la stocker lors de l'appel à OnSuspending pour la récupérer dans OnResuming.

Dans cet événement, il est conseillé de :

  • Rafraîchir les données externes. Une application affichant des données d'internet pourrait automatiquement rafraîchir ces données qui ont pu être modifiées depuis la suspension, selon le type d'application, la période de suspension et les données concernées.

  • Vérifier la connectivité. Si l'application utilise internet, celle-ci pouvait disposer d'une connexion avant la suspension et ne plus en disposer à la reprise. La vérification de la connectivité sera présentée au chapitre suivant.

  • Recommencer une opération ayant échoué dans la méthode OnSuspended.

Selon la durée de suspension, Microsoft conseille d'afficher à la reprise de l'application :

  • Si la durée est courte : l'application telle qu'elle était à la suspension

  • Si la durée est longue : l'application dans l'état d'un nouveau lancement

La définition de "durée courte" et "durée longue" est laissée à la discrétion du développeur selon le type d'application.

Application démarrant, que faire ?

Quand la méthode OnLaunched est appelée, l'application est en cours de démarrage. C'est dans cette méthode qu'il faudra récupérer les données stockées dans la méthode OnSuspending pour paramétrer l'application. Trois cas peuvent se présenter :

  • L'utilisateur lance l'application pour la première fois

  • L'utilisateur a lui-même mis fin à la dernière exécution : il s'attend probablement à retrouver l'application dans son état initial

  • Le système d'exploitation a mis fin à la dernière exécution : l'utilisateur s'attend probablement à retrouver l'application dans son état précédent.

Il est possible de déterminer la cause de la précédente fin d'exécution de cette manière :

protected override void OnLaunched(LaunchActivatedEventArgs e)
{
  switch(e.PreviousExecutionState)
  {
    case ApplicationExecutionState.NotRunning:
      Debug.WriteLine("Première exécution");
      break;
    case ApplicationExecutionState.ClosedByUser:
      Debug.WriteLine("Fermée par l'utilisateur");
      // Ne rien faire et lancer l'application normalement
      break;
    case ApplicationExecutionState.Terminated:
      Debug.WriteLine("Terminée par le système d'exploitation");
      // Ici, on peut proposer l'état précédent de l'application
      break;
  }
}

Tester le bon fonctionnement des états de l'application

Visual Studio propose pour ses émulateurs un simulateur d'états du cycle de vie, en déclenchant les événements.

Pour cela, il est nécessaire de sélectionner dans le menu "Affichage" le sous-menu "Barres d'outils" et cocher l'élément "Emplacement de débogage".

Figure 1.3. Afficher la barre d'outils "Emplacement de débogage"

Afficher la barre d'outils "Emplacement de débogage"

[Note]

Il est conseillé de faire cette manipulation pendant l'exécution de l'application, afin d'accéder à cette barre d'outils à chaque exécution.

Une fois cette manipulation effectuée, la barre d'outils suivante apparaît et vous permettra d'envoyer à votre application les déclencheurs relatifs au cycle de vie :

Figure 1.4. Afficher la barre d'outils "Emplacement de débogage"

Afficher la barre d'outils "Emplacement de débogage"

Exemple d'utilisation des méthodes OnLaunched, OnResuming et OnSuspending :

// Fichier App.xaml.cs
sealed partial class App : Application
{
  // Propriété représentant le nombre de lancements
  public static int CompteurLancement { get; set; }
  // Propriété représentant le nombre de reprises
  public static int CompteurReprise { get; set; }
  // Propriété représentant le nombre de suspensions
  public static int CompteurSuspension { get; set; }
  // Propriété représentant l'espace de stockage local
  public ApplicationDataContainer LocalSettings = ApplicationData.Current.LocalSettings;
  
  public App()
  {
    this.InitializeComponent();
    this.Suspending += OnSuspending;
    // Ajout de l'événement OnResuming
    this.Resuming += OnResuming;
  }

  protected override void OnLaunched(LaunchActivatedEventArgs e)
  {
    // Récupération des valeurs stockées dans l'emplacement LocalSettings
    // Si la valeur n'est pas trouvée, initialisation à zéro
    CompteurLancement = ((int?)LocalSettings.Values["CompteurLancement"]) ?? 0;
    CompteurReprise = ((int?)LocalSettings.Values["CompteurReprise"]) ?? 0;
    CompteurSuspension = ((int?)LocalSettings.Values["CompteurSuspension"]) ?? 0;

    // Incrémentation du compteur concerné
    CompteurLancement++;

    // Reste de la méthode omise
  }

  private void OnResuming(object sender, object e)
  {
    // Incrémentation du compteur concerné
    CompteurReprise++;
  }

  private void OnSuspending(object sender, SuspendingEventArgs e)
  {
    var deferral = e.SuspendingOperation.GetDeferral();

    // Incrémentation du compteur concerné
    CompteurSuspension++;
    // On stocke dans le stockage local les trois compteurs
    LocalSettings.Values["CompteurLancement"] = CompteurLancement;
    LocalSettings.Values["CompteurReprise"] = CompteurReprise;
    LocalSettings.Values["CompteurSuspension"] = CompteurSuspension;

    // On prévient UWP que la méthode OnSuspending est terminée
    deferral.Complete();
  }
}
<!-- Page MainPage.xaml -->
<Page> <!-- Attributs omis pour faciliter la lecture -->
  <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <StackPanel Margin="10">
      <TextBlock x:Name="CompteurLancement" />
      <TextBlock x:Name="CompteurReprise" />
      <TextBlock x:Name="CompteurSuspension" />
      <Button Content="Mettre à jour l'affichage" Click="Button_Click" />
    </StackPanel>
  </Grid>
</Page>
// Fichier MainPage.xaml.cs
public sealed partial class MainPage : Page
{
  public MainPage()
  {
    this.InitializeComponent();
    Actualiser();
  }

  private void Button_Click(object sender, RoutedEventArgs e)
  {
    Actualiser();
  }

  private void Actualiser()
  {
    CompteurLancement.Text = "Compteur Lancement: " + App.CompteurLancement;
    CompteurReprise.Text = "Compteur Reprise: " + App.CompteurReprise;
    CompteurSuspension.Text = "Compteur Suspension: " + App.CompteurSuspension;
  }
}

Figure 1.5. Rendu de l'exemple précédent

Rendu de l'exemple précédent

Demander une autorisation exceptionnelle

Le système d'exploitation Windows 10 offre aux développeurs des autorisations exceptionnelles pour réaliser des opérations ne pouvant rentrer dans le cadre normal. Cette demande peut être refusée par le système, il n'y a donc aucune garantie quant à cette demande. Le système Windows nous propose deux scénarios exceptionnels :

  • Des données sont à sauvegarder et pourraient prendre plus de temps qu'imparti

  • L'application utilise le GPS pour conserver la localisation de l'utilisateur

Ces deux possibilités sont représentées dans une énumération à utiliser obligatoirement pour obtenir l'autorisation exceptionnelle :

private async void OnSuspending(object sender, SuspendingEventArgs args) 
{
  var deferral = args.SuspendingOperation.GetDeferral();
  using(var extension = new ExtendedExecutionSession()
  {
    Reason = ExtendedExecutionReason.SavingData
    // Autre raison possible : ExtendedExecutionReason.LocationTracking
  })
  {
    extension.Description = "Une description explicite de l'opération";
    extension.Revoked += Extension_Revoked;
    ExtendedExecutionResult resultat = await extension.RequestExtensionAsync();
    if(resultat == ExtendedExecutionResult.Denied) {
      // Dans ce cas l'autorisation est refusée, il faudra composer avec
      // le délai normal.
      Debug.WriteLine("Autorisation refusée !");
    }
    else 
    {
      // resultat est égal à ExtendedExecutionResult.Allowed
      // Autorisation acceptée, le délai sera plus important, jusqu'à lancement
      // par le système de l'événement Revoked.
    }
  }
}

private void Extension_Revoked(object sender, ExtendedExecutionRevokedEventArgs args)
{
  Debug.WriteLine("Autorisation autorisée mais révoquée en cours d'opération.");
}
[Note]

En plus de n'avoir aucune garantie de succès, il est à noter que l'application ne possède plus d'interface utilisateur. Il est donc impossible dans le cadre d'une autorisation exceptionnelle d'utiliser les éléments de l'interface (XAML).

Exercice : Conserver une liste d'éléments

Exercice : Conserver une liste d'éléments

1.1.

Cette application aura pour principe de de stocker une liste de fruits. Ces fruits seront de simples chaînes de caractères. Ces fruits pourront être ajoutés par l'utilisateur par le biais d'un formulaire. Le but de cet exercice est de maintenir cette liste de fruits malgré les suspensions et fins d'exécution.

Créez un nouveau projet nommé "UWPLifeCycle". Dans ce projet, créez un nouveau ViewModel appelé "MainViewModel".

Cette classe MainViewModel possèdera une collection observable de chaînes de caractères appelée Fruits. Implémentez l'interface INotifyPropertyChanged sur votre ViewModel afin d'assurer la notification à la définition de la liste.

Dans la page MainPage, ajoutez une propriété statique et publique MainViewModel de type MainViewModel. Initialisez la propriété DataContext de la page à ce ViewModel nouvellement créé.

// Fichier MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
  private ObservableCollection<string> _fruits = 
        new ObservableCollection<string>();
  public ObservableCollection<string> Fruits
  {
    get { return _fruits;  }
    set 
    { 
      _fruits = value;  
      PropertyChanged?.Invoke(this, 
            new PropertyChangedEventArgs("Fruits"));  
    }
  }

  public event PropertyChangedEventHandler PropertyChanged;
}
// Fichier MainPage.xaml.cs
public sealed partial class MainPage : Page
{
  public static MainViewModel MainViewModel = new MainViewModel();
  public MainPage()
  {
    this.InitializeComponent();
    this.DataContext = MainViewModel;
  }
}

1.2.

Dans la page MainPage, créez un simple formulaire avec un champ texte et un bouton. Au clic sur le bouton, si le champ n'est pas vide, le contenu du champ doit être ajouté à Fruits puis le champ texte doit être vidé.

Dans la page MainPage, ajoutez également un contrôle ListView affichant la liste des chaînes de caractères contenues dans Fruits.

<!-- Page MainPage.xaml -->
<Page> <!-- Attributs omis pour faciliter la lecture -->
  <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <StackPanel>
    <TextBox x:Name="Fruit" />
    <Button x:Name="AjoutFruit" Click="AjoutFruit_Click" Content="Ajout Fruit" />
    <ListView ItemsSource="{Binding Fruits}">
      <ListView.ItemTemplate>
        <DataTemplate>
          <TextBlock Text="{Binding}" />
        </DataTemplate>
      </ListView.ItemTemplate>
      </ListView>
    </StackPanel>
  </Grid>
</Page>
// Fichier MainPage.xaml.cs. Reste du fichier omis
private void AjoutFruit_Click(object sender, RoutedEventArgs e)
{
  if(Fruit.Text != string.Empty) {
    MainViewModel.Fruits.Add(Fruit.Text);
    Fruit.Text = string.Empty;
  }
}

1.3.

A la suspension de l'application, utilisez le code suivant pour sérialiser la liste de fruits :

XmlSerializer serializer = 
      new XmlSerializer(MainPage.MainViewModel.Fruits.GetType());
StringWriter stringWriter = new StringWriter();
serializer.Serialize(stringWriter, MainPage.MainViewModel.Fruits);
string serialized = stringWriter.ToString();

Stockez la chaîne de caractère nommée serialized dans le stockage local avec la clef "Fruits".

Dans le constructeur de MainPage, si le champ "Fruits" existe dans le stockage local, utilisez le code suivant pour récupérer la collection observable déserialisée :

XmlSerializer serializer = 
      new XmlSerializer(MainViewModel.Fruits.GetType());
StringReader stringReader = new StringReader(localSettings.Values["Fruits"].ToString());
ObservableCollection<string> fruits = 
      serializer.Deserialize(stringReader) as ObservableCollection<string>;

Affectez à la collection observable Fruits dans le ViewModel la collection observable récupérée grâce au code ci-dessus.

Vérifiez le bon fonctionnement de l'application grâce aux outils de Visual Studio. Le contenu la liste devrait être maintenu indéfiniment.

Figure 1.6. Rendu attendu

Rendu attendu

// Fichier App.xaml.cs. Reste du fichier omis
private void OnSuspending(object sender, SuspendingEventArgs e)
{
  var deferral = e.SuspendingOperation.GetDeferral();
  var serializer = new XmlSerializer(MainPage.MainViewModel.Fruits.GetType());
  var stringWriter = new StringWriter();
  serializer.Serialize(stringWriter, MainPage.MainViewModel.Fruits);
  ApplicationData.Current.LocalSettings.Values["Fruits"] = stringWriter.ToString();
  deferral.Complete();
}
// Fichier MainPage.xaml.cs. Reste du fichier omis
if(ApplicationData.Current.LocalSettings.Values["Fruits"] != null)
{
  XmlSerializer serializer = new XmlSerializer(MainViewModel.Fruits.GetType());
  StringReader stringReader = 
      new StringReader(ApplicationData.Current.LocalSettings.Values["Fruits"].ToString());

  MainViewModel.Fruits = serializer.Deserialize(stringReader) 
      as ObservableCollection<string>;
}

Tâches en arrière plan

Votre application peut déclencher des opérations en arrière plan. Celles-ci peuvent être utilisées :

  • Pour faire des opérations longues sans bloquer l'application principale

  • Pour offrir un service alors que l'application reste en arrière plan (notifications diverses, musique...)

  • Pour offrir un service particulier (application comptant le nombre de pas par exemple)

Les tâches en arrière plan se séparent en deux catégories : attachées à l'application principale (in-process) et décorrélées de l'application principale (out-of-process). Dans les deux cas, les tâches en arrière plan doivent être conçues avec comme considération principale la faible consommation des ressources de l'équipement.

Attachée à l'application (in-process)

Ce type de tâche en arrière plan est la plus simple à mettre en oeuvre et ne nécessite aucune configuration particulière ni de création de nouveau projet.

Pour ce faire, il suffit d'enregistrer la tâche en arrière-plan à l'initialisation de l'application, soit dans le constructeur de la classe App, soit dans la méthode OnLaunched.

sealed partial class App : Application
{
  public App() {
    // Création de la tâche
    var builder = new BackgroundTaskBuilder();
    // Définition d'un nom
    builder.Name = "Tâche en arrière plan";
    // La tâche s'exécutera une fois (second paramètre).
    // Si le second paramètre est à false, le premier paramètre
    // représente l'intervale de durée. 15 minutes étant le minimum.
    builder.SetTrigger(new TimeTrigger(15, true));

    // Enregistrement de la tâche
    BackgroundTaskRegistration enregistrement = builder.Register();
    // Reste de la méthode omise
  }
}

Si la tâche est créée et enregistrée, aucune référence à une quelconque méthode n'apparaît. La méthode OnBackgroundActivated() de la classe App sera celle exécutée au déclenchement de la tâche. Pour ajouter cette méthode, il suffit d'écrire "protected override" pour que toutes les méthodes surchargeables apparaissent.

// Classe App
protected override async void OnBackgroundActivated(BackgroundActivatedEventArgs args)
{
  // Code de la tâche en arrière plan
}

Dans ce type d'exécution, le système d'exploitation reste maître. L'utilisation d'un deferral comme pour la méthode OnSuspended est toujours nécessaire pour les méthodes asynchrones.

Voici un exemple de tâche en arrière plan attachée à l'application générant un nombre aléatoire par seconde, pendant dix secondes.

protected override async void OnBackgroundActivated(BackgroundActivatedEventArgs args)
{
  // Récupération du deferral
  var deferral = args.TaskInstance.GetDeferral();
  for (int i = 0; i < 10; i++)
  {
    Random r = new Random();
    int alea = r.Next(1, 100);
    // Affichage du nombre aléatoire dans la sortie Visual Studio
    Debug.WriteLine("Numéro aléatoire : " + alea);
    // Attente d'une seconde
    await Task.Delay(TimeSpan.FromSeconds(1));
  }
  // Fin de la tâche
  deferral.Complete();
}

Au lancement de l'application, il est possible de sélectionner tout comme les étapes du cycle de vie, chaque tâche en arrière plan pour l'exécuter sans attendre l'intervale de 15 minutes.

Figure 1.7. Sélection de la tâche d'arrière plan

Sélection de la tâche d'arrière plan

Exemple de résultat :

Numéro aléatoire : 43
Numéro aléatoire : 6
Numéro aléatoire : 48
Numéro aléatoire : 90
Numéro aléatoire : 69
Numéro aléatoire : 95
Numéro aléatoire : 38
Numéro aléatoire : 80
Numéro aléatoire : 23
Numéro aléatoire : 65
Le thread 0x1724 s'est arrêté avec le code 0 (0x0).

Décorrélées de l'application (out-of-process)

Une tâche décorrélée possède l'avantage d'être par construction indépendante de l'application principale : en cas de fermeture brutale de la tâche d'arrière plan et contrairement aux tâches in-process, l'application continue de fonctionner, et inversement.

Pour créer une tâche out-of-process, il est nécessaire de créer un nouveau projet de type Windows Runtime Component.

Figure 1.8. Création d'une tâche d'arrière plan

Création d'une tâche d'arrière plan
Création d'une tâche d'arrière plan

Une fois ce projet créé, il sera nécessaire d'ajouter en référence de celui-ci le projet principal pour profiter de toutes les classes, notamment les modèles, objets, propriétés et événements.

Figure 1.9. Ajout de référence

Ajout de référence
Ajout de référence

Cette configuration effectuée, le fichier Class1.cs fourni dans le modèle de projet peut être supprimé.

Pour démontrer le fonctionnement des tâches en arrière plan décorrélées, il sera créé une nouvelle classe nommée ExempleArrierePlan implémentant l'interface IBackgroundTask.

public sealed class ExempleArrierePlan : IBackgroundTask
{
  public void Run(IBackgroundTaskInstance taskInstance)
  {
    // Exécution de la tâche
  }
}

Comme pour les tâches in-process, il faudra utiliser le deferral en cas de méthode asynchrone afin de prévenir le système d'exploitation de la présence d'appels asynchrones.

public async void Run(IBackgroundTaskInstance taskInstance)
{
  // Gestion de la charge omise
  
  var deferral = taskInstance.GetDeferral();

  // Exemple de tâche identique à la section précédente
  for (int i = 0; i < 10; i++)
  {
    Random r = new Random();
    int alea = r.Next(1, 100);
    // Affichage du nombre aléatoire dans la sortie Visual Studio
    Debug.WriteLine("Numéro aléatoire : " + alea);
    // Attente d'une seconde
    await Task.Delay(TimeSpan.FromSeconds(1));
  }

  deferral.Complete();
}

Le contenu de cette tâche out-of-process est réalisé, il ne faudra l'enregistrer au lancement de notre application. Les instructions nécessaires se situent également au constructeur de la classe App ou dans sa méthode OnLaunched, et sont similaires :

// Identique
var builder = new BackgroundTaskBuilder();
builder.Name = "Exemple de tâche";
builder.SetTrigger(new TimeTrigger(15, true));
// On référence le point d'entrée de la tâche avec cette instruction
// Elle représente l'espace de noms "UWPArrierePlan" et le nom de la
// classe à exécuter (dans notre cas, "ExempleArrierePlan")
builder.TaskEntryPoint = "UWPArrierePlan.ExempleArrierePlan";
// Identique
BackgroundTaskRegistration task = builder.Register();

Pour que cette déclaration soit autorisée, elle doit être suivie d'un enregistrement dans le fichier Package.appxmanifest. Cette déclaration est accessible dans l'éditeur de Package.appxmanifest à l'onglet "Déclarations".

Il faudra alors ajouter "Background Tasks" comme déclaration, cocher les types de tâches supportées (dans notre cas, TimerTask) et référencer le point d'entrée de la tâche out-of-process, ici "UWPArrierePlan.ExempleArrierePlan".

Figure 1.10. Déclaration d'une tâche d'arrière plan

Déclaration d'une tâche d'arrière plan

[Note]

Cette déclaration se représentera ensuite dans le code du fichier Package.appxmanifest de cette manière :

<Extensions>
  <Extension Category="windows.backgroundTasks" 
        EntryPoint="UWPArrierePlan.ExempleArrierePlan">
    <BackgroundTasks>
      <Task Type="timer" />
    </BackgroundTasks>
  </Extension>
</Extensions>

Propriétés communes et bonnes pratiques

Les sections détaillant les tâches in-process et out-of-process ne définissaient jusqu'alors que leurs spécificités. Dans cette partie, seront détaillées les propriétés communes et bonnes pratiques nécessaires à l'implémentation de tâches stables et performantes. Pour plus d'informations concernant la liste ci-après, une page dédiée à ce sujet est disponible sur la documentation officielle à cette adresse.

Prise en compte de la disponibilité de l'équipement

Une tâche en arrière plan peut être arrêtée ou refusée si la charge de l'équipement est trop élevée. On parle ici de charge non pas à propos de la batterie mais du processeur. Pour réduire les risques d'interruption, Microsoft offre une méthode retournant le taux de charge de l'équipement. En cas de charge haute, il est conseillé d'abandonner l'exécution.

public void Run(IBackgroundTaskInstance taskInstance)
{
  var charge = BackgroundWorkCost.CurrentBackgroundWorkCost;
  switch(charge)
  {
    case BackgroundWorkCostValue.Low:
    case BackgroundWorkCostValue.Medium:
      break;
    case BackgroundWorkCostValue.High:
      return;
  }
}

Gestion de l'événement Canceled

Une tâche peut ne pas s'exécuter correctement ; dans ce cas, l'événement Canceled sera déclenché.

// Méthode OnBackgroundActivated ou Run
{
  IBackgroundTaskInstance instance = args.TaskInstance;
  // Souscription à l'événement Canceled
  instance.Canceled += Instance_Canceled;

  // Opérations en arrière plan omises
}

private void Instance_Canceled(IBackgroundTaskInstance sender, 
      BackgroundTaskCancellationReason reason)
{
  Debug.WriteLine("La tâche " + sender.Task.Name + "n'a pas pu être exécutée entièrement.");
  Debug.WriteLine("La raison était : " + reason.ToString("g"));
}

Evénements déclenchés à la progression et au succès

Selon le type de tâche effectuée en arrière plan, il est parfois appréciable voire nécessaire de récupérer la progression d'une tâche et effectuer des actions à sa complétion (notifier l'utilisateur, enregistrer la bonne réalisation de la tâche par exemple).

Pour cela, deux événements sont utilisables : Progress et Completed.

// Méthode OnBackgroundActivated ou Run
{
  var deferral = taskInstance.GetDeferral();
  for (int i = 0; i < 10; i++)
  {
    // Exemple de tâche durant une * dix secondes
    await Task.Delay(TimeSpan.FromSeconds(1));
    // La propriété Progress de la tâche est incrémentée de 10
    // à chaque tour de boucle
    taskInstance.Progress += 10;
  }
  deferral.Complete();
}
// Classe App
public App() {
  // Déclaration de la tâche de fond omise
  BackgroundTaskRegistration task = builder.Register();
  task.Completed += Task_Completed;
  task.Progress += Task_Progress;
}

private void Task_Progress(BackgroundTaskRegistration sender, 
      BackgroundTaskProgressEventArgs args)
{
  Debug.WriteLine("Complétion : " + args.Progress + "%");
}

private void Task_Completed(BackgroundTaskRegistration sender, 
      BackgroundTaskCompletedEventArgs args)
{
  Debug.WriteLine("Tâche terminée !");
}
Complétion : 10%
Complétion : 20%
Complétion : 30%
Complétion : 40%
Complétion : 50%
Complétion : 60%
Complétion : 70%
Complétion : 80%
Complétion : 90%
Complétion : 100%
Tâche terminée !

Types de déclenchement et conditions

Au cours des exemples précédents, le déclenchement d'une tâche était effectuée par une instance de classe TimeTrigger, permettant de déclencher une tâche périodiquement. Il existe de nombreuses classes agissant comme des événements. On peut citer par exemple :

  • ContactStoreNotificationTrigger : Se déclenche à l'ajout d'un contact dans l'application Contacts

  • SystemTrigger : Se déclenche quand un événement relatif au système se déclenche (absence de l'utilisateur, reprise de la connexion internet...). La liste complète des possibilités est disponible en suivant ce lien.

  • SocketActivityTrigger : Se déclenche à la réception de données sur un socket précis.

On retrouve également des conditions possibles pour un déclenchement, permettant de lancer une tâche en arrière plan quand toutes les conditions sont requises.

BackgroundTaskBuilder builder = new BackgroundTaskBuilder();
builder.Name = "Tâche en arrière plan";

// Ajout d'une condition nécessitant la présence de l'utilisateur
SystemCondition conditionUtilisateur = new SystemCondition(SystemConditionType.UserPresent);
// Ajout d'une condition nécessitant la connectivité à Internet
SystemCondition conditionInternet    = new SystemCondition(SystemConditionType.InternetAvailable);

builder.AddCondition(conditionUtilisateur);
builder.AddCondition(conditionInternet);

Unicité des tâches

Une tâche en arrière plan s'exécute comme son nom l'indique potentiellement en dehors des périodes d'activités de votre application. A ce titre, l'appel à la méthode Register pourrait sans vérification résulter en l'ajout multiple d'une tâche, et dupliquerait ainsi son exécution.

Pour cela, on peut utiliser la liste AllTasks présente en propriété de la classe BackgroundTaskRegistration :

public App() {
  var builder = new BackgroundTaskBuilder();
  builder.Name = "Exemple de tâche";
  builder.SetTrigger(new TimeTrigger(15, true));
  builder.TaskEntryPoint = "UWPArrierePlan.ExempleArrierePlan";
  Enregistrement(builder);
}

private void Enregistrement(BackgroundTaskBuilder builder) {
  foreach(var tache in BackgroundTaskRegistration.AllTasks)
  {
    if(tache.Value.Name == builder.Name) return; 
  }
  BackgroundTaskRegistration task = builder.Register();
}
[Note]

La librairie UWPCommunityToolkit vous propose une classe simple permettant de gérer les événements avec cette notion d'unicité des tâches. Si le code source de cette classe est disponible sur ce lien et ses modalités d'usage à cette adresse, vous pouvez utiliser le gestionnaire de package NuGet pour inclure à votre projet l'ensemble de la librairie.

Notifications Toasts

UWP propose comme de nombreuses plateformes des fonctionnalités de notifications (appelées "Toast") permettant d'afficher en sur-impression sur l'écran le contenu de votre choix. Les notifications offrent ainsi aux développeurs le moyen de tenir informés leurs utilisateurs de contenus pertinents comme :

  • Des rappels

  • Des actualités

  • La complétion d'une opération longue

  • Et bien d'autres usages

[Note]

Pour une expérience utilisateur optimale, il n'est pas conseillé d'afficher des notifications quand l'application est en avant plan. Si à des fins de tests il est plus simple de déclencher celles-ci au clic d'un bouton, il faudra pour une version finale invoquer les notifications par des tâches d'arrière plan.

Affichage

Les notifications sont universelles et constituées de la manière suivante :

Figure 1.11. Structure d'une notification

Structure d'une notification

Pour créer une notification, il faudra utiliser une nouvelle instance de classe ToastContent, contenant conséquemment deux propriétés, "Visual" et "Actions".

ToastContent contenu = new ToastContent()
{
  Visual = new ToastVisual()
  {
    // Partie "Visual" de la figure ci-dessus
  },
  Actions = new ToastActionsCustom()
  {
    // Partie "Actions" de la figure ci-dessus
  }
};

Au sein de la partie Visual se trouve une unique propriété, "BindingGeneric". C'est au sein de celle-ci que l'on placera nos éléments. La propriété Children contiendra les éléments textuels, et la propriété AppLogoOverride facultative permettra de définir le logo de la notification.

[Note]

Le premier texte de la propriété Children fera office de titre et sera donc présenté en gras à l'utilisateur.

[Warning]

Pour toute inclusion de contenu audio ou image dans une notification (ici une image), il est nécessaire de préfixer le chemin vers la ressource par "ms-appx://" pour signifier à UWP que l'on recherche dans l'application courante.

string titre = "Nouvelle notification";
string description = "Tout savoir sur les notifications UWP !";
string image = "ms-appx:///Assets/Logo1.png";
ToastContent contenu = new ToastContent()
{
  Visual = new ToastVisual()
  {
    BindingGeneric = new ToastBindingGeneric()
    {
      Children =
      {
        new AdaptiveText()
        {
          Text = titre
        },

        new AdaptiveText()
        {
          Text = description
        },
      },
      AppLogoOverride = new ToastGenericAppLogo()
      {
        Source = image,
        // Le logo peut être automatiquement rognée sous
        // la forme d'un cercle. Attention : la qualité
        // de l'image pourra être altéré selon sa définition.
        //HintCrop = ToastGenericAppLogoCrop.Circle
      }
    }
  }
};

ToastNotification notification = new ToastNotification(contenu.GetXml());
ToastNotificationManager.CreateToastNotifier().Show(notification);

Figure 1.12. Rendu de l'exemple précédent (Bureau et Mobile)

Rendu de l'exemple précédent (Bureau et Mobile)

Les actions quant à elles seront représentées en dessous de cette partie "Visual". On dénombre deux types d'actions :

  • Des boutons, permettant d'effectuer une action simple

  • Un champ texte, permettant par exemple de répondre rapidement à un correspondant

Ces deux modalités permettent d'interagir plus simplement et rapidement avec l'application. Il n'est plus nécessaire de cliquer sur la notification et de charger l'entièreté de l'application pour effectuer l'action.

[Note]

Sur mobiles, il faudra manuellement agrandir la notification afin de faire apparaître les actions possibles.

Les actions se représentent de cette manière :

ToastContent contenu = new ToastContent()
{
  Visual = new ToastVisual()
  {
    // Partie "Visual" omise
  },
  Actions = new ToastActionsCustom()
  {
    Buttons =
    {
      // Le premier paramètre contient le texte à afficher
      // Le deuxième est une valeur permettant d'invoquer l'application
      new ToastButton("J'y vais !", "check"),
      new ToastButton("Pas encore", "cancel")
    }
  }
};

Pour inclure un champ de texte, c'est la propriété Inputs qu'il faudra définir, comme le présente l'exemple suivant :

ToastContent contenu = new ToastContent()
{
  Visual = new ToastVisual()
  {
    // Partie "Visual" omise
  },
  Actions = new ToastActionsCustom()
  {
    Inputs =
    {
      new ToastTextBox("text")
      {
        Title = "Réponse",
        PlaceholderContent = "Entrez votre texte",
      }
    },
    Buttons =
    {
      new ToastButton("Répondre", "repondre")
    }
  }
};

Figure 1.13. Notification : Intégration d'un champ texte

Notification : Intégration d'un champ texte

On notera qu'en présence d'un seul bouton, celui-ci ne s'étend pas sur toute la largeur de la notification et est aligné à droite.

Il est également possible d'offrir une liste de choix disponibles pour faciliter l'interaction avec l'utilisateur. En cas de choix proposés parmi une liste, il est conseillé de proposer un menu déroulant plutôt qu'un texte libre :

ToastContent contenu = new ToastContent()
{
  Visual = new ToastVisual()
  {
    // Partie "Visual" omise
  },
  Actions = new ToastActionsCustom()
  {
    Inputs = {
      new ToastSelectionBox("periode")
      {
        Title = "Quand lisez-vous ce cours ?",
        DefaultSelectionBoxItemId = "matin",
        Items =
        {
          new ToastSelectionBoxItem("matin", "Le matin"),
          new ToastSelectionBoxItem("midi", "Le midi"),
          new ToastSelectionBoxItem("apresmidi", "L'après-midi"),
          new ToastSelectionBoxItem("soir", "Le soir"),
          new ToastSelectionBoxItem("nuit", "La nuit")
        }
      }
    },
    Buttons =
    {
      new ToastButton("Valider", "valider")
    }
  }
};

Figure 1.14. Notification : Intégration d'une liste de choix disponibles

Notification : Intégration d'une liste de choix disponibles

Pour finir avec l'affichage des notifications, les boutons peuvent également inclure des images, afin d'améliorer l'expérience utilisateur en proposant des icônes intuitives.

ToastContent contenu = new ToastContent()
{
  Visual = new ToastVisual()
  {
    // Partie "Visual" omise
  },
  Actions = new ToastActionsCustom()
  {
    Buttons =
    {
      new ToastButton("Accepter", "accept")
      {
        ImageUri = "ms-appx:///Assets/check.png"
      },
      new ToastButton("Refuser", "deny")
      {
        ImageUri = "ms-appx:///Assets/cancel.png"
      }
    }
  }
};

Figure 1.15. Notification : Intégration d'icônes

Notification : Intégration d'icônes

Récupération des informations

Offrir des possibilités d'interaction entre l'application et l'utilisateur via des notifications permet d'améliorer l'expérience utilisateur, mais il est indispensable de pouvoir interpréter les actions réalisées. Au clic sur une notification, celle-ci déclenche l'application de laquelle elle est issue, via la méthode OnActivated. Cette méthode est à surcharger dans la classe App :

protected override void OnActivated(IActivatedEventArgs args)
{
  // Exécuté après interaction avec une notification
}

Il est à noter que OnActivated est également un point d'entrée de votre application. A ce titre, il faudra comme pour la méthode OnLaunched créer l'objet Frame représentant la page que l'utilisateur va visiter et implémenter la gestion du bouton Back.

Pour respecter le principe DRY, on peut factoriser le code de la méthode OnLaunched et OnActivated au sein d'une méthode comme ceci :

private Frame InitialiserFrame()
{
    // Cette méthode contient toutes les opérations nécessaires
    // pour initialiser l'affichage de la page au lancement de
    // l'application, que cela vienne d'une notification ou
    // d'un lancement "classique"
    Frame rootFrame = Window.Current.Content as Frame;
    if (rootFrame == null)
    {
        rootFrame = new Frame();

        rootFrame.NavigationFailed += OnNavigationFailed;
        rootFrame.Navigated += OnNavigated;

        Window.Current.Content = rootFrame;

        SystemNavigationManager.GetForCurrentView().BackRequested += OnBackRequested;

        SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
            rootFrame.CanGoBack ?
            AppViewBackButtonVisibility.Visible :
            AppViewBackButtonVisibility.Collapsed;
    }
    // On retourne l'instance de Frame créée
    return rootFrame;
}

protected override void OnActivated(IActivatedEventArgs args)
{
    // Appel à la méthode InitialiserFrame pour effectuer les
    // opérations obligatoires à l'interaction avec la notification
    Frame rootFrame = InitialiserFrame();
    // Récupérer les choix utilisateurs

    Window.Current.Activate();
}

protected override void OnLaunched(LaunchActivatedEventArgs e)
{
#if DEBUG
    if (System.Diagnostics.Debugger.IsAttached)
    {
        this.DebugSettings.EnableFrameRateCounter = true;
    }
#endif
    // Appel à la méthode InitialiserFrame pour effectuer les
    // opérations obligatoires au lancement de l'application
    Frame rootFrame = InitialiserFrame();

    if (e.PrelaunchActivated == false)
    {
        if (rootFrame.Content == null)
        {
            rootFrame.Navigate(typeof(MainPage), e.Arguments);
        }
        Window.Current.Activate();
    }
}

Passé en paramètre de la méthode OnActivated, on retrouve une instance d'une classe implémentant IActivatedEventArgs. Cette variable "args" contient les informations de lancement de l'application ainsi que tout élément potentiellement fourni par l'utilisateur.

[Note]

Une application peut être lancée de bien des manières. On considère un lancement "normal" quand l'utilisateur clique sur l'icône dans la liste des applications disponibles ou par la tuile animée disponible en écran d'accueil. Dans ces deux cas et si l'application n'est pas lancée, la méthode OnLaunched sera déclenchée.

Une application peut également être lancée par une notification, mais aussi par une autre application, par Cortana, etc. Pour tous ces cas, la méthode OnActivated sera déclenchée.

Pour ces raisons, vous devriez toujours tester si le paramètre de la méthode OnActivated est bien du type souhaité.

protected override void OnActivated(IActivatedEventArgs args)
{
  Frame rootFrame = InitialiserFrame();
  
  // Si l'appel vient d'une notification Toast
  if (args is ToastNotificationActivatedEventArgs)
  {
    // On récupère les paramètres de la notification
    var arguments = args as ToastNotificationActivatedEventArgs;
  }
  // Reste de la méthode omise
}

On retrouve notamment dans ces arguments :

  • En propriété Argument contenant la clef du bouton sur lequel l'utilisateur a cliqué. Pour rappel, cette clef a été définie à la création de la notification :

    // Pendant la création de la notification...
    new ToastButton("Valider", "confirmer")
    // La clef du bouton est "confirmer", "Valider" étant le texte à afficher.
  • En propriété UserInput, le contenu de tous les champs (texte, menu déroulant) de la notification. Cette clef est également définie à la création de la notification :

    // Pendant la création de la notification...
    new ToastTextBox("reponse")

Considérant ces deux exemples, voici comment récupérer ces valeurs dans la méthode OnActivated :

protected override void OnActivated(IActivatedEventArgs args)
{
  Frame rootFrame = InitialiserFrame();

  if (args is ToastNotificationActivatedEventArgs)
  {
    var arguments = args as ToastNotificationActivatedEventArgs;
    
    // Ramènera la clef "confirmer" si clic sur le bouton associé
    string clefBouton = arguments.Argument;
    // Ramènera le texte entré par l'utilisateur
    string texteUtilisateur = arguments.UserInput["reponse"].ToString();

    // Il peut être adapté de rediriger l'utilisateur vers une page
    // spéciale en cas d'interaction avec une notification
    rootFrame.Navigate(typeof(ViaNotification), reponse);
  }
}
<!-- Page ViaNotification.xaml -->
<Page> <!-- Attributs omis pour la lisibilité -->
  <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <StackPanel Margin="10">
      <TextBlock x:Name="TexteNotification" />
    </StackPanel>
  </Grid>
</Page>
// Classe ViaNotification.xaml.cs
public sealed partial class ViaNotification : Page
{
  public ViaNotification()
  {
    this.InitializeComponent();
  }

  protected override void OnNavigatedTo(NavigationEventArgs e)
  {
    string reponse = e.Parameter.ToString();
    TexteNotification.Text = reponse;
  }
}

Figure 1.16. Notification : Récupération de valeurs et redirection

Notification : Récupération de valeurs et redirection

Centre de notifications

Au sein de Windows 10, les notifications de toutes les applications sont agrégées dans le Centre de Notification. Celui-ci apparaît au clic sur l'icône dédiée sur ordinateur, alors qu'il est accessible en effectuant un balayage vertical sur téléphone.

Figure 1.17. Centre de notifications

Centre de notifications

[Note]

Au clic sur le nom de l'application, celle-ci se lance. De plus, toutes les notifications relatives à cette application sont supprimées du centre de notification.

Par essence, les notifications permettent d'informer l'utilisateur sur des éléments actuels. Les notifications doivent donc disposer d'une durée de vie maximale pour améliorer l'expérience utilisateur.

Pour ajouter une durée d'expiration à une notification, il faudra utiliser la syntaxe suivante :

// Création de la notification dans la tâche d'arrière plan
ToastNotification notification = new ToastNotification(contenu.GetXml());
// Ici, on définit la propriété ExpirationTime à dans quatre heures.
notification.ExpirationTime = new DateTimeOffset(DateTime.Now, TimeSpan.FromHours(4));
ToastNotificationManager.CreateToastNotifier().Show(notification);

Live Tiles

Les tuiles animées (ou Live Tiles) ont révolutionné l'interface du système d'exploitation Windows. Ni vraiment des icônes, ni tout à fait des widgets, ils ont transformé les icônes en offrant aux applications une vitrine pour afficher des informations. Décriées à la sortie de Windows 8 et son interface appelée à l'époque Metro (aux côtés de la disparition du menu Démarrer et du design plat), elles sont aujourd'hui rentrées dans les usages.

Figure 1.18. Exemple de tuiles animées (Live Tiles)

Exemple de tuiles animées (Live Tiles)

La gestion des tuiles se fait également au travers de la librairie NuGet Microsoft.Toolkit.Uwp.Notifications.

Affichage : Structure et gestion des images

Une tuile animée possède deux faces. La face principale contient cinq couches comprenant :

  • La couleur ou l'image de fond (Plate)

  • Le logo de l'application (App Icon)

  • Le nom de l'application (Short Name)

  • Le badge présent en bas à droite (Badge, facultatif)

  • Le contenu de la tuile, un texte court (Content, facultatif)

Ces éléments se retrouvent ou non dans une tuile. Ainsi, dans l'exemple précédent, les tuiles de gauche ne disposent que des trois permiers éléments (Plate, App Icon et Short Name) quand les tuiles de droite possèdent une mise en page plus complexe.

Figure 1.19. Structure des tuiles (Source : channel9.msdn.com)

Structure des tuiles (Source : channel9.msdn.com)

La face arrière des tuiles permet quant à elle d'offrir un espace libre supplémentaire pour l'affichage d'informations. Cela peut être le début d'un mail, l'agenda du jour ou encore la musique en cours de lecture. Il est à noter qu'en cas de définition de cette face arrière, la tuile sera retournée d'une face à l'autre de temps à autre par le système d'exploitation. Il est donc conseillé d'utiliser la face arrière pour des informations complémentaires d'agrément.

En plus de deux faces pouvant contenir différentes informations, les tuiles existent en différents formats. Les tuiles se placent sur une grille de l'écran de démarrage et peuvent prendre différentes tailles :

  • Petite (Small) : 1x1 case

  • Moyenne (Medium) : 2x2 cases

  • Etendue (Wide) : 4x2 cases

  • Grande (Large, ordinateur uniquement) : 4x4 cases

Vous trouverez dans l'image suivante la grille représentée en rouge, ainsi qu'un nouvel agencement pour mettre en lumière les différentes tailles de tuiles.

Figure 1.20. Grille supportant les tuiles et tailles prédéfinies

Grille supportant les tuiles et tailles prédéfinies

Il n'y a pas de code nécessaire pour créer une tuile de son application. Par défaut, le fond de la tuile animée est transparente et le nom du projet est choisi comme nom d'application. Il n'y a besoin que d'une image, par défaut définie comme un carré barré.

Figure 1.21. Tuile par défaut

Tuile par défaut

Pour changer l'affichage d'une tuile, il faudra se rendre dans l'éditeur du fichier Package.appxmanifest. Au sein de cet éditeur, on trouvera dans l'onglet "Visual Assets":

  • Le nom affiché de l'application via le champ "Short name"

  • La couleur de fond de la tuile via le champ "Background color"

  • Le logo de l'application via les différentes sections

Comme au sein des applications Android et iOS, les applications universelles nécessitent différentes résolutions pour rendre au mieux l'application sous différentes formes : icône du Windows Store, tuile petite, moyenne, étendue, large. Toutes ces icônes sont également déclinées en différentes résolutions pour s'adapter au mieux à tous les équipements Windows 10.

Figure 1.22. Paramétrer les tuiles

Paramétrer les tuiles

Vous trouverez ci-dessous les différentes tailles et leur utilité :

  • Square71x71Logo : Utilisées pour une tuile 1x1

  • Square150x150Logo: Utilisées pour une tuile 2x2

  • Wide310x150Logo : Utilisées pour une tuile 4x2

  • Square310x310Logo : Utilisées pour une tuile 4x4

  • Square44x44Logo : Utilisées pour la liste des applications

  • Store Logo : Utilisées pour le Windows Store

  • Badge Logo : Utilisées pour les notifications quand l'équipement est verrouillé

  • Splash Screen : Utilisées au lancement de l'application

[Note]

La taille de ces images (par exemple "71x71" dans le nom "Square71x71Logo") ne représente que l'image à l'échelle 100. Si vous souhaitez supporter toutes les résolutions pour cette taille de tuile, il vous faudra décliner ces images aux différentes échelles proposées.

Les icônes d'application suivent une normalisation dans leur nom, permettant l'import rapide des images. Pour cela, il sera nécessaire de suivre une syntaxe précise dans la dénomination des images : [NomImage].scale-[valeur].png

Quelques exemples :

  • Square71x71Logo.scale-100.png représente un icône de taille 71x71 à l'échelle 100, donc 71x71 pixels

  • Wide310x150Logo.scale-200.png représente un icône de taille 310x150 à l'échelle 200, donc 620x300 pixels

  • StoreLogo.scale-125.png représente l'icône du Store de taille 50x50, donc 63x63 pixels

En utilisant cette normalisation, le champ texte "Square 71x71 Logo" pourra porter la valeur "Square71x71Logo.png" et Visual Studio en déduira automatiquement les déclinaisons de l'image.

A titre d'information, voici la liste des noms de logos possibles pour votre application.

Figure 1.23. Liste de logos disponibles pour votre application

Liste de logos disponibles pour votre application

[Note]

Pour prolonger l'expérience utilisateur au travers du système d'exploitation, Microsoft liste des recommandations en matière d'images pour les tuiles. L'ensemble de ces recommandations est disponible à cette adresse.

Programmation : Mettre à jour la tuile principale de l'application

Comme présenté en début de section, il est possible d'adapter la tuile principale de l'application de manière programmatique : il s'agira de changer le badge représentant les notifications et le contenu de la tuile, ou définir le contenu pour la face arrière de la tuile.

On parle ici de tuile principale car UWP permet de créer des tuiles secondaires pour une application. Ces tuiles secondaires seront abordées plus tard dans ce chapitre.

Supposons une application proposant trois formats de tuiles : petite, moyenne et étendue. Le rendu sera le suivant.

Figure 1.24. Exemple de tuiles (Mobile)

Exemple de tuiles (Mobile)

Avec une syntaxe ressemblant particulièrement aux notifications UWP, il est possible de mettre à jour la tuile de cette manière :

TileBindingContentAdaptive binding = new TileBindingContentAdaptive()
{
  Children =
  {
    new AdaptiveText()
    {
      Text = "Titre de la tuile",
      // La propriété HintStyle permet de définir comment le texte sera
      // représenté. Ici, on considère le texte comme un sous-titre.
      HintStyle = AdaptiveTextStyle.Subtitle
    },

    new AdaptiveText()
    {
      Text = "Description de la tuile",
      // Ici, le texte sera considéré comme une légende du sous-titre
      HintStyle = AdaptiveTextStyle.CaptionSubtle,
      // La propriété HintWrap permettra de faire apparaître le texte
      // sur plusieurs lignes si possible.
      HintWrap = true
    }
  }
};

Ce binding étant réalisé, il sera ensuite appliqué à l'ensemble des tuiles possibles pour l'application.

TileContent contenu = new TileContent()
{
  Visual = new TileVisual()
  {
    // On associe aux trois types de tuile notre contenu
    // défini précédemment.
    TileSmall = new TileBinding()
    {
      Content = binding
    },

    TileMedium = new TileBinding()
    {
      Content = binding
    },

    TileWide = new TileBinding()
    {
      Content = binding
    },
    // TileLarge = ...
  }
};

Si le code précédent peut sembler redondant, c'est qu'il est utilisé dans un cadre d'exemple. En effet, pour une application complexe, le contenu affiché sur chacune des tuiles ne sera pas le même : on préférera placer très peu d'informations dans une tuile moyenne et beaucoup plus dans une tuile étendue. La tuile petite quant à elle est rarement mise à jour par les applications, car le contenu affichable est extrêmement limité.

Il suffit ensuite d'utiliser le code suivant pour mettre à jour la tuile :

TileNotification notification = new TileNotification(contenu.GetXml());
TileUpdateManager.CreateTileUpdaterForApplication().Update(notification);

Figure 1.25. Exemple de tuiles modifiées (Mobile)

Exemple de tuiles modifiées (Mobile)

[Note]

Comme pour les notifications UWP, il est conseillé de placer une date d'expiration pour cette mise à jour de tuile, afin que l'icône apparaisse de nouveau à la fin du temps imparti.

// Expiration au bout d'une journée
notification.ExpirationTime = new DateTimeOffset(DateTime.Now, TimeSpan.FromDays(1));

Pour réinitialiser la tuile, par exemple après que l'utilisateur ait pu découvrir le contenu mis en avant, il suffit d'appeler la méthode suivante :

TileUpdateManager.CreateTileUpdaterForApplication().Clear();

Création de tuiles secondaires

Les tuiles secondaires sont un moyen de définir pour une application d'autres points d'entrée. On trouve dans Windows 10 plusieurs exemples, comme la création de tuile secondaire pour un contact particulier ou pour la météo d'une ville particulière. Pour un dernier exemple, on citera l'application Twitter permettant de créer une tuile secondaire pour le résultat d'une recherche de son choix. Celles-ci apparaitront sur l'écran d'accueil et pourront également afficher des informations spécialisées.

La création de tuile secondaire est un moyen d'en faire plus avec une application. Contrairement à la mise à jour d'une tuile principale, celle-ci doit se faire dans l'application et non dans une tâche de fond, car elle dépend du bon vouloir de l'utilisateur.

La création d'une tuile secondaire se fait en utilisant une instance de classe SecondaryTile.

private async void AjoutTuile_Click(object sender, RoutedEventArgs e)
{
  SecondaryTile secondaire = new SecondaryTile(
    // Clef de la tuile pour en modifier l'affichage ultérieurement
    "secondaire", 
    // Nom court de la tuile qui pourra être affiché
    "Secondaire", 
    // Paramètre fourni au lancement de l'application pour déterminer
    // quelle action effectuer
    "appel-tuile-secondaire", 
    // Icône de la tuile. Utilisez le nom de votre image sans le suffixe
    // de l'échelle ("scale") pour cibler le maximum de résolutions
    new Uri("ms-appx:///Assets/Square150x150Logo.png"), 
    // Taille de la tuile choisie. Celle-ci pourra être redimensionnée.
    TileSize.Square150x150
  );

  // Une fois la tuile obtenue, celle-ci peut être modifiée.
  // Voici un exemple de modification, rendant le fond de la tuile rouge.
  secondaire.VisualElements.BackgroundColor = new SolidColorBrush(Colors.Red).Color;

  // On affiche le nom de la tuile pour la dimension choisie. Par défaut il n'est pas affiché.
  secondaire.VisualElements.ShowNameOnSquare150x150Logo = true;
  // Création de la tuile
  await secondaire.RequestCreateAsync();
}
[Warning]

Une tuile secondaire, tout comme une tuile principale, déclenche l'appel à la méthode OnLaunched de la classe App.

La tuile créée, il faut désormais faire la différence au lancement de l'application (méthode OnLaunched) entre la tuile principale et la ou les tuiles secondaires.

Pour ce faire, on utilise la propriété Arguments de l'instance de classe LaunchActivatedEventArgs passé en paramètre de la méthode OnLaunched.

protected override void OnLaunched(LaunchActivatedEventArgs e)
{
  if(e.Arguments == string.Empty) 
  {
    // L'application a été lancée par la tuile principale
  }
  else if(e.Arguments == "appel-tuile-secondaire")
  {
    // L'application a été lancée par la tuile secondaire
    // portant pour id la chaîne de caractères spécifiée
  }
  // Reste de la méthode omise
}

Enfin, votre application doit pouvoir supprimer toute tuile secondaire créée. Les recommandations Microsoft conseillent le placement d'un unique bouton pour épingler la tuile secondaire et pour la supprimer, le texte affiché par le bouton s'adaptant à la présence de la tuile ou non.

Ainsi, pour déterminer si une tuile existe, on utilisera la méthode de classe SecondaryTile.Exists. Ci-dessous se trouve un exemple simple de gestion de tuile secondaire compilant les notions vues précédemment.

<!-- Page MainPage.xaml -->
<Page> <!-- Attributs omis pour la lisibilité -->
  <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <StackPanel Margin="10">
      <Button x:Name="GestionTuile" Click="GestionTuile_Click" />
    </StackPanel>
  </Grid>
</Page>
// Fichier MainPage.xaml.cs
public sealed partial class MainPage : Page
{
  private SecondaryTile Secondaire { get; set; }
  public MainPage()
  {
    this.InitializeComponent();
    Secondaire = new SecondaryTile(
        "secondaire", "Secondaire", "appel-tuile-secondaire", 
        new Uri("ms-appx:///Assets/Square150x150Logo.png"), 
        TileSize.Square150x150
    );
    ChangerTexteBouton();
  }

  private void ChangerTexteBouton()
  {
    if (SecondaryTile.Exists("secondaire"))
    {
      GestionTuile.Content = "Supprimer la tuile";
    }
    else
    {
      GestionTuile.Content = "Ajouter la tuile";
    }
  }

  private async void GestionTuile_Click(object sender, RoutedEventArgs e)
  {
    bool resultat;
    if(SecondaryTile.Exists("secondaire"))
    {
      resultat = await Secondaire.RequestDeleteAsync();
    }
    else
    {
      Secondaire.VisualElements.BackgroundColor = new SolidColorBrush(Colors.Red).Color;
      Secondaire.VisualElements.ShowNameOnSquare150x150Logo = true;
      resultat = await Secondaire.RequestCreateAsync();
    }

    if (resultat)
    {
      Debug.WriteLine("Opération effectuée avec succès");
    } else
    {
      Debug.WriteLine("Une erreur est survenue pendant l'opération");
    }
    ChangerTexteBouton();
  }
}

Etape 7 : Configuration de la tuile principale

Configuration de la tuile principale

1.1.

Pour commencer, téléchargez le document archivé Tiles.zip disponible sur notre plateforme de contenus pédagogiques. Cette archive contient de nombreuses images représentant l'icône de l'application sous différents formats.

Configurez le fichier Package.appxmanifest pour :

  • Changer le nom d'affichage en "Articles SUPINFO"

  • Implémenter les différents icônes

  • Afficher le nom de l'application sur les tuiles étendues et grandes.

Figure 1.26. Réponse

Réponse

1.2.

Créez une tâche en arrière-plan in-process permettant de mettre à jour la tuile principale. Cette tâche devra s'exécuter toutes les quatre heures et afficher le titre et la description du premier article.

Pour ce faire :

  • Enregistrez votre tâche en arrière plan dans la méthode OnLaunched de la classe App en veillant à ne pas dupliquer cet enregistrement à chaque lancement de l'application.

  • Ajoutez une condition pour le déclenchement de cette tâche. L'équipement doit avoir accès à internet.

  • Dans la méthode OnBackgroundActivated, récupérez l'instance de la classe RssReader et stockez dans une variable l'article le plus récent. Utilisez l'exemple vu précédemment pour définir en texte principal de la tuile la propriété Title de l'article, et en contenu la propriété Description de l'article.

  • Dans la méthode OnLaunched, utilisez TileNotificationManager pour réinitialiser la tuile.

Utilisez les événements du cycle de vie dans les outils de débogage de Visual Studio pour déclencher votre tâche en arrière plan.

N'oubliez pas d'inclure Microsoft.Toolkit.Uwp.Notifications pour obtenir les classes liées aux tuiles animées.

Figure 1.27. Rendu attendu

Rendu attendu

// Classe App
protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args)
{
  var deferral = args.TaskInstance.GetDeferral();
  RssReader reader = RssReader.GetInstance();
  Article first = reader.Elements.SelectMany(e => e.Elements)
    .OrderByDescending(a => a.PublicationDate).FirstOrDefault();
  if (first != null)
  {
    TileBindingContentAdaptive binding = new TileBindingContentAdaptive()
    {
      Children =
        {
        new AdaptiveText()
        {
          Text = first.Title,
          HintStyle = AdaptiveTextStyle.Subtitle
        },

        new AdaptiveText()
        {
          Text = first.Description,
          HintStyle = AdaptiveTextStyle.CaptionSubtle,
          HintWrap = true
        }
        }
    };
    TileContent content = new TileContent()
    {
      Visual = new TileVisual()
      {
        TileMedium = new TileBinding()
        {
          Content = binding
        },

        TileWide = new TileBinding()
        {
          Content = binding
        },
        TileLarge = new TileBinding()
        {
          Content = binding
        }
      }
    };
    TileNotification notification = new TileNotification(content.GetXml());
    TileUpdateManager.CreateTileUpdaterForApplication().Update(notification);
  }
  deferral.Complete();
}

protected override void OnLaunched(LaunchActivatedEventArgs e)
{
  TileUpdateManager.CreateTileUpdaterForApplication().Clear();
  string backgroundTaskName = "UpdateTile";
  if(BackgroundTaskRegistration.AllTasks.Values
        .Where(t => t.Name == backgroundTaskName).FirstOrDefault() == null)
  {
    BackgroundTaskBuilder builder = new BackgroundTaskBuilder();
    builder.Name = backgroundTaskName;
    builder.AddCondition(new SystemCondition(SystemConditionType.InternetAvailable));
    builder.SetTrigger(new TimeTrigger(240, false));
    BackgroundTaskRegistration task = builder.Register();
  }
  // Reste de la méthode omise
}
About SUPINFO | Contacts & addresses | Teachers | Press | INVESTOR | Conditions of Use & Copyright | Respect of Privacy
Logo de la société Cisco Logo de la société IBM Logo de la société Sun-Oracle Logo de la société Apple Logo de la société Sybase Logo de la société Novell Logo de la société Intel Logo de la société Accenture Logo de la société SAP Logo de la société Prometric Logo du IT Academy Program par Microsoft

SUPINFO International University is globally operated by EDUCINVEST Belgium - Avenue Louise, 534 - 1050 Brussels
and is accredited in France by Association Ecole Supérieure d'Informatique de Paris (ESI SUPINFO)