Aller au menu - Aller au contenu

Icône Boutons, menus et fichiers

Avatar
Mise à jour : 07/01/2011
Difficulté : Intermédiaire Intermédiaire Creative Commons BY-NC-SA
1 755 visites depuis 7 jours, dont 45 sur ce chapitre classé 76/786
Allons plus loin dans la personnalisation de notre fenêtre ! Ici, nous allons manipuler en détail les boutons, créer nos propres menus et découvrir comment lancer une boîte de dialogue d'ouverture de fichier !
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,
Je finirai avec ce programme simple qui nous aura quand même bien occupé en lui rajoutant deux fonctionnalités importantes. La toute première était en fait dans la version non-graphique du programme : quand on donnait une réponse fausse, le programme fournissait tout de suite la bonne solution, ce qui permettait non seulement de vérifier ses connaissances, mais aussi de les réviser quand elles n'étaient pas au point.

La solution facile serait de dire : quand l'utilisateur entre une mauvaise réponse, hop, je lui balance à l'écran une boîte de dialogue avec la bonne réponse. On sait faire, on a vu cela avec les erreurs quand on ne parvient pas à trouver le fichier de questions.

Mais je vais essayer de faire plus subtil que cela. Par exemple, j'aimerais pouvoir donner une seconde chance à l'utilisateur. Parfois il n'a pas la moindre idée de la bonne réponse, mais parfois aussi il peut hésiter entre deux possibilités. Si l'on prend deux piliers de la poésie romantique, Lamartine et Vigny, lequel s'appelait Alphonse, et lequel Alfred ? On peut hésiter, et ce n'est pas la même chose que de ne pas savoir du tout. Il y a encore un cas, celui où l'on connaît parfaitement la bonne réponse et l'on fait une faute de frappe ou d'orthographe stupide. On n'a pas toujours envie d'avoir une fenêtre qui vous saute à la figure avec la bonne réponse.

Je vais donc faire autre chose. Je vais ajouter un bouton qui pourra donner la solution, mais je vais commencer par le désactiver. L'utilisateur entre sa réponse, et si elle est mauvaise, j'active le bouton. A ce moment-là, l'utilisateur a le choix : soit il ne connait vraiment pas la bonne réponse et peut la visualiser en cliquant sur le bouton, soit il peut tenter sa chance une autre fois. S'il a bon, il n'aura qu'un demi-point mais c'est plus encourageant que rien du tout. Et s'il a faux au deuxième essai, à ce moment-là, j'affiche la réponse.

Première chose, je vais définir un attribut qui est le nombre de chances restantes. Au début, il vaudra 2.

Ensuite, je vais rajouter mon bouton pour donner la solution. Comme c'est lorsque je vérifie la réponse que je décide si je dois l'activer ou non, ce bouton, contrairement à l'autre, doit être un attribut et je le préfixe par self point. Et ensuite, j'appelle sa méthode Disable() pour le désactiver. C'est un peu ce que je vous expliquais dans la Pythonnerie 18 avec le magicien : tout sera en place, je vais définir les actions associées au bouton dès le départ, mais j'actionne un commutateur pour le rendre inopérant.

Prochaine étape, je vais mettre mes deux boutons l'un à côté de l'autre, donc je commence par créer encore une boîte horizontale,
Puis j'ajoute mes deux boutons à cette boîte avec un peu d'espace entre, et la boîte horizontale dans la boîte verticale. Rien que du classique.
Je définis une méthode, encore à écrire, donner_solution, et je l'associe à l'événement qui correspond à cliquer sur self.bouton2.

Le gros du travail, c'est dans verifier_reponse qu'il va se passer. J'arrive ici quand l'utilisateur propose une réponse, donc je vais lui commencer par lui enlever une chance, puisqu'il vient de brûler une cartouche. Ensuite, je ne lui proposerai de nouvelle question que si sa réponse était correcte ou s'il a épuisé toutes ses chances. Je vais donc utiliser une variable logique question_suivante et la mettre à False pour dire « non, on ne passe pas à la prochaine question ».

Maintenant, au travail. Le smiley enthousiaste, je ne vais le montrer que si l'utilisateur a donné la bonne réponse du premier coup. Je vais donc tester mes chances restantes, et s'il en reste encore une après celle là, je vais ajouter un point et me montrer enthousiaste. Sinon c'est le deuxième essai et je ne vais ajouter qu'un demi-point, le lot de consolation, et faire usage du smiley que je n'avais pas utilisé jusqu'à présent et que j'ai appelé bmpbien – satisfait, mais sans excès.

Dans un cas comme dans l'autre, j'ai la bonne réponse et je dis, en mettant ma variable logique à True, que l'on peut passer à la question suivante.

C'est si l'utilisateur s'est trompé que ça se complique. Le smiley sera toujours le smiley attristé, pas de problème. S'il reste encore une chance après celle-là, on va activer le bouton donnant la solution : l'utilisateur sera libre ou non de voir la réponse ou de tenter sa chance encore une fois. Au bout de deux essais infructueux en revanche, je vais donner la bonne réponse « de force » en appelant la méthode normalement appelée par le bouton. Là je vais faire quelque chose de pas très joli : si vous vous rappelez, une méthode déclenchée par un événement prend toujours un événement comme paramètre. Je vais donc créer un objet « MouseEvent » , évènement souris, et le passer en argument ; j'aurais pu utiliser n'importe quel événement. Comme je vous le disais, c'est de la programmation de fainéant : normalement je ne devrais pas appeler donner_solution ici, mais une méthode qui serait aussi appelée par donner_solution ; j'essaie de limiter le nombre de méthodes pour ne pas trop vous perdre en cours de route.

Si je peux passer à la question suivante, c'est-à-dire si j'ai eu la bonne réponse, j'affiche le score et j'appelle nouvelle_question. Sinon, j'efface la réponse erronée et je remets le curseur dans la zone de réponse.

Une toute petite modification à nouvelle_question au passage : je remets à 2 le nombre de chances, et je désactive le bouton qui donne la réponse parce que je souhaite au moins un essai (ceci dit, une réponse vide est acceptée comme une mauvaise réponse).

Il ne me reste plus que la nouvelle méthode donner_solution à écrire, et comme depuis la Pythonnerie 20 vous êtes des virtuoses de la fenêtre de dialogue, cela va être facile. Récupération de la bonne réponse, une fenêtre de dialogue pour l'afficher avec juste un bouton OK et une icône « information », affichage en mode modal, c'est-à-dire qui désactive le reste de l'application, destruction au retour, je remets le smiley neutre pour ne pas trop culpabiliser l'utilisateur, j'affiche le score, et je passe à la question suivante.

Une petite démonstration rapide de ce que cela donne...

Pour finir, je vais ajouter une fonctionnalité importante à mon programme : la possibilité de changer à la volée de questionnaire. Pour cela, je vais ajouter un classique menu, avec un choix « Fichiers ». Cela fait un peu bête, un choix unique dans un menu (si l'on n'a qu'un choix, il vaut mieux un bouton), je vais donc rajouter un « Aide » à côté pour garnir un peu. Quand je cliquerai sur « Fichiers » j'ouvrirai un sous-menu, avec là encore deux choix, l'inévitable « Quitter », et surtout « Ouvrir » pour changer de questionnaire. Ce que l'on a en haut s'appelle en wxPython « MenuBar » et c'est ce que j'ai appelé sous-menu que wxPython appelle « Menu ». Quand on cliquera sur « Ouvrir », une fenêtre permettra de choisir un nouveau fichier contenant consigne et couples de questions et de réponse. Avant que vous ne blêmissiez en vous demandant comment on va coder tout cela, je vous rassure tout de suite : ouvrir (ou sauver) un fichier est une opération tellement courante qu'il y a, et c'est vrai dans tous les environnements graphiques, un composant tout fait qui s'occupe de tout. En wxPython cet objet s'appelle un FileDialog.

Retroussons nos manches et allons y. Pour créer la barre de menu, on crée un objet MenuBar, jusque là c'est facile. Ensuite, nous allons ajouter des éléments, ce qui se fait de gauche à droite, assez évident quand on est habitué à écrire en français mais qui ne va pas forcément de soi pour tout le monde. Je vais d'abord créer un objet menu pour mon sous-menu, puis lui ajouter deux options avec sa méthode append() . Trois choses sur lesquelles je veux attirer votre attention ici. Tout d'abord, le premier paramètre passé à la méthode est un entier positif arbitraire. Vous vous rappelez j'espère que je vous ai dit qu'il était possible d'identifier les composants graphiques par un entier positif, ou que l'on pouvait laisser Python leur attribuer un identifiant négatif. Les choix dans les menus sont des composants graphiques particuliers, que l'on peut cliquer de manière individuelle, et c'est le même principe. L'identifiant que l'on attribue est arbitraire, la seule contrainte est de ne pas utiliser deux fois le même. Le deuxième paramètre est l'intitulé, et vous pouvez remarquer les « et » commerciaux qui précèdent une lettre que l'on souhaite utiliser comme raccourci clavier. Par exemple, dans le premier cas, appuyer simultanément sur « Alt » et « Q » devrait faire la même chose que cliquer sur la ligne, ou s'y déplacer avec les flèches et appuyer sur « entrée ». Je dis « devrait », parce que cela ne fonctionne pas toujours, cela dépend des environnements. Mais cela ne coûte rien de le prévoir. Il faut évidemment faire attention à n'utiliser comme raccourcis que des lettres distinctes. La dernière chose, c'est l'appel à la méthode AppendSeparator() qui insère simplement une barre horizontale entre deux options sans aucun rapport pour mieux les distinguer.

Ensuite, j'appelle la méthode Append() de la barre des menus, et cette fois-ci le premier paramètre n'est pas un entier mais le menu que je viens de créer. Pour l'intitulé, c'est pareil.

Deuxième sous-menu, où j'appellerai une méthode pour afficher un vague mode d'emploi, et une méthode pour dénoncer l'auteur. Je rajoute ce deuxième menu à la barre de menu sous l'intitulé « Aide ». J'utilise ensuite la méthode SetMenuBar() pour rajouter la barre de menu à la fenêtre principale.

Il ne nous reste plus, comme avec les boutons, qu'à associer une action, c'est-à-dire l'appel d'une méthode, à l'évènement qui consiste à sélectionner une option d'un menu, évènement qui s'appelle EVT_MENU. Toujours la méthode Bind(), mais cette fois-ci j'associe l'identifiant numérique du choix du menu en spécifiant id=. Je suis obligé d'utiliser l'identifiant parce que je n'ai pas de variable qui contienne un objet de type « choix de menu ».

Vous remarquerez que je n'ai pas besoin d'associer d'action aux choix de la barre de menu : quand on leur associe un menu, l'ouverture de celui-ci est l'action automatique.

Il ne mous reste plus qu'à écrire les différentes méthodes de gestion des évènements … La plus complexe est changement_questionnaire. Tout d'abord, je crée un objet FileDialog, en passant en paramètre la fenêtre parente, self, c'est-à-dire la fenêtre principale, Le message correspond au titre qui sera donné à la fenêtre de dialogue. DefaultDir est le répertoire par défaut, et ici je vais utiliser une fonction du module « OS », comme « Operating System », c'est-à-dire système d'exploitation. Il me faudra donc rajouter au début du programme un « import os ». getcwd() veut dire « Get current work directory », en français « obtiens le répertoire de travail courant ». En pratique, cela veut dire que l'on va regarder dans le répertoire où se trouve le programme .pyw. Je précise ensuite avec DefaultFile mon filtre de recherche de fichiers : je recherche des fichiers texte, donc je précise que je ne veux voir que les fichiers .txt et implicitement les répertoires. Le style wx.FD_OPEN dit que je veux ouvrir un fichier. C'est important, parce que pour sauver un fichier j'ouvrirais exactement le même dialogue, sauf que je dirais wx.FD_SAVE.
Je teste le retour de la méthode ShowModal. Là, de deux choses l'une, soit l'utilisateur a effectivement choisi un fichier, soit il a annulé. S'il a annulé, je n'ai rien à faire mais s'il a choisi un fichier la méthode retourne OK, et je récupère la liste des fichiers sélectionnés avec la méthode GetPaths() (certaines applications, vous l'avez peut-être déjà utilisé, vous permettent d'ouvrir simultanément plusieurs fichiers). Dans ce programme on ne peut ouvrir qu'un fichier, je prends donc le premier élément de la liste, l'élément 0, et je le passe à la méthode chargement. Après quoi, je peux détruire mon dialogue, et passer à la première question.

Quelques modifications très mineures à chargement() : quand je charge un nouveau fichier, il faut que je remette tous mes compteurs à zéro. D'abord je vide la liste des questions, en disant que je remplace l'ensemble des éléments par une liste vide. Je suis forcé de faire cela sinon les nouvelles questions se rajouteraient aux anciennes au lieu de les remplacer. Ensuite je mets mes compteurs à zéro et j'affiche le smiley neutre.

Les autres méthodes pour terminer : une fenêtre de dialogue pour les règles du jeu, je pense ne plus avoir besoin de commenter. Pour signer le forfait, c'est un peu spécial. Je crée un objet, une structure en fait, qui s'appelle AboutDialogInfo et est faite pour contenir des informations standard. L'objet a un certain nombre d'attributs que l'on renseigne, le nom du programme, son numéro de version, le copyright – ici ce que je mets veut dire que vous pouvez faire de que vous voulez de ce programme, même le vendre, à condition que je sois mentionné comme auteur, la liste des auteurs, où vous pourrez rajouter votre nom si vous avez de bonnes idées pour améliorer encore ce programme. Pour afficher ces informations, je les passe à la fonction AboutBox(). Pas de ShowModal(), et pas de Destroy(), la fenêtre est détruite automatiquement quand on en sort.
Mais évidemment, ma méthode préférée c'est celle que j'ai appelée ciao() pour terminer l'application...

Et voilà, c'est fini, et vous avez vu sur cette exemple assez simple au départ beaucoup d'objets graphiques qui vous aideront à donner une allure sympa à certains de vos programmes. Comme je vous l'ai dit tout au début, je ne vous ai montré qu'une toute petite partie et il y a beaucoup à explorer dans la documentation. Je vous montrerai bientôt une autre application graphique, avec d'autres caractéristiques. Mais c'est une application de type base de données, et je ferai dans la prochaine Pythonnerie une grosse parenthèse sur les bases de données et le langage SQL.
Chapitre précédent Sommaire Chapitre suivant

Partager

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