xaaml

7 jours pour 1 app – carnet de bord

J.111 – Conclusion

Et bien me voilà au bout des 7 jours et il est temps de conclure.
Première chose : j’ai atteint mon objectif !
Mon application gère plusieurs forums, peut naviguer dans leurs fils de discussion, regarder leur flux RSS principal …

Mais, l’application présente de nombreuses faiblesses qu’il va me falloir corriger avant d’imaginer pouvoir publier mon application sur le Marketplace. Par exemple, je ne l’ai testé que sur une paire de forums en faisant l’hypothèse que tous les forums phpBB respectaient la même structure (ce que je n’ai pas vérifié) et je n’ai fait aucun effort d’optimisation. Aussi, j’ai déjà relevé quelques bugs (gestion problématiques des publicités apparaissant dans les fils de discussion, …) et quelques approximations (l’accès aux sous-catégories est pour le moment impossible, l’accès est restreint aux premiers messages d’un fil de discussion, peu d’informations affichées, …). Aussi, le design est pour le moment mortel et demanderait un peu de travail !

Toute fois, j’aurais appris beaucoup de chose à travers ce chalenge : développement d’une application mobile, utilisation du Windows Phone SDK 7.1, parsage de document html et xml, gestion de la mémoire, …

Si mon chalenge prend ici fin, mon projet reste vivant. Je vais continuer de développer cette application pour essayer de sortir une première version dans le Marketplace (dans les premières semaines de septembre ?).

Affaire à suivre donc …

~xaaml

J.111 – Aboutissements

Aujourd’hui, c’est le dernier jour de mon projet, et j’ai mis la barre haute. Au programme : corrections des détails concernant la manipulation des forums, navigation au sein des catégories et surtout navigation au sein des fils de discussion !

J’ai commencé la journée par modifier la façon dont les données sont passées d’une page à l’autre de mon application en utilisant un class dans l’App. (1) Cette technique est assez pratique et simplifie la tache lorsqu’il faut transmettre des données complexes.

L’ajout des pages affichant le contenu d’une catégorie et un fil de discussion s’est fait sans trop de souci ayant désormais tous les outils en main pour parser une page et afficher les données par un Binding. J’ai toute fois passé beaucoup de temps à corriger de nombreux petits bugs se faufilant de ci de là …

Une nouveauté aussi : l’application récupère automatique le flux RSS principal associé à chaque forum et l’affiche correctement.

Voici quelques aperçu de mon travail :

       

(ci-dessus : contenu d’une catégorie – contenu d’un topic)

Je termine en signalant cette page (2). Trouvée sur le site du Windows Geek, elle explique très bien et avec beaucoup de détails de quelle façon implémenter une LongListSelector.

~xaaml

(1) : http://www.developpez.net/forums/d1189894/dotnet/general-dotnet/developpement-mobile/windows-phone/passage-parametres-entre-xaml/
(2) : http://windowsphonegeek.com/articles/LongListSelector-walkthrough

J.110 – Le miracle des Toolkit

Voici un rapide compte-rendu de la journée.

Après avoir posée la question sur un forum, on m’a conseillé d’utiliser une LongListSelector (c’est le type de liste utilisée dans le hub contact) pour afficher les catégories d’un forum rassemblée en groupes.

Guidé par un très bon tutoriel (1), j’ai repris entièrement la structure sous laquelle les forums sont manipulés en local afin d’utiliser une telle structure pour afficher les catégories d’un forum. Mais j’ai presque envie de dire que cette amélioration n’est rien devant ce que j’ai découvert : le Windows Phone Silverlight Toolkit (2) ! Ce toolkit propose un certain nombre de control supplémentaire pour développer des applications dont fait parti LongListSelector. Je suis vite tombé sur un eBook (gratuit) très sympa (3) présentant ce toolkit, et en le feuilletant, cela m’a donné quelques nouvelles idées pour la suite …

Un autre objectif de la journée a été de modifier le comportement du bouton de validation d’ajout d’un nouveau forum. Cela n’a pas été bien compliqué, à noter toute fois une particularité pour manipuler les éléments de l’ApplicationBar (4). L’ajout d’une barre de chargement s’est aussi fait sans problème grâce à un autre toolkit : le Coding4Fun Toolkit (5) et son control Progress Overlay.

Par contre, je n’ai pas eu le temps d’implémenter la fonctionnalité permettant de récupérer la favicon des forums, pour demain peut-être.

Quelques images pour terminer :

       

(ci-dessus : liste des catégories – ajout d’un nouveau forum)

~xaaml

(1) : http://www.windowsphonegeek.com/articles/wp7-longlistselector-in-depth–part1-visual-structure-and-api
(2) : http://silverlight.codeplex.com/releases/view/75888
(3) : http://windowsphonegeek.com/WPToolkitBook2nd
(4) : http://stackoverflow.com/questions/5334574/applicationbariconbutton-is-null
(5) : http://coding4fun.codeplex.com/

R.1 – Html Agility

La bibliothèque Html Agility est cœur du fonctionnement de mon application pour récupérer et traiter le contenu des forums. Voici une série d’études de cas montrant de quelle façon cette bibliothèque s’utilise.

Présentation

Html Agility est un parser permettant de manipuler une page html. Il se présente sous la forme d’une bibliothèque .NET.

Installation de la bibliothèque

Il faut bien sur commencer par télécharger le Html Agility Pack. Cela peut se faire sur la page CodePlex de la bibliothèque. (1) Le fichier récupéré est en réalité une archive contenant différente édition de la bibliothèque. Il vous faut alors choisir l’édition qui convient à votre situation. Dans le cas du développement d’une application pour Windows Phone, j’ai utilisé l’édition située dans le dossier sl3-wp. Celle intitulée sl4-windowsphone71 a générée une erreur sous Visual Studio.

Une fois que vous avez récupérez les fichiers, vous pouvez extraire de l’archive l’édition de la bibliothèque qui vous convient. Vous pouvez alors l’utiliser dans Visual Studio en y ajoutant une référence en passant par l’éditeur de solutions de votre projet. (clique-droit sur Références, Ajouter une référence…, Parcourir)

Chargement de la page web

Avant toute manipulation avec Html Agility, il faut charger le code html de la page à parser dans un objet HtmlAgilityPack.HtmlDocument. (ici htmlPage est une chaine de caractère contenant le code de la page étudiée)

// Charge la page web dans une instance de HtmlAgility
HtmlAgilityPack.HtmlDocument document = new HtmlAgilityPack.HtmlDocument();
document.LoadHtml(htmlPage);

Il ne vous restera alors plus qu’à manipuler l’objet document.

Voir une page web comme un arbre

Voyez votre page web comme un arbre aux nombreuses branches. La première balise <html> représente le tronc de l’arbre. Celui est séparé en deux branches principales par <head> et <body>. Les balises <meta> sont comme des feuilles accrochées à la branche <head>. Comprenez que chaque couple de balises structurant la page web (<div>, <p>, <h#>, <a>, …) vient rajouter un embranchement sur leur branche mère. Html Agility permet de naviguer au sein de cet arbre en passant de nœuds en nœuds. Pour accéder à un élément particulier de la page web, il faut avancer progressivement à travers toutes les ramifications jusqu’à cet élément.

Prenons un exemple. Supposons que l’on cherche à examiner la page suivante :

<html>
 <head>
  <titre>Page perso</title>
 </head>

 <body>
  <h1>Bienvenue à tous !</h1>
  <p>Venez découvrir mes <a href="passion.html">passions
     </a> et mes sites <a href="sites.html">préférés</a>.</p>
<p>D’autres paragraphes.</p> <p class="footer">page perso de FooBar</p> <a href="foo.html">Lien</a> <p>D’autres paragraphes.</p> </body> </html>

Première approche

Une première façon d’accéder à un élément de la page web est d’y accéder par les balises qui encadre directement cet élément. Par exemple, le couple de balise <title> est sensé être unique sur une page web. On peut donc y faire référence et récupérer ce qu’elles encadre sans risque d’erreurs. On utilise alors :

title = document.DocumentNode.Descendants("title").First().InnerText;

On accède à l’ensemble des nœuds du documents par document.DocumentNode. La méthode Descendants() avec le paramètre “title” permet d’accéder à l’ensemble des nœuds correspondant à un couple de balise <title> (Html Agility ne sait pas que dans le cas des balises <title>, le couple est unique, situé dans l’en-tête du document html). Notez que Decendants() va chercher dans les descendants directes et indirectes du nœud de référence tous les nœuds correspondants au nom de la balise donné (notez le ‘s’ à la fin de Descendants()). Comme cette méthode peut renvoyer plusieurs résultats, on ne sélectionne que le premier à l’aide de First(). Cette méthode peut prendre des paramètres que l’on étudiera ensuite. Maintenant que le nœud correspondant exactement au couple de balises <title> est sélectionné, on extrait le texte compris entre ces balises par la propriété InnerText.

Finalement, après cette commande, la variable title contient la valeur “Page perso”.

Exercice : quelle commande utiliser pour récupérer le texte entre les balises <h1> ?

Deuxième approche

Imaginons que l’on cherche désormais à récupérer le texte contenu dans le paragraphe de class footer. On pourrait utiliser la même approche que précédemment mais il faut en plus préciser que la balise <p> recherchée possède un attribut particulier. Cela va se faire en ajoutant un paramètre à First().

title = document.DocumentNode.Descendants("p")
.First(x => x.Attributes["class"] == "footer").InnerText;

Exercice : quelle commande utiliser pour récupérer le texte entre le couple de balises <a> pointant sur la page foo.html ?

Troisième approche

Maintenant, on cherche à récupérer les url des deux liens hypertextes situés dans le premier paragraphe. Pour obtenir ces url, ils faut commencer par sélectionner les nœuds correspondant aux balises <a>. Or, elles ne possèdent pas d’attribut particulier pour les distinguer de la dernière balise <a> ; il faut donc préciser que ce sont des balises enfants d’un premier couple de balise <p>.

listeNode_a = document.DocumentNode.Descendants("p")
.First().Descendants("a");

On récupère alors cette liste de liens de la façon suivante :

var liens_brut = (from node in listeNode_a
                  select (string)node.Attributes["href"]);
liens = liens_brut.ToList();

Après exécution de cette commande, liens contient : [“passion.html”, “sites.html”]. (remarquez que vous pouvez rendre ce morceau de code plus compacte en remplaçant dans la deuxième partie listeNode_a par  sa définition, mais l’on perd en clarté)

Voila quelque outils qui devraient vous servir si vous décidez d’utiliser Html Agility !

~xaaml

(1) : http://htmlagilitypack.codeplex.com/1

J.110 – Come back

Me voilà de retour sur mon projet. Il me reste 2 jours pour terminer mon application et autant dire qu’il me reste encore un bon paquet de fonctionnalités à implémenter.

Le gros du travail pour aujourd’hui va être de changer à nouveau la structure qu’utilise mon application pour manipuler en local un forum, ses catégories et ses sous-catégories. En repensant ce système, je vais pouvoir utiliser une autre structure de liste pour afficher les informations à l’écran (longListSelector) qui conviendra mieux à mes besoins.

J’ai aussi prévu de modifier légèrement le comportement du bouton de validation d’ajout d’un nouveau forum : il ne doit être activé que lorsque les données d’un forum ont été téléchargées et validées. Je pense aussi en profiter pour ajouter une barre de chargement.

Enfin, je souhaite que mon application récupère lors de l’ajout d’un nouveau forum la favicon du site. Cela me permettra de l’afficher sous forme d’une vignette dans la liste des forums.

~xaaml

J.110,5 – Pause

Les circonstances m’impose de faire une pause dans mon projet. Ce qui ne signifie pas l’abandon bien sûr ! J’en suis au 6e jour et je compte bien le terminer.

Avant d’avoir une nouvelle période complète où je pourrais me concentrer entièrement au développement de l’application, je compte revenir au fur et à mesure sur certains points techniques auxquels j’ai été confronté et les éclaircir par quelques articles plus détaillés.

A bientôt donc !

~xaaml

J.101 – Retouches

La session de code du jour n’a pas particulièrement bien commencé. La première compilation du projet génère une série d’erreurs. J’ai donc passé une partie de l’après midi à revoir le code décrivant les interactions de l’application avec le fichier où sont stockés les informations sur les forums. Cette révision était en fait bienvenue car le travail que j’avais réalisé hier n’était pas particulièrement propre.

J’ai ensuite commencé à développer une classe permettant de nettoyer les chaines de caractères récupérées par parsing des pages web. J’ai pu ainsi dans la foulé finaliser la capture des descriptions des sous-catégories. Ce travail m’a permis de terminer la page affichant les catégories et sous-catégories d’un forum.

Voici ce que j’ai découvert dans la journée.

Mettre un texte de la couleur du thème du téléphone

On applique au TextBlock la propriété suivante :

Style= »{Binding Source={StaticResource PhoneTextAccentStyle}} »

Utilisation de dictionnaire

Je programme souvent en python, je suis donc habitué à  utiliser des dictionnaires. Il existe en C# une structure équivalente. Pour créer un dictionnaire, on utilise :

using System.Collections.Generic;

Dictionary<key_type, value_type> dico = new Dictionary<key_type, value_type>();

… où key_type et value_type indiquent respectivement le type des clefs du dictionnaire et des valeurs associées. on peut ensuite parcourir un dictionnaire à l’aide d’une boucle foreach.

foreach (var entree in dico)
{
doSomeThing(entree.Key, entree.Value);
}

A noter que entree est ici du type KeyValuePair<key_type, value_type>.

J’ai aussi profité de la journée pour faire un rapide SplashScreen  et une icône temporaire histoire de remplacer le jeu par défaut.

       

(ci-dessus : SplashScreen – page affichant les sujets d’un forum avec les descriptions des catégories)

~xaaml

J.100 – The XML Bataille

Je reprends aujourd’hui mon projet avec les mêmes objectifs d’hier : terminer les fonctionnalités d’ajout/suppression de forum avec l’enregistrement ‘en dur’ des modifications. Autant dire que cela n’a pas été évident puisque je ne savais pas trop comment m’y prendre. Toute fois, j’ai pu établir les faits suivants :

  • Je devais changer la façon dont ma base de donnée était gérée en mémoire : d’une simple liste d’objets, il fallait que je passe à une ObservableCollection.
  • Je devais modifier la localisation du fichier xml où sont enregistrés toutes les informations sur les forums pour utiliser l’Isolated Storage.

Une ObservableCollection se manipule quasiment comme une liste. Elle offre toutes fois un certains nombres de fonctionnalités qui la rend particulièrement pratique en Data Binding.

Prenons un exemple : on chercher à afficher dans une listebox les propriétés d’une collection d’objets appelée ici Collection (on a donc dans la balise <ListBox> ItemsSource = Collection). En utilisant une ObservableCollection pour rassembler ces objets, on a l’avantage que la moindre modifications de la collection est immédiatement reportée sur l’affichage des données. On peut ainsi ajouter un élément à Collection et la listebox est mise à jour.

L’utilisation de l’Isolated Storage m’a en réalité permis d’utiliser les méthodes présentée sur le site WindowsPhone Geek (1). En adaptant le code qui y est présenté, j’ai pu parvenir à mes fins.

Pour récupérer les informations du fichier .xml dans un objet XDocument, j’utilise :

XDocument loadedData = null;
try
{
using (IsolatedStorageFile myIsolatedStorage = IsolatedStorageFile.GetUserStoreForApplication())
{
IsolatedStorageFileStream isoFileStream = myIsolatedStorage.OpenFile(« ForumsDataXML.xml », FileMode.Open);
using (StreamReader reader = new StreamReader(isoFileStream))
{
loadedData = XDocument.Parse(reader.ReadToEnd());
}
}
}
catch
{ }

… et pour enregistrer l’objet XDocument (ici toSaveData) dans le fichier .xml, j’utilise :

using (IsolatedStorageFile myIsolatedStorage = IsolatedStorageFile.GetUserStoreForApplication())
{
using (IsolatedStorageFileStream isoStream = new IsolatedStorageFileStream(« ForumsDataXML.xml », FileMode.Create, myIsolatedStorage))
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
using (XmlWriter writer = XmlWriter.Create(isoStream, settings))
{
toSaveData.Save(writer);
}
}
}

Aussi, la modification d’un XDocument se fait à l’aide de la fonction .Add(new XElement(« nom du couple de balises », valeur)). ou valeur peut être une chaine ou encore une liste de XElement.

En plus de tout le temps que j’ai du passer pour trouver, comprendre et adapter les informations nécessaire à l’établissement du système de sauvegarde, j’ai découvert plusieurs autres choses intéressantes.

Personnalisation des MessageBox

Il y a deux types de MessageBox :

  • MessageBox simple : permet d’afficher un message avec la syntaxe MessageBox.Show(« message »).
  • MessageBox personnalisée : permet d’attribuer à la MessageBox un titre ainsi que la paire de bouton boutons Valider – Annuler. (syntaxe : MessageBox.Show(« Message », « Titre », MessageBoxButton.OKCancel))

Imbrications des Bindings

Je suis amené dans mon application à afficher la liste des forums, catégories et sous catégories respectives. Un certain nombre de fonctionnalités de Data Binding permettent de faire cela assez facilement à partir de liste (ou de Collection) d’objets. Je vous invite à voir cet échange (2) pour avoir plus d’informations.

       

(ci-dessus : évolution de l’affichage des sujets d’un forum)

Mes objectifs pour demain sont :

  • régler le problème de capture de la description des sous-catégories.
  • permettre la consultation d’un sujet (ce qui est tout de même le rôle principal de mon application !).
  • améliorer la robustesse du code.

~xaaml

(1)  : http://www.windowsphonegeek.com/tips/All-about-WP7-Isolated-Storage—Read-and-Save-XML-files-using-XmlWriter
(2) : http://social.msdn.microsoft.com/Forums/fr-FR/wmdevfr/thread/62773b3d-c26c-49da-925f-91bd8277d0f1

J.011 – Html Agility vs. Me

Toute la journée, j’ai eu à me battre avec Html Agility. Voici un compte-rendu des mes batailles. Pour cela, nous allons tout d’abord considérer le code html suivant que nous allons ensuite maltraiter.

<html>
<head>
<title>Titre de la page</title>
</head>
<body>
<div id= »part1″>
<ul>
<li class= »first »>i. Premier</li>
<li>i. Deuxième</li>
</ul>
</div>
<div id= »part2″>
<ul>
<li class= »first »>ii. Premier</li>
</ul>
</div>
</body>
</html>

Préléminaires

La toute première chose à faire pour torturer la page web avec Html Agility est de créer un objet HtmlDocument représentant cette page web.

HtmlAgilityPack.HtmlDocument document = new HtmlAgilityPack.HtmlDocument();
document.LoadHtml(htmlPage);

Récupérer le texte compris dans un couple unique de balises (ex. <title></title>)

On parcourt le document et on sélectionne le premier (et unique par hypothèse) nœud correspondant à notre balise. La propriété .InnerText permet d’obtenir le texte compris entre les balises. (comprendre par texte tout qui apparaitrait normalement à l’écran, sont donc exclue les balises html)

document.DocumentNode.Descendants(« title »).First().InnerText

Remarque : en remplaçant .InnerText par InnerWeb, on récupère tout le code html compris entre les balises.

Récupérer le texte d’un couple de balises repéré par un id

On spécifie la valeur de l’id comme paramètre de First().

document.DocumentNode.Descendants(« div »).First(x => x.Id == « part2 »).Elements(« ul »).First().InnerText

renvoie > ii. Premier

Récupérer une liste de nœuds vérifiant certains critères

On utilise une syntaxe particulière du C# permettant de le faire de façon élégante. Ainsi, si on veut sélectionner toutes les balises <li> ayant pour class first, on utilisera :

var ensemble = (from node in document.DocumentNode.Descendants(« li »)
where node.Attributes.Contains(« class »)
where node.Attributes[« class »].Value == « first »
select node);

On peut alors exploiter cette liste pour afficher ce qui est contenu entre les balises <li> sélectionnées :

foreach (HtmlAgilityPack.HtmlNode li in ensemble)
{
MessageBox.Show(li.InnerText);
}

Récupérer la valeur d’un attribue d’une balise particulière

Il faut être sûr que l’attribut existe. Si on cherche par exemple l’id de la première balise div, on fera :

document.DocumentNode.Descendants(« div »).First().Attributes[« id »].Value

renvoie > part1

Remarques

La fonction Descendants(« balise ») renvoie une collection de tous les nœuds correspondant à balise, ces nœuds n’étant pas forcément des enfants directes du nœuds de référence (le nœud courant). La fonction Elements(« balise ») joue le même rôle mais se limite aux nœuds qui sont des enfants directs du nœud de référence.

La fonction First() permet d’accéder au premier nœud d’un ensemble. Il existe aussi Last() pour atteindre le dernier.

~xaaml

J.011 – Prémice des catégories

Objectif : permettre l’ajout de nouveau forum. Il faut en gros écrire une procédure qui à partir de l’url d’un forum en récupère : le titre, une description, la version de phpBB utilisée, les catégories et sous-catégories du forum, leur description. Une fois que toutes ces informations sont récupérées, il faut les enregistrer dans un fichier xml.

Commençons par la première partie : la récupération des différentes informations. Il me faut tout d’abord réaliser la page sur laquelle l’adresse web du forum doit être entrée. Une TextBox et quelques labels suffisent largement. Lorsque le champ où l’on rentre l’adresse du forum perd le focus, une routine vérifie que l’url est valide, télécharge la page et en extrait les informations. Une partie est affiché immédiatement (nom et description) et le tout attend la validation du formulaire pour être enregistré. Je ne suis pas allé plus loin de ce coté, il ne me reste plus qu’à créer la procédure sauvegardant les donnée récolter dans le fichier xml.

D’autre part, j’ai améliorée la page d’un forum : y apparait désormais les catégories du forum.

Si je n’ai en apparence pas tant avancé aujourd’hui, c’est que j’ai été confronté à quelques difficultés pour maitriser Html Agility. Je pense publier dans la foulée un article exposant les soucis auxquels j’ai été confrontés et les solutions que j’y ai trouvé.

~xaaml