Plan du site  
français  English
pixel
pixel

Articles - Étudiants SUPINFO

Introduction

Les contrôles utilisateur permettent de créer ses propres contrôles composites, basés sur les éléments de la plateforme (ici UWP, mais ce concept existe également avec ASP ou WPF). Nous verrons également par l'exemple un concept important de l'interaction vue/code-behind, les propriétés de dépendance, ou Dependency Properties).

Présentation des contrôles utilisateurs

Un contrôle utilisateur est un contrôle composite, c'est à dire lui-même composé d'un ou plusieurs contrôles. Il possède sa propre balise avec ses propres attributs et possède son propre fonctionnement. Les avantages des contrôles utilisateur sont multiples :

  • Réutiliser ce contrôle dans plusieurs pages et ainsi factoriser le code. On peut prendre l'exemple d'une zone "Panier" sur une application marchande, ou d'une barre de statut affichée à différents endroits de l'application.

  • Créer un élément indépendant de l'application embarquant toute sa logique interne pour faciliter la maintenabilité.

Pour réaliser un contrôle utilisateur, il suffit d'ajouter un nouvel élément "User Control" dans le projet Visual Studio.

Figure 1.1. Création d'un contrôle utilisateur dans Visual Studio

Création d'un contrôle utilisateur dans Visual Studio

Le contrôle créé ressemble particulièrement à une nouvelle page.

<!-- Contrôle ExempleUserControl.xaml -->
<UserControl
  x:Class="CourseApp.ExempleUserControl"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="using:ArticlesApp"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  mc:Ignorable="d"
  d:DesignHeight="300"
  d:DesignWidth="400">

  <Grid>

  </Grid>
</UserControl>
// Fichier ExempleUserControl.xaml.cs
public sealed partial class ExempleUserControl : UserControl
{
  public ExempleUserControl()
  {
    this.InitializeComponent();
  }
}

Tout comme avec une page, il est possible de placer l'ensemble des contrôles vus précédemment dans le balisage XAML, créer des propriétés ou encore gérer des événements. Voyons un exemple de contrôle utilisateur avec deux propriétés.

<!-- Fichier ExempleUserControl.xaml. Reste de la page omise -->
<Grid>
  <StackPanel>
    <TextBlock x:Name="ExemplePrenom" />
    <TextBlock x:Name="ExempleNom" />
  </StackPanel>
</Grid>
// Fichier ExempleUserControl.xaml.cs
public sealed partial class ExempleUserControl : UserControl
{
  public string Prenom { get { return "Jean"; } }
  public string Nom { get { return "Dupont"; } }
  public ExempleUserControl()
  {
    this.InitializeComponent();
    ExemplePrenom.Text = Prenom;
    ExempleNom.Text = Nom;
  }
}

Nous pouvons inclure dans notre page notre contrôle utilisateur de cette manière :

<local:ExempleUserControl Margin="10"/>
[Note]

L'espace de noms "local" est défini dans les attributs de la balise Page. En plaçant les contrôles utilisateur dans un dossier spécifique pour mieux découper l'application, il est nécessaire d'adapter la valeur de l'attribut xmlns:local.

Au sein d'une page vide, voici le rendu de notre contrôle :

Figure 1.2. Rendu d'un contrôle utilisateur basique

Rendu d'un contrôle utilisateur basique

Si l'utilisation d'un contrôle utilisateur est simple et permet de factoriser simplement un élément de page, il prend tout son sens avec l'utilisation des propriétés de dépendance.

Propriétés de dépendance

Une propriété de dépendance (ou DependencyProperty) est une propriété d'un contrôle qui est ouverte et prise en compte par la plateforme UWP, et donc par le reste de l'application. Ce fonctionnement permet plusieurs avantages :

  • Une prise en compte de la propriété côté XAML, créant un attribut spécifique au contrôle utilisateur.

  • Une charge mémoire réduite, car seules les propriétés effectivement modifiées sont stockées dans la mémoire, et l'évaluation de leur valeur est dynamique.

  • Un héritage de valeurs. Si une propriété de dépendance n'est pas définie, l'application utilise la valeur affectée du parent en naviguant dans l'arborescence. C'est pourquoi définir la taille d'un texte sur un élément la définira sur l'ensemble des éléments en son sein.

  • Une gestion par défaut des événements. Utilisées par le binding mais également extensibles, les propriétés de dépendance ont un système d'événement intégré au changement d'une valeur.

Création d'une propriété de dépendance

La création d'une propriété de dépendance étant relativement complexe, voici un exemple en plusieurs étapes. Pour reprendre le schéma précédent, nous allons créer les propriétés de dépendance Prenom et Nom dans notre contrôle utilisateur.

La première étape consiste à créer une propriété de type DependencyProperty dans notre contrôle utilisateur :

public sealed partial class ExempleUserControl : UserControl
{
  public static readonly DependencyProperty PrenomProperty =
    DependencyProperty.Register("Prenom", typeof(string), typeof(ExempleUserControl),
    new PropertyMetadata(string.Empty, new PropertyChangedCallback(OnPrenomChanged)));

  // Reste de la classe omise
}

Notre propriété est constituée des éléments suivants :

  • static: La propriété de dépendance, bien qu'enregistrant les modifications des valeurs dans toutes les instances de notre contrôle utilisateur, est relative à la classe et non aux instances elles-même.

  • readonly : Cette propriété est définie une fois et ne peut être modifiée. Il est important de comprendre que l'objet DependencyProperty est utilisé pour suivre le comportement d'une propriété Prenom définie plus tard.

  • DependencyProperty : Notre objet est de ce type.

  • PrenomProperty : Par convention, les propriétés de dépendance suivent le schéma de nommage suivant : Nom de la propriété + "Property", et ce malgré le mélange des langages visible dans cet exemple.

L'affectation de cette propriété est prise en charge par la méthode DependencyProperty.Register, appel constitué des éléments suivants :

  • "Prenom" : La propriété concernée, définie dans le contrôle utilisateur. Attention à respecter la casse.

  • typeof(string) : Type de la propriété concernée, ici string.

  • typeof(ExempleUserControl) : Type du contrôle utilisateur, ici ExempleUserControl.

  • new PropertyMetadata : Un objet permettant de suivre les modifications de la propriété. Le premier argument définit la valeur de notre propriété avant tout changement (ici une chaîne vide, string.Empty), et le deuxième argument soumet une fonction à appeler au changement de la valeur. Cette méthode est "OnPrenomChanged" et suit le schéma de nommage suivant : "On" + Nom de la propriété + "Changed".

[Note]

Si la constuction d'une propriété de dépendance peut paraître fastidieuse, il est possible d'écrire "propdp" dans votre classe et d'appuyer sur la touche "Tab" une fois. Cela générera le code présenté ci-dessus. Il faudra cependant le modifier pour correspondre à vos besoins.

Après avoir créé la propriété de dépendance, il faut modifier notre propriété Prenom et créer la méthode OnPrenomChanged.

// Fichier ExempleUserControl.xaml.cs
public string Prenom
{
  get { return (string)GetValue(PrenomProperty); }
  set { SetValue(PrenomProperty, value); }
}

public static readonly DependencyProperty PrenomProperty =
  DependencyProperty.Register("Prenom", typeof(string), typeof(ExempleUserControl), 
    new PropertyMetadata(string.Empty, new PropertyChangedCallback(OnPrenomChanged)));

private static void OnPrenomChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    // Faire quelque chose à la modification de la valeur Prenom
}

Notre propriété Prenom devient une devanture pour le contrôle utilisateur : en souhaitant accéder à la donnée, l'accesseur (get) renvoie à la valeur de notre propriété de dépendance. De même, en souhaitant modifier la valeur de Prenom, le mutateur (set) définit la valeur de notre propriété de dépendance.

La méthode OnPrenomChanged est appelée par la propriété de dépendance après chaque modification de valeur. Nous allons nous en servir pour mettre à jour nos bindings. Pour le moment, répétons le code précédent avec la propriété Nom.

public string Prenom
{
  get { return (string)GetValue(PrenomProperty); }
  set { SetValue(PrenomProperty, value); }
}

public string Nom
{
  get { return (string)GetValue(NomProperty); }
  set { SetValue(NomProperty, value); }
}

public static readonly DependencyProperty PrenomProperty =
  DependencyProperty.Register("Prenom", typeof(string), typeof(ExempleUserControl), 
    new PropertyMetadata(string.Empty, new PropertyChangedCallback(OnPrenomChanged)));

public static readonly DependencyProperty NomProperty =
  DependencyProperty.Register("Nom", typeof(string), typeof(ExempleUserControl),
    new PropertyMetadata(string.Empty, new PropertyChangedCallback(OnNomChanged)));

private static void OnPrenomChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
  // Faire quelque chose à la modification de la valeur Prenom
}
private static void OnNomChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
  // Faire quelque chose à la modification de la valeur Nom
}

Nous pouvons maintenant utiliser notre contrôle utilisateur avec un attribut Prenom et un attribut Nom :

<local:ExempleUserControl Prenom="Jean" Nom="Dupont" Margin="10"/>

Pour rendre l'exemple fonctionnel, il est important de savoir que la création du contrôle utilisateur se déroule avant la définition des valeurs par les attributs. En d'autres termes, définir leur valeur des éléments TextBlocks dans le constructeur ne fonctionnera pas car à l'exécution du constructeur, les attributs Prenom et Nom du balisage XAML n'a pas encore été évalués.

La solution simple pour notre exemple est d'affecter la valeur des TextBlocks quand notre contrôle utilisateur est chargé en utilisant l'événement Loaded :

// Fichier ExempleUserControl.xaml.cs. Reste de la classe omise
public ExempleUserControl()
{
  this.InitializeComponent();
  Loaded += ExempleUserControl_Loaded;
}

private void ExempleUserControl_Loaded(object sender, RoutedEventArgs e)
{
  ExemplePrenom.Text = Prenom;
  ExempleNom.Text = Nom;
}
[Note]

Si l'événement Loaded permet ici d'avoir un exemple fonctionnel, il ne sera plus utilisé par la suite pour l'affection de valeurs. L'utilisation des bindings permettra la mise à jour de notre affichage à chaque changement de valeur, sans code supplémentaire dédié à cet effet.

Propriétés de dépendance et Bindings

Comme dit précédemment, les propriétés de dépendance disposent d'un système d'événement intégré et compatibles avec les Bindings. Le but des contrôles utilisateur est de fonctionner également avec les Bindings, pour offrir une facilité de développement et une gestion simplifiée au changement des valeurs.

Imaginons notre contrôle utilisateur comme suit :

<!-- Fichier ExempleUserControl.xaml. Reste de la page omise -->
<Grid>
  <StackPanel>
    <TextBlock x:Name="ExemplePrenom" Text="{Binding Prenom}" />
    <TextBlock x:Name="ExempleNom" Text="{Binding Nom}" />
  </StackPanel>
</Grid>

Le constructeur de notre contrôle utilisateur n'a plus nécessité de l'événement Loading, cependant il faut définir la propriété DataContext.

public ExempleUserControl()
{
  this.InitializeComponent();
  (this.Content as FrameworkElement).DataContext = this;
}
[Note]

Dans un contrôle utilisateur, il faut définir la propriété DataContext non pas de l'objet courant this, mais de son contenu, grâce à la syntaxe disponible dans le bloc ci-dessus. Si les raisons de ce comportement dépassent le cadre de ce cours, vous trouverez plus d'explications en suivant ce lien (Anglais).

La dernière étape est d'appliquer la modification des valeurs à notre contrôle utilisateur, et ce quelle que soit l'origine des modifications. Pour ceci, nous utiliserons les méthodes OnPrenomChanged et OnNomChanged.

private static void OnPrenomChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
  // Le premier argument de cette méthode est l'instance courante du contrôle ExempleUserControl
  // Le deuxième argument représente le détail des événements, contenant notamment l'ancienne et
  // la nouvelle valeur.
  ExempleUserControl self = d as ExempleUserControl;
  // La simple affectation de cette valeur déclenchera les Bindings et changera à la fois le 
  // visuel et la valeur de Prenom dans notre contrôle utilisateur.
  self.Prenom = e.NewValue as string;
}
private static void OnNomChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
  ExempleUserControl self = d as ExempleUserControl;
  self.Nom = e.NewValue as string;
}

Pour expérimenter le bon fonctionnement de ce système, nous pouvons créer un bouton dans la page vide (en dehors du contrôle utilisateur, et y appliquer nos changements.

<!-- TestPage.xaml. Reste de la page omise -->
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
  <local:ExempleUserControl x:Name="ExempleUserControl" Margin="10"
                            Prenom="Jean" Nom="Dupont" />
  <Button Content="Changer Valeurs" Click="Button_Click" />
</Grid>
public sealed partial class TestPage : Page
{
  public TestPage()
  {
    this.InitializeComponent();
  }

  private void Button_Click(object sender, RoutedEventArgs e)
  {
    ExempleUserControl.Prenom = "Marie";
    ExempleUserControl.Nom = "Durand";
  }
}

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

Rendu de l'exemple précédent

Etape 4 : Navigation par catégories et refactorisation

Dans cet étape, nous allons créer une navigation par groupes dans un panneau latéral. Cette navigation sera intégrée dans un contrôle utilisateur. Nous allons également utiliser un deuxième contrôle utilisateur pour la liste des articles.

Exercice corrigé : Navigation par catégories et refactorisation

1.1.

Créez un nouveau dossier et nommez-le "Controls". Ce dossier contiendra tous nos contrôles utilisateurs. Créez un nouveau contrôle utilisateur appelé NavPane. Ce contrôle aura pour but d'afficher une liste de groupes.

Dans ce contrôle, créez une propriété dénommée Groups de type List<Group> avec une propriété de dépendance associée. Dans la méthode OnGroupsChanged, appliquez les modifications à l'instance courante comme vu précédemment.

// Fichier NavPane.xaml.cs
public sealed partial class NavPane : UserControl
{
  public static readonly DependencyProperty GroupsProperty =
    DependencyProperty.Register("Groups", typeof(List<Group>), typeof(NavPane),
    new PropertyMetadata(null, new PropertyChangedCallback(OnGroupsChanged)));
  private static void OnGroupsChanged(DependencyObject d, 
    DependencyPropertyChangedEventArgs e)
  {
    NavPane i = d as NavPane;
    i.Groups = e.NewValue as List<Group>;
  }
  public List<Group> Groups
  {
    get { return (List<Group>)GetValue(GroupsProperty); }
    set { SetValue(GroupsProperty, value); }
  }
  public NavPane()
  {
    this.InitializeComponent();
  }
}

1.2.

Dans MainPage, ajoutez un contrôle SplitView. Le contenu sera la liste d'articles, le panneau contiendra le contrôle NavPane créé précédemment. La propriété DisplayMode du contrôle SplitView doit être définie à Inline, et la propriété IsPaneOpen doit être définie à true.

Dans le constructeur de MainPage, affectez à la propriété Groups de NavPane les groupes fournis par la classe GroupProvider.

<!-- MainPage.xaml -->
<Page> <!-- Attributs omis pour faciliter la lecture -->
  <!-- Page.Resources omis -->
  <SplitView DisplayMode="Inline" IsPaneOpen="True">
    <SplitView.Pane>
      <local:NavPane x:Name="NavPane" />
    </SplitView.Pane>
    <SplitView.Content>
      <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <!-- Contenu de MainPage -->
      </Grid>
    </SplitView.Content>
  </SplitView>
  <!-- Page.BottomAppBar omis -->
</Page>
// Classe MainPage. Reste de la classe omise
public MainPage() {
  // Reste du constructeur omis
  this.InitializeComponent();
  NavPane.Groups = GroupProvider.Groups;
}

1.3.

Dans NavPane, créez un élément ListView. Cet élément représentera nos catégories. Utilisez les bindings pour créer l'affichage suivant :

Figure 1.4. Rendu attendu du contrôle utilisateur NavPane

Rendu attendu du contrôle utilisateur NavPane

Attentes techniques :

  • Le fond de chaque élément doit être la propriété Color de l'objet Group.

  • La hauteur de chaque icône doit être de hauteur et largeur 50 avec une taille de police de 30. La police utilisée doit être Segoe MDL2 Assets, et le contenu de l'icône doit être la propriété Icon de l'objet Group. Pour center l'icône de taille 30 dans un carré de 50x50, vous pouvez utiliser la propriété Padding.

  • Le nom de la catégorie doit être de taille 18 et être centré verticalement par rapport à l'icône.

  • Pour obtenir un rendu optimal, il est conseillé de définir dans une balise ListView.ItemContainerStyle la propriété HorizontalContentAlignment à Stretch et la propriété Padding à 0.

N'oubliez pas d'affecter dans le constructeur de NavPane le DataContext comme présenté précédemment.

<!-- Contrôle Utilisateur NavPane.xaml -->
<UserControl> <!-- Attributs omis pour faciliter la lecture -->
  <Grid>
    <ListView ItemsSource="{Binding Groups}" x:Name="GroupsListView">
      <ListView.ItemContainerStyle>
        <Style TargetType="ListViewItem">
          <Setter Property="HorizontalContentAlignment" Value="Stretch" />
          <Setter Property="Padding" Value="0" />
        </Style>
      </ListView.ItemContainerStyle>
      <ListView.ItemTemplate>
        <DataTemplate>
          <StackPanel Orientation="Horizontal" Background="{Binding Color}">
            <TextBlock FontFamily="Segoe MDL2 Assets" FontSize="30" 
                       Text="{Binding Icon}" Width="50" Height="50" Padding="10" />
            <TextBlock Text="{Binding Name}" FontSize="18" VerticalAlignment="Center" />
          </StackPanel>
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>
  </Grid>
</UserControl>
// Fichier NavPane.xaml.cs
// Reste de la classe omise
public NavPane()
{
  this.InitializeComponent();
  (this.Content as FrameworkElement).DataContext = this;
}

1.4.

Ajoutez avant la ListView un élément visuel supplémentaire comme dans l'image suivante :

Figure 1.5. Rendu attendu de l'élément supplémentaire

Rendu attendu de l'élément supplémentaire

Pour cela, téléchargez dans le dossier 3WIN de notre plateforme de ressources https://courses.supinfo.com l'image NavPane-30x30.png et ajoutez l'élément existant à votre projet. Mimez ensuite le rendu des éléments ListViewItem en appliquant le même style à cet élément.

Rappel : Pour afficher l'image dans votre application il faudra utiliser le contrôle Image, avec la balise Image.Source pour en définir le contenu (voir chapitre 2).

<!-- Contrôle Utilisateur NavPane.xaml. Reste du fichier omis. -->
<Grid>
  <StackPanel Orientation="Vertical">
    <StackPanel Orientation="Horizontal" Background="White" Height="50">
      <Image Margin="10">
        <Image.Source>
          <BitmapImage UriSource="/Assets/NavPane-30x30.png" />
        </Image.Source>
      </Image>
      <TextBlock Foreground="Black" Text="Tous les articles" 
                 FontSize="18" VerticalAlignment="Center" />
    </StackPanel>
    <ListView ItemsSource="{Binding Groups}" x:Name="GroupsListView">
      <!-- Contenu de ListView -->
    </ListView>
  </StackPanel>
</Grid>

1.5.

Ajoutez un événement au clic (PointerPressed) sur l'élément visuel "Tous les articles" ainsi qu'au clic sur un élément du contrôle ListView. Ces deux méthodes devront notifier la page principale du choix de l'utilisateur de sélectionner un groupe. Pour cela, nous allons créer notre propre événement relatif au contrôle NavPane. Cet événement aura une propriété de type Group, permettant de faire une modification d'affichage.

Créez un nouveau fichier dans Classes nommé Events.cs. A l'intérieur, définissez la classe RefreshCalledEventArgs étendant la classe EventArgs. Cette classe aura pour unique propriété SelectedGroup, de type Group.

<!-- Contrôle Utilisate NavPane.xaml. Reste du fichier omis -->
<StackPanel Orientation="Vertical">
  <StackPanel Orientation="Horizontal" x:Name="Group_All" Background="White" 
              Height="50" PointerPressed="Group_All_PointerPressed">
    <!-- Contenu de StackPanel -->
  </StackPanel>
  <ListView ItemsSource="{Binding Groups}" x:Name="GroupsListView" 
            IsItemClickEnabled="True" ItemClick="GroupsListView_ItemClick">
    <!-- Contenu de ListView -->
  </ListView>
</StackPanel>
// Classe NavPanel.xaml.cs. Reste de la classe omise
private void Group_All_PointerPressed(object sender, PointerRoutedEventArgs e)
{

}

private void GroupsListView_ItemClick(object sender, ItemClickEventArgs e)
{

}
// Fichier Events.cs
public class RefreshCalledEventArgs : EventArgs
{
    public Group SelectedGroup { get; set; }
}

1.6.

Dans la classe NavPane, créez un nouvel événement RefreshCalled de type EventHandler<RefreshCalledEventArgs>. Créez une nouvelle méthode OnRefreshCalled prenant en paramètre un objet de type RefreshCalledEventArgs. Cette méthode OnRefreshCalled devra déclencher l'événement RefreshCalled.

Il reste à relier nos événements PointerPressed et Click. Dans la méthode Group_All_PointerPressed, appelez la méthode OnRefreshCalled avec un objet vide. Dans la méthode GroupsListView_ItemClick, appelez la méthode OnRefreshCalled avec un objet contenant le groupe sélectionné.

public event EventHandler<RefreshCalledEventArgs> RefreshCalled;

private void OnRefreshCalled(RefreshCalledEventArgs ea)
{
  RefreshCalled?.Invoke(this, ea);
}
private void Group_All_Click(object sender, PointerRoutedEventArgs e)
{
  OnRefreshCalled(new RefreshCalledEventArgs());
}

private void GroupsListView_ItemClick(object sender, ItemClickEventArgs e)
{
  Group group = e.ClickedItem as Group;
  OnRefreshCalled(new RefreshCalledEventArgs()
  {
    SelectedGroup = group
  });
}

1.7.

Nous disposons maintenant d'un contrôle utilisateur NavPane déclenchant un événement au choix du groupe. Dans MainPage, écoutez l'événement RefreshCalled. Au lancement de l'événement, la liste d'articles doit s'adapter au choix de l'utilisateur. S'il clique sur "Tous les articles", la liste d'articles doit tous les présenter. S'il clique sur un groupe, seul les articles de ce groupe doivent être présentés.

[Note]

N'appelez pas la méthode Reset(), car le but de cette navigation est de filtrer les articles, non de déclencher une opération externe de récupération de flux RSS.

// Classe MainPage. Reste de la classe omise
public MainPage() {
  // Reste du constructeur omis
  NavPane.RefreshCalled += NavPane_RefreshCalled;
}

private void NavPane_RefreshCalled(object sender, RefreshCalledEventArgs e)
{
  Articles.Clear();
  var insert = RssReader.Elements.SelectMany(x => x.Elements);
  if(e.SelectedGroup != null) 
  {
    insert = insert.Where(a => a.Category.Group == e.SelectedGroup);
  }
  foreach (Article a in insert.OrderByDescending(x => x.PublicationDate)) 
  {
    Articles.Add(a);
  }
}

1.8.

Terminons pour le moment avec NavPane en ajoutant un effet visuel quand un groupe est sélectionné. Par défaut, c'est le groupe "Tous les articles" qui est sélectionné. Au clic sur un groupe, celui-ci sera sélectionné.

Pour ce faire, créez une méthode Select privée dans NavPane prenant en paramètre le groupe sélectionné (peut être nul si le groupe "Tous les articles" est sélectionné) et appliquant une opacité de 1 au groupe concerné, et 0.5 aux autres.

[Note]

Pour obtenir l'instance de classe ListViewItem en fonction d'un objet lié (ici un groupe), utilisez la méthode suivante :

ListViewItem lvi = GroupsListView.ContainerFromItem(group) as ListViewItem;

Appelez cette méthode Select :

  • Dans la méthode OnRefreshCalled

  • Quand le contrôle NavPane est chargé (événement Loaded)

// Classe NavPane. Reste de la classe omises
public NavPane() {
  // Reste du constructeur omis
  Loaded += NavPane_Loaded;
}

private void NavPane_Loaded(object sender, RoutedEventArgs e)
{
  Select();
}

private void OnRefreshCalled(RefreshCalledEventArgs ea)
{
  Select(ea.SelectedGroup);
  RefreshCalled?.Invoke(this, ea);
}

private void Select(Group selected = null)
{
  int groupId = (selected == null ? -1 : selected.Id);
  foreach (Group g in Groups)
  {
    ListViewItem lvi = GroupsListView.ContainerFromItem(g) as ListViewItem;
    if (lvi != null) lvi.Opacity = (g.Id == groupId ? 1 : .5);
  }
  if (groupId == -1) Group_All.Opacity = 1;
  else Group_All.Opacity = .5;
}

1.9.

Réalisons également un contrôle utilisateur pour la liste d'articles. Créez un nouveau contrôle utilisateur nommé ArticlesList dans le dossier Controls. Dans ce contrôle, définissez une propriété de dépendance Articles de type ObservableCollection<Article>. Implémentez OnArticlesListChanged et définissez le DataContext du contrôle utilisateur comme vu précédemment.

public sealed partial class ArticlesList : UserControl
{
  public ObservableCollection<Article> Articles
  {
    get { return (ObservableCollection<Article>)GetValue(ArticlesProperty); }
    set { SetValue(ArticlesProperty, value); }
  }

  public static readonly DependencyProperty ArticlesProperty =
    DependencyProperty.Register("Articles", typeof(ObservableCollection<Article>), 
      typeof(ArticlesList), 
        new PropertyMetadata(new ObservableCollection<Article>(), OnArticlesListChanged));

  private static void OnArticlesListChanged(DependencyObject d, 
    DependencyPropertyChangedEventArgs e)
  {
    ArticlesList self = d as ArticlesList;
    self.Articles = e.NewValue as ObservableCollection<Article>;
  }

  public ArticlesList()
  {
    this.InitializeComponent();
    (this.Content as FrameworkElement).DataContext = this;
  }
}

1.10.

Déplacez la Grid contenant la ListView de MainPage chargé de l'affichage des articles. N'oubliez pas de déplacer également la référence au dictionnaire de ressources.

A ce moment, l'événement au clic d'un élément de la liste n'est plus relié. Nous allons comme pour NavPane créer une classe d'événement spécialisée dans Events appelée NavigationCalledEventArgs. Cette classe possèdera une propriété Parameter de type object, et une propriété Type de type Type pour symboliser la page concernée. De cette manière, notre classe pourra être réutilisée autre part dans l'application.

public class NavigationCalledEventArgs : EventArgs
{
  public Type Type { get; set; }
  public object Parameter { get; set; }
}

1.11.

Créez un événement de type EventHandler<NavigationCalledEventArgs> appelé NavigationCalled dans ArticlesList. Au clic sur un article, le contrôle utilisateur doit déclencher l'événement en passant en paramètre le type de page ReadPage pour Type et l'article en question pour Parameter.

La page MainPage doit écouter cet événement pour rediriger l'utilisateur vers la page spécifiée en passant en paramètre l'article spécifié.

// Classe ArticlesList. Reste de la classe omise
public event EventHandler<NavigationCalledEventArgs> NavigationCalled;

private void ListView_ItemClick(object sender, ItemClickEventArgs e)
{
  Article a = e.ClickedItem as Article;
  NavigationCalled?.Invoke(this, new NavigationCalledEventArgs()
  {
    Parameter = a,
    Type = typeof(ReadPage)
  });
}
<!-- MainPage.xaml. Reste du fichier omis -->
<SplitView DisplayMode="Inline" IsPaneOpen="True">
  <SplitView.Pane>
    <local:NavPane x:Name="NavPane" />
  </SplitView.Pane>
  <SplitView.Content>
    <local:ArticlesList x:Name="ArticlesList" Articles="{Binding Articles}" />
  </SplitView.Content>
</SplitView>
// Classe MainPage. Reste de la classe omise
public MainPage() {
  // Reste du constructeur omis
  ArticlesList.NavigationCalled += ArticlesList_NavigationCalled;
}

private void ArticlesList_NavigationCalled(object sender, NavigationCalledEventArgs e)
{
  Frame.Navigate(e.Type, e.Parameter);
}
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)