Plan du site  
français  English
pixel
pixel

Articles - Étudiants SUPINFO

Introduction

Après avoir vu la plateforme UWP, les contrôles et l'interaction entre vue et code-behind, nous allons réaliser notre première application complète. Le but de cette application sera de visionner une partie des différentes publications réalisées par les étudiants de SUPINFO International University, accessibles à cette adresse : https://www.supinfo.com/articles/.

Les fonctionnalités de cette application nous permettront de dresser un panorama riche des fonctionnalités de la plateforme, au travers de différents concepts :

  • Lecture de flux RSS : Les données des articles seront récupérées via un flux RSS disponible sur notre site institutionnel.

  • Chargement asynchrone des articles : Les applications connectées au réseau Internet nécessitent des dispositions particulières incluant notamment la gestion de l'asynchronicité.

  • Visualisation internet : Notre application ne récupérera qu'une partie du contenu de chacun des articles. Pour visionner l'article en entier, notre application contiendra une vue Web.

  • Stockage de paramètres : Afin de détailler le fonctionnement des paramètres locaux ou partagés, nous implémenterons deux paramètres à notre application.

  • Navigation entre différentes pages : Pour cette application, nous disposerons de trois pages : la page principale listant tous les articles, la page de visionnage d'un article, et la page des paramètres.

  • Contrôles Utilisateurs : Pour mieux entre-connecter les différents éléments de notre vue et les rendre ré-utilisables, nous développerons deux contrôles utilisateur (UserControl).

  • Utilisation de la ré-architecture : Pour rendre l'utilisation de notre application optimale également sur mobile, nous ré-architecturerons la page principale spécialement pour les smartphones.

D'autres éléments plus périphériques seront également présentés pendant la réalisation de cette application : dictionnaires de ressources, gestion des icônes et tuiles de l'application, utilisation de la police de caractères dédiée aux icônes fournie par Microsoft, et bien d'autres.

Figure 1.1. Rendu visuel de l'application

Rendu visuel de l'application

Etape 1 : affichage d'un contenu temporaire

Avant de plonger dans le développement de cette application, il est nécessaire de décrire succinctement le format RSS (dérivé du XML) pour mieux comprendre les données que nous aurons à notre disposition.

Le format RSS

Ce format destiné à notre usage, à savoir récupérer les nouvelles publications d'une plateforme, a l'avantage d'être extrêmement simple structurellement, comme son acronyme le souligne. Voici la structure de base d'un flux RSS, ou le contenu au sein des balises a été remplacé par une description de celles-ci :

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Titre du flux</title>
    <link>Lien vers la visualisation du contenu</link>
    <description>Description du site</description>
    <language>Langue utilisée dans les publications et/ou dans ce fichier</language>
    <copyright>Nom de la licence</copyright>
    <item>
      <title>Titre d'un article</title>
      <link>Lien direct vers l'article</link>
      <description>Description ou premiers mots de l'article</description>
      <pubDate>Date de publication</pubDate>
    </item>
    <!-- Insérer ici autant de balises item que de publications -->
  </channel>
</rss>

Après cette présentation du modèle, observons plus en détail les adresses et retours des flux RSS de notre site institutionnel.

Flux RSS accessibles

Ces publications sont réparties en cinq groupes : Développement, Organisation, Système et Réseau, Bas niveau et Gestion de données. Ces cinq groupes sont ensuite découpés en catégories au nombre de 19, chacune étant identifiée par un nombre.

Les flux RSS sont de la forme : https://www.supinfo.com/api/supinfo?action=rss&tags={numéro de la catégorie}, ou la mention {numéro de la catégorie} doit être remplacé par un nombre en 1 et 19. En effectuant une requête sur la catégorie 1, voici le retour obtenu :

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Articles dans la catégorie Securité</title>
    <link>https://www.supinfo.com/articles/1-security</link>
    <description>
      SUPINFO International University a toujours eu à cœur, au delà de sa mission 
      d’enseignement supérieur, de participer à la progression et la diffusion des 
      connaissances et des savoirs. [Nos étudiants] vous invitent à consulter leurs 
      articles, rédigés dans le cadre de leurs études et recherches personnelles.
    </description>
    <language>fr-fr</language>
    <copyright>SUPINFO International University</copyright>
    <item>
      <title>Voiture autonome, voiture ethique ?</title>
      <link>https://www.supinfo.com/articles/single/4010-voiture-autonome-voiture-ethique</link>
      <description>
        L’aire de la voiture autonome se rapproche de plus en plus. Si déjà plusieurs 
        firmes annoncent que leur véhicule est désormais prêt une...
      </description>
      <pubDate>Sun, 12 Feb 2017 12:43:00 +0100</pubDate>
    </item>
    <!-- Suite des articles omise pour la lisibilité -->
  </channel>
</rss>

Au sein de notre application, nous allons utiliser ces différents flux RSS au lieu d'un unique lien pour mieux appréhender les bénéfices offerts par l'asynchrone.

Exercice : Création de l'application et des modèles

Après avoir créé un nouveau projet (que nous appelerons ici ArticlesApp), nous commencerons par créer les modèles de l'application ainsi qu'un MWE (example minimal fonctionnel). Nous créerons donc un nouveau dossier nommé "Classes" dans le projet fraîchement créé. Pour cela, il suffit de réaliser un clic-droit sur le projet, survoler "Ajouter" puis cliquer sur "Nouveau Dossier".

Au sein de ce dossier, nous créerons un nouveau fichier Models.cs qui contiendra nos trois modèles : Article, et Category et Group.

Exercice corrigé : Création de l'application et des modèles

1.1.

Créer le modèle Article. Il contiendra toutes les informations au sein d'une balise "item" du flux RSS. Un article possède également une catégorie que nous allons représenter, et un identifiant unique contenu dans le lien qui pourra nous être utile plus tard.

public class Article {
  public int Id { get; set; }
  public Category Category { get; set; }
  // Informations contenues dans le flux RSS
  public string Title { get; set; }
  public string Description { get; set; }
  public Uri Link { get; set; }
  public DateTime PublicationDate { get; set; }
}

1.2.

Créer le modèle Category. Une catégorie contient les informations de la balise channel en dehors des balises "item". Elle contient également un identifiant unique, un groupe et une liste d'articles que nous représenterons.

public class Category {
  public int Id { get; set; }
  public string Name { get; set; }
  public Group Group { get; set; }
  // Informations contenues dans le flux RSS
  public string Title { get; set; }
  public string Description { get; set; }
  public Uri Link { get; set; }
  public string Language { get; set; }
  public string Copyright { get; set; }
}

1.3.

Créer le modèle Group. Un groupe contiendra pour le moment uniquement un identifiant unique et un nom.

public class Group {
  public int Id { get; set; }
  public string Name { get; set; }
}

1.4.

Une fois cette modélisation terminée, réalisons un affichage simple avec des données temporaires pour manipuler nos modèles dans notre code-behind.

Pour cela, ajoutez dans la classe MainPage une collection observable d'articles dénommée Articles, puis créez deux articles dans cette liste en définissant :

  • Le titre et la description de l'article

  • L'id et le nom de la catégorie

  • L'id et le nom du groupe

public class MainPage : Page
{
  private ObservableCollection<Article> _articles = new ObservableCollection<Article>();
  public ObservableCollection<Article> Articles { 
    get { return _articles; } 
    set { _articles = value; } 
  }

  public MainPage() 
  {
    Articles.Add(new Article()
    {
      Title = "Titre de l'article A",
      Description = "Description de l'article A",
      Category = new Category()
      {
        Id = 1,
        Name = "Categorie A",
        Group = new Group()
        {
          Id = 1,
          Name = "Premier groupe"
        }
      }
    });
    Articles.Add(new Article()
    {
      Title = "Titre de l'article B",
      Description = "Description de l'article B",
      Category = new Category()
      {
        Id = 1,
        Name = "Categorie B",
        Group = new Group()
        {
          Id = 1,
          Name = "Premier groupe"
        }
      }
    });
    DataContext = this;
    this.InitializeComponent();
  }
}

1.5.

Enfin, réalisons la vue en XAML. Créez un contrôle ListView contenant dans son DataTemplate un StackPanel horizontal contenant :

  • Le titre de l'article

  • Le nom de la catégorie

  • Le nom du groupe

Utilisez les bindings. Pour naviguer dans les propriétés d'un objet, utilisez le caractère "." (point).

<Page> <!-- Attributs omis pour la lisibilité -->
  <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <ListView ItemsSource="{Binding Articles}">
      <ListView.ItemTemplate>
        <DataTemplate>
          <StackPanel Orientation="Horizontal">
            <TextBlock Text="{Binding Title}" />
            <TextBlock Text="{Binding Category.Name}" />
            <TextBlock Text="{Binding Category.Group.Name}" />
          </StackPanel>
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>
  </Grid>
</Page>

Figure 1.2. Première étape : Liaison de données temporaires à la vue

Première étape : Liaison de données temporaires à la vue

Récupération du contenu

La récupération d'un flux XML, par extension d'un flux RSS, est réalisée par la classe XmlDocument (espace de noms Windows.Data.Xml.Dom), appelé communément parser XML. Cette classe permet la récupération de flux, qu'il vienne d'une source interne (fichier de l'application) ou externe (fichier sur l'équipement, sur un stockage externe comme une carte SD ou une clef USB, internet). Elle permet également une représentation objet des éléments en son sein, ce qui facilite la récupération des données, l'itération, la recherche et la navigation dans son contenu. Enfin, et bien que nous n'en aurons pas l'utilité dans ce cours, elle permet également de créer des documents XML.

Pour charger un document externe, il faut utiliser ce modèle :

XmlDocument xmlDoc;
try {
  xmlDoc = await XmlDocument.loadFromUriAsync(url);
} catch(Exception e) {
  // Gestion de l'exception
}
[Warning]

La gestion de l'exception est nécessaire, car le flux RSS peut être mal formaté ou indisponible.

Le mot-clef "await" est également nécessaire, pour des notions d'asynchrone que nous détaillerons dans la section suivante.

[Note]

Bien que plus indirect et de fait plus imposante, il est également possible de récupérer le contenu d'un flux XML de cette manière :

Uri uri = "URI cible";
WebRequest webRequest = WebRequest.Create(uri);
XmlDocument xmlDoc = new XmlDocument();
using (WebResponse response = await webRequest.GetResponseAsync())
using (Stream content = response.GetResponseStream())
using (StreamReader reader = new StreamReader(content))
{
  string strContent = reader.ReadToEnd();
  xmlDoc.LoadXml(strContent);
}

Cette méthode utilise le concept de flux détaillé au chapitre 6 et consiste à créer un objet WebRequest effectuant une requête HTTP vers l'adresse cible. Malgré sa verbosité, elle reste très utilisée pour des raisons de performance.

L'objet XmlDocument offre ensuite différentes possibilités de sélection d'élément(s), comme LINQ (présenté dans le cours 2NET) ou XPath, une syntaxe spécifique aux documents de ce type pour rechercher des éléments. Voyons ici les deux possibilités avec la structure XML suivante :

<?xml version="1.0" encoding="UTF-8"?>
<Disquaire>
  <Disques>
    <Disque>
      <Auteur>Chanteur 1</Auteur>
      <Parution>16 février 2016</Parution>
    </Disque>
    <Disque>
      <Auteur>Chanteur 2</Auteur>
      <Parution>24 novembre 2005</Parution>
    </Disque>
  </Disques>
</Disquaire>

Pour récupérer une liste contenant nos deux éléments Disque, voici l'opération via LINQ :

// On récupère le dernier élément. Note : La déclaration xml compte comme un élément.
IXmlNode disquaire = xmlDoc.LastChild;
// On récupère dans les éléments de notre balise Disquaire la balise Disques
// La méthode Single ne retourne qu'un seul élément. Si plusieurs éléments sont trouvés,
// une exception est levée.
IXmlNode disques = disquaire.ChildNodes.Where(x => x.LocalName == "Disques").Single();
// De la même manière, on récupère notre liste de balises Disque, qu'on stocke dans un objet List.
List<IXmlNode> listeDisques = disques.ChildNodes.Where(x => x.LocalName == "Disque").ToList();

La syntaxe XPath permet de simplifier au maximum la requête pour sélectionner les éléments, mais nécessite une connaissance syntaxique :

// On sélectionne à partir de la racine du document via la syntaxe "//"
// On descend dans l'arborescence avec le caractère "/"
// On utilise la méthode SelectNodes pour récupérer plusieurs résultats
XmlNodeList listeDisques = xmlDoc.SelectNodes("//Disquaire/Disques/Disque");

Notez le type de retour, ici XmlNodeList, un objet spécialement prévu pour travailler nos éléments.

Quelle que soit la méthode utilisée ci-dessus, les éléments IXmlNode peuvent ensuite être manipulés pour extraire les valeurs souhaitées.

foreach(IXmlNode disque in listeDisques) {
  IXmlNode xmlAuteur = itemNode.SelectSingleNode("Auteur");
  IXmlNode xmlParution = itemNode.SelectSingleNode("Parution");

  // xmlAuteur et xmlParution peuvent être vides
  if(xmlAuteur == null || xmlParution == null) {
    // Gestion d'erreurs
    continue;
  }

  string auteur = xmlAuteur.InnerText.ToString();
  string parution = xmlParution.InnerText.ToString();
}

Asynchrone avec async et await

La récupération de fichiers comme la majorité des opérations externes sont asynchrones, et nécessitent donc une gestion particulière.

Une opération asynchrone admet devant son appel l'opérateur await.

public async void MethodeExemple() {
  // Opérations avant l'appel
  await OperationAsynchrone();
  // Opérations après l'appel
}

Dans ce cas, les opérations avant l'appel sont exécutées, puis la méthode OperationAsynchrone est appelée. Sans attendre le retour de l'opération, le programme sort de MethodeExemple et continue son exécution. Au retour de l'opération asynchrone, le programme exécute les opérations après l'appel, puis reprend son cours.

Voici un exemple en pseudo-code illustrant cette notion :

MethodeA() {
  Operation A1
  Operation A2 -> MethodeB();
  Operation A3
  Operation A4 
  Operation A5
}

MethodeB() {
  Operation B1
  Operation B2
  await OperationB3 -> MethodeC();
  Operation B4
  Operation B5
}

MethodeC() {
  Operation C1
  Operation C2
  Operation C3
}

Si nous représentons le déroulement de ce pseudo-code au cours du temps, nous pourrions obtenir ceci :

Figure 1.3. Exemple de déroulement d'une opération asynchrone

Exemple de déroulement d'une opération asynchrone

[Note]

Le déroulement et l'ordonnancement des opérations asynchrones étant par essence imprédictible, le temps d'exécution de nos opérations C1, C2 et C3 comparé au temps d'exécution des opérations A3 et A4 sont sujettes à variation d'une exécution du programme à une autre. Cependant, le déroulement des autres opérations est déterministe.

Pour appeler une méthode asynchrone, il est nécessaire de déclarer la méthode entourante avec le mot-clef async. De plus, elles ne peuvent avoir que deux types de retour : soit void, soit un objet Task<T>, ou T est le type de retour de la méthode. Enfin, elles doivent disposer d'au moins un appel à une méthode await.

public async void MethodeA() { // Cette méthode possède au moins un appel utilisant await
  OperationAsynchroneVoid(); // Cette méthode est asynchrone, mais il est impossible
                             // d'attendre une méthode avec un type de retour void.
                             // En effet, comment attendre une méthode qui ne renvoie rien ?
  int Resultat = OperationAsynchroneInt();
}

public async void OperationAsynchroneVoid() {
  await Task.Delay(TimeSpan.FromSeconds(1)); // Bloque le thread courant pendant une seconde
}

public async Task<int> OperationAsynchroneInt() { // Retourne un type int après opération
  await Task.Delay(TimeSpan.FromSeconds(1));
  return 2;
}

Etape 2 : affichage des données des flux RSS

Pour réaliser une récupération de données, nous allons tout d'abord modéliser notre structure. Celle-ci possèdera deux classes :

  • RssElement : Elle représente un flux et sa gestion. Elle disposera donc entre autres d'une adresse à laquelle récupérer le contenu XML, et d'une liste d'articles. C'est également cette classe qui réalisera la construction des objets Article.

  • RssReader : Elle représente l'orchestration des différentes requêtes vers les flux RSS. Elle possèdera donc naturellement une liste d'objets RssElement, mais également des événements pour notifier le reste de l'application de la fin des opérations.

Le but de cette étape est d'afficher dans notre page principale la liste des articles dans une mise en page simple.

Exercice : Création de la couche de lecture de flux

Exercice corrigé : Création de la couche de lecture de flux

1.1.

Dans le dossier Classes, créez un nouveau fichier nommé ArticlesRss.cs contenant une classe RssElement. Cette classe possédera les propriétés suivantes :

  • Category : Du type Category, elle permettra d'associer les articles récupérés à leur catégorie dans notre application.

  • EndPoint : Du type Uri et caractérisant l'adresse HTTP à laquelle récupérer le contenu XML

  • Articles : La liste d'articles que nous souhaitons récupérer

Notre classe RssElement possèdera également une unique méthode, Fetch, qui réalisera la construction des objets Article et retournant un booléen : vrai si l'opération a réussi, false si elle a échoué.

class RssElement
{
  public Category Category { get; set; }
  public Uri EndPoint { get; set; }
  private List<Article> _articles = new List<Article>();
  public List<Article> Articles
  {
    get { return _articles; }
    set { _articles = value; }
  }

  public async Task<bool> Fetch()
  {
    // Cette méthode produira une erreur, car elle ne retourne rien
    // pour le moment. De plus, elle ne possède aucune instruction
    // appelée avec le mot-clef await.
  }
}

1.2.

La méthode Fetch doit tout d'abord charger de manière asynchrone le flux RSS en appelant la méthode LoadFromUriAsync de la classe XmlDocument. En cas d'erreur à cette étape, la méthode doit retourner faux.

Après la récupération, utilisez les différentes balises contenues dans l'élément XML channel pour définir les différentes propriétés de la catégorie :

  • Title

  • Description

  • Link

  • Language

  • Copyright

Elle doit ensuite récupérer la liste des éléments IXmlNode correspondant aux balises item présentes dans notre flux RSS. Pour chacun de ces éléments, il nous faut récupérer leurs attributs :

  • title

  • link

  • description

  • pubDate

Après avoir vérifié qu'ils n'étaient pas absents, nous avons toutes les informations nécessaires pour créer notre objet Article, à l'exception de son identifiant. Utilisons la propriété Category de notre objet RssElement pour définir la catégorie de l'article en cours.

Après avoir traité tous les articles, cette méthode Fetch doit retourner vrai.

public async Task<bool> Fetch()
{
  XmlDocument xmlDoc;
  try
  {
    xmlDoc = await XmlDocument.LoadFromUriAsync(EndPoint);
  }
  catch (Exception)
  {
    return false;
  }
  IXmlNode categoryTitleNode = xmlDoc.SelectSingleNode("//rss/channel/title");
  IXmlNode categoryDescriptionNode = xmlDoc.SelectSingleNode("//rss/channel/description");
  IXmlNode categoryLinkNode = xmlDoc.SelectSingleNode("//rss/channel/link");
  IXmlNode categoryLanguageNode = xmlDoc.SelectSingleNode("//rss/channel/language");
  IXmlNode categoryCopyrightNode = xmlDoc.SelectSingleNode("//rss/channel/copyright");
  Category.Title = categoryTitleNode.InnerText.ToString();
  Category.Description = categoryDescriptionNode.InnerText.ToString();
  Category.Link = new Uri(categoryLinkNode.InnerText.ToString());
  Category.Language = categoryLanguageNode.InnerText.ToString();
  Category.Copyright = categoryCopyrightNode.InnerText.ToString();

  XmlNodeList itemNodes = xmlDoc.SelectNodes("//rss/channel/item");
  foreach (IXmlNode itemNode in itemNodes)
  {
    IXmlNode titleNode = itemNode.SelectSingleNode("title");
    IXmlNode linkNode = itemNode.SelectSingleNode("link");
    IXmlNode descNode = itemNode.SelectSingleNode("description");
    IXmlNode dateNode = itemNode.SelectSingleNode("pubDate");

    if (titleNode == null || linkNode == null || descNode == null || dateNode == null) 
      continue;
    Articles.Add(new Article()
    {
      Title = titleNode.InnerText.ToString(),
      Description = descNode.InnerText.ToString(),
      Link = new Uri(linkNode.InnerText.ToString()),
      PublicationDate = DateTime.Parse(dateNode.InnerText.ToString()),
      Category = this.Category
    });
  }
  return true;
}

1.3.

Pour terminer notre classe RssElement, il nous suffit d'inclure la dernière propriété non définie de nos objets Article, leur identifiant.

Dans la balise link de nos articles, un format se dessine. Tous les articles ont un lien de la forme suivante : https://www.supinfo.com/articles/single/{identifiant}-{titre de l'article}

Utilisez les méthodes d'instance des chaînes de caractère pour isoler l'identifiant. N'oubliez pas de transformer cet identifiant en type int soit via la méthode Parse, soit via la méthode TryParse.

Ce format d'adresse étant spécifique à notre application, créez une constante dénommée ID_PATTERN dans RssElement pour le stocker.

class RssElement {
  const string ID_PATTERN = "https://www.supinfo.com/articles/single/";
  // Reste de la classe omis
}            
public async Task<bool> Fetch()
{
  // Pendant la récupération des propriétés des articles
  int articleId = int.Parse(linkNode.InnerText.Replace(ID_PATTERN, "").Split('-').First());
  Elements.Add(new Article() {
    Id = articleId,
    // Reste des propriétés omis
  });
}

1.4.

Créons maintenant la classe RssReader. Elle possèdera les propriétés suivantes :

  • Elements : Une liste d'objets RssElement

  • ElementsChanged : Une propriété EventHandler symbolisant la fin de chargement d'un objet RssElement.

  • LoadCompleted : Une propriété EventHandler symbolisant la fin de chargement de tous les objets RssElement.

Pour réduire les risques d'appels multiples, nous utiliserons également le modèle de design Singleton. Si vous ne connaissez pas le modèle Singleton, n'hésitez pas à suivre ce lien.

Enfin, nous stockerons au sein d'une constante dénommée ARTICLES_URI le modèle de l'adresse HTTP utilisé pour récupérer les flux RSS. Pour rappel, elle est de la forme suivante : https://www.supinfo.com/api/supinfo?action=rss&tags={id}

class RssReader
{
  const string ARTICLES_URI = "https://www.supinfo.com/api/supinfo?action=rss&tags=";

  private static RssReader _inst;
  private List<RssElement> _elements = new List<RssElement>();
  public List<RssElement> Elements { get { return _elements; } }

  public event EventHandler ElementsChanged;
  public event EventHandler LoadCompleted;

  public static RssReader GetInstance()
  {
    if (_inst == null) _inst = new RssReader();
    return _inst;
  }
  private RssReader()
  {
  }
}            

1.5.

Créons maintenant différentes méthodes au sein de notre classe RssReader :

  • Load : Cette méthode prend en paramètre un objet Uri et un objet Category

    Son but est de créer un nouvel objet RssElement, d'en définir les propriétés EndPoint avec le premier paramètre et Category avec le second, puis d'en appeler la méthode Fetch avec le mot-clef await.

  • Start : Cette méthode sera appelée par le constructeur pour démarrer la récupération des données.

    Au sein de cette méthode, il faudra créer une boucle itérant de 1 à 19 et en son sein d'appeler la méthode Load définie ci-dessus. En utilisant l'itérateur, nous pouvons créer les deux paramètres.

private RssReader()
{
  Start();
}

private void Start()
{
  string uri = ARTICLES_URI;
  for(int i = 1; i < 20; i++)
  {
    Load(new Uri(uri + i), new Category()
    {
      Id = i
    });
  }
}

private async void Load(Uri uri, Category c)
{
  RssElement e = new RssElement()
  {
    Category = c,
    EndPoint = uri
  };
  Elements.Add(e);
  await e.Fetch();
}             

1.6.

Créons maintenant la liaison avec nos événements.

Pour ce faire, la première étape est d'ajouter dans la classe RssElement une propriété booléenne Completed, qui nous permettra de suivre l'état du chargement. Cette propriété sera définie à vrai quand l'opération asynchrone de la méthode Fetch sera complétée.

Ensuite, il nous faut créer une méthode OnElementCompleted, prenant en paramètre notre objet RssElement récemment complété. Cette méthode devra :

  • Invoquer l'événement ElementsChanged. Son premier paramètre sera l'objet RssElement venant juste d'être complété. Le deuxième argument sera EventArgs.Empty, une propriété spéciale de la classe EventArgs utilisée quand aucun paramètre supplémentaire ne doit être transmis.

  • Vérifier si tous les objets RssElement sont complétés. Si c'est le cas, il nous faudra invoquer le deuxième événement, LoadCompleted. Le première paramètre de cet appel sera l'objet RssReader, et le deuxième paramètre EventArgs.Empty.

class RssElement {
  public bool Completed { get; set; }
  // Reste de la classe omis
}
// Classe RssReader
private async void Load(Uri uri, Category c)
{
  // Reste de la méthode omise
  e.Completed = true;
  OnElementCompleted(e);
}
// Classe RssReader
private void OnElementCompleted(RssElement e)
{
  ElementsChanged?.Invoke(e, EventArgs.Empty);
  if (Elements.Where(r => r.Completed == false).FirstOrDefault() == null)
  {
    LoadCompleted?.Invoke(this, EventArgs.Empty);
  }
}

1.7.

Dernière étape pour la liaison avec notre page en XAML. Il nous faudra notre instance de classe RssReader non initialisée en propriété de notre classe MainPage.

Dans le constructeur de notre page, obtenons l'instance de RssReader et écoutons l'événement LoadCompleted. Visual Studio vous permet d'auto-compléter cette instruction en écrivant RssReader.LoadCompleted +=, puis en appuyant sur la touche Tabulation.

Dans cette nouvelle méthode, récupérons l'ensemble des articles grâce à LINQ et sa méthode SelectMany, et pour chacun, ajoutons les à la collection observable Articles de notre MainPage.

N'oubliez pas de supprimer vos articles de test rédigés à la section 2 de ce chapitre.

public sealed partial class MainPage : Page
{
  private RssReader RssReader;
  public MainPage()
  {
    RssReader = RssReader.GetInstance();
    RssReader.LoadCompleted += RssReader_LoadCompleted;
    DataContext = this;
    this.InitializeComponent();
  }

  private void RssReader_LoadCompleted(object sender, EventArgs e)
  {
    foreach(Article a in RssReader.Elements.SelectMany(x => x.Elements))
    {
      Articles.Add(a);
    }
  }
}

Figure 1.4. Rendu visuel des articles

Rendu visuel des articles

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