Aller au menu - Aller au contenu

Icône Gérer les erreurs

Avatar
Mise à jour : 07/01/2011
Difficulté : Intermédiaire Intermédiaire Creative Commons BY-NC-SA
1 755 visites depuis 7 jours, dont 17 sur ce chapitre classé 76/786
Dans ce chapitre, nous allons étudier ensemble les problèmes d'affichage que nous pouvons rencontrer et trouver un moyen de gérer les erreurs qui surviennent lors de l'exécution du programme.
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

Transcript

Le transcript ci-dessous correspond au texte que vous avez entendu dans la vidéo de ce chapitre.


Bonjour,

Comme promis, nous allons maintenant améliorer le programme de la Pythonnerie précédente. Cette amélioration va se faire en plusieurs temps. Nous allons tout d'abord corriger des problèmes visuels, améliorer un tout petit peu l'ergonomie, réorganiser le code et rendre le programme davantage robuste.

En fait, on peut avoir quelques soucis avec le programme de quizz tel qu'il est écrit à présent. Pour les mettre en évidence, j'ai ajouté à ma liste d'auteurs Edgar Poe, et François-René de Chateaubriand. Et j'ai légèrement trafiqué le programme pour que le tirage des questions ne soit plus aléatoire.

Supposons donc que le premier nom à surgir soit celui de Poe. Je rentre son prénom, et malheur, quand Chateaubriand apparaît – et bien heureusement que je sais que c'est Chateaubriand. Le problème est facile à comprendre : je vous ai dit que quand, dans la méthode init , j'appelais la méthode SetSizerAndFit , la fenêtre ajustait automatiquement sa taille au contenu. Et le contenu initial était un nom très court. Du coup, la fenêtre se trouve sous-dimensionnée dès que l'on a un nom ; même de longueur moyenne.

Je pourrais imaginer appeler des méthodes (il y en a) pour adapter la taille de la fenêtre à chaque question. Le problème est que la fenêtre passerait son temps à se redimensionner, ce qui ne serait pas très plaisant à utiliser. Il y a une autre solution. Quand je charge mon fichier, je lis toutes les questions et toutes les réponses. Je peux repérer à ce moment là les valeurs les plus longues, et essayer de dimensionner les éléments graphiques dès le départ pour que ces valeurs puissent y tenir sans déborder.

Mais qu’est-ce que c'est la valeur la plus longue ? Nous sommes en environnement graphique, et en environnement graphique on travaille généralement avec des polices variables. J'ai ici 10 i, qui occupent une certaine largeur. Il ne me faut pas beaucoup de m pour avoir une suite de caractères qui occupe davantage de place. Compter le nombre de caractères ne pourra donc donner qu'une idée approximative de la taille. Mais il y a plus ennuyeux. Connaissant le nombre de caractères, comment en déduire une taille qui dans un environnement graphique se calculera en pixels ? Parce qu'en fait, la largeur réellement occupée, c'est avant tout une question de police.

Ce que je vous propose pour régler cette question est un mécanisme relativement simple. Tout d'abord, à la lecture de mon fichier, je vais identifier les longueurs les plus grandes, tant pour les questions que pour les réponses. Je vais me constituer un pseudo-texte de cette longueur maximale, uniquement constitué de x – une lettre de taille moyenne (si j’étais paranoïaque je pourrais employer m ou w – ou je pourrais raffiner en mettant une majuscule. Après, c’est une question d’ajustement). Puis, et je vais faire tout cela pendant que je suis toujours à m'activer en coulisses, avant même de montrer quoi que ce soit à l'écran, je vais mettre ce pseudo-texte représentant ce que je peux avoir de plus long dans un élément graphique ; je vais ensuite utiliser une méthode qui me permet de connaître la taille d'un élément graphique, puis une seconde méthode qui permet de fixer la taille. Je vais donc fixer la taille au plus grand, et quand je mettrai dans l’élément une « vraie » valeur le tour sera joué.

Je vais donc, au début de ma méthode de chargement, mettre à zéro la plus grande longueur rencontrée pour les questions et pour les réponses. Je vais lire mon fichier, et quand j’ajoute quelque chose à ma liste de questions, je contrôle la longueur de la question, et si elle est plus grande que la plus grande valeur rencontrée jusqu’à présent, je la mémorise à la place. Je fais la même chose avec les réponses.

A la fin, je mets à la place de la question et de la réponse avec la méthode SetLabel le nombre de x qui va bien (je vous rappelle que multiplier une lettre par un nombre revient à la répéter ce nombre de fois). Puis j'appelle la méthode de redimensionnement que nous allons voir tout de suite. J'attire tout de même votre attention sur le fait que, du coup, la zone de réponse a elle aussi besoin de devenir un attribut de la classe.

Voyons donc cette méthode de redimensionnement. Les éléments graphiques ont une méthode GetSizeTuple() qui retourne deux valeurs à la fois - une largeur et une hauteur - dans cet ordre. Pour récupérer deux valeurs à la fois, on écrit comme ici variable1 virgule variable2 égale – appel de la méthode. La méthode SetSizeHints permet de spécifier la taille que l'on veut, en précisant des valeurs minimales et des valeurs maximales. Ici je mets les mêmes valeurs dans les deux cas. Après cela, je recalcule la taille des boîtes en appelant leur méthode LayOut - mise-en-page, si vous voulez. Évidemment, du coup, ces deux boites doivent être des attributs de la classe pour que je puisse les voir dans cette méthode. Puis j'appelle la méthode Fit() de la fenêtre principale pour ajuster la taille globale de la fenêtre. Si vous vous rappelez, dans les pythonneries précédentes, j'appelais SetSizerAndFit qui faisait deux opérations à la fois – accrocher la disposition, puis ajuster la taille. Ici, la disposition est déjà raccrochée, donc je n'appelle qu’une méthode Fit() , je recentre parce qu'avec tout ça j'ai toutes les chances d'être décalé, et enfin je déclenche l’événement comme quoi la taille a changé. C'est le même événement que l'on déclenche quand on redimensionne la fenêtre. En fait, c'est surtout pour le gestionnaire de fenêtre, qui fera par exemple se redessiner ce qui était caché par la fenêtre avant et ne l'est plus maintenant si sa taille a réduit. Et finalement, j’appelle la méthode Refresh() qui redessine, dans la fenêtre même, ce qui a besoin d'être redessiné.

Quand ma fenêtre est dimensionnée pour que même la question la plus longue y tienne, je pourrai tirer une nouvelle question et cela va être la fonction de la méthode nouvelle_question qui fait ce que nous connaissons déjà, à savoir piocher une nouvelle question, j'incrémente ici mon compteur de questions, c'est plus logique, je la mets dans l'objet graphique question, puis j'efface la réponse précédente de l’utilisateur. Je vais quand même ajouter une touche nouvelle, l'appel à la méthode SetFocus() de l'objet réponse utilisateur. Cette méthode rend cet objet actif et y positionne le curseur. Elle a le même effet que si l'utilisateur avait cliqué dans la zone d’entrée pour pouvoir fournir sa réponse.

Vous comprendrez, je pense, que l'introduction de ces méthodes va demander une certaine refonte de la méthode init . Les conseils que donnait aux apprentis poètes, il y a de cela plus de trois siècles, Nicolas Boileau dans son « Art Poétique » sont toujours en grande partie valables pour les développeurs informatiques. Et s'il ne faut pas tomber dans le perfectionnisme, il faut construire sur des bases saines qui permettront de faire évoluer le programme. Je vais donc, rapidement, restructurer la méthode init . Tout d'abord, les attributs qui ne correspondent pas à des éléments graphiques, les compteurs et le tableau des questions. Ensuite les éléments graphiques, dont le seul qui n'ait pas besoin d'être vu par une autre méthode est le bouton OK.

Ensuite, mes dispositions qui doivent être des attributs puisqu'on y fait référence dans la méthode redimensionne. Ensuite, je range tous mes éléments qui dans la boite verticale, qui dans la boite horizontale, la boite horizontale dans la verticale, pour les disposer comme je veux. Après, j'accroche l'action verifier_reponse au bouton, et je vais apporter une légère amélioration en rattachant la même action à l’évènement EVT_TEXT_ENTER quand il est émis par l'objet reponse_utilisateur. Qu'est-ce que c'est que c’est événement ? C'est ce que produit le fait d'appuyer sur la touche « Entrée » quand on a tapé la réponse. En d’autres termes, appuyer sur « Entrée » aura le même effet que cliquer sur OK.
Ensuite une légère modification : j’attache la boite verticale à la fenêtre, mais uniquement avec SetSizer() , pas SetSizerAndFit() , puisque l'ajustement de la taille se fera après avoir chargé les données.
Et au final, je charge le fichier questions.txt puis je tire ma première question.

Pour terminer avec la restructuration du code je peux maintenant simplifier verifier_reponse en appelant nouvelle_question après avoir vérifié et changé le score. Le comptage des questions, rappel, se fait dans nouvelle_question.

Voilà, le code est plus propre et prêt à supporter des évolutions. Mais ne peut-on rendre le programme plus robuste ? Si vous vous rappelez la Pythonnerie n°9, j’avais introduit les exceptions dans le cadre d’un fichier de questions absent. Ce n’est pas parce que l'on est en environnement graphique que les mêmes problèmes ne se produiront pas ! Voyons ce qui se passe si je renomme mon fichier. Si sous Windows je double-clique sur le .pyw, cela va donner à peu près cela... Bon, je lis vite, mais il y a des limites. En fait, si je lance le programme depuis Idle, la fenêtre va rester affichée un peu plus longtemps, et voici ce qu’elle contient. On retrouve le même genre de message mystérieux qu’en mode console, dans une fenêtre qui s’ouvre pour la circonstance.

Nous allons, comme précédemment, attraper l’exception au vol, mais mettre le message dans une fenêtre comme vous en avez probablement déjà vu beaucoup, une fenêtre qui demande de cliquer sur un bouton pour continuer.

Je vais donc commencer par modifier ma méthode chargement et mettre dans un bloc try la lecture du fichier et tout ce qui en découle.

Attaquons nous à la partie intéressante, la partie except dans laquelle je n’attrape que les erreurs auxquelles je m’attends, à savoir IOError, dont je récupère le numéro de message, que je n’utiliserai pas, et le texte du message d’erreur.

Je vais créer un objet de la classe MessageDialog , qui est sensiblement différent des autres éléments graphiques sur le plan des paramètres qu’il attend à la création. Toujours self pour indiquer la fenêtre parente, mais ensuite le texte qui doit apparaître dans le fenêtre, le titre à lui donner dans le bandeau, puis le paramètre don le nom est « style » et qui spécifie ce à quoi ressemblera la fenêtre.

J’appelle ensuite la méthode ShowModal() de l’objet que je viens de créer, ce qui a pour effet de l'afficher. On parle de « fenêtre modale » quand on a une fenêtre qui bloque tout le reste tant que l'on n'a pas cliqué sur ses boutons ou qu'on ne l'a pas fermée. Tant qu’elle est active, le reste de l'application est désactivé.

Cette méthode retourne soit wx.ID_OK (à ne pas confondre avec wx.OK , qui est le style) si je clique sur le bouton, soit wx.ID_CANCEL si je la ferme sans cliquer sur le bouton. Dans la mesure où tout ce qui m'importe c’est que l'utilisateur ait vu le message, inutile de vérifier cette réponse.

Un petit panorama très rapide des boîtes de message. Ce que nous venons juste de voir, c'est la boîte d'erreur classique : le texte, le titre, le bouton, et l'icône facultative dont l'apparence peut varier suivant le système sur lequel l’application tourne. Ce genre de boîte est purement informatif (il y a également une icône wx.ICON_INFORMATION si vous voulez être moins alarmant).

On peut également avoir une boîte de message pour demander une décision à l'utilisateur – le message, le titre, un style wx.YES_NO correspondant à deux boutons, combiné à une icône interrogative. Deux choses importantes à remarquer : d'une part, le bouton de fermeture de la fenêtre est désactivé. Ce sera oui ou non, il n'y a pas d'échappatoire possible. L'autre chose, c'est que, bien entendu, il me faut récupérer cette fois ce sur quoi l'utilisateur aura cliqué, et j'obtiendrai wx.ID_YES ou wx.ID_NO suivant le cas.

Enfin, je peux aussi avoir les deux boutons (ou simplement un bouton OK) combinés avec wx.CANCEL pour ajouter un bouton d'annulation en cas de fausse manœuvre. Une icône différente, et surtout ce coup-ci je peux fermer la fenêtre sans cliquer sur un bouton, ce qui sera identique en fait à cliquer sur le bouton d’annulation et retournera wx.ID_CANCEL . Il va sans dire que là aussi je veux contrôler la réponse.

Retournons à notre méthode chargement. Quand je clique sur OK, la fenêtre disparaît mais en fait elle est toujours là, invisible – elle ne peut pas disparaître tout à fait et retourner une valeur. C’est la raison pour laquelle il faut la détruire explicitement en appelant sa méthode Destroy() . Après quoi, très important, je lance de nouveau l'exception. Pourquoi cela ? Eh bien, normalement, après le chargement, je fais plein de choses, à commencer par tirer une question, ce qui, évidemment, risque de s’avérer problématique si je n'ai pas trouvé le fichier. Comme j'ai attrapé l’exception, pour Python cela devient en quelque sorte une affaire classée et pour lui tout va bien quand je retourne de la méthode. Relancer l’exception est ma manière de dire à la méthode init que tout n'a pas marché comme sur des roulettes.

Du coup je modifie init , je mets chargement et ce qui suit dans un bloc try , et si j'ai une exception, sachant que l’utilisateur a vu le message d’erreur, la fenêtre se détruit elle-même et l’application se termine. Nous n’en avons pas encore terminé avec ce programme, mais ce sera pour la prochaine fois.
Chapitre précédent Sommaire Chapitre suivant

Partager

Il n'y a pas encore de commentaire pour ce tuto.