Altice Stock Checker : développez votre première application avec une interface graphique

Joyeux Noël, Patrick ! 40
Accès libre
image dediée
Développeurs TUTO
David Legrand

Après avoir appris à développer votre première application console sous Windows, il est temps de passer à l'étape suivante en ajoutant la gestion d'une fenêtre. Pour cela, développons ensemble un outil de suivi du cours de l'action Altice : l'Altice Stock Checker

Premier grand défi de notre dossier : développer une petite application permettant de suivre le cours de l'action du groupe Altice, plutôt volatile ces derniers temps. Bien entendu, le cas pourra fonctionner avec n'importe quelle autre valeur, ou sous la forme d'une option si vous le désirez.

Nous voulons une application accessible à tout moment, indépendante du navigateur. Le suivi devant pouvoir se faire de manière simple et visuelle, il nous faudra une interface graphique. Nous nous tournerons donc vers C#, un langage reposant sur le framework .NET et plutôt facile à appréhender pour un débutant, via Visual Studio Community.

L'application se limitera alors à un fonctionnement sous Windows pour ce premier exercice. Et puisqu'il faut lui trouver un nom, ce sera Altice Stock Checker (aka Vérifieur du Cours d'Altice). 

Notre dossier sur la programmation et le développement d'application :

Affichez et configurez votre première fenêtre

Commençons par préciser à nouveau que notre but n'est pas ici de tout vous faire connaître de C# ou de la programmation orientée objet. Des livres entiers étant consacrés au sujet, il serait bien présomptueux de penser pouvoir tout simplifier en quelques articles. Certaines notions pourront donc vous parfois vous échapper, c'est tout à fait normal et ça ne doit pas vous empêcher pas de vous lancer.

Nous aurons l'occasion de développer différents éléments au fur et à mesure de la publication de ce dossier. Nous placerons néanmoins tous les liens nécessaires vous permettant d'approfondir le sujet si vous le désirez. N'hésitez pas à lire notre précédent article qui posait déjà quelques bases.

Nous y avons développé une application affichant « Hello, World ! » sous Visual Studio. Seul problème : elle ne sert... à rien (si ce n'est à contenter votre égo de créateur). Passons donc la seconde avec l'affichage d'une fenêtre en créant un projet (CTRL + MAJ + N) de type Application Windows Forms, nommé AlticeStockChecker.

Ici on retrouve la même chose que pour l'application précédente, mais à quelques différences près. Tout d'abord, ce n'est pas un éditeur de code que nous obtenons par défaut, mais un éditeur de fenêtre (ou Concepteur de vues) avec le fichier Form1.cs d'ouvert. Celui-ci dispose de propriétés, que l'on voit en bas à droite de la fenêtre.

Visual Studio C# WinForms

Si on lance le débogage de l'application (F5), on obtiendra une fenêtre vide, sans aucune autre action possible que son déplacement, redimensionnement ou fermeture.

Nous allons donc l'adapter, en interdisant son redimensionnement et en lui donnant un nom. Il faut passer par les Propriétés de la fenêtre, notamment la valeur FormBorderStyle qui doit passer de Sizable à FixedSingle. Les valeurs MinimizeBox et MaximizeBox doivent en outre basculer sur False.

Pour le nom, nous pourrions également utiliser une propriété, mais nous allons passer par une solution qui pourra évoluer avec le nom du projet. On édite donc le code de Form.cs, via la touche F7 (clic droit sur Form1.cs dans l'Explorateur de solutions, Afficher le code).

Classe, objet et instanciation : les bases à connaître

Comme pour notre application précédente, on se retrouve avec une longue liste de directives using inutilisées, l'espace de nom de l'application, et une classe Form1. Celle-ci est publique, donc accessible à l'intérieur et hors de l'application, mais également partielle donc pouvant être composée de plusieurs fichiers. Elle est composée d'une méthode Form1() qui sera lancée par défaut et initialise la fenêtre.

C'est toujours le fichier Program.cs qui est lancé à l'exécution de l'application. Cette fois, nous ne le modifierons pas puisqu'il contient déjà tout ce qu'il faut pour lancer automatiquement la fenêtre Form1 qui va concentrer nos efforts.

Contrairement à notre programme précédent, la classe Form1 n'est cette fois pas statique. Elle peut donc être « instanciée ».  C'est l'un des principes de la programmation orientée objet (POO) où l'on peut définir une structure (ici une classe) qui sert à créer des objets selon un modèle précis. C'est ce qui se passe ici.

On prend souvent l'exemple d'une voiture qui pourrait être définie par différentes propriétés comme sa couleur, son moteur, son nombre de roues, de portes, son propriétaire. Elle est également attachée à des capacités (ici des méthodes) comme avancer, reculer, freiner, etc.

Instancier l'objet voiture revient à en créer une qui aura ses propres propriétés et sur laquelle on pourra agir spécifiquement. On peut le faire autant de fois qu'on le désire.

Le code prendrait alors la forme suivante :

Voiture caisseDeSeb = new Voiture(); // On créé une instance de Voiture
caisseDeSeb.marque = Lada; // On lui attribue une propriété
caisseDeSeb.Avancer(); // On lance la méthode Avancer();

Ainsi, lorsque l'application est lancée, c'est un exemplaire de la fenêtre qui est créé et affiché, selon un modèle défini. Cette notion est importante ici car pour modifier le titre de la fenêtre, qui correspond à la variable Text, depuis le code de la fenêtre elle-même nous devons donc faire référence à l'instance de l'objet créé.

Pour cela, on utilise le mot-clé this. Le nom de l'application est pour sa part contenu dans la variable Application.ProductName. Ce qui donne la ligne de code suivante pour donner au titre de la fenêtre la valeur du nom de l'application, toutes deux étant des variables de type string :

Définition de la stratégie à suivre

Maintenant que nous avons configuré la base de l'écrin de notre application, posons-nous la question de ce que nous voulons lui faire faire. Comme expliqué au départ, nous souhaitons suivre l'évolution du cours d'Altice. Il nous faut d'abord récupérer cette information.

Les API publiques, ne nécessitant pas d'inscription, ne sont pas légion dans le domaine. Nous allons donc regarder du côté d'un service comme Yahoo Finance. Sur cette page, on retrouve la valeur d'Altice indiquée en temps réel.

Mais si on regarde le code source, la zone n'est pas identifiée par un élément unique. Il ne sera donc pas aisé d'extraire la valeur. Heureusement, Yahoo place une variable root.App.main dans la section « Data » de son code, où l'on peut retrouver une information qui nous sera plutôt utile :

"currentPrice":{"raw":8.456,"fmt":"8.46"}

Il nous suffit donc de récupérer la page, d'extraire la valeur « raw » et de l'afficher dans notre fenêtre. Pour ce premier guide, nous ferons donc simple et un peu « cracra » (l'idéal étant de passer par une API plutôt que du « scrapping »). Nous afficherons également la valeur en rouge ou en vert si elle varie à la hausse ou à la baisse.

Récupération du code de la page

Commençons par la fonction de base, celle permettant de récupérer le contenu de la page sous la forme d'un texte, que nous placerons dans la variable webPageCode. Pour cela, C# dispose d'un outil prêt à l'emploi : la classe WebClient et la méthode Downloadstring().

Tout ce que nous avons à faire c'est créer une instance de WebClient et de lui transmettre l'URL de la page concernée pour la télécharger. Si tout se passe bien, le code source sera récupéré instantanément. Pour le vérifier, nous modifions le code de notre application de la manière suivante :

Problème, WebClient apparaît comme souligné en rouge. La raison est simple : VisualStudio ne sait pas à quoi cela fait référence en l'état actuelle des choses, cette classe n'étant pas reconnue par défaut.

L'EDI peut néanmoins vous aider à corriger la situation très simplement : au survol de WebClient, il vous sera proposé d'Afficher les corrections éventuelles, une fonction accessible via les raccourcis Alt+Entrée ou CTRL+; et qui vous fera plusieurs propositions. Les deux premières peuvent être utilisées :

  • Ajouter la directive using System.Net pour intégrer l'espace de noms contenant WebClient
  • Remplacer WebClient par System.Net.WebClient

Nous opterons pour la première solution qui a l'avantage de ne pas nécessiter d'ajouter System.Net à tous les éléments que nous utiliserons par la suite. Une fois sélectionnée, la directive sera automatiquement ajoutée au début du code.

Visual Studio C# Correction

Pour vérifier que tout fonctionne, nous lançons une exécution un peu spéciale avec un clic droit sur l'accolade fermée après la dernière ligne de code, puis en sélectionnant Exécuter jusqu'au curseur (CTRL+F10).

Cela va lancer l'application, mais s'arrêter à l'endroit précis que nous avons désigné, tout en nous permettant de connaître la valeur des différents éléments au moment de l'arrêt. Ainsi, d'un simple survol sur la variable webPageCode (ou dans la zone Variables locales) on peut voir qu'elle contient du texte, que l'on peut récupérer entièrement d'un clic sur la loupe.

En le collant dans une application comme le Bloc-notes de Windows et en cherchant (CTRL+F) la valeur currentPrice on peut voir le résultat attendu apparaître :

  • Visual Studio C# Récupération Valeur
  • Visual Studio C# Récupération Valeur
  • Visual Studio C# Récupération Valeur
  • Visual Studio C# Récupération Valeur

Vous pouvez à tout moment poursuivre l'exécution de l'application en cliquant sur Continuer dans la barre d'outils (F5) ou la stopper en cliquant sur le carré rouge (MAJ+F5) situé à sa droite.

Il est d'usage de placer un WebClient dans une instruction (et non une directive) using(), ce qui permet de libérer les ressources utilisées dès la fin de la procédure. Cela isole également les éléments contenus au sein du bloc, ce qui pourra parfois poser problème comme nous le verrons plus bas.

Dans une application comme celle que nous développons aujourd'hui cela ne sera pas d'une grande nécessité, mais c'est une habitude à prendre car elle s'avèrera essentielle dans des applications plus complexes afin d'éviter que trop de ressources soient utilisées pour rien.

Cela donne au final le code suivant :

Extraction de la valeur recherchée

Nous disposons d'une variable de type string contenant la valeur que nous cherchons à afficher, il nous faut désormais l'extraire. Pour cela, il existe plusieurs solutions plus ou moins lourdes et complexes. De notre côté nous allons opter pour ce que l'on nomme une expression régulière.

Pour faire simple, il s'agit d'une sorte de « masque » que l'on applique à une variable, sous la forme d'une règle à la composition parfois complexe. Dans notre cas la règle sera la suivante :

Regex.Match(webPageCode,
"currentPrice\":{\"raw\":(.+?),",
RegexOptions.Singleline).Groups[1].Value;

Pour résumer, cela signifie que nous allons chercher dans la variable webPageCode la valeur composée d'une suite de caractères située après « "currentPrice":{"raw": »  et avant « , ».

Notez que nous utilisons des caractères d'échappement « \ » permettant au compilateur de faire la différence entre un « " » situé à l'intérieur de la variable et celui utilisé comme élément du code. Si l'on reprend l'exemple donné plus haut, cela devrait nous récupérer la valeur suivante :

8.456

Nous avons néanmoins trois problèmes à résoudre. Le premier est que notre variable webPageCode n'existe désormais plus qu'à l'intérieur de notre bloc using(). Elle ne peut donc pas être utilisée en dehors.

Nous devons alors déclarer cette variable avant ce bloc afin de pouvoir l'utiliser après. Pour cela nous allons l'initialiser avec la valeur String.empty. Ensuite, il nous faut attribuer le résultat de l'application de l'expression régulière à une autre variable, nous la nommerons rawStock.

Enfin, les expressions régulières ne peuvent être utilisées qu'à travers l'espace de nom System.Text.RegularExpressions, qu'il faudra donc ajouter via une directive using comme nous l'avons fait précédemment pour System.Net. Nous en profiterons pour Supprimer et trier les directives using d'un clic droit dans la fenêtre de l'éditeur de code (CTRL+R, CTRL+G).

Cela nous mène au code suivant :

Affichage de la valeur dans la fenêtre

Maintenant que nous avons récupéré une valeur, nous allons l'afficher. Pour cela, il faut créer une zone dédiée et revoir un peu l'organisation de notre Form1. Retournez dans l'onglet Form1.cs [Design], cliquez sur la fenêtre puis modifier la propriété Size en lui donnant la valeur « 300;117 » (en pixels).

Rendez-vous ensuite dans la Boîte à outils (tout à gauche de la fenêtre) puis sélectionnez Contrôles communs > Label. Vous pourrez alors placer un label d'un clic dans la fenêtre. Déplacez-le en haut à gauche jusqu'à voir deux barres bleues apparaître. Celles-ci servent à guider le placement des éléments pour assurer une certaine « marge ».

Sélectionnez ensuite ce label pour modifier les propriétés suivantes :

  • (Name) : lblStock
  • AutoSize : False
  • Font : Arial Black; 25pt; style=Bold
  • Size : 260;55
  • TextAlign : MiddleCenter

Maintenant, il ne nous reste plus qu'à afficher la valeur dans le label pour que tout fonctionne. Cela est plutôt simple puisqu'il suffit d'attribuer la variable rawStock à la variable Text du label.

Notre code devient le suivant :

Mise à jour régulière de la valeur avec un timer

Maintenant que notre application affiche la valeur lors de son lancement, il faut penser à la mettre à jour de manière régulière. Pour cela, C# dispose de la classe Timer qui permettent d'exécuter une portion de code toutes les x millisecondes, une période connue sous le petit nom de tick.

Pour en ajouter un à notre application, il faut se rendre dans la section Composants de la Boîte à outils, et cliquer au sein de la fenêtre Form1. Il apparaîtra en bas de l'interface de Visual Studio. 

Visual Studio C# Méthode

Cliquez dessus pour faire apparaître ses propriétés et modifiez Enabled à True et Interval pour lui donner la valeur « 15000 ». Ainsi, la mise à jour sera effectuée toutes les 15 secondes, dès le lancement de l'application.

Cela ne sert à rien de descendre plus bas pour diverses raisons. Tout d'abord, la récupération de la page n'est pas totalement instantanée. Ensuite, il faut penser à préserver les serveurs de Yahoo. Enfin, notre objectif est de suivre l'évolution de la valeur tout au long d'une journée, quatre fois par minute est largement suffisant.

En effectuant un double-clic sur le timer, vous créerez une méthode timer_Tick, dont le contenu sera exécuté à chaque intervalle, dès que le timer sera lancé. On peut donc y déplacer toute la portion du code qui est utilisée pour récupérer la valeur du cours d'Altice et la placer dans le label.

Cela donne le code suivant :

Si vous l'exécutez, vous noterez un problème : la valeur n'est mise à jour pour la première fois qu'au bout de 15 secondes, après que la première intervalle soit passée. Pour corriger cela, il faut un peu réorganiser notre code. Notez que si vous effectuez des tests hors des horaires d'ouverture de la bourse, la valeur ne sera jamais mise à jour.

Première phase de rangement

Notre objectif est d'exécuter le téléchargement du code de la page puis l'extraction et la mise à jour de la valeur du cours au lancement de l'application, puis à chaque tick du timer.

Pour cela, nous devons créer une méthode dans laquelle nous placerons notre code afin de la lancer comme bon nous semble. Là encore, Visual Studio nous simplifie la vie et il vous suffit de sélectionner la portion de code à l'intérieur de la méthode timer1_Tick, d'effectuer un clic droit puis de sélectionner Actions rapides et refactorisation pour lancer une extraction sous la forme d'une nouvelle méthode.

  • Visual Studio C# Méthode
  • Visual Studio C# Méthode
  • Visual Studio C# Méthode

Celle-ci se verra attribuer un nom générique, que vous pourrez modifier directement aux différents endroits où il y est fait référence. Nous opterons pour GetAndCleanStockValue(), avant de valider avec la touche Entrée. Désormais, il suffit d'ajouter une référence à cette méthode au lancement de l'application pour récupérer la valeur avant le premier tick.

Lancez le débogage (F5), cela fonctionne parfaitement ! Voici le code de l'application à ce stade :

Conversion d'un string en float

Mais voilà, ce que nous avons récupéré n'est pas à proprement parler une valeur de cours boursier. Il s'agit en réalité d'un texte contenant l'équivalent de cette valeur. Dit autrement, la variable rawStock étant de type string, elle ne peut pas être utilisée pour effectuer des opérations mathématiques par exemple.

Pour cela, nous allons la convertir en float (ou single), qui désigne une valeur à virgule flottante de 32 bits. C# propose une méthode prévue à cet effet, float.TryParse(), qui tente la conversion d'une variable de type string en float en l'attribuant à une variable tierce. Le résultat est également renvoyé sous la forme d'un booléen (true ou false).

Il y a un autre élément à prendre en compte : la valeur que nous récupérons est issue d'un site américain, avec une convention américaine pour la gestion des nombres. Ainsi, le séparateur entre la partie entière et la partie décimale est un point, alors qu'il s'agit d'une virgule pour la France.

Là aussi, il existe une solution simple : la possibilité de préciser la convention de départ lors de la conversion, ce qui nous permet de récupérer automatiquement un nombre sous la bonne forme. La première étape est de déclarer et d'initialiser une variable stock de type float et de valeur nulle :

float stock = 0.0f;

Ensuite on passe à la tentative de conversion de rawStock, en attribuant le résultat à la variable stock si tout se passe bien, en précisant que la « Culture » de départ est celle d'un nombre en convention américaine :

float.TryParse(rawStock, 
                NumberStyles.Number,
                CultureInfo.CreateSpecificCulture("en-US"),
                out stock);

Nous en profiterons pour rajouter le terme « euros » à la valeur obtenue, pour obtenir une variable de type string définitive à utiliser dans notre label. Nous ne gèrerons pas le pluriel en partant du principe que la valeur d'Altice sera toujours supérieure à deux euros. On pourra néanmoins revenir sur ce point plus tard afin de peaufiner notre code.

Ici, nous utilisons la méthode string.Format() qui permet de constituer une variable finalStock de type string avec du texte et des variables que l'on place sous la forme d'éléments de type {0}, {1}, {2}, etc. Il nous faut également utiliser stock, qui est de type float, sous la forme d'un variable de type string avec la méthode ToString() :

string finalStock = string.Format("{0} euros", stock.Tostring());

Nous obtenons ainsi bien le résultat escompté, avec un affichage dans la convention du système d'exploitation tel qu'il est configuré et la possibilité d'effectuer des calculs sur la valeur stock lorsque cela est nécessaire.

Voici le code complet de l'application à ce stade :

On ajoute un peu de couleur

Cela va justement être nécessaire pour l'ajout de la dernière fonctionnalité de l'application : la couleur permettant de savoir si la valeur est à la hausse ou à la baisse d'un tick à l'autre. Pour cela nous utiliserons la propriété ForeColor de lblStock, qui désigne la couleur de la police du label.

Pour procéder nous allons utiliser une méthode assez basique : à chaque tick nous enregistrerons la valeur récupérée dans une variable lastStock, et nous la comparerons à chaque fois à la nouvelle valeur trouvée. Si cette dernière est supérieure, nous utiliserons la couleur verte, si elle est inférieure la couleur rouge, sinon la couleur noire.

Pour commencer, nous devons déclarer une variable lastStock de type float. Mais celle-ci ne peut pas l'être dans la méthode GetAndCleanStockValue() puisqu'elle est exécutée en repartant de zéro toutes les 15 secondes. Elle doit être accessible tout au long de l'exécution du programme, et ne pas dépendre d'une méthode en particulier.

Elle doit donc être déclarée au sein de la classe Form1, avant la première méthode, de la sorte :

float lastStock = 0.0f;

Ainsi, elle sera accessible depuis n'importe quelle méthode de l'application, dont GetAndCleanStockValue(). Nous y ajoutons la série de conditions suivantes sous la forme d'un ensemble if ... elseif ... else afin de traduire la gestion des couleurs exprimée plus haut.

Par sécurité un autre élément sera pris en compte : si la valeur précédente est nulle, c'est que l'on est dans la première initialisation de l'application, nous n'utiliserons alors ni la couleur rouge, ni la couleur verte.

Cela donne la portion de code suivante :

Gestion d'erreur

Notre application est désormais pleinement fonctionnelle, et correspond à notre cahier des charges de départ. Pour finaliser totalement le travail, il faut néanmoins apporter quelques petites améliorations.

La première est de gérer les éventuelles erreurs qui peuvent survenir hors de l'application. En effet, la connexion internet peut être en panne, tout comme le site de Yahoo. Celui-ci peut évoluer et ne plus fonctionner avec notre application. Il faut donc gérer tous ces cas pour éviter de se retrouver avec un méchant message d'erreur.

Pour cela, C# met à notre disposition le mécanisme try-catch. Il permet d'encadrer une portion de code (try) de manière à effectuer une action au cas où une erreur, aussi connue sous le petit nom d'Exception, arrive (catch). On peut procéder de différentes manières, plus ou moins précises, pour réagir différemment selon les situations.

Ici, nous nous contenterons d'une gestion assez basique avec un seul block try-catch qui englobera tout le contenu de la méthode GetAndCleanStockValue(). Si jamais une erreur intervient, on affichera tout simplement « Erreur » dans le label :

try
{
    ...
}
catch (System.Exception)
{
    lblStock.Text = "Erreur";
}

Pour vérifier que cela fonctionne, il suffit d'ajouter une lettre dans l'URL utilisée pour le WebClient et de lancer un débogage (F5). Normalement, le résultat devrait être sans appel :

Visual Studio C# Erreur

Découper le code pour le rendre plus lisible et réutilisable

On peut également améliorer la lisibilité de notre code. En effet, actuellement, une action globale est lancée, alors que dans la pratique il y a trois phases au fonctionnement de notre application : la récupération du contenu de la page, l'extraction de la valeur et la mise à jour du label.

On peut donc diviser GetAndCleanStockValue() en trois méthodes, ce qui nous permettra par la suite de les améliorer individuellement, ou même de les réutiliser dans d'autres applications plus simplement comme nous aurons l'occasion de voir dans un prochain article.

Comme précédemment, nous utiliserons l'extraction de méthode. Commençons par le téléchargement du code qui concerne toute la partie encadrée par l'instruction using(), que l'on nommera GetHTMLCode(). Ici, l'extraction est « bête » puisqu'elle emporte avec elle l'URL de la page dans la méthode. Une bonne chose à faire est de la transmettre sous la forme d'un paramètre, ce qui permettra de l'utiliser avec n'importe quelle URL.

Une fois la modification effectuée, cela donne la portion de code suivante :

Comme on peut le voir, elle prend une variable url de type string en paramètre, et renvoie une valeur de type string également (mot-clé return). Ainsi, cette méthode peut être simplement appelée de la sorte :

string webPageCode = string.Empty;
webPageCode = GetHTMLCode("https://finance.yahoo.com/quote/ATC.AS?p=ATC.AS");

On peut ensuite faire la même chose avec toute la portion de code qui gère l'extraction de la valeur sous la forme d'une variable de type float, de l'expression régulière à l'utilisation de float.TryParse(). On nommera cette méthode, qui prendra une variable string en paramètre et renverra une variable float, ExtractFloatValue().

Une fois la modification effectuée, cela donne la portion de code suivante :

Finissons par le reste du code de GetAndCleanStockValue() qui sert à mettre en forme et à jour le contenu du label. Ici l'extraction permettra d'obtenir une méthode qui ne renverra rien (void) que l'on nommera UpdateLabelWithColor().

Une fois la modification effectuée, cela donne la portion de code suivante :

Votre application est là, mais vous pouvez encore l'améliorer

C'est terminé ! Votre application est fonctionnelle, votre code est segmenté. On peut déplacer les méthodes dans un ordre un peu plus logique avant de compiler définitivement l'application et l'utiliser au quotidien. Pour cela il suffit comme pour le précédent guide de passer à Release plutôt que Debug dans la barre d'outils puis de Générer le projet (F6).

L'application se trouvera dans le répertoire AlticeStockChecker\bin\Release. Le dossier de votre projet peut être ouvert via un clic droit dans l'Explorateur de solution (Ouvrir le dossier dans l'Explorateur de fichiers).

Elle reste perfectible sur différents points. On pourrait lui ajouter une icône personnalisée, garder en mémoire les valeurs les plus hautes et plus basses de la journée, ou même la rendre un peu plus fonctionnelle.

Car notre code a un défaut majeur, il n'est pas multi-thread. Ainsi, pendant que l'application récupère le contenu de la page depuis les serveurs de Yahoo, son interface est figée puisque cela n'est pas effectué en tâche de fond, un point que nous creuserons dans un prochain guide.

D'ici là, n'hésitez pas à nous dire ce que vous avez pensé de cet article, de vos attentes pour d'éventuels autres guides du genre ou même des idées d'outils que vous souhaiteriez partager. Nous tenterons de prendre en compte vos remarques pour faire évoluer ce dossier en fonction de vos différentes demandes.

Vous pouvez aussi vous rendre sur notre forum qui dispose d'une section dédiée à la programmation.


chargement
Chargement des commentaires...