« Programmation Python/Classes et Interfaces graphiques » : différence entre les versions

Contenu supprimé Contenu ajouté
Tavernier (discussion | contributions)
adaptation en wikisyntaxe, typo, retouches
Ligne 1 :
{{todo|mettreajouter en formeimages}}
 
La programmation orientée objet convient particulièrement bien au développement d'applications avec interface graphique. Des bibliothèques de classes comme ''Tkinter'' ou ''wxPython'' fournissent une base de ''widgets'' très étoffée, que nous pouvons adapter à nos besoins par dérivation. Dans ce chapitre, nous allons utiliser à nouveau la bibliothèque ''Tkinter'', mais en appliquant les concepts décrits dans les pages précédentes, et en nous efforçant de mettre en évidence les avantages qu'apporte l'''orientation objet'' dans nos programmes.
Ligne 395 :
 
{{Exercices}}
<ol>
1.8.Modifiez le script de manière à ce que l'axe de référence vertical comporte lui aussi une échelle, avec 5 tirets de part et d'autre de l'origine.
<li>Modifiez le script de manière à ce que l'axe de référence vertical comporte lui aussi une échelle, avec 5 tirets de part et d'autre de l'origine.</li>
1.9.Comme les widgets de la classe Canvas() dont il dérive, votre widget peut intégrer des indications textuelles. Il suffit pour cela d'utiliser la méthode create_text(). Cette méthode attend au moins trois arguments : les coordonnées x et y de l'emplacement où vous voulez faire apparaître votre texte, et puis le texte lui-même, bien entendu. D'autres arguments peuvent être transmis sous forme d'options, pour préciser par exemple la police de caractères et sa taille. Afin de voir comment cela fonctionne, ajoutez provisoirement la ligne suivante dans le constructeur de la classe OscilloGraphe(), puis relancez le script :
<li>Comme les ''widgets'' de la classe <code>Canvas()</code> dont il dérive, votre ''widget'' peut intégrer des indications textuelles. Il suffit pour cela d'utiliser la méthode <code>create_text()</code>. Cette méthode attend au moins trois arguments : les coordonnées x et y de l'emplacement où vous voulez faire apparaître votre texte, et puis le texte lui-même, bien entendu. D'autres arguments peuvent être transmis sous forme d'options, pour préciser par exemple la police de caractères et sa taille. Afin de voir comment cela fonctionne, ajoutez provisoirement la ligne suivante dans le constructeur de la classe <code>OscilloGraphe()</code>, puis relancez le script :
self.create_text(130, 30, text = "Essai", anchor =CENTER)
 
<pre>
self.create_text(130, 30, text = "Essai", anchor =CENTER)
</pre>
 
Utilisez cette méthode pour ajouter au widget les indications suivantes aux extrémités des axes de référence : e (pour « élongation ») le long de l'axe vertical, et t (pour « temps ») le long de l'axe horizontal. Le résultat pourrait ressembler à ceci (figure de gauche) :
{{image manquante}}
1.10.Vous pouvez compléter encore votre widget, en y faisant apparaître une grille de référence, plutôt que de simples tirets le long des axes. Pour éviter que cette grille ne soit trop visible, vous pouvez colorer ses traits en gris (option fill = 'grey'), comme dans la figure de droite.
</li>
1.11.Complétez encore votre widget en y faisant apparaître des repères numériques.
<li>Vous pouvez compléter encore votre ''widget'', en y faisant apparaître une grille de référence, plutôt que de simples tirets le long des axes. Pour éviter que cette grille ne soit trop visible, vous pouvez colorer ses traits en gris (option <code>fill = 'grey'</code>), comme dans la figure de droite.</li>
<li>Complétez encore votre widget en y faisant apparaître des repères numériques.</li>
</ol>
{{fin}}
 
== « Curseurs » : un widget composite ==
 
Dans l'exercice précédent, vous avez construit un nouveau type de widget que vous avez sauvegardé dans le module <code>oscillo.py</code>. Conservez soigneusement ce module, car vous l'intégrerez bientôt dans un projet plus complexe.
 
Pour l'instant, vous allez construire encore un autre widget, plus interactif cette fois. Il s'agira d'une sorte de panneau de contrôle comportant trois curseurs de réglage et une case à cocher. Comme le précédent, ce widget est destiné à être réutilisé dans une application de synthèse.
Pour l'instant, vous allez construire encore un autre ''widget'', plus interactif cette fois. Il s'agira d'une sorte de panneau de contrôle comportant trois curseurs de réglage et une case à cocher. Comme le précédent, ce ''widget'' est destiné à être réutilisé dans une application de synthèse.
 
=== Présentation du widget « Scale » ===
 
{{image manquante}}
 
Commençons d'abord par découvrir un widget de base, que nous n'avions pas encore utilisé jusqu'ici :
Le ''widget'' <code>Scale</code> se présente comme un curseur qui coulisse devant une échelle. Il permet à l'utilisateur de choisir rapidement la valeur d'un paramètre quelconque, d'une manière très attrayante.
 
Le petit script ci-dessous vous montre comment le paramétrer et l'utiliser dans une fenêtre :
 
<pre>
from Tkinter import *
 
Ligne 428 ⟶ 441 :
 
root.mainloop()
</pre>
 
Ces lignes ne nécessitent guère de commentaires.
 
Vous pouvez créer des widgets Scale de n'importe quelle taille (option length), en orientation horizontale (comme dans notre exemple) ou verticale (option orient = VERTICAL).
Vous pouvez créer des ''widgets'' <code>Scale</code> de n'importe quelle taille (option <code>length</code>), en orientation horizontale (comme dans notre exemple) ou verticale (option <code>orient = VERTICAL</code>).
Les options from_ (attention : n'oubliez pas le caractère 'souligné' !) et to définissent la plage de réglage. L'intervalle entre les repères numériques est défini dans l'option tickinterval, etc.
 
La fonction désignée dans l'option command est appelée automatiquement chaque fois que le curseur est déplacé, et la position actuelle du curseur par rapport à l'échelle lui est transmise en argument. Il est donc très facile d'utiliser cette valeur pour effectuer un traitement quelconque. Considérez par exemple le paramètre x de la fonction updateLabel(), dans notre exemple.
Les options <code>from_</code> (attention : n'oubliez pas le caractère 'souligné' !) et <code>to</code> définissent la plage de réglage. L'intervalle entre les repères numériques est défini dans l'option <code>tickinterval</code>, etc.
Le widget Scale constitue une interface très intuitive et attrayante pour proposer différents réglages aux utilisateurs de vos programmes. Nous allons à présent l'incorporer en plusieurs exemplaires dans une nouvelle classe de widget : un panneau de contrôle destiné à choisir la fréquence, la phase et l'amplitude pour un mouvement vibratoire, dont nous afficherons ensuite le graphique élongation/temps à l'aide du widget oscilloGraphe construit dans les pages précédentes.
 
La fonction désignée dans l'option <code>command</code> est appelée automatiquement chaque fois que le curseur est déplacé, et la position actuelle du curseur par rapport à l'échelle lui est transmise en argument. Il est donc très facile d'utiliser cette valeur pour effectuer un traitement quelconque. Considérez par exemple le paramètre <code>x</code> de la fonction <code>updateLabel()</code>, dans notre exemple.
 
Le ''widget'' <code>Scale</code> constitue une interface très intuitive et attrayante pour proposer différents réglages aux utilisateurs de vos programmes. Nous allons à présent l'incorporer en plusieurs exemplaires dans une nouvelle classe de ''widget'' : un panneau de contrôle destiné à choisir la fréquence, la phase et l'amplitude pour un mouvement vibratoire, dont nous afficherons ensuite le graphique élongation/temps à l'aide du ''widget'' <code>oscilloGraphe</code> construit dans les pages précédentes.
 
=== Construction d'un panneau de contrôle à trois curseurs ===
 
Comme le précédent, le script que nous décrivons ci-dessous est destiné à être sauvegardé dans un module, que vous nommerez cette fois <code>curseurs.py</code>. Les classes que vous sauvegardez ainsi seront réutilisées (par importation) dans une application de synthèse que nous décrirons un peu plus loin3loin<ref>Vous pourriez bien évidemment aussi enregistrer plusieurs classes dans un même module.</ref>. Nous attirons votre attention sur le fait que le code ci-dessous peut être raccourci de différentes manières (Nous y reviendrons). Nous ne l'avons pas optimisé d'emblée, parce que cela nécessiterait d'y incorporer un concept supplémentaire (les expressions ''lambda''), ce que nous préférons éviter pour l'instant.
 
Vous savez déjà que les lignes de code placées à la fin du script permettent de tester son fonctionnement. Vous devriez obtenir une fenêtre semblable à celle-ci :
 
{{image manquante}}
 
<pre>
1.from Tkinter import *
2.from math import pi
Ligne 493 ⟶ 516 :
53. root.bind('<Control-Z>', afficherTout)
54. root.mainloop()
</pre>
 
Ce panneau de contrôle permettra à vos utilisateurs de régler aisément la valeur des paramètres indiqués (fréquence, phase &et amplitude), lesquels pourront alors servir à commander l'affichage de graphiques élongation/temps dans un ''widget'' de la classe <code>OscilloGraphe()</code> construite précédemment, comme nous le montrerons dans l'application de synthèse.
 
;Commentaires
 
<ul>
Ligne 6 : La méthode « constructeur » utilise un paramètre optionnel coul. Ce paramètre permettra de choisir une couleur pour le graphique soumis au contrôle du widget. Le paramètre boss sert à réceptionner la référence d'une fenêtre maîtresse éventuelle (voir plus loin).
<li>Ligne 6 : La méthode <code>constructeur</code> utilise un paramètre optionnel <code>coul</code>. Ce paramètre permettra de choisir une couleur pour le graphique soumis au contrôle du ''widget''. Le paramètre <code>boss</code> sert à réceptionner la référence d'une fenêtre maîtresse éventuelle (voir plus loin).</li>
Ligne 7 : Activation du constructeur de la classe parente (pour hériter sa fonctionnalité).
Ligne 9 : Déclaration de quelques variables d'instance. Leurs vraies valeurs seront déterminées par les méthodes des lignes 29 à 40 (gestionnaires d'événements).
Ligne 11 : Cette instruction instancie un objet de la classe IntVar(), laquelle fait partie du module Tkinter au même titre que les classes similaires DoubleVar(), StringVar() et BooleanVar(). Toutes ces classes permettent de définir des « variables Tkinter », lesquels sont en fait des objets, mais qui se se comportent comme des variables à l'intérieur des widgets Tkinter.
Ainsi l'objet référencé dans self.chk contient l'équivalent d'une variable de type entier, dans un format utilisable par Tkinter. Pour accéder à sa valeur depuis Python, il faut utiliser des méthodes spécifiques de cette classe d'objets : la méthode set() permet de lui assigner une valeur, et la méthode get() permet de la récupérer (ce que l'on met en pratique à la ligne 47).
Ligne 12 : L'option variable de l'objet checkbutton est associée à la variable Tkinter définie à la ligne précédente. (Nous ne pouvons pas référencer directement une variable ordinaire dans la définition d'un widget Tkinter, parce que Tkinter lui-même est écrit dans un langage qui n'utilise pas les mêmes conventions que Python pour formater ses variables. Les objets construits à partir des classes de variables Tkinter sont donc nécessaires pour assurer l'interface).
Ligne 13 : L'option command désigne la méthode que le système doit invoquer lorsque l'utilisateur effectue un clic de souris dans la case à cocher.
Lignes 14 à 24 : Ces lignes définissent les trois widgets curseurs, en trois instructions similaires. Il serait plus élégant de programmer tout ceci en une seule instruction, répétée trois fois à l'aide d'une boucle. Cela nécessiterait cependant de faire appel à un concept que nous n'avons pas encore expliqué (les fonctions/expressions lamdba), et la définition du gestionnaire d'événements associé à ces widgets deviendrait elle aussi plus complexe. Conservons donc pour cette fois des instructions séparées : nous nous efforcerons d'améliorer tout cela plus tard.
Lignes 26 à 40 : Les 4 widgets définis dans les lignes précédentes possèdent chacun une option command. Pour chacun d'eux, la méthode invoquée dans cette option command est différente : la case à cocher active la méthode setCurve(), le premier curseur active la méthode setFrequency(), le second curseur active la méthode setPhase(), et le troisième curseur active la méthode setAmplitude(). Remarquez bien au passage que l'option command des widgets Scale transmet un argument à la méthode associée (la position actuelle du curseur), alors que la même option command ne transmet rien dans le cas du widget Checkbutton.
 
<li>Ligne 7 : Activation du constructeur de la classe parente (pour hériter sa fonctionnalité).</li>
Ces 4 méthodes (qui sont donc les gestionnaires des événements produits par la case à cocher et les trois curseurs) provoquent elles-mêmes chacune l'émission d'un nouvel événement4, en faisant appel à la méthode event_generate().
 
<li>Ligne 9 : Déclaration de quelques variables d'instance. Leurs vraies valeurs seront déterminées par les méthodes des lignes 29 à 40 (gestionnaires d'événements).</li>
 
<li>Ligne 11 : Cette instruction instancie un objet de la classe <code>IntVar()</code>, laquelle fait partie du module ''Tkinter'' au même titre que les classes similaires <code>DoubleVar()</code>, <code>StringVar()</code> et <code>BooleanVar()</code>. Toutes ces classes permettent de définir des ''variables Tkinter'', lesquels sont en fait des objets, mais qui se se comportent comme des variables à l'intérieur des ''widgets Tkinter''.
 
Ainsi l'objet référencé dans <code>self.chk</code> contient l'équivalent d'une variable de type entier, dans un format utilisable par ''Tkinter''. Pour accéder à sa valeur depuis Python, il faut utiliser des méthodes spécifiques de cette classe d'objets : la méthode <code>set()</code> permet de lui assigner une valeur, et la méthode <code>get()</code> permet de la récupérer (ce que l'on met en pratique à la ligne 47).</li>
 
<li>Ligne 12 : L'option variable de l'objet <code>checkbutton</code> est associée à la variable ''Tkinter'' définie à la ligne précédente. (Nous ne pouvons pas référencer directement une variable ordinaire dans la définition d'un ''widget Tkinter'', parce que ''Tkinter'' lui-même est écrit dans un langage qui n'utilise pas les mêmes conventions que Python pour formater ses variables. Les objets construits à partir des classes de variables Tkinter sont donc nécessaires pour assurer l'interface).</li>
 
<li>Ligne 13 : L'option <code>command</code> désigne la méthode que le système doit invoquer lorsque l'utilisateur effectue un clic de souris dans la case à cocher.</li>
 
<li>Lignes 14 à 24 : Ces lignes définissent les trois ''widgets'' curseurs, en trois instructions similaires. Il serait plus élégant de programmer tout ceci en une seule instruction, répétée trois fois à l'aide d'une boucle. Cela nécessiterait cependant de faire appel à un concept que nous n'avons pas encore expliqué (les fonctions/expressions ''lamdba''), et la définition du gestionnaire d'événements associé à ces widgets deviendrait elle aussi plus complexe. Conservons donc pour cette fois des instructions séparées : nous nous efforcerons d'améliorer tout cela plus tard.</li>
 
<li>Lignes 26 à 40 : Les 4 ''widgets'' définis dans les lignes précédentes possèdent chacun une option <code>command</code>. Pour chacun d'eux, la méthode invoquée dans cette option command est différente : la case à cocher active la méthode <code>setCurve()</code>, le premier curseur active la méthode <code>setFrequency()</code>, le second curseur active la méthode <code>setPhase()</code>, et le troisième curseur active la méthode <code>setAmplitude()</code>. Remarquez bien au passage que l'option <code>command</code> des ''widgets'' <code>Scale</code> transmet un argument à la méthode associée (la position actuelle du curseur), alors que la même option <code>command</code> ne transmet rien dans le cas du ''widget'' <code>Checkbutton</code>.
 
Ces 4 méthodes (qui sont donc les gestionnaires des événements produits par la case à cocher et les trois curseurs) provoquent elles-mêmes chacune l'émission d'un nouvel événement<ref>En fait, on devrait plutôt appeler cela un message (qui est lui-même la notification d'un événement). Veuillez relire à ce sujet les explications de la page {{todo}} : Programmes pilotés par des événements.</ref>, en faisant appel à la méthode <code>event_generate()</code>.
 
Lorsque cette méthode est invoquée, Python envoie au système d'exploitation exactement le même message-événement que celui qui se produirait si l'utilisateur enfonçait simultanément les touches <Ctrl>, <Maj> et <Z> de son clavier.
 
Nous produisons ainsi un message-événement bien particulier, qui peut être détecté et traité par un gestionnaire d'événement associé à un autre widget (voir page suivante). De cette manière, nous mettons en place un véritable système de communication entre widgets : chaque fois que l'utilisateur exerce une action sur notre panneau de contrôle, celui-ci génère un événement spécifique, qui signale cette action à l'attention des autres widgets présents.
Nous produisons ainsi un message-événement bien particulier, qui peut être détecté et traité par un gestionnaire d'événement associé à un autre widget (voir page suivante). De cette manière, nous mettons en place ''un véritable système de communication entre widgets'' : chaque fois que l'utilisateur exerce une action sur notre panneau de contrôle, celui-ci génère un événement spécifique, qui signale cette action à l'attention des autres ''widgets'' présents.
Note : {{remarque|nous aurions pu choisir une autre combinaison de touches (ou même carrément un autre type d'événement). Notre choix s'est porté sur celle-ci parce qu'il y a vraiment très peu de chances que l'utilisateur s'en serve alors qu'il examine notre programme. Nous pourrons cependant produire nous-mêmes un tel événement au clavier à titre de test, lorsque le moment sera venu de vérifier le gestionnaire de cet événement, que nous mettrons en place par ailleurs.}}</li>
 
<li>Lignes 42 à 54 : Comme nous l'avions déjà fait pour <code>oscillo.py</code>, nous complétons ce nouveau module par quelques lignes de code au niveau principal. Ces lignes permettent de tester le bon fonctionnement de la classe : elles ne s'exécutent que si on lance le module directement, comme une application à part entière. Veillez à utiliser vous-même cette technique dans vos propres modules, car elle constitue une bonne pratique de programmation : l'utilisateur de modules construits ainsi peut en effet (re)découvrir très aisément leur fonctionnalité (en les exécutant) et la manière de s'en servir (en analysant ces quelques lignes de code).
Dans ces lignes de test, nous construisons une fenêtre principale root qui contient deux ''widgets'' : un ''widget'' de la nouvelle classe <code>ChoixVibra()</code> et un widget de la classe <code>Label()</code>.
 
À la ligne 53, nous associons à la fenêtre principale un gestionnaire d'événement : tout événement du type spécifié déclenche désormais un appel de la fonction <code>afficherTout()</code>.
 
A la ligne 53, nous associons à la fenêtre principale un gestionnaire d'événement : tout événement du type spécifié déclenche désormais un appel de la fonction afficherTout().
Cette fonction est donc notre gestionnaire d'événement spécialisé, qui est sollicité chaque fois qu'un événement de type <Maj-Ctrl-Z> est détecté par le système d'exploitation.
 
Comme nous l'avons déjà expliqué plus haut, nous avons fait en sorte que de tels événements soient produits par les objets de la classe <code>ChoixVibra()</code>, chaque fois que l'utilisateur modifie l'état de l'un ou l'autre des trois curseurs, ou celui de la case à cocher.
Conçue seulement pour effectuer un test, la fonction afficherTout() ne fait rien d'autre que provoquer l'affichage des valeurs des variables associées à chacun de nos quatre widgets, en (re)configurant l'option text d'un widget de classe Label().
 
Conçue seulement pour effectuer un test, la fonction <code>afficherTout()</code> ne fait rien d'autre que provoquer l'affichage des valeurs des variables associées à chacun de nos quatre ''widgets'', en (re)configurant l'option text d'un ''widget'' de classe <code>Label()</code>.</li>
Ligne 47, expression fra.chk.get() : nous avons vu plus haut que la variable mémorisant l'état de la case à cocher est un objet-variable Tkinter. Python ne peut pas lire directement le contenu d'une telle variable, qui est en réalité un objet-interface. Pour en extraire la valeur, il faut donc faire usage d'une méthode spécifique de cette classe d'objets : la méthode get().
 
<li>Ligne 47, expression <code>fra.chk.get()</code> : nous avons vu plus haut que la variable mémorisant l'état de la case à cocher est un ''objet-variable Tkinter''. Python ne peut pas lire directement le contenu d'une telle variable, qui est en réalité un objet-interface. Pour en extraire la valeur, il faut donc faire usage d'une méthode spécifique de cette classe d'objets : la méthode <code>get()</code>.</li>
</ul>
 
;Propagation des événements
 
Le mécanisme de communication décrit ci-dessus respecte la hiérarchie de classes des ''widgets''. Vous aurez noté que la méthode qui déclenche l'événement est associée au ''widget'' dont nous sommes en train de définir la classe, par l'intermédiaire de <code>self</code>. En général, un message-événement est en effet associé à un ''widget'' particulier (par exemple, un clic de souris sur un bouton est associé à ce bouton), ce qui signifie que le système d'exploitation va d'abord examiner s'il existe un gestionnaire pour ce type d'événement, qui soit lui aussi associé à ce ''widget''. S'il en existe un, c'est celui-là qui est activé, et la propagation du message s'arrête. Sinon, le message-événement est « présenté » successivement aux ''widgets'' maîtres, dans l'ordre hiérarchique, jusqu'à ce qu'un gestionnaire d'événement soit trouvé, ou bien jusqu'à ce que la fenêtre principale soit atteinte.
 
Les événements correspondant à des frappes sur le clavier (telle la combinaison de touches <Maj-Ctrl-Z> utilisée dans notre exercice) sont cependant toujours expédiés directement à la fenêtre principale de l'application. Dans notre exemple, le gestionnaire de cet événement doit donc être associé à la fenêtre root.
Les événements correspondant à des frappes sur le clavier (telle la combinaison de touches <Maj-Ctrl-Z> utilisée dans notre exercice) sont cependant toujours expédiés directement à la fenêtre principale de l'application. Dans notre exemple, le gestionnaire de cet événement doit donc être associé à la fenêtre <code>root</code>.
{{Exercices :}}
1.12.# Votre nouveau ''widget'' hérite des propriétés de la classe <code>Frame()</code>. Vous pouvez donc modifier son aspect en modifiant les options par défaut de cette classe, à l'aide de la méthode <code>configure()</code>. Essayez par exemple de faire en sorte que le panneau de contrôle soit entouré d'une bordure de 4 pixels ayant l'aspect d'un sillon (bd = 4, relief = GROOVE). Si vous ne comprenez pas bien ce qu'il faut faire, inspirez-vous du script <code>oscillo.py</code> (ligne 10).
1.13.# Si l'on assigne la valeur <code>1</code> à l'option <code>showvalue</code> des ''widgets'' <code>Scale()</code>, la position précise du curseur par rapport à l'échelle est affichée en permanence. Activez donc cette fonctionnalité pour le curseur qui contrôle le paramètre « <code>phase »</code>.
1.14.# L'option <code>troughcolor</code> des ''widgets'' <code>Scale()</code> permet de définir la couleur de leur glissière. Utilisez cette option pour faire en sorte que la couleur des glissières des 3 curseurs soit celle qui est utilisée comme paramètre lors de l'instanciation de votre nouveau ''widget''.
1.15.# Modifiez le script de telle manière que les ''widgets'' curseurs soient écartés davantage les uns des autres (options <code>padx</code> et <code>pady</code> de la méthode<code> pack()</code>).
{{fin}}
 
== Intégration de widgets composites dans une application synthèse ==
 
Dans les exercices précédents, nous avons construit deux nouvelles classes de ''widgets'' : le ''widget'' <code>OscilloGraphe()</code>, canevas spécialisé pour le dessin de sinusoïdes, et le ''widget'' <code>ChoixVibra()</code>, panneau de contrôle à trois curseurs permettant de choisir les paramètres d'une vibration.
Ces widgets sont désormais disponibles dans les modules oscillo.py et curseurs.py5
Nous allons à présent les utiliser dans une application synthèse, qui pourrait illustrer votre cours de physique : un widget OscilloGraphe() y affiche un, deux, ou trois graphiques superposés, de couleurs différentes, chacun d'entre eux étant soumis au contrôle d'un widget ChoixVibra() :
 
Ces ''widgets'' sont désormais disponibles dans les modules <code>oscillo.py</code> et <code>curseurs.py</code><ref>Il va de soi que nous pourrions rassembler toutes les classes que nous construisons dans un seul module.</ref>.
 
Nous allons à présent les utiliser dans une application synthèse, qui pourrait illustrer votre cours de physique : un ''widget'' <code>OscilloGraphe()</code> y affiche un, deux, ou trois graphiques superposés, de couleurs différentes, chacun d'entre eux étant soumis au contrôle d'un ''widget'' <code>ChoixVibra()</code> :
 
{{image manquante}}
 
Le script correspondant est reproduit ci-après.
 
Nous attirons votre attention sur la technique mise en œuvre pour provoquer un rafraîchissement de l'affichage dans le canevas par l'intermédiaire d'un événement, chaque fois que l'utilisateur effectue une action quelconque au niveau de l'un des panneaux de contrôle.
 
Rappelez-vous que les applications destinées à fonctionner dans une interface graphique doivent être conçues comme des « programmes pilotés par les événements » (voir page 86).
Rappelez-vous que les applications destinées à fonctionner dans une interface graphique doivent être conçues comme des « programmes pilotés par les événements » (voir page {{todo}}).
 
En préparant cet exemple, nous avons arbitrairement décidé que l'affichage des graphiques serait déclenché par un événement particulier, tout à fait similaire à ceux que génère le système d'exploitation lorsque l'utilisateur accomplit une action quelconque. Dans la gamme (très étendue) d'événements possibles, nous en avons choisi un qui ne risque guère d'être utilisé pour d'autres raisons, pendant que notre application fonctionne : la combinaison de touches <Maj-Ctrl-Z>.
Lorsque nous avons construit la classe de widgets ChoixVibra(), nous y avons donc incorporé les instructions nécessaires pour que de tels événements soient générés, chaque fois que l'utilisateur actionne l'un des curseurs ou modifie l'état de la case à cocher. Nous allons à présent définir le gestionnaire de cet événement et l'inclure dans notre nouvelle classe : nous l'appellerons montreCourbes() et il se chargera de rafraîchir l'affichage. Étant donné que l'événement concerné est du type <enfoncement d'une touche>, nous devrons cependant le détecter au niveau de la fenêtre principale de l'application.
 
Lorsque nous avons construit la classe de ''widgets'' <code>ChoixVibra()</code>, nous y avons donc incorporé les instructions nécessaires pour que de tels événements soient générés, chaque fois que l'utilisateur actionne l'un des curseurs ou modifie l'état de la case à cocher. Nous allons à présent définir le gestionnaire de cet événement et l'inclure dans notre nouvelle classe : nous l'appellerons <code>montreCourbes()</code> et il se chargera de rafraîchir l'affichage. Étant donné que l'événement concerné est du type <enfoncement d'une touche>, nous devrons cependant le détecter au niveau de la fenêtre principale de l'application.
 
<pre>
1.from oscillo import *
2.from curseurs import *
Ligne 596 ⟶ 643 :
44.if __name__ == '__main__':
45. ShowVibra().mainloop()
</pre>
 
;Commentaires
 
<ul>
Lignes 1-2 : Nous pouvons nous passer d'importer le module Tkinter : chacun de ces deux modules s'en charge déjà.
<li>Lignes 1-2 : Nous pouvons nous passer d'importer le module ''Tkinter'' : chacun de ces deux modules s'en charge déjà.</li>
Ligne 4 : Puisque nous commençons à connaître les bonnes techniques, nous décidons de construire l'application elle-même sous la forme d'une classe, dérivée de la classe Frame() : ainsi nous pourrons plus tard l'intégrer toute entière dans d'autres projets, si le cœur nous en dit.
Lignes 8-10 : Définition de quelques variables d'instance (3 listes) : les trois courbes tracées seront des objets graphiques, dont les couleurs sont pré-définies dans la liste self.couleur ; nous devons préparer également une liste self.trace pour mémoriser les références de ces objets graphiques, et enfin une liste self.controle pour mémoriser les références des trois panneaux de contrôle.
Lignes 13 à 15 : Instanciation du widget d'affichage. Étant donné que la classe OscilloGraphe() a été obtenue par dérivation de la classe Canvas(), il est toujours possible de configurer ce widget en redéfinissant les options spécifiques de cette classe (ligne 13).
Lignes 18 à 20 : Pour instancier les trois widgets « panneau de contrôle », on utilise une boucle. Leurs références sont mémorisées dans la liste self.controle préparée à la ligne 10. Ces panneaux de contrôle sont instanciés comme esclaves du présent widget, par l'intermédiaire du paramètre self. Un second paramètre leur transmet la couleur du tracé à contrôler.
Lignes 23-24 : Au moment de son instanciation, chaque widget Tkinter reçoit automatiquement un attribut master qui contient la référence de la fenêtre principale de l'application. Cet attribut se révèle particulièrement utile si la fenêtre principale a été instanciée implicitement par Tkinter, comme c'est le cas ici.
Rappelons en effet que lorsque nous démarrons une application en instanciant directement un widget tel que Frame, par exemple (c'est ce que nous avons fait à la ligne 4), Tkinter instancie automatiquement une fenêtre maîtresse pour ce widget (un objet de la classe Tk()).
Comme cet objet a été créé automatiquement, nous ne disposons d'aucune référence dans notre code pour y accéder, si ce n'est par l'intermédiaire de l'attribut master que Tkinter associe automatiquement à chaque widget.
Nous nous servons de cette référence pour redéfinir le bandeau-titre de la fenêtre principale (à la ligne 24), et pour y attacher un gestionnaire d'événement (à la ligne 23).
Lignes 27 à 40 : La méthode décrite ici est le gestionnaire des événements <Maj-Ctrl-Z> spécifiquement déclenchés par nos widgets ChoixVibra() (ou « panneaux de contrôle »), chaque fois que l'utilisateur exerce une action sur un curseur ou une case à cocher. Dans tous les cas, les graphiques éventuellement présents sont d'abord effacés (ligne 28) à l'aide de la méthode delete() : le widget OscilloGraphe() a hérité cette méthode de sa classe parente Canvas().
Ensuite, de nouvelles courbes sont retracées, pour chacun des panneaux de contrôle dont on a coché la case « Afficher ». Chacun des objets ainsi dessinés dans le canevas possède un numéro de référence, renvoyé par la méthode traceCourbe() de notre widget OscilloGraphe().
Les numéros de référence de nos dessins sont mémorisés dans la liste self.trace.
Ils permettent d'effacer individuellement chacun d'entre eux (cfr. instruction de la ligne 28).
Lignes 38-40 : Les valeurs de fréquence, phase & amplitude que l'on transmet à la méthode traceCourbe() sont les attributs d'instance correspondants de chacun des trois panneaux de contrôle, eux-mêmes mémorisés dans la liste self.controle. Nous pouvons récupérer ces attributs en utilisant la qualification des noms par points.
 
<li>Ligne 4 : Puisque nous commençons à connaître les bonnes techniques, nous décidons de construire l'application elle-même sous la forme d'une classe, dérivée de la classe <code>Frame()</code> : ainsi nous pourrons plus tard l'intégrer toute entière dans d'autres projets, si le cœur nous en dit.</li>
Exercices :
1.16.Modifiez le script, de manière à obtenir l'aspect ci-dessous (écran d'affichage avec grille de référence, panneaux de contrôle entourés d'un sillon) :
1.17.Modifiez le script, de manière à faire apparaître et contrôler 4 graphiques au lieu de trois. Pour la couleur du quatrième graphique, choisissez par exemple : 'blue', 'navy', 'maroon', ...
1.18.Aux lignes 33-35, nous récupérons les valeurs des fréquence, phase & amplitude choisies par l'utilisateur sur chacun des trois panneaux de contrôle, en accédant directement aux attributs d'instance correspondants. Python autorise ce raccourci - et c'est bien pratique – mais cette technique est dangereuse. Elle enfreint l'une des recommandations de la théorie générale de la « programmation orientée objet », qui préconise que l'accès aux propriétés des objets soit toujours pris en charge par des méthodes spécifiques. Pour respecter cette recommandation, ajoutez à la classe ChoixVibra() une méthode supplémentaire que vous appellerez valeurs(), et qui renverra un tuple contenant les valeurs de la fréquence, la phase et l'amplitude choisies. Les lignes 33 à 35 du présent script pourront alors être remplacées par quelque chose comme :
freq, phase, ampl = self.control[i].valeurs()
1.19.Écrivez une petite application qui fait apparaître une fenêtre avec un canevas et un widget curseur (Scale). Dans le canevas, dessinez un cercle, dont l'utilisateur pourra faire varier la taille à l'aide du curseur.
1.20.Écrivez un script qui créera deux classes : une classe "Application", dérivée de Frame(), dont le constructeur instanciera un canevas de 400x400 pixels, ainsi que deux boutons. Dans le canevas, vous instancierez un objet de la classe "Visage" décrite ci-après.
La classe "Visage" servira à définir des objets graphiques censés représenter des visages humains simplifiés. Ces visages seront constitués d'un cercle principal dans lequel trois ovales plus petits représenteront deux yeux et une bouche (ouverte). Une méthode "fermer" permettra de remplacer l'ovale de la bouche par une ligne horizontale. Une méthode "ouvrir" permettra de restituer la bouche de forme ovale.
Les deux boutons définis dans la classe "Application" serviront respectivement à fermer et à ouvrir la bouche de l'objet Visage installé dans le canevas.
(Vous pouvez vous inspirer de l'exemple de la page 91 pour composer une partie du code).
1.21.Exercice de synthèse : élaboration d'un dictionnaire de couleurs.
 
<li>Lignes 8-10 : Définition de quelques variables d'instance (3 listes) : les trois courbes tracées seront des objets graphiques, dont les couleurs sont pré-définies dans la liste <code>self.couleur</code> ; nous devons préparer également une liste <code>self.trace</code> pour mémoriser les références de ces objets graphiques, et enfin une liste <code>self.controle</code> pour mémoriser les références des trois panneaux de contrôle.</li>
But : réaliser un petit programme utilitaire, qui puisse vous aider à construire facilement et rapidement un nouveau dictionnaire de couleurs, lequel permettrait l'accès technique à une couleur quelconque par l'intermédiaire de son nom usuel en français.
 
<li>Lignes 13 à 15 : Instanciation du ''widget'' d'affichage. Étant donné que la classe <code>OscilloGraphe()</code> a été obtenue par dérivation de la classe <code>Canvas()</code>, il est toujours possible de configurer ce ''widget'' en redéfinissant les options spécifiques de cette classe (ligne 13).</li>
Contexte : En manipulant divers objets colorés avec Tkinter, vous avez constaté que cette bibliothèque graphique accepte qu'on lui désigne les couleurs les plus fondamentales sous la forme de chaînes de caractères contenant leur nom en anglais : 'red', 'blue', etc.
 
<li>Lignes 18 à 20 : Pour instancier les trois ''widgets'' « panneau de contrôle », on utilise une boucle. Leurs références sont mémorisées dans la liste <code>self.controle</code> préparée à la ligne 10. Ces panneaux de contrôle sont instanciés comme esclaves du présent ''widget'', par l'intermédiaire du paramètre <code>self</code>. Un second paramètre leur transmet la couleur du tracé à contrôler.</li>
 
<li>Lignes 23-24 : Au moment de son instanciation, chaque ''widget Tkinter'' reçoit automatiquement un attribut master qui contient la référence de la fenêtre principale de l'application. Cet attribut se révèle particulièrement utile si la fenêtre principale a été instanciée implicitement par ''Tkinter'', comme c'est le cas ici.
 
Rappelons en effet que lorsque nous démarrons une application en instanciant directement un ''widget'' tel que <code>Frame</code>, par exemple (c'est ce que nous avons fait à la ligne 4), ''Tkinter'' instancie automatiquement une fenêtre maîtresse pour ce ''widget'' (un objet de la classe <code>Tk()</code>).
 
Comme cet objet a été créé automatiquement, nous ne disposons d'aucune référence dans notre code pour y accéder, si ce n'est par l'intermédiaire de l'attribut <code>master</code> que ''Tkinter'' associe automatiquement à chaque ''widget''.
 
Nous nous servons de cette référence pour redéfinir le bandeau-titre de la fenêtre principale (à la ligne 24), et pour y attacher un gestionnaire d'événement (à la ligne 23).</li>
 
<li>Lignes 27 à 40 : La méthode décrite ici est le gestionnaire des événements <Maj-Ctrl-Z> spécifiquement déclenchés par nos ''widgets'' <code>ChoixVibra()</code> (ou « panneaux de contrôle »), chaque fois que l'utilisateur exerce une action sur un curseur ou une case à cocher. Dans tous les cas, les graphiques éventuellement présents sont d'abord effacés (ligne 28) à l'aide de la méthode <code>delete()</code> : le ''widget'' <code>OscilloGraphe()</code> a hérité cette méthode de sa classe parente <code>Canvas()</code>.
 
Ensuite, de nouvelles courbes sont retracées, pour chacun des panneaux de contrôle dont on a coché la case « Afficher ». Chacun des objets ainsi dessinés dans le canevas possède un numéro de référence, renvoyé par la méthode <code>traceCourbe()</code> de notre ''widget'' <code>OscilloGraphe()</code>.
 
Les numéros de référence de nos dessins sont mémorisés dans la liste <code>self.trace</code>. Ils permettent d'effacer individuellement chacun d'entre eux (cfr. instruction de la ligne 28).</li>
 
<li>Lignes 38-40 : Les valeurs de fréquence, phase & amplitude que l'on transmet à la méthode <code>traceCourbe()</code> sont les attributs d'instance correspondants de chacun des trois panneaux de contrôle, eux-mêmes mémorisés dans la liste <code>self.controle</code>. Nous pouvons récupérer ces attributs en utilisant la qualification des noms par points.</li>
</ul>
 
{{Exercices}}
<ol>
<li>Modifiez le script, de manière à obtenir l'aspect ci-dessous (écran d'affichage avec grille de référence, panneaux de contrôle entourés d'un sillon) :
 
{{image manquante}}
</li>
 
<li>Modifiez le script, de manière à faire apparaître et contrôler 4 graphiques au lieu de trois. Pour la couleur du quatrième graphique, choisissez par exemple : <code>'blue'</code>, <code>'navy'</code>, <code>'maroon'</code>, ...</li>
 
<li>Aux lignes 33-35, nous récupérons les valeurs des fréquence, phase & amplitude choisies par l'utilisateur sur chacun des trois panneaux de contrôle, en accédant directement aux attributs d'instance correspondants. Python autorise ce raccourci - et c'est bien pratique – mais cette technique est dangereuse. Elle enfreint l'une des recommandations de la théorie générale de la « programmation orientée objet », qui préconise que l'accès aux propriétés des objets soit toujours pris en charge par des méthodes spécifiques. Pour respecter cette recommandation, ajoutez à la classe <code>ChoixVibra()</code> une méthode supplémentaire que vous appellerez <code>valeurs()</code>, et qui renverra un tuple contenant les valeurs de la fréquence, la phase et l'amplitude choisies. Les lignes 33 à 35 du présent script pourront alors être remplacées par quelque chose comme :
 
<pre>
freq, phase, ampl = self.control[i].valeurs()</li>
</pre>
 
<li>Écrivez une petite application qui fait apparaître une fenêtre avec un canevas et un ''widget'' curseur (<code>Scale</code>). Dans le canevas, dessinez un cercle, dont l'utilisateur pourra faire varier la taille à l'aide du curseur.</li>
 
<li>Écrivez un script qui créera deux classes : une classe « Application », dérivée de <code>Frame()</code>, dont le constructeur instanciera un canevas de 400x400 pixels, ainsi que deux boutons. Dans le canevas, vous instancierez un objet de la classe « Visage » décrite ci-après.
 
La classe « Visage » servira à définir des objets graphiques censés représenter des visages humains simplifiés. Ces visages seront constitués d'un cercle principal dans lequel trois ovales plus petits représenteront deux yeux et une bouche (ouverte). Une méthode "fermer" permettra de remplacer l'ovale de la bouche par une ligne horizontale. Une méthode « ouvrir » permettra de restituer la bouche de forme ovale.
 
Les deux boutons définis dans la classe « Application » serviront respectivement à fermer et à ouvrir la bouche de l'objet « Visage » installé dans le canevas.
(Vous pouvez vous inspirer de l'exemple de la page {{todo}} pour composer une partie du code).</li>
 
<li>''Exercice de synthèse : élaboration d'un dictionnaire de couleurs.''
 
''But'' : réaliser un petit programme utilitaire, qui puisse vous aider à construire facilement et rapidement un nouveau dictionnaire de couleurs, lequel permettrait l'accès technique à une couleur quelconque par l'intermédiaire de son nom usuel en français.
 
''Contexte'' : En manipulant divers objets colorés avec ''Tkinter'', vous avez constaté que cette bibliothèque graphique accepte qu'on lui désigne les couleurs les plus fondamentales sous la forme de chaînes de caractères contenant leur nom en anglais : <code>'red'</code>, <code>'blue'</code>, etc.
 
Vous savez cependant qu'un ordinateur ne peut traiter que des informations numérisées. Cela implique que la désignation d'une couleur quelconque doit nécessairement tôt ou tard être encodée sous la forme d'un nombre. Il faut bien entendu adopter pour cela une une convention, et celle-ci peut varier d'un système à un autre. L'une de ces conventions, parmi les plus courantes, consiste à représenter une couleur à l'aide de trois octets, qui indiqueront respectivement les intensités des trois composantes rouge, verte et bleue de cette couleur.
 
Cette convention peut être utilisée avec ''Tkinter'' pour accéder à n'importe quelle nuance colorée. Vous pouvez en effet lui indiquer la couleur d'un élément graphique quelconque, à l'aide d'une chaîne de 7 caractères telle que <code>'#00FA4E"'</code>. Dans cette chaîne, le premier caractère (#) signifie que ce qui suit est une valeur hexadécimale. Les six caractères suivants représentent les 3 valeurs hexadécimales des 3 composantes R, V &et B.
Pour visualiser concrètement la correspondance entre une couleur quelconque et son code, vous pouvez essayer le petit programme utilitaire tkColorChooser.py (qui se trouve généralement dans le sous-répertoire /lib-tk de votre installation de Python).
 
Pour visualiser concrètement la correspondance entre une couleur quelconque et son code, vous pouvez essayer le petit programme utilitaire ''tkColorChooser.py'' (qui se trouve généralement dans le sous-répertoire ''/lib-tk'' de votre installation de Python).
Étant donné qu'il n'est pas facile pour les humains que nous sommes de mémoriser de tels codes hexadécimaux, Tkinter est également doté d'un dictionnaire de conversion, qui autorise l'utilisation de noms communs pour un certain nombre de couleurs parmi les plus courantes, mais cela ne marche que pour des noms de couleurs exprimés en anglais.
 
Étant donné qu'il n'est pas facile pour les humains que nous sommes de mémoriser de tels codes hexadécimaux, ''Tkinter'' est également doté d'un dictionnaire de conversion, qui autorise l'utilisation de noms communs pour un certain nombre de couleurs parmi les plus courantes, mais cela ne marche que pour des noms de couleurs exprimés en anglais.
Le but du présent exercice est de réaliser un logiciel qui facilitera la construction d'un dictionnaire équivalent en français, lequel pourrait ensuite être incorporé à l'un ou l'autre de vos propres programmes. Une fois construit, ce dictionnaire serait donc de la forme : {'vert':'#00FF00', 'bleu':'#0000FF', ... etc ...}.
 
Le but du présent exercice est de réaliser un logiciel qui facilitera la construction d'un dictionnaire équivalent en français, lequel pourrait ensuite être incorporé à l'un ou l'autre de vos propres programmes. Une fois construit, ce dictionnaire serait donc de la forme : <code>{'vert':'#00FF00', 'bleu':'#0000FF', ... etc ...}</code>.
Cahier des charges :
 
;Cahier des charges :
 
L'application à réaliser sera une application graphique, construite autour d'une classe.
Ligne 648 ⟶ 723 :
Lorsque le dictionnaire contiendra déjà un certain nombre de données, il devra être possible de le tester, c'est-à-dire d'entrer un nom de couleur en français et de retrouver le code hexadécimal correspondant à l'aide d'un bouton (avec affichage éventuel d'une zone colorée).
 
Un bouton provoquera l'enregistrement du dictionnaire dans un fichier texte. Un autre permettra de reconstruire le dictionnaire à partir du fichier.</li>
 
1.22.Le script ci-dessous correspond à une ébauche de projet dessinant des ensembles de dés à jouer disposés à l'écran de plusieurs manières différentes (cette ébauche pourrait être une première étape dans la réalisation d'un logiciel de jeu).
<li>Le script ci-dessous correspond à une ébauche de projet dessinant des ensembles de dés à jouer disposés à l'écran de plusieurs manières différentes (cette ébauche pourrait être une première étape dans la réalisation d'un logiciel de jeu).
 
L'exercice consistera à analyser ce script et à le compléter. Vous vous placerez ainsi dans la situation d'un programmeur chargé de continuer le travail commencé par quelqu'un d'autre, ou encore dans celle de l'informaticien prié de participer à un travail d'équipe.
A) Commencez par analyser ce script, et ajoutez-y des commentaires, en particulier aux lignes marquées : #*** , afin de montrer que vous comprenez ce que doit faire le programme à ces emplacements :
 
<pre>
from Tkinter import *
 
Ligne 711 ⟶ 790 :
Projet(500, 300).mainloop()
</pre>
 
Modifiez ensuite ce script, afin qu'il corresponde au cahier des charges suivant :
 
* Le canevas devra être plus grand : 600 x 600 pixels.
* Les boutons de commande devront être déplacés à droite et espacés davantage.
* La taille des points sur une face de dé devra varier proportionnellement à la taille de cette face
 
B) Modifiez ensuite ce script, afin qu'il corresponde au cahier des charges suivant :
Le canevas devra être plus grand : 600 x 600 pixels.
Les boutons de commande devront être déplacés à droite et espacés davantage.
La taille des points sur une face de dé devra varier proportionnellement à la taille de cette face
Variante 1 : Ne conservez que les 2 boutons A et B. Chaque utilisation du bouton A fera apparaître 3 nouveaux dés (de même taille, plutôt petits) disposés sur une colonne (verticale), les valeurs de ces dés étant tirées au hasard entre 1 et 6. Chaque nouvelle colonne sera disposée à la droite de la précédente. Si l'un des tirages de 3 dés correspond à 4, 2, 1 (dans n'importe quel ordre), un message « gagné » sera affiché dans la fenêtre (ou dans le canevas). Le bouton B provoquera l'effacement complet (pas seulement les points !) de tous les dés affichés.
 
Variante 2 : Ne conservez que les 2 boutons A et B. Le bouton A fera apparaître 5 dés disposés en quinconce (c.à.d. comme les points d'une face de valeur 5). Les valeurs de ces dés seront tirées au hasard entre 1 et 6, mais il ne pourra pas y avoir de doublons. Le bouton B provoquera l'effacement complet (pas seulement les points !) de tous les dés affichés.
 
Variante 3 : Ne conservez que les 3 boutons A, B et C. Le bouton A fera apparaître 13 dés de même taille disposés en cercle. Chaque utilisation du bouton B provoquera un changement de valeur du premier dé, puis du deuxième, du troisième, etc. La nouvelle valeur d'un dé sera à chaque fois égale a sa valeur précédente augmentée d'une unité, sauf dans le cas ou la valeur précédente était 6 : dans ce cas la nouvelle valeur est 1, et ainsi de suite. Le bouton C provoquera l'effacement complet (pas seulement les points !) de tous les dés affichés.
 
Variante 4 : Ne conservez que les 3 boutons A, B et C. Le bouton A fera apparaître 12 dés de même taille disposés sur deux lignes de 6. Les valeurs des dés de la première ligne seront dans l'ordre 1, 2, 3, 4, 5, 6. Les valeurs des dés de la seconde ligne seront tirées au hasard entre 1 et 6. Chaque utilisation du bouton B provoquera un changement de valeur aléatoire du premier dé de la seconde ligne, tant que cette valeur restera différente de celle du dé correspondant dans la première ligne. Lorsque le 1er dé de la 2e ligne aura acquis la valeur de son correspondant, c'est la valeur du 2e dé de la seconde ligne qui sera changée au hasard, et ainsi de suite, jusqu'à ce que les 6 faces du bas soient identiques à celles du haut. Le bouton C provoquera l'effacement complet (pas seulement les points !) de tous les dés affichés.
Variante 4 : Ne conservez que les 3 boutons A, B et C. Le bouton A fera apparaître 12 dés de même taille disposés sur deux lignes de 6. Les valeurs des dés de la première ligne seront dans l'ordre 1, 2, 3, 4, 5, 6. Les valeurs des dés de la seconde ligne seront tirées au hasard entre 1 et 6. Chaque utilisation du bouton B provoquera un changement de valeur aléatoire du premier dé de la seconde ligne, tant que cette valeur restera différente de celle du dé correspondant dans la première ligne. Lorsque le 1er dé de la 2e ligne aura acquis la valeur de son correspondant, c'est la valeur du 2e dé de la seconde ligne qui sera changée au hasard, et ainsi de suite, jusqu'à ce que les 6 faces du bas soient identiques à celles du haut. Le bouton C provoquera l'effacement complet (pas seulement les points !) de tous les dés affichés.</li>
</ol>
{{fin}}
 
== Notes ==
{{références}}