IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Apprendre à programmer avec Python


précédentsommairesuivant

Chapitre 14 : Et pour quelques widgets de plus ...

Les pages qui suivent contiennent des indications et et des exemples complémentaires qui pourront vous être utiles pour le développement de vos projets personnels. Il ne s'agit évidemment pas d'une documentation de référence complète sur Tkinter. Pour en savoir plus, vous devrez tôt ou tard consulter des ouvrages spécialisés, comme par exemple l'excellent Python and Tkinter programming de John E. Grayson, dont vous trouverez la référence complète à la page 8.

14-1. Les « boutons radio »

Les widgets « boutons radio » permettent de proposer à l'utilisateur un ensemble de choix mutuellement exclusifs. On les appelle ainsi par analogie avec les boutons de sélection que l'on trouvait jadis sur les postes de radio. Ces boutons étaient conçus de telle manière qu'un seul à la fois pouvait être enfoncé : tous les autres ressortaient automatiquement.

La caractéristique essentielle de ces widgets est qu'on les utilise toujours par groupes. Tous les boutons radio faisant partie d'un même groupe sont associés à une seule et même variable Tkinter, mais chacun d'entre eux se voit aussi attribuer une valeur particulière.

Lorsque l'utilisateur sélectionne l'un des boutons, la valeur correspondant à ce bouton est affectée à la variable Tkinter commune.
Image non disponible
 
Sélectionnez
1. from Tkinter import *
2.
3. class RadioDemo(Frame):
4.     """Démo : utilisation de widgets 'boutons radio'"""
5.     def __init__(self, boss =None):
6.         """Création d'un champ d'entrée avec 4 boutons radio"""
7.         Frame.__init__(self)
8.         self.pack()
9.         # Champ d'entrée contenant un petit texte :
10.        self.texte = Entry(self, width =30, font ="Arial 14")
11.        self.texte.insert(END, "La programmation, c'est génial")
12.        self.texte.pack(padx =8, pady =8)
13.        # Nom français et nom technique des quatre styles de police :                  
14.        stylePoliceFr =["Normal", "Gras", "Italique", "Gras/Italique"]
15.        stylePoliceTk =["normal", "bold", "italic"  , "bold italic"]
16.        # Le style actuel est mémorisé dans un 'objet-variable' Tkinter ;
17.        self.choixPolice = StringVar()
18.        self.choixPolice.set(stylePoliceTk[0])
19.        # Création des quatre 'boutons radio' :
20.        for n in range(4):
21.            bout = Radiobutton(self,
22.                               text = stylePoliceFr[n],
23.                               variable = self.choixPolice,
24.                               value = stylePoliceTk[n],
25.                               command = self.changePolice)
26.            bout.pack(side =LEFT, padx =5)
27.
28.    def changePolice(self):
29.        """Remplacement du style de la police actuelle"""
30.        police = "Arial 15 " + self.choixPolice.get() 
31.        self.texte.configure(font =police)
32.                           
33.if __name__ == '__main__':
34.    RadioDemo().mainloop()

Commentaires :

  • Ligne 3 : Cette fois encore, nous préférons construire notre petite application comme une classe dérivée de la classe Frame(), ce qui nous permettrait éventuellement de l'intégrer sans difficulté dans une application plus importante.
  • Ligne 8 : En général, on applique les méthodes de positionnement des widgets (pack(), grid(), ou place()) après instanciation de ceux-ci, ce qui permet de choisir librement leur disposition à l'intérieur des fenêtres maîtresses. Comme nous le montrons ici, il est cependant tout à fait possible de déjà prévoir ce positionnement dans le constructeur du widget.
  • Ligne 11 : Les widgets de la classe Entry disposent de plusieurs méthodes pour accéder à la chaîne de caractères affichée. La méthode get() permet de récupérer la chaîne entière. La méthode delete() permet d'en effacer tout ou partie (cfr. projet « Code des couleurs », page 174). La méthode insert() permet d'insérer de nouveaux caractères à un emplacement quelconque (c'est-à-dire au début, à la fin, ou même à l'intérieur d'une chaîne préexistante éventuelle). Cette méthode s'utilise donc avec deux arguments, le premier indiquant l'emplacement de l'insertion (utilisez 0 pour insérer au début, END pour insérer à la fin, ou encore un indice numérique quelconque pour désigner un caractère dans la chaîne).
  • Lignes 14-15 : Plutôt que de les instancier dans des instructions séparées, nous préférons créer nos quatre boutons à l'aide d'une boucle. Les options spécifiques à chacun d'eux sont d'abord préparées dans les deux listes stylePoliceFr et stylePoliceTk : la première contient les petits textes qui devront s'afficher en regard de chaque bouton, et la seconde les valeurs qui devront leur être associées.
  • Lignes 17-18 : Comme expliqué à la page précédente, les quatre boutons forment un groupe autour d'une variable commune. Cette variable prendra la valeur associée au bouton radio que l'utilisateur décidera de choisir. Nous ne pouvons cependant pas utiliser une variable ordinaire pour remplir ce rôle, parce que les attributs internes des objets Tkinter ne sont accessibles qu'au travers de méthodes spécifiques. Une fois de plus, nous utilisons donc ici un objet-variable Tkinter, de type 'chaîne de caractères', que nous instancions à partir de la classe StringVar()., et auquel nous donnons une valeur par défaut à la ligne 18.
  • Lignes 20 à 26 : Instanciation des quatre boutons radio. Chacun d'entre eux se voit attribuer une étiquette et une valeur différentes, mais tous sont associés à la même variable Tkinter commune (self.choixPolice). Tous invoquent également la même méthode self.changePolice(), chaque fois que l'utilisateur effectue un clic de souris sur l'un ou l'autre.
  • Lignes 28 à 31 : Le changement de police s'obtient par re-configuration de l'option font du widget Entry. Cette option attend un tuple contenant le nom de la police, sa taille, et éventuellement son style. Si le nom de la police ne contient pas d'espaces, le tuple peut aussi être remplacé par une chaîne de caractères. Exemples :
    ('Arial', 12, 'italic')
    ('Helvetica', 10)
    ('Times New Roman', 12, 'bold italic')
    "Verdana 14 bold"
    "President 18 italic"


    Voir également les exemples de la page 221.

14-2. Utilisation des cadres (frames) pour la composition d'une fenêtre

Vous avez déjà abondamment utilisé la classe de widgets Frame() (« cadre », en français), notamment pour créer de nouveaux widgets complexes par dérivation.

Le petit script ci-dessous vous montre l'utilité de cette même classe pour regrouper des ensembles de widgets et les disposer d'une manière déterminée dans une fenêtre. Il vous démontre également l'utilisation de certaines options décoratives (bordures, relief, etc.).

Pour composer la fenêtre ci-contre, nous avons utilisé deux cadres f1 et f2, de manière à réaliser deux groupes de widgets bien distincts, l'un à gauche et l'autre à droite. Nous avons coloré ces deux cadres pour bien les mettre en évidence, mais ce n'est évidemment pas indispensable.

Le cadre f1 contient lui-même 6 autres cadres, qui contiennent chacun un widget de la classe Label(). Le cadre f2 contient un widget Canvas() et un widget Button(). Les couleurs et garnitures sont de simples options.
Image non disponible
 
Sélectionnez
1. from Tkinter import *
2.
3. fen = Tk()
4. fen.title("Fenêtre composée à l'aide de frames")
5. fen.geometry("300x300")
6.
7. f1 = Frame(fen, bg = '#80c0c0')
8. f1.pack(side =LEFT, padx =5)  
9.
10.fint = [0]*6			
11.for (n, col, rel, txt) in [(0, 'grey50', RAISED, 'Relief sortant'),
12.                           (1, 'grey60', SUNKEN, 'Relief rentrant'),
13.                           (2, 'grey70', FLAT, 'Pas de relief'),
14.                           (3, 'grey80', RIDGE, 'Crête'),
15.                           (4, 'grey90', GROOVE, 'Sillon'),
16.                           (5, 'grey100', SOLID, 'Bordure')]:
17.    fint[n] = Frame(f1, bd =2, relief =rel)
18.    e = Label(fint[n], text =txt, width =15, bg =col)
19.    e.pack(side =LEFT, padx =5, pady =5)
20.    fint[n].pack(side =TOP, padx =10, pady =5)
21.
22.f2 = Frame(fen, bg ='#d0d0b0', bd =2, relief =GROOVE)
23.f2.pack(side =RIGHT, padx =5)
24.
25.can = Canvas(f2, width =80, height =80, bg ='white', bd =2, relief =SOLID)
26.can.pack(padx =15, pady =15)
27.bou =Button(f2, text='Bouton')
28.bou.pack()
29.
30.fen.mainloop()
  • Lignes 3 à 5 : Afin de simplifier au maximum la démonstration, nous ne programmons pas cet exemple comme une nouvelle classe. Remarquez à la ligne 5 l'utilité de la méthode geometry() pour fixer les dimensions de la fenêtre principale.
  • Ligne 7 : Instanciation du cadre de gauche. La couleur de fond (une variété de bleu cyan) est déterminée par l'argument bg (background). Cette chaîne de caractères contient en notation hexadécimale la description des trois composantes rouge, verte et bleue de la teinte que l'on souhaite obtenir : Après le caractère # signalant que ce qui suit est une valeur numérique hexadécimale, on trouve trois groupes de deux symboles alphanumériques. Chacun de ces groupes représente un nombre compris entre 1 et 255. Ainsi 80 correspond à 128, et c0 correspond à 192 en notation décimale. Dans notre exemple, les composantes rouge, verte et bleue de la teinte à représenter valent donc respectivement 128, 192 & 192.
    En application de cette technique descriptive, le noir serait obtenu avec #000000, le blanc avec #ffffff, le rouge pur avec #ff0000, un bleu sombre avec #000050, etc.
  • Ligne 8 : Puisque nous lui appliquons la méthode pack(), le cadre sera automatiquement dimensionné par son contenu. L'option side =LEFT le positionnera à gauche dans sa fenêtre maîtresse. L'option padx =5 ménagera un espace de 5 pixels à sa gauche et à sa droite (nous pouvons traduire « padx » par « espacement horizontal »).
  • Ligne 10 : Dans le cadre f1 que nous venons de préparer, nous avons l'intention de regrouper 6 autres cadres similaires contenant chacun une étiquette. Le code correspondant sera plus simple et plus efficient si nous instancions ces widgets dans une liste plutôt que dans des variables indépendantes. Nous préparons donc cette liste avec 6 éléments que nous remplacerons plus loin.
  • Lignes 11 à 16 : Pour construire nos 6 cadres similaires, nous allons parcourir une liste de 6 tuples contenant les caractéristiques particulières de chaque cadre. Chacun de ces tuples est constitué de 4 éléments : un indice, une constante Tkinter définissant un type de relief, et deux chaînes de caractères décrivant respectivement la couleur et le texte de l'étiquette.
    La boucle for effectue 6 itérations pour parcourir les 6 éléments de la liste. A chaque itération, le contenu d'un des tuples est affecté aux variables n, col, rel et txt (et ensuite les instructions des lignes 17 à 20 sont exécutées). Le parcours d'une liste de tuples à l'aide d'une boucle for constitue donc une construction particulièrement compacte, qui permet de réaliser de nombreuses affectations avec un très petit nombre d'instructions.
  • Ligne 17 : Les 6 cadres sont instanciés comme des éléments de la liste fint. Chacun d'entre eux est agrémenté d'une bordure décorative de 2 pixels de large, avec un certain effet de relief.
  • Lignes 18-20 : Les étiquettes ont toutes la même taille, mais leurs textes et leurs couleurs de fond diffèrent. Du fait de l'utilisation de la méthode pack(), c'est la dimension des étiquettes qui détermine la taille des petits cadres. Ceux-ci à leur tour déterminent la taille du cadre qui les regroupe (le cadre f1). Les options padx et pady permettent de réserver un petit espace autour de chaque étiquette, et un autre autour de chaque petit cadre. L'option side =TOP positionne les 6 petits cadres les uns en dessous des autres dans le cadre conteneur f1.
  • Lignes 22-23 : Préparation du cadre f2 (cadre de droite). Sa couleur sera une variété de jaune, et nous l'entourerons d'une bordure décorative ayant l'aspect d'un sillon.
  • Lignes 25 à 28 : Le cadre f2 contiendra un canevas et un bouton. Notez encore une fois l'utilisation des options padx et pady pour ménager des espaces autour des widgets (Considérez par exemple le cas du bouton, pour lequel cette option n'a pas été utilisée : de ce fait, il entre en contact avec la bordure du cadre qui l'entoure). Comme nous l'avons fait pour les cadres, nous avons placé une bordure autour du canevas. Sachez que d'autres widgets acceptent également ce genre de décoration : boutons, champs d'entrée, etc.

14-3. Comment déplacer des dessins à l'aide de la souris

Le widget canevas est l'un des points forts de la bibliothèque graphique Tkinter. Il intègre en effet un grand nombre de dispositifs très efficaces pour manipuler des dessins. Le script ci-après est destiné à vous montrer quelques techniques de base. Si vous voulez en savoir plus, notamment en ce qui concerne la manipulation de dessins composés de plusieurs parties, veuillez consulter l'un ou l'autre ouvrage de référence traitant de Tkinter.

Au démarrage de notre petite application, une série de dessins sont tracés au hasard dans un canevas (il s'agit en l'occurrence de simples ellipses colorées). Vous pouvez déplacer n'importe lequel de ces dessins en les « saisissant » à l'aide de votre souris.

Lorsqu'un dessin est déplacé, il passe à l'avant-plan par rapport aux autres, et sa bordure apparaît plus épaisse pendant toute la durée de sa manipulation.

Image non disponible

Pour bien comprendre la technique utilisée, vous devez vous rappeler qu'un logiciel utilisant une interface graphique est un logiciel « piloté par les événements » (revoyez au besoin les explications de la page 85). Dans cette application, nous allons mettre en place un mécanisme qui réagit aux événements : « enfoncement du bouton gauche de la souris », « déplacement de la souris, le bouton gauche restant enfoncé », « relâchement du bouton gauche ».

Ces événements sont générés par le système d'exploitation et pris en charge par l'interface Tkinter. Notre travail de programmation consistera donc simplement à les associer à des gestionnaires différents (fonctions ou méthodes).

 
Sélectionnez
# Exemple montrant comment faire en sorte que les objets dessinés dans un
# canevas puissent être manipulés à l'aide de la souris
 
from Tkinter import *
from random import randrange
 
class Draw(Frame):
    "classe définissant la fenêtre principale du programme"
    def __init__(self):
        Frame.__init__(self)
        # mise en place du canevas - dessin de 15 ellipses colorées :
        self.c = Canvas(self, width =400, height =300, bg ='ivory')
        self.c.pack(padx =5, pady =3)
        for i in range(15):
            # tirage d'une couleur au hasard :
            coul =['brown','red','orange','yellow','green','cyan','blue',
                   'violet', 'purple'][randrange(9)]
            # tracé d'une ellipse avec coordonnées aléatoires :
            x1, y1 = randrange(300), randrange(200)
            x2, y2 = x1 + randrange(10, 150), y1 + randrange(10, 150)
            self.c.create_oval(x1, y1, x2, y2, fill =coul)
        # liaison d'événements <souris> au widget <canevas> :
        self.c.bind("<Button-1>", self.mouseDown)
        self.c.bind("<Button1-Motion>", self.mouseMove)
        self.c.bind("<Button1-ButtonRelease>", self.mouseUp)
        # mise en place d'un bouton de sortie :
        b_fin = Button(self, text ='Terminer', bg ='royal blue', fg ='white',
                       font =('Helvetica', 10, 'bold'), command =self.quit)
        b_fin.pack(pady =2)
        self.pack()
 
    def mouseDown(self, event):
        "Op. à effectuer quand le bouton gauche de la souris est enfoncé"
        self.currObject =None
        # event.x et event.y contiennent les coordonnées du clic effectué :
        self.x1, self.y1 = event.x, event.y
        # <find_closest> renvoie la référence du dessin le plus proche :
        self.selObject = self.c.find_closest(self.x1, self.y1)
        # modification de l'épaisseur du contour du dessin :
        self.c.itemconfig(self.selObject, width =3)
        # <lift> fait passer le dessin à l'avant-plan :
        self.c.lift(self.selObject)
 
    def mouseMove(self, event):
        "Op. à effectuer quand la souris se déplace, bouton gauche enfoncé"
        x2, y2 = event.x, event.y
        dx, dy = x2 -self.x1, y2 -self.y1
        if self.selObject:
            self.c.move(self.selObject, dx, dy)
            self.x1, self.y1 = x2, y2
 
    def mouseUp(self, event):
        "Op. à effectuer quand le bouton gauche de la souris est relâché"
        if self.selObject:
            self.c.itemconfig(self.selObject, width =1)
            self.selObject =None
 
if __name__ == '__main__':
    Draw().mainloop()

Commentaires :

Le script contient essentiellement la définition d'une classe graphique dérivée de Frame().

Comme c'est souvent le cas pour les programmes exploitant les classes d'objets, le corps principal du script se résume à une seule instruction composée, dans laquelle on réalise deux opérations consécutives : instanciation d'un objet de la classe définie précédemment, et activation de sa méthode mainloop() ( laquelle démarre l'observateur d'événements).

Le constructeur de la classe Draw() présente une structure qui doit vous être devenue familière, à savoir : appel au constructeur de la classe parente, puis mise en place de divers widgets.

Dans le widget canevas, nous instancions 15 dessins sans nous préoccuper de conserver leurs références dans des variables. Nous pouvons procéder ainsi parce que Tkinter conserve lui-même une référence interne pour chacun de ces objets. (Si vous travaillez avec d'autres bibliothèques graphiques, vous devrez probablement prévoir une mémorisation de ces références).

Les dessins sont de simples ellipses colorées. Leur couleur est choisie au hasard dans une liste de 9 possibilités, l'indice de la couleur choisie étant déterminé par la fonction randrange() importée du module random.

Le mécanisme d'interaction est installé ensuite : on associe les trois identificateurs d'événements <b><Button-1></b>, <b><Button1-Motion></b> et <b><Button1-ButtonRelease></b> concernant le widget canevas, aux noms des trois méthodes choisies comme gestionnaires d'événements.

Lorsque l'utilisateur enfonce le bouton gauche de sa souris, la méthode mouseDown() est donc activée, et le système d'exploitation lui transmet en argument un objet event, dont les attributs x et y contiennent les coordonnées du curseur souris dans le canevas, déterminées au moment du clic.

Nous mémorisons directement ces coordonnées dans les variables d'instance self.x1 et self.x2, car nous en aurons besoin par ailleurs. Ensuite, nous utilisons la méthode find_closest() du widget canevas, qui nous renvoie la référence du dessin le plus proche. (Note : cette méthode bien pratique renvoie toujours une référence, même si le clic de souris n'a pas été effectué à l'intérieur du dessin).

Le reste est facile : la référence du dessin sélectionné est mémorisée dans une variable d'instance, et nous pouvons faire appel à d'autres méthodes du widget canevas pour modifier ses caractéristiques. En l'occurrence, nous utilisons les méthodes itemconfig() et lift() pour épaissir son contour et le faire passer à l'avant-plan.

Le « transport » du dessin est assuré par la méthode mouseMove(), invoquée à chaque fois que la souris se déplace alors que son bouton gauche est resté enfoncé. L'objet event contient cette fois encore les coordonnées du curseur souris, au terme de ce déplacement. Nous nous en servons pour calculer les différences entre ces nouvelles coordonnées et les précédentes, afin de pouvoir les transmettre à la méthode move() du widget canevas, qui effectuera le transport proprement dit.

Nous ne pouvons cependant faire appel à cette méthode que s'il existe effectivement un objet sélectionné, et il nous faut veiller également à mémoriser les nouvelles coordonnées acquises.

La méthode mouseUp() termine le travail. Lorsque le dessin transporté est arrivé à destination, il reste à annuler la sélection et rendre au contour son épaisseur initiale. Ceci ne peut être envisagé que s'il existe effectivement une sélection, bien entendu.

14-4. Python Mega Widgets

Les modules Pmw constituent une extension intéressante de Tkinter. Entièrement écrits en Python, ils contiennent toute une bibliothèque de widgets composites, construits à partir des classes de base de Tkinter. Dotés de fonctionnalités très étendues, ces widgets peuvent se révéler fort précieux pour le développement rapide d'applications complexes. Si vous souhaitez les utiliser, sachez cependant que les modules Pmw ne font pas partie de l'installation standard de Python : vous devrez donc toujours vérifier leur présence sur les machines cibles de vos programmes.

Il existe un grand nombre de ces méga-widgets. Nous n'en présenterons ici que quelques-uns parmi les plus utiles. Vous pouvez rapidement vous faire une idée plus complète de leurs multiples possibilités, en essayant les scripts de démonstration qui les accompagnent (lancez par exemple le script all.py , situé dans le répertoire .../Pmw/demos).

14-4-1. « Combo Box »

Les méga-widgets s'utilisent aisément. La petite application ci-après vous montre comment mettre en oeuvre un widget de type ComboBox (boîte de liste combinée à un champ d'entrée). Nous l'avons configuré de la manière la plus habituelle (avec une boîte de liste déroulante).

Lorsque l'utilisateur de notre petit programme choisit une couleur dans la liste déroulante (il peut aussi entrer un nom de couleur directement dans le champ d'entrée), cette couleur devient automatiquement la couleur de fond pour la fenêtre maîtresse.

Dans cette fenêtre maîtresse, nous avons ajouté un libellé et un bouton, afin de vous montrer comment vous pouvez accéder à la sélection opérée précédemment dans le ComboBox lui-même (le bouton provoque l'affichage du nom de la dernière couleur choisie).
Image non disponible
 
Sélectionnez
1. from Tkinter import *
2. import Pmw          
3.
4. def changeCoul(col):
5.     fen.configure(background = col)
6.
7. def changeLabel():
8.     lab.configure(text = combo.get())
9.
10.couleurs = ('navy', 'royal blue', 'steelblue1', 'cadet blue',
11.            'lawn green', 'forest green', 'dark red',
12.            'grey80','grey60', 'grey40', 'grey20')
13.    
14.fen = Pmw.initialise()
15.bou = Button(fen, text ="Test", command =changeLabel)
16.bou.grid(row =1, column =0, padx =8, pady =6)
17.lab = Label(fen, text ='néant', bg ='ivory')
18.lab.grid(row =1, column =1, padx =8)
19.
20.combo = Pmw.ComboBox(fen, labelpos = NW,
21.                     label_text = 'Choisissez la couleur :',
22.                     scrolledlist_items = couleurs,
23.                     listheight = 150,
24.                     selectioncommand = changeCoul)
25.combo.grid(row =2, columnspan =2, padx =10, pady =10)
26.
27.fen.mainloop()

Commentaires :

  • Lignes 1 & 2 : On commence par importer les composants habituels de Tkinter, ainsi que le module Pmw.
  • Ligne 14 : Pour créer la fenêtre maîtresse, il faut utiliser de préférence la méthode Pmw.initialise(), plutôt que d'instancier directement un objet de la classe Tk(). Cette méthode veille en effet à mettre en place tout ce qui est nécessaire afin que les widgets esclaves de cette fenêtre puissent être détruits correctement lorsque la fenêtre elle-même sera détruite. Cette méthode installe également un meilleur gestionnaire des messages d'erreurs.
  • Ligne 12 : L'option labelpos détermine l'emplacement du libellé qui accompagne le champ d'entrée. Dans notre exemple, nous l'avons placé au-dessus, mais vous pourriez préférer le placer ailleurs, à gauche par exemple (labelpos = W). Notez que cette option est indispensable si vous souhaitez un libellé (pas de valeur par défaut).
  • Ligne 14 : L'option selectioncommand transmet un argument à la fonction invoquée : l'item sélectionné dans la boîte de liste. Vous pourrez également retrouver cette sélection à l'aide de la méthode get(), comme nous le faisons à la ligne 8 pour actualiser le libellé.

14-4-2. Remarque concernant l'entrée de caractères accentués

Nous vous avons déjà signalé précédemment que Python est tout à fait capable de prendre en charge les alphabets du monde entier (grec, cyrillique, arabe, japonais, etc. - voir notamment page 40). Il en va de même pour Tkinter. En tant que francophone, vous souhaiterez certainement que les utilisateurs de vos scripts puissent entrer des caractères accentués dans les widgets Entry, Text et leurs dérivés (ComboBox, ScrolledText).

Veuillez donc prendre bonne note que lorsque vous entrez dans l'un de ces widgets une chaîne contenant un ou plusieurs caractères non-ASCII (tel qu'une lettre accentuée, par exemple), Tkinter encode cette chaîne suivant la norme UTF-8. Si votre ordinateur utilise plutôt le codage Latin-1 par défaut (ce qui est très souvent le cas), vous devrez convertir la chaîne avant de pouvoir l'afficher.

Cela peut se faire très aisément en utilisant la fonction intégrée encode(). Exemple :

 
Sélectionnez
 -*- coding: Latin-1 -*-
 
from Tkinter import *
 
def imprimer():
    ch1 = e.get()                 # le widget Entry renvoie une chaîne utf8
    ch2 = ch1.encode("Latin-1")   # conversion utf8 -> Latin-1
    print ch2
 
f = Tk()
e = Entry(f)
e.pack()
Button(f, text ="afficher", command =imprimer).pack()
f.mainloop()

Essayez ce petit script en entrant des chaînes avec caractères accentués dans le champ d'entrée.

Essayez encore, mais en remplaçant l'instruction « print ch2 » par « print ch1 ». Concluez.

14-4-3. « Scrolled Text »

Ce méga-widget étend les possibilités du widget Text sandard, en lui associant un cadre, un libellé (titre) et des barres de défilement.

Comme le démontrera le petit script ci-dessous, il sert fondamentalement à afficher des textes, mais ceux-ci peuvent être mis en forme et intégrer des images.

Vous pouvez également rendre « cliquables » les éléments affichés (textes ou images), et vous en servir pour déclencher toutes sortes de mécanismes.
Image non disponible

Dans l'application qui génère la figure ci-dessus, par exemple, le fait de cliquer sur le nom « Jean de la Fontaine » provoque le défilement automatique du texte (scrolling), jusqu'à ce qu'une rubrique décrivant cet auteur devienne visible dans le widget (Voir page suivante le script correspondant).

D'autres fonctionnalités sont présentes, mais nous ne présenterons ici que les plus fondamentales. Veuillez donc consulter les démos et exemples accompagnant Pmw pour en savoir davantage.

Gestion du texte affiché : Vous pouvez accéder à n'importe quelle portion du texte pris en charge par le widget grâce à deux concepts complémentaires, les index et les balises :

  • Chaque caractère du texte affiché est référencé par un index, lequel doit être une chaîne de caractères contenant deux valeurs numériques reliées par un point (ex : "5.2"). Ces deux valeurs indiquent respectivement le numéro de ligne et le numéro de colonne où se situe le caractère.
  • N'importe quelle portion du texte peut être associée à une ou plusieurs balise(s), dont vous choisissez librement le nom et les propriétés. Celles-ci vous permettent de définir la police, les couleurs d'avant- et d'arrière-plan, les événements associés, etc.

Note : Pour la bonne compréhension du script ci-dessous, veuillez considérer que le texte de la fable traitée doit être accessible, dans un fichier nommé « CorbRenard.txt ».

 
Sélectionnez
1. from Tkinter import *
2. import Pmw          
3.
4. def action(event=None):
5.     """défilement du texte jusqu'à la balise <cible>"""
6.     index = st.tag_nextrange('cible', '0.0', END)
7.     st.see(index[0])
8.
9. # Instanciation d'une fenêtre contenant un widget ScrolledText :  
10.fen = Pmw.initialise()
11.st = Pmw.ScrolledText(fen,
12.                      labelpos =N,
13.                      label_text ="Petite démo du widget ScrolledText",
14.                      label_font ='Times 14 bold italic',
15.                      label_fg = 'navy', label_pady =5,
16.                      text_font='Helvetica 11 normal', text_bg ='ivory',
17.                      text_padx =10, text_pady =10, text_wrap ='none', 
18.                      borderframe =1,
19.                      borderframe_borderwidth =3,
20.                      borderframe_relief =SOLID,
21.                      usehullsize =1,
22.                      hull_width =370, hull_height =240)
23.st.pack(expand =YES, fill =BOTH, padx =8, pady =8)
24.
25.# Définition de balises, liaison d'un gestionnaire d'événement au clic de souris : 
26.st.tag_configure('titre', foreground ='brown', font ='Helvetica 11 bold italic')
27.st.tag_configure('lien', foreground ='blue', font ='Helvetica 11 bold')
28.st.tag_configure('cible', foreground ='forest green', font ='Times 11 bold')
29.st.tag_bind('lien', '<Button-1>', action)
30.
31.titre ="""Le Corbeau et le Renard
32.par Jean de la Fontaine, auteur français
33.\n"""
34.auteur ="""
35.Jean de la Fontaine
36.écrivain français (1621-1695)
37.célèbre pour ses Contes en vers,
38.et surtout ses Fables, publiées
39.de 1668 à 1694."""
40.
41.# Remplissage du widget Text (2 techniques) :
42.st.importfile('CorbRenard.txt')
43.st.insert('0.0', titre, 'titre')
44.st.insert(END, auteur, 'cible')
45.# Insertion d'une image :
46.photo =PhotoImage(file= 'Penguin.gif')
47.st.image_create('6.14', image =photo)
48.# Mise en oeuvre dynamique d'une balise :
49.st.tag_add('lien', '2.4', '2.23')
50.
51.fen.mainloop()

Commentaires :

  • Lignes 4-7 : Cette fonction est un gestionnaire d'événement, qui est appelé lorsque l'utilisateur effectue un clic de souris sur le nom de l'auteur (cfr. lignes 27 & 29). A la ligne 6, on utilise la méthode tag_nextrange() du widget pour trouver les index de la portion de texte associée à la balise « cible ». La recherche de ces index est limitée au domaine défini par les 2e & 3e arguments (dans notre exemple, on recherche du début à la fin du texte entier). La méthode tag_nextrange() renvoie une liste de deux index (ceux des premier et dernier caractères de la portion de texte associée à la balise « cible »). A la ligne 7, nous nous servons d'un seul de ces index (le premier) pour activer la méthode see(). Celle-ci provoque un défilement automatique du texte (scrolling), de telle manière que le caractère correspondant à l'index transmis devienne visible dans le widget (avec en général un certain nombre des caractères qui suivent).
  • Lignes 9 à 23 : Construction classique d'une fenêtre destinée à afficher un seul widget. Dans le code d'instanciation du widget, nous avons inclus un certain nombre d'options destinées à vous montrer une petite partie des nombreuses possibilités de configuration.
  • Ligne 12 : L'option labelpos détermine l'emplacement du libellé (titre) par rapport à la fenêtre de texte. Les valeurs acceptées s'inspirent des lettres utilisées pour désigner les points cardinaux (N, S, E, W, ou encore NE, NW, SE, SW). Si vous ne souhaitez pas afficher un libellé, il vous suffit tout simplement de ne pas utiliser cette option.
  • Lignes 13 à 15 : Le libellé n'est rien d'autre qu'un widget Label standard, intégré dans le widget composite ScrolledText. On peut accéder à toutes ses options de configuration, en utilisant la syntaxe qui est présentée dans ces lignes : on y voit qu'il suffit d'associer le préfixe label_ au nom de l'option que l'on souhaite activer, pour définir aisément les couleurs d'avant- et d'arrièreplans, la police, la taille, et même l'espacement à réserver autour du widget (option pady).
  • Lignes 16-17 : En utilisant une technique similaire à celle qui est décrite ci-dessus pour le libellé, on peut accéder aux options de configuration du widget Text intégré dans ScrolledText. Il suffit cette fois d'associer aux noms d'option le préfixe text_.
  • Lignes 18 à 20 : Il est prévu un cadre (un widget Frame) autour du widget Text. L'option borderframe = 1 permet de le faire apparaître. On accède ensuite à ses options de configuration d'une manière similaire à celle qui a été décrite ci-dessus pour label_ et text_.
  • Lignes 21-22 : Ces options permettent de fixer globalement les dimensions du widget. Une autre possibilité serait de définir plutôt les dimensions de son composant Text (par exemple à l'aide d'options telles que text_width et text_height), mais alors les dimensions globales du widget risqueraient de changer en fonction du contenu (apparition/disparition automatique de barres de défilement). Note : le mot hull désigne le contenant global, c.à.d. le méga-widget lui-même.
  • Ligne 23 : Les options expand = YES et fill = BOTH de la méthode pack() indiquent que le widget concerné pourra être redimensionné à volonté, dans ses 2 dimensions horiz. et verticale.
  • Lignes 26 à 29 : Ces lignes définissent les trois balises « titre », « lien » et « cible » ainsi que le formatage du texte qui leur sera associé. La ligne 29 précise en outre que le texte associé à la balise « lien » sera « cliquable », avec indication du gestionnaire d'événement correspondant.
  • Ligne 42 : Importation de texte à partir d'un fichier. Note : Il est possible de préciser l'endroit exact où devra se faire l'insertion, en fournissant un index comme second argument.
  • Lignes 43-44 : Ces instructions insèrent des fragments de texte (respectivement au début et à la fin du texte préexistant), en associant une balise à chacun d'eux.
  • Ligne 49 : L'association des balises au texte est dynamique. A tout moment, vous pouvez activer une nouvelle association (comme nous le faisons ici en rattachant la balise « lien » à une portion de texte préexistante). Note : pour « détacher » une balise, utilisez la méthode tag_delete().

14-4-4. « Scrolled Canvas »

Le script ci-après vous montre comment vous pouvez exploiter le méga-widget ScrolledCanvas, lequel étend les possibilités du widget Canvas standard en lui associant des barres de défilement, un libellé et un cadre. Notre exemple constitue en fait un petit jeu d'adresse, dans lequel l'utilisateur doit réussir à cliquer sur un bouton qui s'esquive sans cesse. (Note : si vous éprouvez vraiment des difficultés pour l'attraper, commencez d'abord par dilater la fenêtre).

Image non disponible

Le widget Canvas est très versatile : il vous permet de combiner à volonté des dessins, des images bitmap, des fragments de texte, et même d'autres widgets, dans un espace parfaitement extensible. Si vous souhaitez développer l'un ou l'autre jeu graphique, c'est évidemment le widget qu'il vous faut apprendre à maîtriser en priorité.

Comprenez bien cependant que les indications que nous vous fournissons à ce sujet dans les présentes notes sont forcément très incomplètes. Leur objectif est seulement de vous aider à comprendre quelques concepts de base, afin que vous puissiez ensuite consulter les ouvrages de référence spécialisés dans de bonnes conditions.

Notre petite application se présente comme une nouvelle classe FenPrinc(), obtenue par dérivation à partir de la classe de méga-widgets Pmw.ScrolledCanvas(). Elle contient donc un grand canevas muni de barres de défilement, dans lequel nous commençons par planter un décor constitué de 80 ellipses de couleur dont l'emplacement et les dimensions sont tirés au hasard.

Nous y ajoutons également un petit clin d'oeil sous la forme d'une image bitmap, destinée avant tout à vous rappeler comment vous pouvez gérer ce type de ressource.

Nous y installons enfin un véritable widget : un simple bouton, en l'occurrence, mais la technique mise en oeuvre pourrait s'appliquer à n'importe quel autre type de widget, y compris un gros widget composite comme ceux que nous avons développés précédemment. Cette grande souplesse dans le développement d'applications complexes est l'un des principaux bénéfices apportés par le mode de programmation « orientée objet ».

Le bouton s'anime dès qu'on l'a enfoncé une première fois. Dans votre analyse du script ci-après, soyez attentifs aux méthodes utilisées pour modifier les propriétés d'un objet existant.

 
Sélectionnez
1. from Tkinter import *
2. import Pmw
3. from random import randrange
4.
5. Pmw.initialise()
6. coul =['sienna','maroon','brown','pink','tan','wheat','gold','orange','plum',
7.        'red','khaki','indian red','thistle','firebrick','salmon','coral']
8. 
9. class FenPrinc(Pmw.ScrolledCanvas):
10.    """Fenêtre principale : canevas extensible avec barres de défilement"""
11.    def __init__(self):
12.        Pmw.ScrolledCanvas.__init__(self,
13.                 usehullsize =1, hull_width =500, hull_height =300,
14.                 canvas_bg ='grey40', canvasmargin =10,                 
15.                 labelpos =N, label_text ='Attrapez le bouton !',
16.                 borderframe =1,
17.                 borderframe_borderwidth =3)
18.        # Les options ci-dessous doivent être précisées après initialisation :         
19.        self.configure(vscrollmode ='dynamic', hscrollmode ='dynamic')        
20.        self.pack(padx =5, pady =5, expand =YES, fill =BOTH)
21.        
22.        self.can = self.interior()        # accès au composant canevas
23.        # Décor : tracé d'une série d'ellipses aléatoires :
24.        for r in range(80):
25.            x1, y1 = randrange(-800,800), randrange(-800,800) 
26.            x2, y2 = x1 + randrange(40,300), y1 + randrange(40,300)
27.            couleur = coul[randrange(0,16)]
28.            self.can.create_oval(x1, y1, x2, y2, fill=couleur, outline='black')
29.        # Ajout d'une petite image GIF :    
30.        self.img = PhotoImage(file ='linux2.gif')    
31.        self.can.create_image(50, 20, image =self.img)    
32.        # Dessin du bouton à attraper :    
33.        self.x, self.y = 50, 100
34.        self.bou = Button(self.can, text ="Start", command =self.start)
35.        self.fb = self.can.create_window(self.x, self.y, window =self.bou)
36.        self.resizescrollregion()
37.            
38.    def anim(self):
39.        if self.run ==0:
40.            return
41.        self.x += randrange(-60, 61)
42.        self.y += randrange(-60, 61)
43.        self.can.coords(self.fb, self.x, self.y)
44.        self.configure(label_text = 'Cherchez en %s %s' % (self.x, self.y))
45.        self.resizescrollregion()
46.        self.after(250, self.anim)
47.        
48.    def stop(self):
49.        self.run =0   
50.        self.bou.configure(text ="Restart", command =self.start)   
51.    
52.    def start(self):
53.        self.bou.configure(text ="Attrapez-moi !", command =self.stop)   
54.        self.run =1
55.        self.anim()    
56.        
57.##### Main Program ##############
58.
59.if __name__ == '__main__':
60.    FenPrinc().mainloop()

Commentaires :

  • Ligne 6 : Tous ces noms de couleurs sont acceptés par Tkinter. Vous pourriez bien évidemment les remplacer par des descriptions hexadécimales, comme nous l'avons expliqué page 201.
  • Lignes 12 à 17 : Ces options sont très similaires à celles que nous avons décrites plus haut pour le widget ScrolledText. Le présent méga-widget intègre un composant Frame, un composant Label, un composant Canvas et deux composants Scrollbar. On accède aux options de configuration de ces composants à l'aide d'une syntaxe qui relie le nom du composant et celui de l'option par l'intermédiaire d'un caractère « souligné ».
  • Ligne 19 : Ces options définissent le mode d'apparition des barres de défilement. En mode « static », elles sont toujours présentes. En mode « dynamic », elles disparaissent si les dimensions du canevas deviennent inférieures à celles de la fenêtre de visualisation.
  • Ligne 22 : La méthode interior() renvoie la référence du composant Canvas intégré dans le méga-widget ScrolledCanvas. Les instructions suivantes (lignes 23 à 35) installent ensuite toute une série d'éléments dans ce canevas : des dessins, une image et un bouton.
  • Lignes 25 à 27 : La fonction randrange() permet de tirer au hasard un nombre entier compris dans un certain intervalle (Veuillez vous référer aux explications de la page 142).
  • Ligne 35 : C'est la méthode create_window() du widget Canvas qui permet d'y insérer n'importe quel autre widget (y compris un widget composite). Le widget à insérer doit cependant avoir été défini lui-même au préalable comme un esclave du canevas ou de sa fenêtre maîtresse. La méthode create_window() attend trois arguments : les coordonnées X et Y du point où l'on souhaite insérer le widget, et la référence de ce widget.
  • Ligne 36 : La méthode resizescrollregion() réajuste la situation des barres de défilement de manière à ce qu'elles soient en accord avec la portion du canevas actuellement affichée.
  • Lignes 38 à 46 : Cette méthode est utilisée pour l'animation du bouton. Après avoir repositionné le bouton au hasard à une certaine distance de sa position précédente, elle se ré-appelle ellemême après une pause de 250 millisecondes. Ce bouclage s'effectue sans cesse, aussi longtemps que la variable self.run contient une valeur non-nulle.
  • Lignes 48 à 55 : Ces deux gestionnaires d'événement sont associés au bouton en alternance. Ils servent évidemment à démarrer et à arrêter l'animation.

14-4-5. Barres d'outils avec bulles d'aide - expressions lambda

De nombreux programmes comportent une ou plusieurs « barres d'outils » (toolbar) constituées de petits boutons sur lesquels sont représentés des pictogrammes (icônes). Cette façon de faire permet de proposer à l'utilisateur un grand nombre de commandes spécialisées, sans que celles-ci n'occupent une place excessive à l'écran (un petit dessin vaut mieux qu'un long discours, dit-on).

La signification de ces pictogrammes n'est cependant pas toujours évidente, surtout pour les utilisateurs néophytes. Il est donc vivement conseillé de compléter les barres d'outils à l'aide d'un système de bulles d'aide (tool tips), qui sont des petits messages explicatifs apparaissant automatiquement lorsque la souris survole les boutons concernés.

L'application décrite ci-après comporte une barre d'outils et un canevas. Lorsque l'utilisateur clique sur l'un des boutons de la barre, le pictogramme qu'il porte est recopié dans le canevas, à un emplacement choisi au hasard :

Image non disponible

Dans notre exemple, chaque bouton apparaît entouré d'un sillon. Vous pouvez aisément obtenir d'autres aspects en choisissant judicieusement les options relief et bd (bordure) dans l'instruction d'instanciation des boutons. En particulier, vous pouvez choisir relief = FLAT et bd =0 pour obtenir des petits boutons « plats », sans aucun relief.

La mise en place des bulles d'aide est un jeu d'enfant. Il suffit d'instancier un seul objet Pmw.Balloon pour l'ensemble de l'application, puis d'associer un texte à chacun des widgets auxquels on souhaite associer une bulle d'aide, en faisant appel autant de fois que nécessaire à la méthode bind() de cet objet.

 
Sélectionnez
1. from Tkinter import *
2. import Pmw
3. from random import randrange
4.
5. # noms des fichiers contenant les icônes (format GIF):
6. images =('floppy_2','papi2','pion_1','pion_2','help_4')
7. textes =('sauvegarde','papillon','joueur 1','joueur 2','Aide')
8.
9. class Application(Frame):
10.    def __init__(self):
11.        Frame.__init__(self)
12.        # Création d'un objet <bulle d'aide> (un seul suffit) :
13.        tip = Pmw.Balloon(self)
14.        # Création de la barre d'outils (c'est un simple cadre) :
15.        toolbar = Frame(self, bd =1)
16.        toolbar.pack(expand =YES, fill =X)
17.        # Nombre de boutons à construire : 
18.        nBou = len(images)
19.        # Les icônes des boutons doivent être placées dans des variables
20.        # persistantes. Une liste fera l'affaire :
21.        self.photoI =[None]*nBou
22.        
23.        for b in range(nBou):
24.            # Création de l'icône (objet PhotoImage Tkinter) :
25.            self.photoI[b] =PhotoImage(file = images[b] +'.gif')
26.            
27.            # Création du bouton.:
28.            # On utilise une expression "lambda" pour transmettre
29.            # un argument à la méthode invoquée comme commande :
30.            bou = Button(toolbar, image =self.photoI[b], relief =GROOVE,
31.                         command = lambda arg =b: self.action(arg))
32.            bou.pack(side =LEFT)
33.            
34.            # association du bouton avec un texte d'aide (bulle) :
35.            tip.bind(bou, textes[b])    
36.
37.        self.ca = Canvas(self, width =400, height =200, bg ='orange')
38.        self.ca.pack()
39.        self.pack()
40.
41.    def action(self, b):
42.        "l'icône du bouton b est recopiée dans le canevas"
43.        x, y = randrange(25,375), randrange(25,175)
44.        self.ca.create_image(x, y, image =self.photoI[b])
45.        
46.Application().mainloop()

Métaprogrammation. Expressions lambda :

Vous savez qu'en règle générale, on associe à chaque bouton une commande, laquelle est une méthode ou une fonction particulière qui se charge d'effectuer le travail lorsque le bouton est activé. Or dans l'application présente, tous les boutons doivent faire à peu près la même chose (recopier un dessin dans le canevas), la seule différence entre eux étant le dessin concerné.

Pour simplifier notre code, nous voudrions donc pouvoir associer l'option command de tous nos boutons avec une seule et même méthode (ce sera la méthode action() ), mais en lui transmettant à chaque fois la référence du bouton particulier utilisé, de manière à ce que l'action accomplie puisse être différente pour chacun d'eux.

Une difficulté se présente, cependant, parce que l'option command du widget Button accepte seulement une valeur ou une expression, et non une instruction. Il est donc permis de lui indiquer la référence d'une fonction, mais pas de l'invoquer véritablement en lui transmettant des arguments éventuels (c'est la raison pour laquelle on indique le nom de cette fonction sans lui adjoindre de parenthèses).

On peut résoudre cette difficulté de deux manières :

  • Du fait de son caractère dynamique, Python accepte qu'un programme puisse se modifier lui-même, par exemple en définissant de nouvelles fonctions au cours de son exécution (c'est le concept de métaprogrammation).

    Il est donc possible de définir à la volée une fonction qui utilise des paramètres, en indiquant pour chacun de ceux-ci une valeur par défaut, et ensuite d'invoquer cette même fonction sans arguments là où ceux-ci ne sont pas autorisés. Puisque la fonction est définie en cours d'exécution, les valeurs par défaut peuvent être les contenus de variables, et le résultat de l'opération est un véritable transfert d'arguments.

    Pour illustrer cette technique, remplacez les lignes 27 à 31 du script par les suivantes :

     
    Sélectionnez
    # Création du bouton.:
    # On définit à la volée une fonction avec un paramètre, dont
    # la valeur par défaut est l'argument à transmettre.
    # Cette fonction appelle la méthode qui nécessite un argument :
     
    def agir(arg = b):
        self.action(arg)
     
    # La commande associée au bouton appelle la fonction ci-dessus :
    bou = Button(toolbar, image =self.photoI[b], relief =GROOVE,
                 command = agir)

  • Tout ce qui précède peut être simplifié en faisant appel à une expression lambda. Ce mot réservé Python désigne une expression qui renvoie un objet fonction, similaire à ceux que vous créez avec l'instruction def, mais avec la différence que lambda étant une expression et non une instruction, on peut l'utiliser comme interface afin d'invoquer une fonction (avec passage d'arguments) là où ce n'est normalement pas possible. Notez au passage qu'une telle fonction est anonyme (elle ne possède pas de nom).

    Par exemple, l'instruction :

     
    Sélectionnez
    lambda ar1=b, ar2=c : bidule(ar1,ar2)

    renvoie la référence d'une fonction anonyme qui aura elle-même invoqué la fonction bidule() en lui transmettant les arguments b et c , ceux-ci étant utilisés comme valeurs par défaut dans la définition des paramètres de la fonction.

    Cette technique utilise finalement le même principe que la précédente, mais elle présente l'avantage d'être plus concise, raison pour laquelle nous l'avons utilisée dans notre script. En revanche, elle est un peu plus difficile à comprendre :

     
    Sélectionnez
    command = lambda arg =b: self.action(arg)

    Dans cette portion d'instruction, la commande associée au bouton se réfère à une fonction anonyme dont le paramètre arg possède une valeur par défaut : la valeur de l'argument b.
    Invoquée sans argument par la commande, cette fonction anonyme peut tout de même utiliser son paramètre (avec la valeur par défaut) pour faire appel à la méthode cible self.action(), et l'on obtient ainsi un véritable transfert d'argument vers cette méthode .

Nous ne détaillerons pas davantage ici la question des expressions lambda, car elle déborde du cadre que nous nous sommes fixés pour cet ouvrage d'initiation. Si vous souhaitez en savoir plus, veuillez donc consulter l'un ou l'autre des ouvrages de référence cités dans la bibliographie.

14-5. Fenêtres avec menus

Nous allons décrire à présent la construction d'une fenêtre d'application dotée de différents types de menus « déroulants », chacun de ces menus pouvant être « détaché » de l'application principale pour devenir lui-même une petite fenêtre indépendante, comme dans l'illustration ci-dessous.

Cet exercice un peu plus long nous servira également de révision, et nous le réaliserons par étapes, en appliquant une stratégie de programmation que l'on appelle développement incrémental.

Comme nous l'avons déjà expliqué précédemment55, cette méthode consiste à commencer l'écriture d'un programme par une ébauche, qui ne comporte que quelques lignes seulement mais qui est déjà fonctionnelle. On teste alors cette ébauche soigneusement afin d'en éliminer les bugs éventuels. Lorsque l'ébauche fonctionne correctement, on y ajoute une fonctionnalité supplémentaire. On teste ce complément jusqu'à ce qu'il donne entière satisfaction, puis on en ajoute un autre, et ainsi de suite...

Cela ne signifie pas que vous pouvez commencer directement à programmer sans avoir au préalable effectué une analyse sérieuse du projet, dont au moins les grandes lignes devront être convenablement décrites dans un cahier des charges clairement rédigé.
Image non disponible

Il reste également impératif de commenter convenablement le code produit, au fur et à mesure de son élaboration. S'efforcer de rédiger de bons commentaires est en effet nécessaire, non seulement pour que votre code soit facile à lire (et donc à maintenir plus tard, par d'autres ou par vous-même), mais aussi pour que vous soyez forcés d'exprimer ce que vous souhaitez vraiment que la machine fasse (Cfr. Erreurs sémantiques, page 15)


Cahier des charges de l'exercice :

Notre application comportera simplement une barre de menus et un canevas. Les différentes rubriques et options des menus ne serviront qu'à faire apparaître des fragments de texte dans le canevas ou à modifier des détails de décoration, mais ce seront avant tout des exemples variés, destinés à donner un aperçu des nombreuses possibilités offertes par ce type de widget, accessoire indispensable de toute application moderne d'une certaine importance.

Nous souhaitons également que le code produit dans cet exercice soit bien structuré. Pour ce faire, nous ferons usage de deux classes : une classe pour l'application principale, et une autre pour la barre de menus. Nous voulons procéder ainsi afin de bien mettre en évidence la construction d'une application type incorporant plusieurs classes d'objets interactifs.

55 Voir page 16 : Recherche des erreurs et expérimentation

14-5-1. Première ébauche du programme :

Lorsque l'on construit l'ébauche d'un programme, il faut tâcher d'y faire apparaître le plus tôt possible la structure d'ensemble, avec les relations entre les principaux blocs qui constitueront l'application définitive. C'est ce que nous nous sommes efforcés de faire dans l'exemple ci-dessous :

 
Sélectionnez
1. from Tkinter import *
2.
3. class MenuBar(Frame):
4.     """Barre de menus déroulants"""
5.     def __init__(self, boss =None):
6.         Frame.__init__(self, borderwidth =2)
7.        
8.         ##### Menu <Fichier> #####
9.         fileMenu = Menubutton(self, text ='Fichier')
10.        fileMenu.pack(side =LEFT)
11.        # Partie "déroulante" :
12.        me1 = Menu(fileMenu)
13.        me1.add_command(label ='Effacer', underline =0,
14.                        command = boss.effacer)
15.        me1.add_command(label ='Terminer', underline =0,
16.                        command = boss.quit)
17.        # Intégration du menu :
18.        fileMenu.configure(menu = me1)    
19.
20.class Application(Frame):
21.    """Application principale"""
22.    def __init__(self, boss =None):
23.        Frame.__init__(self)
24.        self.master.title('Fenêtre avec menus')
25.        mBar = MenuBar(self)
26.        mBar.pack()
27.        self.can = Canvas(self, bg='light grey', height=190,
28.                          width=250, borderwidth =2)
29.        self.can.pack()
30.        self.pack()
31.
32.    def effacer(self):
33.        self.can.delete(ALL)
34.
35.if __name__ == '__main__':
36.    app = Application()
37.    app.mainloop()
Veuillez donc encoder ces lignes et en tester l'exécution. Vous devriez obtenir une fenêtre avec un canevas gris clair surmonté d'une barre de menus. A ce stade, la barre de menus ne comporte encore que la seule rubrique « Fichier ».

Cliquez sur la rubrique « fichier » pour faire apparaître le menu correspondant : l'option « Effacer » n'est pas encore fonctionnelle (elle servira à effacer le contenu du canevas), mais l'option « Terminer » devrait déjà vous permettre de fermer proprement l'application.
Image non disponible

Comme tous les menus gérés par Tkinter, le menu que vous avez créé peut être converti en menu « flottant » : il suffit de cliquer sur la ligne pointillée apparaissant en-tête de menu. Vous obtenez ainsi une petite fenêtre satellite, que vous pouvez alors positionner où bon vous semble sur le bureau.

Analyse du script :

La structure de ce petit programme devrait désormais vous apparaître familière : afin que les classes définies dans ce script puissent éventuellement être (ré)utilisées dans d'autres projets par importation, comme nous l'avons déjà expliqué précédemment56, le corps principal du programme (lignes 35 à 37) comporte l'instruction classique : if __name__ == '__main__':

Les deux instructions qui suivent consistent seulement à instancier un objet app et à faire fonctionner sa méthode mainloop(). Comme vous le savez certainement, nous aurions pu également condenser ces deux instructions en une seule.

L'essentiel du du programme se trouve cependant dans les définitions de classes qui précèdent :

La classe MenuBar() contient la description de la barre de menus. Dans l'état présent du script, elle se résume à une ébauche de constructeur.

  • Ligne 5 : Le paramètre boss réceptionne la référence de la fenêtre maîtresse du widget au moment de son instanciation. Cette référence va nous permettre d'invoquer les méthodes associées à cette fenêtre maîtresse, aux lignes 14 & 16.
  • Ligne 6 : Activation obligatoire du constructeur de la classe parente.
  • Ligne 9 : Instanciation d'un widget de la classe Menubutton(), défini comme un « esclave » de self (c'est-à-dire l'objet composite « barre de menus » dont nous sommes occupés à définir la classe). Comme l'indique son nom, ce type de widget se comporte un peu comme un bouton : une action se produit lorsque l'on clique dessus.
  • Ligne 12 : Afin que cette action consiste en l'apparition véritable d'un menu, il reste encore à définir celui-ci : ce sera encore un nouveau widget, de la classe Menu() cette fois, défini luimême comme un « esclave » du widget Menubutton instancié à la ligne 9.
  • Lignes 13 à 16 : On peut appliquer aux widgets de la classe Menu() un certain nombre de méthodes spécifiques, chacune d'elles acceptant de nombreuses options. Nous utilisons ici la méthode add_command() pour installer dans le menu les deux items « Effacer » et « Terminer ». Nous y intégrons tout de suite l'option underline, qui sert à définir un raccourci clavier : cette option indique en effet lequel des caractères de l'item doit apparaître souligné à l'écran. L'utilisateur sait alors qu'il lui suffit de frapper ce caractère au clavier pour que l'action correspondant à cet item soit activée (comme s'il avait cliqué dessus à l'aide de la souris).
    L'action à déclencher lorsque l'utilisateur sélectionne l'item est désignée par l'option command. Dans notre script, les commandes invoquées sont toutes les deux des méthodes de la fenêtre maîtresse, dont la référence aura été transmise au présent widget au moment de son instanciation par l'intermédiaire du paramètre boss. La méthode effacer(), que nous définissons nous-même plus loin, servira à vider le canevas. La méthode prédéfinie quit() provoque la sortie de la boucle mainloop() et donc l'arrêt du réceptionnaire d'événements associé à la fenêtre d'application.
  • Ligne 18 : Lorsque les items du menu ont été définis, il reste encore à reconfigurer le widget maître Menubutton de manière à ce que son option « menu » désigne effectivement le Menu que nous venons de construire. En effet, nous ne pouvions pas déjà préciser cette option lors de la définition initiale du widget Menubutton, puisqu'à ce stade le Menu n'existait pas encore. Nous ne pouvions pas non plus définir le widget Menu en premier lieu, puisque celui-ci doit être défini comme un « esclave » du widget Menubutton. Il faut donc bien procéder en trois étapes comme nous l'avons fait, en faisant appel à la méthode configure(). (Cette méthode peut être appliquée à n'importe quel widget préexistant pour en modifier l'une ou l'autre option).

La classe Application() contient la description de la fenêtre principale du programme ainsi que les méthodes gestionnaires d'événements qui lui sont associées.

  • Ligne 20 : Nous préférons faire dériver notre application de la classe Frame(), qui présente de nombreuses options, plutôt que de la classe primordiale Tk(). De cette manière, l'application toute entière est encapsulée dans un widget, lequel pourra éventuellement être intégré par la suite dans une application plus importante. Rappelons que de toute manière, Tkinter instanciera automatiquement une fenêtre maîtresse de type Tk() pour contenir de cette Frame.
  • Lignes 23-24 : Après l'indispensable activation du constructeur de la classe parente, nous utilisons l'attribut master que Tkinter associe automatiquement à chaque widget, pour référencer la fenêtre principale de l'application (la fenêtre maîtresse dont nous venons de parler au paragraphe précédent) et en redéfinir le bandeau-titre.
  • Lignes 25 à 29 : Instanciation de deux widgets esclaves pour notre Frame principale.
    La « barre de menus » est évidemment le widget défini dans l'autre classe.
  • Ligne 30 : Comme n'importe quel autre widget, notre Frame principale doit être mise en place.
  • Lignes 32-33 : La méthode servant à effacer le canevas est définie dans la classe présente (puisque l'objet canevas en fait partie), mais elle est invoquée par l'option command d'un widget esclave défini dans l'autre classe. Comme nous l'avons expliqué plus haut, ce widget esclave reçoit la référence de son widget maître par l'intermédiaire du paramètre boss.
    Toutes ces références sont hiérarchisées à l'aide de la qualification des noms par points.

56 Voir page 172: Modules contenant des bibliothèques de classes

14-5-2. Ajout de la rubrique « Musiciens »

Continuez le développement de ce petit programme, en ajoutant les lignes suivantes dans le constructeur de la classe MenuBar() (après la ligne 18) :

 
Sélectionnez
##### Menu <Musiciens> #####
self.musi = Menubutton(self, text ='Musiciens')
self.musi.pack(side =LEFT, padx ='3')
# Partie "déroulante" du menu <Musiciens> :
me1 = Menu(self.musi)
me1.add_command(label ='17e siècle', underline =1,
                foreground ='red', background ='yellow',
                font =('Comic Sans MS', 11),
                command = boss.showMusi17)
me1.add_command(label ='18e siècle', underline =1,
                foreground='royal blue', background ='white',
                font =('Comic Sans MS', 11, 'bold'),
                command = boss.showMusi18)
# Intégration du menu :
self.musi.configure(menu = me1)

... ainsi que les définitions de méthodes suivantes à la classe Application() (après la ligne 33) :

 
Sélectionnez
def showMusi17(self):
    self.can.create_text(10, 10, anchor =NW, text ='H. Purcell',
                         font=('Times', 20, 'bold'), fill ='yellow')
 
def showMusi18(self):
    self.can.create_text(245, 40, anchor =NE, text ="W. A. Mozart",
                         font =('Times', 20, 'italic'), fill ='dark green')

Lorsque vous y aurez ajouté toutes ces lignes, sauvegardez le script et exécutez-le.

Votre barre de menus comporte à présent une rubrique supplémentaire : la rubrique « Musiciens ».

Le menu correspondant propose deux items qui sont affichés avec des couleurs et des polices personnalisées. Vous pourrez vous inspirer de ces techniques décoratives pour vos projets personnels. A utiliser avec modération !

Les commandes que nous avons associées à ces items sont évidemment simplifiées afin de ne pas alourdir l'exercice : elles provoquent l'affichage de petits textes sur le canevas.
Image non disponible

Analyse du script

Les seules nouveautés introduites dans ces lignes concernent l'utilisation de polices de caractères bien déterminées (option font), ainsi que de couleurs pour l'avant-plan (option foreground) et le fond (option background) des textes affichés.

Veuillez noter encore une fois l'utilisation de l'option underline pour désigner les caractères correspondant à des raccourcis claviers (en n'oubliant pas que la numérotation des caractères d'une chaîne commence à partir de zéro), et surtout que l'option command de ces widgets accède aux méthodes de l'autre classe, par l'intermédiaire de la référence mémorisée dans l'attribut boss.

La méthode create_text() du canevas doit être utilisée avec deux arguments numériques, qui sont les coordonnées X et Y d'un point dans le canevas. Le texte transmis sera positionné par rapport à ce point, en fonction de la valeur choisie pour l'option anchor : Celle-ci détermine comment le fragment de texte doit être « ancré » au point choisi dans le canevas, par son centre, par son coin supérieur gauche, etc., en fonction d'une syntaxe qui utilise l'analogie des points cardinaux géographiques (NW = angle supérieur gauche, SE = angle inférieur droit, CENTER = centre, etc.)

14-5-3. Ajout de la rubrique « Peintres » :

Cette nouvelle rubrique est construite d'une manière assez semblable à la précédente, mais nous lui avons ajouté une fonctionnalité supplémentaire : des menus « en cascade ». Veuillez donc ajouter les lignes suivantes dans le constructeur de la classe MenuBar() :

 
Sélectionnez
##### Menu <Peintres> #####
self.pein = Menubutton(self, text ='Peintres')
self.pein.pack(side =LEFT, padx='3')
# Partie "déroulante" :
me1 = Menu(self.pein)
me1.add_command(label ='classiques', state=DISABLED)
me1.add_command(label ='romantiques', underline =0,
                command = boss.showRomanti)
# Sous-menu pour les peintres impressionistes :
me2 = Menu(me1)
me2.add_command(label ='Claude Monet', underline =7,
                command = boss.tabMonet)
me2.add_command(label ='Auguste Renoir', underline =8,
                command = boss.tabRenoir)
me2.add_command(label ='Edgar Degas', underline =6,
                command = boss.tabDegas)
# Intégration du sous-menu :
me1.add_cascade(label ='impressionistes', underline=0, menu =me2)
# Intégration du menu :
self.pein.configure(menu =me1)

... et les définitions suivantes dans la classe Application() :

 
Sélectionnez
def showRomanti(self):
    self.can.create_text(245, 70, anchor =NE, text = "E. Delacroix",
                         font =('Times', 20, 'bold italic'), fill ='blue')
 
def tabMonet(self):
    self.can.create_text(10, 100, anchor =NW, text = 'Nymphéas à Giverny',
                         font =('Technical', 20), fill ='red')
 
def tabRenoir(self):
    self.can.create_text(10, 130, anchor =NW,
                         text = 'Le moulin de la galette',
                         font =('Dom Casual BT', 20), fill ='maroon')
 
def tabDegas(self):
    self.can.create_text(10, 160, anchor =NW, text = 'Danseuses au repos',
                         font =('President', 20), fill ='purple')

Analyse du script :

Vous pouvez réaliser aisément des menus en cascade, en enchaînant des sous-menus les uns aux autres jusqu'à un niveau quelconque (il vous est cependant déconseillé d'aller au-delà de 5 niveaux successifs : vos utilisateurs s'y perdraient).

Un sous-menu est défini comme un menu « esclave » du menu de niveau précédent (dans notre exemple, me2 est défini comme un menu « esclave » de me1). L'intégration est assurée ensuite à l'aide de la méthode add_cascade().

L'un des items est désactivé (option state = DISABLED). L'exemple suivant vous montrera comment vous pouvez activer ou désactiver à volonté des items, par programme.

14-5-4. Ajout de la rubrique « Options » :

La définition de cette rubrique est un peu plus compliquée, parce que nous allons y intégrer l'utilisation de variables internes à Tkinter.

Les fonctionnalités de ce menu sont cependant beaucoup plus élaborées : les options ajoutées permettent en effet d'activer ou de désactiver à volonté les rubriques « Musiciens » et « Peintres », et vous pouvez également modifier à volonté l'aspect de la barre de menus elle-même.

Veuillez donc ajouter les lignes suivantes dans le constructeur de la classe MenuBar() :
Image non disponible
 
Sélectionnez
##### Menu <Options> #####
optMenu = Menubutton(self, text ='Options')
optMenu.pack(side =LEFT, padx ='3')
# Variables Tkinter :
self.relief = IntVar()
self.actPein = IntVar()
self.actMusi = IntVar()
# Partie "déroulante" du menu :
self.mo = Menu(optMenu)
self.mo.add_command(label = 'Activer :', foreground ='blue')
self.mo.add_checkbutton(label ='musiciens',
                        command = self.choixActifs, variable =self.actMusi)
self.mo.add_checkbutton(label ='peintres',
                        command = self.choixActifs, variable =self.actPein)
self.mo.add_separator()
self.mo.add_command(label = 'Relief :', foreground ='blue')
for (v, lab) in [(0,'aucun'), (1,'sorti'), (2,'rentré'),
                 (3,'sillon'), (4,'crête'), (5,'bordure')]:
    self.mo.add_radiobutton(label =lab, variable =self.relief,
                            value =v, command =self.reliefBarre)
# Intégration du menu :
optMenu.configure(menu = self.mo)

... ainsi que les définitions de méthodes suivantes (toujours dans la classe MenuBar()) :

 
Sélectionnez
def reliefBarre(self):
    choix = self.relief.get()
    self.configure(relief =[FLAT,RAISED,SUNKEN,GROOVE,RIDGE,SOLID][choix])
 
def choixActifs(self):
    p = self.actPein.get()
    m = self.actMusi.get()
    self.pein.configure(state =[DISABLED, NORMAL][p])
    self.musi.configure(state =[DISABLED, NORMAL][m])

Analyse du script

a) Menu avec « cases à cocher »

Notre nouveau menu déroulant comporte deux parties. Afin de bien les mettre en évidence, nous avons inséré une ligne de séparation ainsi que deux « faux items » (« Activer : » et « Relief : ») qui servent simplement de titres. Nous faisons apparaître ceux-ci en couleur pour que l'utilisateur ne les confonde pas avec de véritables commandes.

Les items de la première partie sont dotées de « cases à cocher ». Lorsque l'utilisateur effectue un clic de souris sur l'un ou l'autre de ces items, les options correspondantes sont activées ou désactivées, et ces états « actif / inactif » sont affichés sous la forme d'une coche. Les instructions qui servent à mettre en place ce type de rubrique sont assez explicites. Elles présentent en effet ces items comme des widgets de type « chekbutton » :

 
Sélectionnez
self.mo.add_checkbutton(label = 'musiciens', command = choixActifs,
                        variable = mbu.me1.music)

Il est important de comprendre ici que ce type de widget comporte nécessairement une variable interne, destinée à mémoriser l'état « actif / inactif » du widget. Cette variable ne peut pas être une variable Python ordinaire, parce que les classes de la bibliothèque Tkinter sont écrites dans un autre langage. Et par conséquent, on ne pourra accéder à une telle variable interne qu'à travers une interface. Cette interface, appelée « variable Tkinter », est en fait un objet, que l'on crée à partir d'une classe particulière, qui fait partie du module Tkinter au même titre que les classes de widgets. L'utilisation de ces « objets-variables » est relativement simple :

  • La classe IntVar() permet de créer des objets équivalents à des variables de type entier. On commence donc par créer un ou plusieurs de ces objets-variables, que l'on mémorise dans notre exemple comme de nouveaux attribiuts d'instance :

     
    Sélectionnez
    self.actMusi =IntVar()

    Après cette affectation, l'objet référencé dans self.actMusi contient désormais l'équivalent d'une variable de type entier, dans un format spécifique à Tkinter.

  • Ensuite, on associe l'option « variable » de l'objet checkbutton à la variable Tkinter ainsi définie :

     
    Sélectionnez
    self.mo.add_checkbutton(label ='musiciens', variable =self.actMusi)
  • Il est nécessaire de procéder ainsi en deux étapes, parce que Tkinter ne peut pas directement assigner des valeurs aux variables Python. Pour une raison similaire, il n'est pas possible à Python de lire directement le contenu d'une variable Tkinter. Il faut utiliser pour cela une méthode spécifique de cette classe d'objets : la méthode get()57 :

     
    Sélectionnez
    m = self.actMusi.get()

    Dans cette instruction, nous affectons à m (variable ordinaire de Python) le contenu d'une variable Tkinter (laquelle est elle-même associée à un widget bien déterminé).

Tout ce qui précède peut vous paraître un peu compliqué. Considérez simplement qu'il s'agit de votre première rencontre avec les problèmes d'interfaçage entre deux langages de programmation différents, utilisés ensemble dans un projet composite.


b) Menu avec choix exclusifs

La deuxième partie du menu « Options » permet à l'utilisateur de choisir l'aspect que prendra la barre de menus, parmi six possibilités. Il va de soi que l'on ne peut activer qu'une seule de ces possibilités à la fois. Pour mettre en place ce genre de fonctionnalité, on fait classiquement appel appel à des widgets de type « boutons radio ». La caractéristique essentielle de ces widgets est que plusieurs d'entre eux doivent être associés à une seule et même variable Tkinter. A chaque bouton radio correspond alors une valeur particulière, et c'est cette valeur qui est affectée à la variable lorsque l'utilisateur sélectionne le bouton.

Ainsi, l'instruction :

 
Sélectionnez
self.mo.add_radiobutton(label ='sillon', variable =self.relief,
                        value =3, command =self.reliefBarre)

configure un item du menu «Options» de telle manière qu'il se comporte comme un bouton radio.

Lorsque l'utilisateur sélectionne cet item, la valeur 3 est affectée à la variable Tkinter self.relief (celle-ci étant désignée à l'aide de l'option variable du widget), et un appel est lancé en direction de la méthode reliefBarre(). Celle-ci récupère alors la valeur mémorisée dans la variable Tkinter pour effectuer son travail.

Dans le contexte particulier de ce menu, nous souhaitons proposer 6 possibilités différentes à l'utilisateur. Il nous faut donc six « boutons radio », pour lesquels nous pourrions encoder six instructions similaires à celle que nous avons reproduite ci-dessus, chacune d'elles ne différant des cinq autres que par ses options value et label. Dans une situation de ce genre, la bonne pratique de programmation consiste à placer les valeurs de ces options dans une liste, et à parcourir ensuite cette liste à l'aide d'une boucle for, afin d'instancier les widgets avec une instruction commune :

 
Sélectionnez
for (v, lab) in [(0,'aucun'), (1,'sorti'), (2,'rentré'),
                 (3,'sillon'), (4,'crête'), (5,'bordure')]:
    self.mo.add_radiobutton(label =lab, variable =self.relief,
                            value =v, command =self.reliefBarre

La liste utilisée est une liste de 6 tuples (valeur, libellé). A chacune des 6 itérations de la boucle, un nouvel item radiobutton est instancié, dont les options label et value sont extraites de la liste par l'intermédiaire des variables lab et v.


Dans vos projets personnels, il vous arrivera fréquemment de constater que vous pouvez ainsi remplacer des suites d'instructions similaires, par une structure de programmation plus compacte (en général, la combinaison d'une liste et d'une boucle, comme dans l'exemple ci-dessus).

Vous découvrirez petit à petit encore d'autres techniques pour alléger votre code : nous en fournissons encore un exemple dans le paragraphe suivant. Tâchez cependant de garder à l'esprit cette règle essentielle, qu'un bon programme doit avant tout rester lisible et commenté.


c) Contrôle du flux d'exécution à l'aide d'une liste

Veuillez à présent considérer la définition de la méthode reliefBarre() :

A la première ligne, la méthode get() nous permet de récupérer l'état d'une variable Tkinter qui contient le numéro du choix opéré par l'utilisateur dans le sous-menu « Relief : ».

A la seconde ligne, nous utilisons le contenu de la variable choix pour extraire d'une liste de six éléments celui qui nous intéresse. Par exemple, si choix contient la valeur 2, c'est l'option SUNKEN qui sera utilisée pour reconfigurer le widget.

La variable choix est donc utilisée ici comme un index, servant à désigner un élément de la liste. En lieu et place de cette construction compacte, nous aurions pu programmer une série de tests conditionnels, comme par exemple :

 
Sélectionnez
if choix ==0:
    self.configure(relief =FLAT)
elif choix ==1:
    self.configure(relief =RAISED)
elif choix ==2:
    self.configure(relief =SUNKEN)
...
etc.

D'un point de vue strictement fonctionnel, le résultat serait exactement le même. Vous admettrez cependant que la construction que nous avons choisie est d'autant plus efficiente, que le nombre de possibilités de choix est élevé. Imaginez par exemple que l'un de vos programmes personnels doive effectuer une sélection dans un très grand nombre d'éléments : avec une construction du type cidessus, vous seriez peut-être amené à encoder plusieurs pages de « elif » !


Nous utilisons encore la même technique dans la méthode choixActifs(). Ainsi l'instruction :

 
Sélectionnez
self.pein.configure(state =[DISABLED, NORMAL][p])

utilise le contenu de la variable p comme index pour désigner lequel des deux états DISABLED, NORMAL doit être sélectionné pour reconfigurer le menu « Peintres ».

Lorsqu'elle est appelée, la méthode choixActifs() reconfigure donc les deux rubriques « Peintres » et « Musiciens » de la barre de menus, pour les faire apparaître « normales » ou « désactivées » en fonction de l'état des variables m et p, lesquelles sont elles-mêmes le reflet de variables Tkinter.

Ces variables intermédiaires m et p ne servent en fait qu'à clarifier le script. Il serait en effet parfaitement possible de les éliminer, et de rendre le script encore plus compact, en utilisant la composition d'instructions. On pourrait par exemple remplacer les deux instructions :

 
Sélectionnez
m = self.actMusi.get()
self.musi.configure(state =[DISABLED, NORMAL][m])

par une seule, telle que :

 
Sélectionnez
self.musi.configure(state =[DISABLED, NORMAL][self.actMusi.get()])

Notez cependant que ce que l'on gagne en compacité se paie d'une certaine perte de lisibilité.


d) Pré-sélection d'une rubrique

Pour terminer cet exercice, voyons encore comment vous pouvez déterminer à l'avance certaines sélections, ou bien les modifier par programme.

Veuillez donc ajouter l'instruction suivante dans le constructeur de la classe Application() (juste avant l'instruction self.pack() ,par exemple) :

 
Sélectionnez
mBar.mo.invoke(2)

Lorsque vous exécutez le script ainsi modifié, vous constatez qu'au départ la rubrique « Musiciens » de la barre de menus est active, alors que la rubrique « Peintres » ne l'est pas. Programmées comme elles le sont, ces deux rubriques devraient être actives toutes deux par défaut. Et c'est effectivement ce qui se passe si nous supprimons l'instruction :

 
Sélectionnez
mBar.mo.invoke(2)

Nous vous avons suggéré d'ajouter cette instruction au script, pour vous montrer comment vous pouvez effectuer par programme la même opération que celle que l'on obtient normalement avec un clic de souris.

L'instruction ci-dessus invoque le widget mBar.mo en actionnant la commande associée au deuxième item de ce widget. En consultant le listing, vous pouvez vérifier que ce deuxième item est bien l'objet de type checkbutton qui active/désactive le menu « Peintres » (Rappelons encore une fois que l'on numérote toujours à partir de zéro).

Au démarrage du programme, tout se passe donc comme si l'utilisateur effectuait tout de suite un premier clic sur la rubrique « Peintres » du menu « Options », ce qui a pour effet de désactiver le menu correspondant.

57 Pour écrire dans une variable Tkinter, il faudrait utiliser la méthode set() . Exemple :
self.actMusi.set(45)


précédentsommairesuivant

Licence Creative Commons
Le contenu de cet article est rédigé par Gérard Swinnen et est mis à disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2013 Developpez.com.