Plan du site  
français  English
pixel
pixel

Articles - Étudiants SUPINFO

Introduction

Après avoir réalisé la récupération de contenus externes et rendu leur affichage dans notre application, nous allons détailler par l'exemple deux nouvelles possibilités de la plateforme UWP. Au travers de ce chapitre, nous allons nous intéresser à la navigation et l'entrelacement des pages (d'un point de vue ergonomique comme programmatique) ainsi qu'aux dictionnaires de ressources permettant de centraliser un ensemble de propriétés et les rendre accessibles dans plusieurs pages.

Navigation au sein d'une application

Une navigation aisée est un élément important de l'expérience utilisateur et suppose plusieurs éléments :

  • Une structure efficace de liens entre les pages, formant ainsi une hiérarchie claire entre les différentes parties de l'application.

  • Des éléments visuels de mise en page ergonomiques et connus, pour dissiper toute interrogation de l'utilisateur quant aux possibilités de l'application.

  • Des éléments contextuels dans l'application comme des boutons ou des liens.

Structurer son application

Quand l'utilisateur affiche la page principale de votre application, nous offrons en tant que concepteurs de celle-ci des possibilités de naviguer vers différentes pages. Par itération, on peut construire l'arbre de hiérarchie des pages, qui doit rester aussi simple que possible et éviter au maximum les redondances.

On représente la navigation au sein de l'application comme un arbre enraciné, une structure mathématique composée de différents noeuds (ici, nos pages), reliés par des arêtes (ici, nos possibilités de navigation) et où toutes les arêtes se rejoignent en un point (ici, le point d'entrée de notre application).

Figure 1.1. Exemple de hiérarchie de navigation dans une application

Exemple de hiérarchie de navigation dans une application

[Note]

La structure mathématique de l'arbre décrite plus haut ne doit pas posséder de cycle. Dans notre figure ci-dessus, il est très déconseillé de relier par exemple la page 2A et la page 2C1.

On peut décompter deux catégories de navigation dans notre exemple ci-dessus : la navigation par pairs, dans notre cas les liaisons horizontales, et la navigation

  • La navigation par pairs : Liant entre elles plusieurs pages, l'utilisateur peut passer d'une page à une autre dans n'importe quel ordre. On peut l'utiliser pour souligner les différentes fonctionnalités d'une application ou pour créer des catégories. Dans notre schéma, ce sont les liaisons horizontales.

  • La navigation par hiérarchie : Chaque page possède un seul et unique parent, mais une page peut avoir plusieurs enfants. Pour naviguer sur une page, il est nécessaire de naviguer par son parent. On peut l'utiliser par exemple pour afficher des détails sur un élément, la page parente étant une vue d'ensemble et ses enfants directs des vues plus précises. Dans notre schéma, ce sont les liaisons verticales.

Une application devra combiner les deux types de navigations selon les besoins et c'est une étape préliminaire nécessaire pour offrir une expérience optimale.

Eléments visuels

Si nous avons déjà pu voir plusieurs contrôles de mise en page dans les chapitres précédents, les contrôles qui vont suivre sont spécialement dédiés à la navigation.

Pivot et PivotItem

Dédié à la navigation par pairs, l'élément Pivot se constitue de plusieurs éléments PivotItem, chacun représentant une vue de l'application.

Un contrôle PivotItem peut posséder l'attribut "Header" pour définir un simple texte. Si vous souhaitez réaliser des constructions plus compliquées, il est possible d'ajouter la balise PivotItem.Header prévue à cet effet.

<Page> <!-- Attributs omis pour la lisibilité -->
  <Pivot Background="White">
    <PivotItem>
      <PivotItem.Header>
        <StackPanel Orientation="Horizontal">
          <TextBlock>Categorie</TextBlock>
          <TextBlock>A</TextBlock>
        </StackPanel>
      </PivotItem.Header>
      <TextBlock>Contenu 1</TextBlock>
    </PivotItem>
    <PivotItem>
      <PivotItem.Header>
        <StackPanel Orientation="Horizontal">
          <TextBlock>Categorie</TextBlock>
          <TextBlock>B</TextBlock>
        </StackPanel>
      </PivotItem.Header>
      <TextBlock>Contenu 2</TextBlock>
    </PivotItem>
  </Pivot>
</Page>

Figure 1.2. Rendu visuel d'un élément Pivot

Rendu visuel d'un élément Pivot

Au clic sur un des en-têtes ou en faisant glisser le contenu vers la gauche, la navigation se fait en déclenchant une animation sobre soulignant la navigation.

[Note]

L'élément Pivot a un point commun avec ListView et GridView : ils héritent tous trois de l'élément de base ItemsControl.

Hub

Figure 1.3. Architecture d'un Hub (Source: docs.microsoft.com)

Architecture d'un Hub (Source: docs.microsoft.com)

A l'inverse du Pivot se comportant comme des onglets et focalisant l'attention de l'utilisateur sur celui qu'il visite, le Hub est prévu pour explorer le contenu horizontalement. Tout l'enjeu de ce contrôle est d'afficher une section tout en laissant apparaître une portion de la section suivante pour inviter l'utilisateur à parcourir l'application.

<Hub Header="Mon application" Background="White">
  <HubSection Header="Liste d'articles">
    <DataTemplate>
      <ListView ItemsSource="{Binding Articles}" Width="300">
        <ListView.ItemTemplate>
          <DataTemplate>
            <TextBlock Text="{Binding Title}" TextWrapping="Wrap"/>
          </DataTemplate>
        </ListView.ItemTemplate>
      </ListView>
    </DataTemplate>
  </HubSection>
  <HubSection Header="A propos">
    <DataTemplate>
      <TextBlock>Lorem ipsum dolor sit amet</TextBlock>
    </DataTemplate>
  </HubSection>
</Hub>

Figure 1.4. Rendu d'un Hub

Rendu d'un Hub

Le contrôle Hub propose une grande flexibilité, car il est possible de définir des tailles différentes à chaque contrôle HubSection, présentant des éléments complètement différents avec une mise en page adaptée à chaque partie de votre application. Les différents éléments HubSection peuvent aussi s'agencer verticalement sur de plus petits équipements comme les smartphones.

Si le rapport avec le contrôle Pivot peut être fait dans le code XAML, le Hub suggère et incite à l'exploration quand le Pivot offre une claire séparation de ses éléments. Le Hub est non-exhaustif et peut s'assimiler à une converture de magazine, quand le Pivot est exaustif et rappelle les onglets. Entre ces deux possibilités, le choix se fera en fonction de l'expérience que vous voulez offrir à vos utilisateurs.

Figure 1.5. Exemple de Hub (Source : msdn.microsoft.com)

Exemple de Hub (Source : msdn.microsoft.com)

SplitView

Le contrôle SplitView est utilisé pour faire un panneau de navigation rétractable ou non, survolant ou non le contenu en cas d'ouverture.

Figure 1.6. Exemple de SplitView (Source: docs.microsoft.com)

Exemple de SplitView (Source: docs.microsoft.com)

<Page> <!-- Attributs omis pour la lisibilité -->
  <SplitView IsPaneOpen="False" CompactPaneLength="40" OpenPaneLength="200">
    <SplitView.Pane>
      <TextBlock>Panneau de navigation</TextBlock>
    </SplitView.Pane>
    <SplitView.Content>
      <TextBlock>Contenu principal de la page</TextBlock>
    </SplitView.Content>
  </SplitView>
</Page>

On remarque plusieurs attributs dans cet exemple :

  • IsPaneOpen : Attribut définissant l'état initial du panneau de navigation. S'il est vrai, le panneau sera ouvert par défaut à l'affichage de la page. C'est également l'attribut à modifier si l'on souhaite implémenter un bouton agissant sur l'ouverture ou la fermeture du panneau, dans le cas d'un menu "burger" (☰) par exemple.

  • CompactPaneLength : La largeur du panneau quand il est rétracté.

  • OpenPaneLength : La largeur du panneau quand il est étendu.

Ce contrôle dispose de plusieurs comportements, définissable par l'attribut DisplayMode :

  • Overlay : Le panneau est par défaut caché. Quand le panneau est ouvert, il passe au-dessus du contenu.

  • Inline : Le panneau est toujours ouvert et est côte-à-côté avec le contenu.

  • CompactOverlay : Le panneau est toujours visible, avec une taille définie par l'attribut CompactPaneLength (par défaut 48). Quand le panneau est ouvert, il passe au-dessus du contenu.

  • CompactInline : Le panneau est toujours visible, avec une taille définie par l'attribut CompactPaneLength (par défaut 48). Quand le panneau est ouvert, il réduit la place allouée au contenu.

Enfin, il est possible d'utiliser l'attribut PanePlacement pour définir si le panneau est à gauche ou à droite du contenu. Il est positionné par défaut à gauche.

CommandBar

Un contrôle CommandBar permet d'afficher à n'importe quel endroit d'une page une zone horizontale de contrôles, majoritairement des boutons. Cette barre peut être utilisée pour offrir des commandes spécifiques à un contrôle : on peut prendre l'exemple d'un lecteur média au sein d'une application avec une barre de commandes dédiée à la lecture (Play, Pause, Stop, ...). Il est également possible de placer cette barre de commande en bas d'une page, en utilisant la balise Page.BottomAppBar.

Une barre de commande est constituée de trois zones :

  • Content : Optionnelle, elle place les commandes à gauche de la barre. Il est possible de positionner d'autres éléments comme un TextBlock pour afficher une information.

  • PrimaryCommands : Zone utilisée par défaut si non précisé. Elle place les commandes à droite. Chaque commande supplémentaire sera affichée à gauche de la précédente.

  • SecondaryCommands : Optionnelle, elle fait apparaître l'ellipse à droite de la barre. Au clic sur cette ellipse, ces commandes sont affichées sans icône. C'est également dans cette zone que se retrouveront les commandes primaires si l'équipement n'a pas une largeur suffisante pour tous les afficher.

<Page> <!-- Attributs omis pour la lisibilité -->
  <Grid Background="White">
    <ListView ItemsSource="{Binding Articles}" Width="300">
      <ListView.ItemTemplate>
        <DataTemplate>
          <TextBlock Text="{Binding Title}" TextWrapping="Wrap"/>
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>
  </Grid>
  <Page.BottomAppBar>
    <CommandBar>
      <CommandBar.Content>
        <TextBlock Margin="10">Statut : En cours</TextBlock>
      </CommandBar.Content>
      <AppBarButton Icon="Folder" Label="Parcourir" />
      <AppBarButton Icon="RepeatAll" Label="Rafraîchir" />
      <AppBarButton Icon="Add" Label="Ajouter" />
      <CommandBar.SecondaryCommands>
        <AppBarButton Label="A propos" />
        <AppBarButton Label="Paramètres" />
      </CommandBar.SecondaryCommands>
    </CommandBar>
  </Page.BottomAppBar>
</Page>

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

Rendu de l'exemple précédent

Navigation par code-behind

La navigation par code-behind consiste à rediriger l'utilisateur vers une autre page dans le cadre d'un événement. Quand il clique sur un bouton ou un lien, un élément de liste ou fait une action avec tout autre contrôle, il est possible de le rediriger programmatiquement.

[Note]

Dans le cas d'une balise HyperlinkButton, il est possible de définir l'attribut NavigateUri pour rediriger l'utilisateur sans aucun code-behind.

La syntaxe de cet appel est décrit dans l'exemple ci-dessous :

// Exemple d'événement déclenché au clic sur un bouton d'un élément CommandBar
private void AppBarButton_Click(object sender, RoutedEventArgs e)
{
  // En considérant que la page ciblée soit appelée "DeuxiemePage.xaml"
  Frame.Navigate(typeof(DeuxiemePage));
}

Il est également possible de passer un argument à la page ciblée, par le second argument de Frame.Navigate. Cet argument sera très utile pour contextualiser la navigation de l'utilisateur. En prenant l'exemple de notre liste d'articles, en considérant que si l'utilisateur clique sur un élément de la liste, il soit redirigé vers une page de visualisation de ce même article, il nous faudra envoyer à notre deuxième page l'article en question.

<Grid Background="White">
  <!-- Notez les attributs suivants :
       - ItemClick : Evénement. Méthode à appeler au Click.
       - IsItemClickEnabled : Confirme l'interactivité des éléments.
  -->
  <ListView ItemsSource="{Binding Articles}" ItemClick="ListView_ItemClick" 
            IsItemClickEnabled="True">
    <ListView.ItemTemplate>
      <DataTemplate>
        <TextBlock Text="{Binding Title}" TextWrapping="Wrap"/>
      </DataTemplate>
    </ListView.ItemTemplate>
  </ListView>
</Grid>
private void ListView_ItemClick(object sender, ItemClickEventArgs e)
{
  // On récupère l'article grâce à la propriété ClickedItem de l'objet ItemClickEventArgs
  Article a = e.ClickedItem as Article;
  // On redirige l'utilisateur vers la page DeuxiemePage en transférant également l'article
  Frame.Navigate(typeof(DeuxiemePage), a);
}

Pour récupérer l'article dans la deuxième page, il suffit de surcharger la méthode OnNavigatedTo. Pour cela, après avoir écrit "protected override" dans le code-behind de la deuxième page, IntelliSense nous la propose parmi de nombreuses autres méthodes. Après génération, il ne nous reste plus qu'à récupérer l'article en tant que paramètre de la navigation :

protected override void OnNavigatedTo(NavigationEventArgs e)
{
  base.OnNavigatedTo(e);
  Article a = e.Parameter as Article;
}

Le deuxième argument de la méthode Frame.Navigate() étant de type object, il est possible de transférer une structure plus complexe, comme une liste d'objets ou un objet construit de toutes pièces contenant de nombreuses propriétés.

Etape 3 : Visualisation des articles

Dans cette étape, nous allons réaliser la partie graphique de la visualisation d'articles.

Exercice corrigé : Visualisation d'articles

1.1.

Pour commencer, téléchargez le document archivé disponible sur notre plateforme de contenus pédagogiques. Cette archive contient deux fichiers, DataProviders.cs et 3WIN_Helpers.cs.

  • DataProviders.cs contient deux classes statiques fournissant l'ensemble des catégories et des groupes pour notre application. Ces classes nous permettront de renforcer notre classe RssReader et contient les couleurs et icônes utilisées pour l'affichage des vignettes.

  • 3WIN_Helpers.cs contient comme son nom l'indique des méthodes utilitaires dont nous nous servirons pour ce projet.

Ajoutez un constructeur dans la classe Group prenant en paramètre l'id (int), le nom (string), l'icône (char) et la couleur (string). Ajoutez une propriété Icon (string) et une propriété Color (SolidColorBrush). Utilisez la méthode _3WIN_Color_Helper.GetBytesFromHex pour initialiser les bonnes valeurs grâce au constructeur.

Ajoutez un constructeur dans la classe Category prenant en paramètre l'id (int), le nom (string) et l'id du groupe duquel elle dépend (int). Utilisez ces valeurs pour construire votre objet.

public class Group {
  public string Icon { get; set; }
  public SolidColorBrush Color { get; set; }
  // Reste des propriétés omises

  public Group(int id, string name, char icon, string colorCode)
  {
    Id = id; Name = name; Icon = icon.ToString();
    byte[] colors = _3WIN_ColorHelper.GetBytesFromHex(colorCode);
    Color = new SolidColorBrush(Windows.UI.Color.FromArgb(
      255, colors[0], colors[1], colors[2]
    ));
  }
}
public class Category {
  // Propriétés omises
  public Category(int id, string name, int groupId)
  {
    Id = id; Name = name;
    Group = GroupProvider.Groups.Where(g => g.Id == groupId).First();
  }
}

1.2.

Modifiez la méthode Start de la classe RssReader. Au lieu d'utiliser une boucle de 1 au nombre de catégories, utilisez la classe CategoryProvider.

class RssReader {
  // Reste de la classe omis
  private void Start() {
    string uri = ARTICLES_URI;  
    foreach (Category c in CategoryProvider.Categories)
    {
      Load(new Uri(uri + c.Id), c);
    }
}

1.3.

Créez une nouvelle page ReadPage. Puis, dans la page principale, ajoutez un événement au clic sur un élément de la liste. Au clic, l'utilisateur devra naviguer vers ReadPage, l'article cliqué étant passé en paramètre de navigation.

Ajouter le code suivant dans la classe Article :

private static string CONTENT_URI = 
    "https://www.supinfo.com/api/supinfo?action=content&articleId=";
public Uri DecoratedUri { get { return new Uri(CONTENT_URI + Id); } }

La propriété DecoratedUri permet d'afficher le contenu d'un article au format HTML.

Dans ReadPage, utilisez un contrôle WebView et définissez-lui en propriété Source notre nouvelle propriété Article.DecoratedUri.

<!-- MainPage.xaml. Reste du fichier omis -->
<ListView ItemsSource="{Binding Articles}" ItemClick="ListView_ItemClick" 
          IsItemClickEnabled="True">
  <!-- ... -->
</ListView>
// MainPage.xaml.cs. Reste du fichier omis
private void ListView_ItemClick(object sender, ItemClickEventArgs e)
{
  Article a = e.ClickedItem as Article;
  Frame.Navigate(typeof(ReadPage), a);
}
<!-- ReadPage.xaml.cs -->
<Page> <!-- Attributs omis pour la lecture -->
  <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <WebView x:Name="MainWebView"/>
  </Grid>
</Page>
// ReadPage.cs
public sealed partial class ReadPage : Page
{
  public Article Article { get; set; }
  public ReadPage()
  {
    this.InitializeComponent();
  }
  protected override void OnNavigatedTo(NavigationEventArgs e)
  {
    if (e.Parameter is Article) Article = e.Parameter as Article;
    else Frame.Navigate(typeof(MainPage));

    MainWebView.Source = Article.DecoratedUri;
  }
}

Testez votre application. Au clic sur un élément de la liste, vous devriez être redirigé vers cette nouvelle page affichant le contenu de l'article.

1.4.

Dans ReadPage, affichez le titre de l'article au dessus du contrôle WebView. Ce texte devrait être présenté par un texte de taille 28 et ne pas être collé au contrôle WebView. Il existe plusieurs solutions concernant le XAML.

<!-- ReadPage.xaml. Reste de la page omise -->
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" 
      DataContext="{Binding Article}">
  <Grid.RowDefinitions>
    <RowDefinition Height="60"/>
    <RowDefinition Height="*"/>
  </Grid.RowDefinitions>
  <TextBlock FontSize="28" Text="{Binding Title}" Margin="10" Grid.Row="0"/>
  <WebView x:Name="MainWebView" Grid.Row="1" Margin="10, 10, 10, 0"/>
</Grid>
// ReadPage.cs. Reste de la classe omise
protected override void OnNavigatedTo(NavigationEventArgs e)
{
  // Reste de la méthode omise
  DataContext = this;
}

1.5.

Ajoutez une barre de commande dans la page ReadPage avec un nouvel élément ayant comme icône World et le texte "Lire sur site". Au clic sur cet élément, le navigateur de l'utilisateur doit s'ouvrir avec comme adresse l'attribut Link de notre Article. Utilisez pour ceci la méthode _3WIN_HttpHelper.WebLaunch.

<!-- ReadPage.xaml. Reste de la page omise -->
<Page.BottomAppBar>
  <CommandBar>
    <AppBarButton Icon="World" Label="Lire sur site" Click="AppBarButton_Click"/>
  </CommandBar>
</Page.BottomAppBar>
// ReadPage.xaml.cs. Reste de la page omise.
private void AppBarButton_Click(object sender, RoutedEventArgs e)
{
  _3WIN_HttpHelper.WebLaunch(Article.Link);
}

1.6.

Ajouter dans la page MainPage une barre de commande avec un nouvel élément ayant comme icône RepeatAll et le texte "Rafraîchir". Au clic sur cet élément, l'ensemble de la procédure de récupération d'articles doit se faire. Pour ceci, implémentez une méthode Reset dans la classe RssReader, qui vide la liste d'objets RssElement et qui appelle la méthode Start.

Enfin, quand l'événement LoadCompleted est déclenché, videz dans la page MainPage la liste des articles avant de la populer de nouveau.

<!-- MainPage.xaml. Reste de la page omise -->
<Page.BottomAppBar>
  <CommandBar>
    <AppBarButton Icon="Refresh" Label="Rafraîchir" Click="Refresh_Click"/>
  </CommandBar>
</Page.BottomAppBar>
// MainPage.xaml.cs. Reste de la classe omise
private void Refresh_Click(object sender, RoutedEventArgs e)
{
  RssReader.Reset();
}
private void RssReader_LoadCompleted(object sender, EventArgs e)
{
  Articles.Clear();
  // Suite de la méthode omise
}
// Classe RssReader. Reste de la classe omise
public void Reset()
{
  this.Elements.Clear(); 
  Start();
}

La page MainPage écoutant l'événement LoadCompleted de la classe RssReader, le simple fait de relancer la méthode Reset appliquera automatiquement les modifications côté utilisateur.

1.7.

Modifiez l'affichage de la liste d'articles. Indiquez une bordure grise d'un pixel (uniquement sur la bordure basse). Pour ceci, utilisez la balise ListView.ItemContainerStyle.

<ListView.ItemContainerStyle>
  <Style TargetType="ListViewItem">
    <Setter Property="BorderBrush" Value="Gray"/>
    <Setter Property="BorderThickness" Value="0, 1, 0, 0"/>
  </Style>
</ListView.ItemContainerStyle>

1.8.

Créez une nouvelle propriété DateLabel de type string dans la classe Article. Surchargez l'accesseur (get).

  • Si l'écart entre la date de publication de l'article et de la date actuelle est supérieure à 99 jours, retournez "99+j"

  • Si cet écart est supérieur à 1 jour, retournez ce nombre de jours suivi de la lettre "j".

  • Si cet écart est supérieur à 1 heure, retournez ce nombre d'heures suivi de la lettre "h".

  • Si cet écart est inférieur ou égal à 1 heure, retournez le caractère "!".

// Classe Article. Reste de la classe omise.
public string DateLabel
{
  get
  {
    DateTime d = DateTime.Now;
    TimeSpan ts = d - PublicationDate;
    if (ts.Days > 99) return "99+j";
    if (ts.Days > 0) return ts.Days + "j";
    if (ts.Hours > 0) return ts.Hours + "h";
    else return "!";
  }
}

Dictionnaire de Ressources

Créer soi-même des ressources

Avec plusieurs pages se pose la question de la cohérence des interfaces au sein de l'ensemble de l'application. En effet, il est conseillé de respecter une charte graphique commune tout au long de l'utilisation de l'application. Cette charte graphique peut se présenter de différentes manières, mais inclus entre autres des dimensions, des tailles et styles de police, des couleurs.

Il n'est pas souhaitable de dupliquer les styles des éléments dans toutes nos pages ; cela deviendrait fastidieux et difficile à maintenir. Pour cela, la plateforme UWP nous propose des styles sous forme XAML à appliquer sur nos éléments.

Pour ce faire, nous utiliserons une balise RessourceDictionary.

<ResourceDictionary>
  <Style x:Name="ArticleTitle" TargetType="TextBlock">
    <Setter Property="Foreground" Value="Red" />
    <Setter Property="FontSize" Value="20" />
    <Setter Property="FontStyle" Value="Italic" />
  </Style>
</ResourceDictionary>

Dans notre exemple, on définit un style nommé ArticleTitle qui peut uniquement s'appliquer aux attributs TextBlock. Il est conseillé de définir cet attribut TargetType pour activer l'auto-complétion des propriétés et valeurs dans les balises Setter. Ces balises définissent une valeur à assigner à une propriété, ici une couleur de texte rouge, une taille de police de 20 et un style de texte en italique.

Pour appliquer ces styles sur une balise, il suffit d'y définir l'attribut Style comme suit :

<Grid Background="White">
  <ListView ItemsSource="{Binding Articles}" ItemClick="ListView_ItemClick" 
            IsItemClickEnabled="True">
    <ListView.ItemTemplate>
      <DataTemplate>
        <TextBlock Text="{Binding Title}" TextWrapping="Wrap" 
                   Style="{StaticResource ArticleTitle}"/>
      </DataTemplate>
    </ListView.ItemTemplate>
  </ListView>
</Grid>

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

Rendu de l'exemple précédent

Ce dictionnaire de ressource peut être défini :

  • Dans le fichier App.xaml. Ces styles seront accessibles pour toute l'application et chargés au lacement de celle-ci. De fait, il est déconseillé de mettre trop d'éléments dans ce fichier car celui peut nuire à la rapidité de lancement de l'application. Exemple de dictionnaire dans App.xaml :

    <Application> <!-- Attributs omis pour la lisibilité -->
      <Application.Resources>
        <ResourceDictionary>
          <!-- Placer les styles ici -->
        </ResourceDictionary>
      </Application.Resources>
    </Application>
  • Dans la page concernée. Dans ce cas, les styles ne sont applicables qu'à la page en cours. Le gain réside dans la maintenabilité, en appliquant à plusieurs éléments d'une même page un style défini une seule fois :

    <Page> <!-- Attributs omis pour la lisibilité -->
      <Page.Resources>
        <ResourceDictionary>
          <Style x:Name="ArticleTitlePage" TargetType="TextBlock">
            <Setter Property="Foreground" Value="Red" />
            <Setter Property="FontSize" Value="20" />
            <Setter Property="FontStyle" Value="Italic" />
          </Style>
        </ResourceDictionary>
      </Page.Resources>
      <Grid Background="White">
        <TextBlock Style="{StaticResource ArticleTitlePage}">Hello World</TextBlock>
      </Grid>
    </Page>
  • Au sein d'un fichier dictionnaire de ressources. Il est possible et conseillé de séparer les styles au sein de différents fichiers pour mieux maintenir les styles, ceux-ci pouvant devenir très nombreux pour une application complète. Pour cela, il faudra créer un nouvel élément dans le projet et sélectionner "Dictionnaire de ressources" :

    <!-- Fichier Styles.xaml -->
    <ResourceDictionary
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:CourseApp">
      <Style x:Name="ArticleTitlePage" TargetType="TextBlock">
        <Setter Property="Foreground" Value="Red" />
        <Setter Property="FontSize" Value="20" />
        <Setter Property="FontStyle" Value="Italic" />
      </Style>
    </ResourceDictionary>

    L'inclusion de ce dictionnaire de ressources, qu'elle soit dans le fichier App.xaml ou dans une page, se déroule comme suit :

    <ResourceDictionary Source="/Styles.xaml" />
[Note]

Beaucoup de contrôles comme Page peuvent intégrer une balise "Resources". Dans ce cas, la ou les ressources seront définies dans cette balise et non dans toute la page.

[Note]

Si l'attribut "Name" d'un Style n'est pas défini, alors tous les contrôles ciblés par l'attribut TargetType utiliseront ce style.

L'héritage est permis dans toutes les définitions de styles grâce à l'attribut BasedOn.

<Style x:Name="StyleDeBase" TargetType="TextBlock">
  <Setter Property="Foreground" Value="White" />
</Style>
<Style x:Name="StyleEtendu" TargetType="TextBlock" 
       BasedOn="{StaticResource StyleDeBase}">
  <Setter Property="FontSize" Value="20" />
</Style>

Dans l'exemple précédent, tous les éléments TextBlock utilisant le style StyleEtendu bénéficieront d'une couleur blanche et d'une taille de police de 20.

Pour finir, il est possible de relier plusieurs dictionnaires de ressources différents dans une même page grâce à la balise ResourceDictionary.MergedDictionaries. Dans ce cas, les différents dictionnaires de ressources seront virtuellement concaténés pour inclure tous les styles nécessaires au bon fonctionnement.

<ResourceDictionary>
  <ResourceDictionary.MergedDictionaries>
    <ResourceDictionary Source="Dictionnaire1.xaml"/>
    <ResourceDictionary Source="Dictionnaire2.xaml"/>
  </ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

Ressources fournies par UWP

Pour offrir une expérience utilisateur similaire et efficace sur l'ensemble de la plateforme, Microsoft vous propose un ensemble de styles déjà présents façonnant Modern UI.

Ces ressources peuvent se découper en trois catégories :

  • Une enumération de type Color. La liste exhaustive des 24 couleurs système se trouve sur le site de la documentation officielle Microsoft ici.

  • Une énumération de type SolidColorBrush. La liste de tous ces styles se trouve ici.

  • Une liste de styles prédéfinis pour la typographie, permettant d'appliquer entre autres la hiérarchie visuelle de l'image suivante. Pour ceux-ci, vous trouverez plus d'explications ici.

[Note]

Si vous souhaitez découvrir en détail l'implémentation de tous les styles et animations, vous trouverez le code XAML utilisé par UWP au chemin suivant :

C:\Program Files (x86)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\{Version}\Generic\

Le fichier themeresources.xaml contient l'ensemble des ressources, et generic.xaml définit leur implémentation dans les différents thèmes de l'application : Default, Light, Dark et HighContrast).

UWP propose trois thèmes par défaut pour ses applications : Light avec des textes noirs sur fond blanc, Dark avec des textes blancs sur fond noir, et HighContrast pour les personnes possédant des déficiences visuelles.

Si vous souhaitez créer vos propres styles visuels, il est conseillé de décliner ces styles dans les trois thèmes en se basant sur les éléments existants dans UWP. Pour ce faire, utilisez l'attribut x:Key des balises ResourceDictionary avec l'une des valeurs Light, Dark et HighContrast.

<ResourceDictionary
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="using:CourseApp">
  <ResourceDictionary.ThemeDictionaries>
    <ResourceDictionary x:Key="Light">
      <SolidColorBrush x:Key="ArticleTitlePage" Color="Red" />
    </ResourceDictionary>
    <ResourceDictionary x:Key="Dark">
      <SolidColorBrush x:Key="ArticleTitlePage" Color="Blue" />
    </ResourceDictionary>
    <ResourceDictionary x:Key="HighContrast">
      <SolidColorBrush x:Key="ArticleTitlePage" Color="Green" />
    </ResourceDictionary>
  </ResourceDictionary.ThemeDictionaries>

  <Style x:Name="ArticleTitlePage" TargetType="TextBlock">
    <Setter Property="FontSize" Value="20" />
    <Setter Property="FontStyle" Value="Italic" />
    <Setter Property="Foreground" Value="{StaticResource ArticleTitlePage}" />
  </Style>
</ResourceDictionary>

Dans cet exemple, les contrôles TextBlock avec l'attribut Style="{StaticResource ArticleTitlePage}" possèderont :

  • Quelque soit le thème : une taille de police de 20 rédigé en italique

  • Pour le thème clair, une couleur rouge

  • Pour le thème sombre, une couleur bleue

  • Pour le thème à haut contraste, une couleur verte

Bouton Retour

Quelque soit le matériel utilisé, un bouton "Retour" est désormais utilisable avec UWP, et ceci sans avoir besoin de l'intégrer dans l'interface. Ce bouton est placée dans les applications Desktop dans la barre de l'application, tandis qu'il est présent dans la version mobile soit dans le bandeau rétractable du système, soit comme bouton matériel.

Figure 1.9. Bouton Back - Application PC en haut, Mobile en bas

Bouton Back - Application PC en haut, Mobile en bas

Ce bouton retour permet de revenir si possible à la vue précédente tout en offrant une expérience là-encore unifiée entre toutes les applications de l'utilisateur : il n'est pas nécessaire d'implémenter soi-même ce système avec un bouton dans la mise en page, l'utilisateur sait en permanence s'il est possible de revenir en arrière, et si oui, comment.

Ce bouton n'est pas forcément à implémenter sur toutes les pages. Par exemple, la page principale n'aurait que peu d'utilité à en disposer. A l'opposé et pour notre application, revenir à la page principale après avoir visionné un article fait sens.

Pour implémenter le bouton Back, il est nécessaire d'ajouter dans le code-behind ce bloc de code :

SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
    AppViewBackButtonVisibility.Visible; 
// Utilisez la possibilité "Collapsed" pour cacher le bouton "Back"

UWP peut déterminer si l'application peut revenir en arrière ou non, de par le chemin qu'a effectué l'utilisateur et la hiérarchie des pages. La propriété CanGoBack vous permettra de consolider votre système de navigation en offrant ou non la visibilité de ce bouton.

if(this.Frame.CanGoBack) {
  SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
      AppViewBackButtonVisibility.Visible; 
} else {
  SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
      AppViewBackButtonVisibility.Collapsed;
}
[Note]

Ce système, bien que simple à mettre en place, peut se révéler rapidement très répétitif et fastidieux. Il est cependant possible d'implémenter ce comportement pour toutes les pages de l'application, et ce sans aucun risque de mauvaise interprétation. Ainsi, pour chaque page, s'il est possible de revenir en arrière, alors le choix sera offert à l'utilisateur.

Pour implémenter le bouton Retour dans toutes les pages de l'application, il est nécessaire d'ajouter du code dans le fichier App.xaml.cs, fichier code-behind exécuté au lancement de l'application. Notre but sera de définir un comportement à chaque navigation vers une page, pour déterminer si ce bouton doit apparaître ou non.

// Fichier App.xaml.cs
// Reste du fichier omis
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
  // Code déjà présent dans le fichier à la création du projet
  Frame rootFrame = Window.Current.Content as Frame;
  if (rootFrame == null)
  {
    rootFrame = new Frame();

    rootFrame.NavigationFailed += OnNavigationFailed;
    // En dessous du délégué écoutant l'événement NavigationFailed,
    // ajoutons un délégué écoutant l'événement Navigated.
    rootFrame.Navigated += OnNavigated;

    // Suite de la méthode...

    // A la fin de la méthode, il faudra ajouter un délégué écoutant l'événement
    // BackRequested pour la vue courante (page principale au lancement de l'application,
    // ou page vue en dernier si l'application sort d'un état suspendu. Pour celle-ci, 
    // s'il est possible de revenir en arrière, affichons le bouton Retour.
    SystemNavigationManager.GetForCurrentView().BackRequested += OnBackRequested;
    SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
      rootFrame.CanGoBack ?
      AppViewBackButtonVisibility.Visible :
      AppViewBackButtonVisibility.Collapsed;
}

Il ne reste plus qu'à rédiger les deux méthodes OnNavigated et OnBackRequested. La première déterminera s'il faut afficher le bouton Retour pour la page vers laquelle on naviguera, l'autre pour déterminer le comportement à effectuer en cas de clic sur le bouton Retour.

private void OnBackRequested(object sender, BackRequestedEventArgs e)
{
  Frame rootFrame = Window.Current.Content as Frame;
  // Vérification supplémentaire, si la page courante peut effectuer le
  // retour arrière, alors on appelle la méthode GoBack en considérant
  // l'événement effectué.
  if (rootFrame.CanGoBack)
  {
    e.Handled = true;
    rootFrame.GoBack();
  }
}
private void OnNavigated(object sender, NavigationEventArgs e)
{
  // A chaque navigation vers une page, on détermine si la page de destination
  // supporte le retour en arrière, et on met à jour la visibilité du bouton Retour.
  SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
    ((Frame)sender).CanGoBack ?
    AppViewBackButtonVisibility.Visible :
    AppViewBackButtonVisibility.Collapsed;
}

Police Segoe MDL2 Assets

Cette courte section aura pour but de présenter la police de caractères Segoe MDL2 Assets. Cette police un peu spéciale n'est pas constituée de lettres ou de chiffres mais d'icônes. En effet, il est devenu courant de ne plus utiliser d'images pour les éléments de navigation et la signalétique d'un site mais bien une police de caractères particulière constituée d'icônes.

Cette méthode, bien que possible depuis de nombreuses années notamment via les polices Webdings et Windings ont connu un nouvel essor avec les technologies Web et se retrouvent aujourd'hui extrêmement utilisées. On peut citer par exemple material-icons et font-awesome.

La police Segoe MDL2 Assets est dans la même veine et propose de nombreuses icônes. Il est conseillé d'en user pour les applications UWP, car elles renforcent l'ergonomie générale de la plateforme. La liste complète se trouve à cette adresse : https://docs.microsoft.com/en-us/windows/uwp/style/segoe-ui-symbol-font

[Note]

Comme précisé dans cette documentation, il est possible d'utiliser une énumération via code-behind. Ainsi, pour accéder au symbole AcceptLegacy (E10B), vous pouvez utiliser la syntaxe suivante en XAML :

<TextBlock FontFamily="Segoe MDL2 Assets" Text="&#xE10B;" />

Ou la syntaxe suivante en C# :

TextBlock.Text = '\xE10B'.ToString();

Etape 4 : Présentation des articles

Etape 4.2 : Présentation des articles

1.1.

Modifiez l'affichage des articles dans la liste. Reproduisez l'interface ci-dessous.

Figure 1.10. Représentation d'un Article

Représentation d'un Article

Attentes techniques :

  • Une marge de 5 doit être observée autour de l'élément de liste (constituant l'ensemble du visuel ci-dessus).

  • Le bloc de couleur à gauche est de 60 de haut sur 80 de large.

  • Le bloc de couleur grise en dessous est de 20 de haut sur 80 de large (ces deux éléments mis ensemble créent donc un carré de 80 de haut sur 80 de large).

  • L'icône doit être de police "Segoe MDL2 Assets" pour rendre l'icône correctement, de taille 24, blanc et centré horizontalement et verticalement.

  • Le nom de la catégorie doit être de taille 10, blanc et centré horizontalement et verticalement.

  • Le titre de l'article doit être de taille 24.

  • La description de l'article doit pouvoir prendre plusieurs lignes (propriété TextWrapping).

  • Utilisez le dictionnaire de ressources pour définir vos styles que vous appliquerez ensuite à vos éléments.

Quelques informations supplémentaires :

  • Concernant la zone de gauche : l'icône se trouve en propriété Icon d'un objet Group, quand la couleur se trouve en propriété Color d'un objet Category.

  • Pour l'alignement vertical des textes en XAML, utile pour l'icône comme pour le nom de la catégorie, il est conseillé de les englober dans une balise Border de la taille du bloc parent, l'alignement vertical n'étant pas supporté par tous les contrôles englobant.

<!-- ArticleStyles.xaml -->
<ResourceDictionary
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="using:CourseApp">
  <Style x:Name="ArticleBoxStyle" TargetType="RelativePanel">
    <Setter Property="Width" Value="80" />
    <Setter Property="Height" Value="80" />
    <Setter Property="VerticalAlignment" Value="Center" />
  </Style>
  <Style x:Name="ArticleUpperBoxStyle" TargetType="Grid">
    <Setter Property="Width" Value="80" />
    <Setter Property="Height" Value="60" />
  </Style>
  <Style x:Name="ArticleLowerBoxStyle" TargetType="Border">
    <Setter Property="Width" Value="80" />
    <Setter Property="Height" Value="20" />
    <Setter Property="Background" Value="Gray" />
  </Style>
  <Style x:Name="ArticleTextStyle" TargetType="TextBlock">
    <Setter Property="Foreground" Value="White" />
    <Setter Property="TextAlignment" Value="Center" />
    <Setter Property="VerticalAlignment" Value="Center" />
  </Style>
  <Style x:Name="ArticleSmallTextStyle" TargetType="TextBlock">
    <Setter Property="Foreground" Value="White" />
    <Setter Property="FontSize" Value="10" />
  </Style>
  <Style x:Name="DateLabelStyle" TargetType="TextBlock" 
         BasedOn="{StaticResource ArticleSmallTextStyle}">
    <Setter Property="Margin" Value="10" />
  </Style>
  <Style x:Name="IconLabelStyle" TargetType="TextBlock" 
         BasedOn="{StaticResource ArticleTextStyle}">
    <Setter Property="FontFamily" Value="Segoe MDL2 Assets" />
    <Setter Property="FontSize" Value="24" />
  </Style>
  <Style x:Name="BottomLabelStyle" TargetType="TextBlock" 
         BasedOn="{StaticResource ArticleTextStyle}">
    <Setter Property="FontSize" Value="10" />
  </Style>
  <Style x:Name="ArticleTitleStyle" TargetType="TextBlock">
    <Setter Property="FontSize" Value="24" />
  </Style>
</ResourceDictionary>
<ListView ItemsSource="{Binding Articles}" ItemClick="ListView_ItemClick" 
          IsItemClickEnabled="True">
  <ListView.ItemContainerStyle>
    <Style TargetType="ListViewItem">
      <Setter Property="BorderBrush" Value="Gray"/>
      <Setter Property="BorderThickness" Value="0, 1, 0, 0"/>
    </Style>
  </ListView.ItemContainerStyle>
  <ListView.ItemTemplate>
    <DataTemplate>
      <Grid Margin="5">
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="80" />
          <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <RelativePanel Grid.Column="0" Style="{StaticResource ArticleBoxStyle}">
          <Grid Background="{Binding Category.Group.Color}" 
              RelativePanel.AlignTopWithPanel="True"
              Style="{StaticResource ArticleUpperBoxStyle}">
            <TextBlock x:Name="DateLabel" Text="{Binding DateLabel}"
              Style="{StaticResource DateLabelStyle}" />
            <TextBlock x:Name="IconLabel" Text="{Binding Category.Group.Icon}"
              Style="{StaticResource IconLabelStyle}"/>
          </Grid>
          <Border RelativePanel.AlignBottomWithPanel="True"
              Style="{StaticResource ArticleLowerBoxStyle}">
            <TextBlock x:Name="BottomLabel" Text="{Binding Category.Name}"
              Style="{StaticResource BottomLabelStyle}" />
          </Border>
        </RelativePanel>
        <StackPanel Orientation="Vertical" Margin="10, 0, 0, 0" Grid.Column="1">
          <TextBlock Text="{Binding Title}" Style="{StaticResource ArticleTitleStyle}" />
          <TextBlock Text="{Binding Description}" TextWrapping="Wrap" />
        </StackPanel>
      </Grid>
    </DataTemplate>
  </ListView.ItemTemplate>
</ListView>

1.2.

Ajoutez un titre au dessus de la liste d'articles avec le style TitleTextBlockStyle faisant partie de la typographie UWP avec pour texte "Articles des étudiants de SUPINFO".

Pour finir cet exercice, nous allons trier les articles par ordre de publication décroissante. Modifiez la requête LINQ dans la boucle foreach de MainPage pour récupérer les articles dans cet ordre.

<!-- MainPage.xaml. Reste de la classe omise. -->
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
  <Grid.RowDefinitions>
    <RowDefinition Height="60" />
    <RowDefinition Height="*" />
  </Grid.RowDefinitions>
  <TextBlock Text="Articles des étudiants de SUPINFO" Margin="10,10,0,0" 
    Style="{StaticResource TitleTextBlockStyle}" TextWrapping="Wrap" />
  <ListView ItemClick="RootListView_ItemClick" IsItemClickEnabled="True" 
            ItemsSource="{Binding Articles}" Grid.Row="1">
    <!-- ... -->
  </ListView>
</Grid>
// Méthode RssLoader_LoadCompleted
foreach (Article a in RssReader.Elements.SelectMany(x => x.Elements)
                               .OrderByDescending(x => x.PublicationDate))
{
  // ...
}

1.3.

Implémentez le bouton Back dans votre application en modifiant le fichier App.xaml.cs comme vu précédemment.

En utilisant le bouton Back après avoir navigué vers un article, vous devriez remarquer que la liste de la page principale est vide. En effet, si l'instance de RssReader a bien récupéré les articles précédemment, le constructeur a été imaginé sans prendre en compte la possibilité que la récupération d'articles ait déjà eu lieu. Modifiez le constructeur pour prendre ce cas en compte.

// Fichier MainPage.xaml.cs. Reste du fichier omis
public MainPage()
{
  // Reste du constructeur omis
  if (RssReader.Elements.SelectMany(x => x.Elements).Count() > 0)
    RssReader_LoadCompleted(this, EventArgs.Empty);
}

1.4.

Les publications sur notre site pouvant être dépendants de plusieurs catégories, des articles peuvent apparaître plusieurs fois dans la liste affichée dans l'application. Pour éviter cela, nous allons utiliser la méthode Distinct de LINQ. Celle-ci utilisant les méthodes Equals et GetHashCode, implémentez ces deux méthodes en considérant l'identité de votre classe comme son Id.

Une fois ces deux méthodes implémentées, utilisez Distinct dans la requête de récupération des articles dans MainPage.

// Fichier Models.cs, classe Article. Reste de la classe omise
public override int GetHashCode()
{
  return Id;
}

public override bool Equals(object obj)
{
  return ((Article)obj).Id == Id;
}
// Fichier MainPage.xaml.cs. Reste de la classe omise
// Méthode RssLoader_LoadCompleted
foreach (Article a in RssReader.Elements.SelectMany(x => x.Elements).Distinct()
                               .OrderByDescending(x => x.PublicationDate))
{
A propos de SUPINFO | Contacts & adresses | Enseigner à SUPINFO | Presse | Conditions d'utilisation & Copyright | Respect de la vie privée | Investir
Logo de la société Cisco, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société IBM, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Sun-Oracle, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Apple, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Sybase, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Novell, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Intel, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Accenture, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société SAP, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Prometric, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo du IT Academy Program par Microsoft, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management

SUPINFO International University
Ecole d'Informatique - IT School
École Supérieure d'Informatique de Paris, leader en France
La Grande Ecole de l'informatique, du numérique et du management
Fondée en 1965, reconnue par l'État. Titre Bac+5 certifié au niveau I.
SUPINFO International University is globally operated by EDUCINVEST Belgium - Avenue Louise, 534 - 1050 Brussels