• Aucun résultat trouvé

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 curseurs.py. Les classes que vous sauvegardez ainsi se-ront réutilisées (par importation) dans une application de synthèse que nous décrirons un peu plus loin58. 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 fonction-nement. Vous devriez obtenir une fenêtre semblable à celle-ci :

1# from Tkinter import * 2# from math import pi 3#

4# class ChoixVibra(Frame):

5# """Curseurs pour choisir fréquence, phase & amplitude d'une vibration"""

6# def __init__(self, boss =None, coul ='red'): 12# Checkbutton(self, text='Afficher', variable=self.chk,

13# fg = self.coul, command = self.setCurve).pack(side=LEFT) 14# # Définition des 3 widgets curseurs :

15# Scale(self, length=150, orient=HORIZONTAL, sliderlength =25, 16# label ='Fréquence (Hz) :', from_=1., to=9., tickinterval =2, 17# resolution =0.25,

18# showvalue =0, command = self.setFrequency).pack(side=LEFT) 19# Scale(self, length=150, orient=HORIZONTAL, sliderlength =15,

20# label ='Phase (degrés) :', from_=-180, to=180, tickinterval =90, 21# showvalue =0, command = self.setPhase).pack(side=LEFT)

22# Scale(self, length=150, orient=HORIZONTAL, sliderlength =25, 23# label ='Amplitude :', from_=1, to=9, tickinterval =2, 24# showvalue =0, command = self.setAmplitude).pack(side=LEFT) 25#

26# def setCurve(self):

27# self.event_generate('<Control-Z>') 28#29# def setFrequency(self, f):

30# self.freq = float(f)

31# self.event_generate('<Control-Z>') 32#

33# def setPhase(self, p):

34# pp =float(p)

35# self.phase = pp*2*pi/360 # conversion degrés -> radians 36# self.event_generate('<Control-Z>')

37#

38# def setAmplitude(self, a):

39# self.ampl = float(a)

40# self.event_generate('<Control-Z>') 41#

42# #### Code pour tester la classe : ###

58Vous pourriez bien évidemment aussi enregistrer plusieurs classes dans un même module.

43#

44# if __name__ == '__main__':

45# def afficherTout(event=None):

46# lab.configure(text = '%s - %s - %s - %s' %

53# root.bind('<Control-Z>', afficherTout) 54# root.mainloop()

Ce panneau de contrôle permettra à vos utilisateurs de régler aisément la valeur des paramètres indiqués (fréquence, phase & amplitude), lesquels pourront alors servir à commander l'affichage de graphiques élongation/temps dans un widget de la classe OscilloGraphe() construite précé-demment, comme nous le montrerons dans l'application de synthèse.

Commentaires :

• Ligne 6 : La méthode « constructeur » utilise un paramètre optionnel coul. Ce paramètre per-mettra 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).

• 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étermi-né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 mo-dule Tkinter au même titre que les classes similaires DoubleVar(), StringVar() et Boolean-Var(). 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 (voir ci-après).

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 va-leur, et la méthode get() permet de la récupérer (ce que l'on mettra 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'inter-face).

• Ligne 13 : L'option command désigne la méthode que le système doit invoquer lorsque l'utili-sateur effectue un clic de souris dans la case à cocher.

• Lignes 14 à 24 : Ces lignes définissent les trois widgets curseurs, en trois instructions simi-laires. 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 ou expressions lamdba), et la définition du gestion-naire 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 ac-tive la méthode setAmplitude(). Remarquez bien au passage que l'option command des wid-gets 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.

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énement59, en faisant appel à la méthode event_generate().

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 ma-niè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 : 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 mo-ment sera venu de vérifier le gestionnaire de cet événemo-ment, que nous mettrons en place par ailleurs.

• Lignes 42 à 54 : Comme nous l'avions déjà fait pour oscillo.py, 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 wid-gets : un widget de la nouvelle classe ChoixVibra() et un widget de la classe Label().

A la ligne 53, nous associons à la fenêtre principale un gestionnaire d'événement : tout événe-ment 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 ChoixVibra(), 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

59En 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 89 : Programmes pilotés par des événements.

(re)configurant l'option text d'un widget de classe Label().

• 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().

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 self. En général, un message-événe-ment 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.

Exercices :

13.13 Votre nouveau widget hérite des propriétés de la classe Frame(). Vous pouvez donc modifier son aspect en modifiant les options par défaut de cette classe, à l'aide de la méthode configure(). 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 oscillo.py (ligne 10).

13.14 Si l'on assigne la valeur 1 à l'option showvalue des widgets Scale(), 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 « phase ».

13.15 L'option troughcolor des widgets Scale() 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.

13.16 Modifiez le script de telle manière que les widgets curseurs soient écartés davantage les uns des autres (options padx et pady de la méthode pack()).