Quelques widgets

\(\newcommand{\ds}{\displaystyle}\) \(\newcommand{\Frac}{\ds\frac}\) \(\renewcommand{\r}{\mathbb{ R}}\) \(\newcommand{\C}{\mathbb{ C}}\) \(\newcommand{\n}{\mathbb{ N}}\) \(\newcommand{\z}{\mathbb{ Z}}\) \(\newcommand{\Q}{\mathbb{ Q}}\) \(\newcommand{\N}{\mathbb{ N}}\) \(\newcommand{\n}{\mathbb{ N}}\) \(\newcommand{\ol}{\overline}\) \(\newcommand{\abs}[1]{\left| \,{#1} \right|}\) \(\newcommand{\pv}{\;;\;}\) \(\newcommand{\ens}[1]{\left\{ {#1} \right\}}\) \(\newcommand{\mens}[1]{\setminus\left\{ {#1} \right\}}\) \(\newcommand{\Par}[1]{\left({#1}\right)}\)

Quelques widgets

Le widget label

Un label est un widget constitué d’un court message textuel (en français, on devrait dire étiquette). Par exemple :

../../../_images/creer_integrer_widget.png
from tkinter import *

root = Tk()
lbl=Label(root, text="Coucou !", font='Arial 30 bold')
lbl.pack(padx=15, pady=15)

root.mainloop()

Le label est créé avec le constructeur Label. Le texte est transmis avec l’option text et on peut aussi changer la police avec l’option font.

Un label est un des widgets les plus simples et est souvent utilisé. Il est soit statique (une description par exemple) soit dynamique (comme un compteur ou une durée qui évoluent).

Voici un exemple de labels qui indiquent le nombre de carrés générés aléatoirement sur un canevas

../../../_images/example_label.gif

Ici, il y a deux labels :

  • un label statique, à gauche, qui porte la mention Nombre de carrés,
  • un label dynamique, à droite, qui indique la valeur du nombre de carrés présents sur le canevas.

Le compteur change toutes les demi-secondes.

Le code correspondant est :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from tkinter import *
from random import randrange

WIDTH=200
HEIGHT=200

COTE=20

root = Tk()
cnv = Canvas(root, width=WIDTH, height=HEIGHT, background="ivory")
cnv.pack()

def dessiner():
    global cpt
    k=randrange(3)
    cpt+=k
    for i in range(k):
        color="#%s%s%s" %(randrange(10),randrange(10),
                          randrange(10))
        x=randrange(WIDTH)
        y=randrange(HEIGHT)
        cnv.create_rectangle(x, y, x+COTE, y+COTE, fill=color,
                             outline='')

descr = Label(root, text="Nombre de\ncarrés", font='Arial 10 bold')
descr.pack(side='left')

compteur = Label(root, text="0", font='Arial 15 bold')
compteur.pack(side='right')

def animer():
    dessiner()
    compteur['text']=str(cpt)
    cnv.after(500, animer)

cpt=0
animer()

root.mainloop()
  • Ligne 25 : création d’un label descr portant le texte "Nombre de carrés" et placé en bas à gauche de l’interface.
  • Ligne 28 : création d’un label compteur placé en bas à droite et indiquant le nombre de carrés.
  • lignes 25 et 28 : un label est créé avec le constructeur Label qui est un widget Tkinter. Un label peut utiliser une police que l’on définit avec l’option font.
  • Ligne 34 : toutes les demi-secondes, le texte du label est mis à jour en indiquant le nombre de carrés aléatoires (cf. fonction dessiner lignes 13-23) présents sur le canevas.
  • Ligne 33 : instruction pour mettre à jour le label compteur.

Créer et intégrer un widget en une seule instruction

L’interface graphique suivante :

../../../_images/creer_integrer_widget.png

montre un court morceau de texte et qui est placé dans un widget de type label. Le code correspondant est :

1
2
3
4
5
6
7
from tkinter import *

root = Tk()
lbl=Label(root, text="Coucou !", font='Arial 30 bold')
lbl.pack(padx=15, pady=15)

root.mainloop()

La création du widget (ligne 4) et l’intégration du widget dans la fenêtre en utilisant un gestionnaire de géométrie (pack ici, ligne 5) sont effectuées dans deux instructions différentes. Toutefois, la variable lbl n’est pas réutilisé ailleurs dans le code. Dans ce cas, il est possible de faire les deux opérations en une seule ce qui dispense de définir une variable désignant le widget. D’où le code plus simple suivant :

1
2
3
4
5
6
from tkinter import *

root = Tk()
Label(root, text="Coucou !", font='Arial 30 bold').pack(padx=15, pady=15)

root.mainloop()
  • Ligne 4 : création et placement du widget en une seule instruction.

Attention, bugs en vue !

Cette méthode n’est pas toujours bien comprise et peut être source de bugs, en particulier chez les débutants. Il est donc préférable de l’éviter, surtout que son bénéfice est réduit. Dès qu’une interface a un peu de complexité, il est rare qu’un widget qui a été crée ne soit pas référencé dans le code et donc l’astuce ci-dessus devient une nuisance. Certains pensent alors utiliser un code comme celui-ci :

1
2
3
4
5
6
7
8
from tkinter import *

root = Tk()
lbl=Label(root, text="Coucou !", font='Arial 30 bold').pack(padx=15, pady=15)

print(lbl["text"])

root.mainloop()

L’intention ici est d’afficher dans la console (cf. ligne 6) le contenu du label (« coucou », ligne 4). Certes, une variable lbl a été créée sauf qu’elle ne référence pas le label mais le retour de l’appel Label(...).pack() qui lui vaut None. Donc la ligne 6 va planter le programme :

    print(lbl["text"])
TypeError: 'NoneType' object is not subscriptable

Pour s’en sortir, il faut découpler la création du widget et son placement avec la méthode pack. D’où le code correct suivant :

from tkinter import *

root = Tk()
lbl=Label(root, text="Coucou !", font='Arial 30 bold')
lbl.pack(padx=15, pady=15)

print(lbl["text"])

root.mainloop()

qui affiche :

Coucou !

Le widget bouton

Tkinter dispose d’un widget bouton avec la classe Button. On peut donc créer un bouton qui réagira de façon appropriée à chaque clic du bouton.

Exemple :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
from tkinter import *
from random import randrange

WIDTH=200
HEIGHT=200

COTE=20

root = Tk()
cnv = Canvas(root, width=WIDTH, height=HEIGHT, background="ivory")
cnv.pack()

def dessiner():
    color="#%s%s%s" %(randrange(10),randrange(10),randrange(10))
    x=randrange(WIDTH)
    y=randrange(HEIGHT)
    cnv.create_rectangle(x, y, x+COTE, y+COTE, fill=color, outline='')

btn = Button(root, text="Carré\naléatoire", command=dessiner)
btn.pack()

root.mainloop()

qui produit :

../../../_images/exemple_bouton.gif

Lorsque l’utilisateur clique sur le bouton, un carré coloré aléatoire est placé sur le canevas.

Décrivons le code en rapport avec le bouton :

  • Ligne 19 : un bouton est créé. Il permet l’affichage du texte avec l’option text.
  • Ligne 19 : on peut donner une option command au bouton : cette option doit pointer vers la fonction qui sera exécutée lorsque l’utilisateur appuie sur le bouton. Cette fonction doit être définie avant la définition du bouton (ici ligne 13, la fonction dessiner). Ce type de fonction de commande ne doit recevoir aucun argument.
  • lignes 13-17 : lorsqu’on clique sur le bouton, la fonction dessiner est appelée. Cette fonction dessine avec une couleur aléatoire (ligne 14) à une position aléatoire (lignes 15-16) un carré sur le canevas (ligne 17).

La longueur variable de texte dans un bouton peut modifier la taille du bouton et donc de la fenêtre parente. Pour éviter cela, on peut imposer des dimensions au bouton :

  • height = nombre de lignes
  • width = nombre de caractères

Si plus de caractères que la capacité permise par la largeur sont donnés dans le texte, des caractères seront invisibles :

from tkinter import *

root = Tk()

def play():
    b['text']='ABCDEFGHIJKLM'

b=Button(root, text="Play", command=play, width=5)
b.pack(padx=50, pady=20)
root.mainloop()

qui affiche après un clic sur le bouton :

../../../_images/largeur_bouton.gif

On peut afficher un bouton coloré avec l’option bg (qui signifie background) :

1
2
3
4
5
6
7
from tkinter import *

root = Tk()

b=Button(root, text="Bouton\nrouge", width=5, bg="red")
b.pack(padx=50, pady=20)
root.mainloop()
../../../_images/bouton_rouge.png

Un bouton pour montrer une image

Un bouton, de même que le canevas est un widget. Le code ci-dessous place un bouton à côté d’un canevas :

bouton_image.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from tkinter import *

SIDE=400
root = Tk()
cnv = Canvas(root, width=SIDE, height=SIDE, bg='ivory')
cnv.pack()

btn=Button(root, text="Nouveau")
btn.pack()

root.mainloop()
../../../_images/bouton_image1.png
  • Ligne 8 : un bouton est construit avec le constructeur Button (c’est une classe).
  • Ligne 9 : comme pour tout widget, il faut l’inclure dans son environnement avec une méthode particulière, ici la méthode pack.
  • Ligne 8 : le texte passé dans l’option text est affiché sur le bouton.
  • Si on clique sur le bouton, rien ne se passe de visible. Pour lier une action à un bouton, il faudrait lui passer une option command.

Donnons une possibilité d’action au bouton : chaque fois qu’on clique le bouton, un logo 80x80 est dessiné sur le canevas :

../../../_images/bouton_image11.gif

Voici le code :

bouton_image1.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
from tkinter import *
from random import randrange

SIDE=400
root = Tk()
cnv = Canvas(root, width=SIDE, height=SIDE, bg='ivory')
cnv.pack()

logo = PhotoImage(file="python80.png")

def show():
    center= (randrange(SIDE),randrange(SIDE))
    cnv.create_image(center, image=logo)

btn=Button(root, text="Nouveau", command=show)
btn.pack()

root.mainloop()
  • Ligne 15 : une option command a été donnée au constructeur Button : command référence une fonction sans paramètre, ici la fonction show, qui est exécutée à chaque pression sur le bouton.
  • Lignes 11-13 : la fonction show ne peut prendre aucun paramètre ; elle dessine un logo Python aléatoire sur le canevas.

Slider basique

Un slider (en français, un curseur) est un widget permettant de modifier une variable ou un état en faisant glisser un curseur sur un axe :

../../../_images/slider_minimal.gif

Dans l’exemple ci-dessus, le curseur est mobile et indique le rayon du cercle qui est dessiné sur le canevas.

Le code correspondant est :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
from tkinter import *

root = Tk()
cnv = Canvas(root, width=400, height=400)
cnv.pack()

old=None

def rayon(r):
    global old
    r=int(r)
    cnv.delete(old)
    old=cnv.create_oval(200-r,200-r,200+r, 200+r)

curseur = Scale(root, orient = "horizontal", command=rayon, from_=0, to=200)
curseur.pack()

root.mainloop()
  • Ligne 15 : création d’un curseur.

  • Ligne 16 : placement du curseur.

  • Ligne 15 : quelques options du curseur :

    • orient : l’orientation horizontale ou verticale
    • command : la fonction qui est appelée quand on déplace le curseur (ici, la fonction rayon)
    • from_ : la valeur (numérique) en début du curseur (par défaut à gauche pour une orientation horizontale) transmise à la fonction de commande ; le blanc souligné qui termine from_ est utilisé car on ne peut pas utiliser la variable from puisque c’est un mot-clé du langage Python.
    • to : la valeur (numérique) en fin de curseur (par défaut à droite pour une orientation horizontale) transmise à la fonction de commande ;
  • Lignes 9-18 : la fonction de commande appelée lorsque le curseur est modifié. Il semblerait que, par défaut, cette fonction reçoive en argument une chaîne de caractères représentant un nombre entier et qui correspond à la position du curseur dans sa graduation.

On peut aussi changer la longueur du curseur avec l’option length. Par exemple,

curseur = Scale(root, command=tirer, from_=0, to=180, length=200)

va créer un curseur de 200 pixels de longueur.

Le widget entrée

Le widget Entry (entrée en français) est un widget analogue aux champs des formulaires html : l’utilisateur communique avec le programme en lui transmettant dans une zone de texte des données écrites au clavier (ou par copier-coller).

Exemple simple

Dans l’exemple ci-dessous, on montre comment une entrée transmet son contenu à une autre partie du programme. L’utilisateur remplit l’entrée avec du texte et s’il clique sur un bouton, cela affiche le contenu de l’entrée dans la console :

../../../_images/demo_simple_entree.gif

Voici le code commenté correspondant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from tkinter import *

root = Tk()

my_entry = Entry(root)
my_entry.pack()

def afficher():
    print(my_entry.get())

bouton=Button(root, text="Afficher", command=afficher)
bouton.pack(padx=50, pady=10)

root.mainloop()
  • Lignes 5-6 : définition et placement de l’entrée my_entry
  • Ligne 9 : une entrée Tkinter a une la méthode get qui permet de récupérer le contenu de l’entrée. Ici, ce contenu est affiché dans la console avec la fonction print.
  • Ligne 10-11 : définition et placement du bouton qui, lorsqu’il est cliqué, appelle la fonction afficher.

Autre exemple

Voici un autre exemple, du même ordre mais un peu moins simple :

../../../_images/demo_entree.gif

L’utilisateur entre au clavier un entier dans le champ en bas de la fenêtre et il valide cette entrée par appui sur la touche ESPACE. Cela provoque immédiatement l’affichage sur le canvas de carrés aléatoirement placés et en nombre égal à la valeur tapée au clavier.

Le code correspondant est :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from tkinter import *
from random import randrange

WIDTH=200
HEIGHT=200

COTE=20

root = Tk()
cnv = Canvas(root, width=WIDTH, height=HEIGHT, background="ivory")
cnv.pack()

def dessiner(event):
    n=int(my_entry.get())
    color="#%s%s%s" %(randrange(10),randrange(10),randrange(10))
    for i in range(n):
        x=randrange(WIDTH)
        y=randrange(HEIGHT)
        cnv.create_rectangle(x, y, x+COTE, y+COTE, fill=color, outline='')

my_entry = Entry(root)
my_entry.pack()
my_entry.bind('<space>', dessiner)

root.mainloop()
  • Ligne 21 : création d’une entrée.
  • Ligne 22 : placement de l’entrée dans sa fenêtre.
  • Ligne 23 : association de l’entrée my_entry avec une touche du clavier : quand l’entrée a le focus, si on appuie sur une certaine touche indiquée comme premier argument de bind``(dans notre exemple la touche ENTRÉE référencée par ``<Return>), une fonction de commande est exécutée (ici la fonction dessiner)
  • Lignes 13-19 : la fonction appelée lorsque l’entrée est validée. Cette fonction reçoit un événement event. Cet événement correspond à l’appui sur la touche ENTRÉE mais n’est pas véritablement utilisé par la fonction dessiner. L’objet my_entry dispose d’une méthode get permettant de capturer le contenu textuel de l’entrée, ici un entier. Cet entier est lu sous forme de chaîne de caractères, donc il faut le convertir avec la fonction int (ligne 14). Le reste du code crée un carré aléatoire et le dessine sur le canevas.

Noter que le widget Entry ne gère pas automatiquement la validation du contenu de l’entrée par appui sur une touche (cf. ligne 23).

Options d’un widget : lecture, écriture

Un widget (par exemple un canevas, un bouton, etc) est initialisé avec son constructeur. Par exemple, ci-dessous

../../../_images/constructeur_widget.png
1
2
3
4
5
6
7
from tkinter import *

root = Tk()
L=Label(root, text="Encore!", bg='pink', width=25)
L.pack(padx=50, pady=20)

root.mainloop()

à la ligne 4, le constructeur Label définit le texte du label, la couleur de fond et sa longueur.

Une fois le widget construit, on peut souhaiter consulter la valeur d’une option mais aussi la modifier. C’est possible de deux façons :

  • soit en utilisant le widget comme on utiliserait un dictionnaire,
  • soit en utilisant la méthode configure et un ou plusieurs arguments nommés pour accéder aux options.

Le code ci-dessous illustre ces deux possibilités :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from tkinter import *

root = Tk()
lbl=Label(root, text="Encore!", bg='pink', width=25)
lbl.pack(padx=50, pady=20)

print(lbl['text'])
print(lbl.configure('text'))
print(lbl.cget('text'))

root.mainloop()
12
13
14
Encore!
('text', 'text', 'Text', '', 'Encore!')
Encore!

En lecture, on peut accéder, par exemple, à la valeur de l’option text du label en utilisant :

  • le label comme dictionnaire (ligne 7)
  • la méthode configure (ligne 8)
  • la méthode cget (ligne 9) dont le résultat est moins facilement interprétable.

Concernant les modifications (« écriture »), soit le code suivant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from tkinter import *

root = Tk()

def animer(i):
    if i<5:
        print(i)
        L['text']+="Encore!"
        root.after(500, animer, i+1)
    if i==5:
        L.configure(text=L['text']+'FIN')


L=Label(root, text="Encore!")
L.pack(padx=50, pady=20)

animer(0)

root.mainloop()

Le programme crée un label contenant le texte Encore! et modifie le texte du label en le répétant 5 fois et en terminant en ajoutant le mot FIN.

Ci-dessous l’évolution du widget :

../../../_images/modif_widget.gif

Deux méthodes sont ici utilisées :

  • Ligne 8 : on accède à l’option du label L en utilisant L comme un dictionnaire avec pour clé le nom de l’option, donc ici, on modofoe L['text'] ;
  • Ligne 11 : on accède à l’option du label L en utilisant la méthode configure du label et l’argument nommé correspondant à l’option que l’on veut changer, ici text.

Image sur un bouton

Sur un bouton, il est usuel de placer du texte. Mais on peut placer une image à la place de texte (ci-dessous, deux images sont placées):

../../../_images/image_bouton.png

On suppose qu’on a placé une image python.gif à la racine du fichier source. Le code correspondant est :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from tkinter import *

root = Tk()
logo = PhotoImage(file="python.gif")
btnA=Button(root, image=logo)
btnA.pack(padx=10, pady=10)
btnB=Button(root, width=50, height=50, image=logo)
btnB.pack(padx=10, pady=10)

root.mainloop()
  • Ligne 5 : on crée un bouton repéré par une image ; aucune dimension n’est donné donc le bouton prend la taille de l’image. Noter que l’image doit être converti au format PhotoImage (ligne 4).
  • Ligne 7 : une autre image est créée. On lui impose des dimensions. Les dimensions sont mesurées sur l’image cible à partir de son coin supérieur gauche.
  • Lignes 5 et 7 : pour simplifier l’illustration, le clic sur les boutons n’a aucune action visible.

Options d’un widget entrée

Le code ci-dessous montree quelques options d’une entrée :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from tkinter import *
root = Tk()

my_entry = Entry(root,
                 font='Arial 60 bold',
                 width='5',
                 bg='lavender',
                 insertofftime=500,
                 relief=FLAT)
my_entry.pack(padx=20, pady=20)
my_entry.focus_set()

root.mainloop()

qui produit :

../../../_images/option_entree.gif

Par défaut, une entrée n’a pas le focus autrement dit, elle n’est pas en état de lire des caractères. On l’active :

  • soit avec la touche TAB
  • soit en cliquant dans la zone de saisie de l’entrée.

Mais on peut forcer l’acquisition du focus par la méthode focus_set (ligne 11). Donc, dès que l’application s’ouvre, l’entrée est réceptive aux touches de clavier.

La couleur de fond de la zone d’édition est déterminée par l’option bg (ligne 7).

Il est possible de choisir avec l’option font la police et la taille de la police de la zone de saisie (ligne 5). On peut décider de limiter le nombre de caractères que peut accepter l’entrée (ligne 5). La taille en pixels de la zone de saisie en sera modifiée mais cette taille dépendra aussi la taille de la police.

Quand le focus est actif, un curseur guide la saisie. Le clignotement du curseur est modifiable avec l’option insertofftime (ligne 500) qui désigne la durée en millisecondes entre deux apparition du curseur (si 0 alors aucun clignotement). La couleur du curseur est contrôlée par l’option insertbackground.

L’entrée est un entourée d’un bord dont l’épaisseur est contrôlée par l’option border.

Le style de relief de l’entrée est contrôlé par l’option relief, par défaut, on a relief=SUNKEN (ligne 9).

On peut contrôler le texte entré par l’utilisateur grâce à l’option textvariable qui doit être initialisée sur une variable de contrôle, par exemple StringVar. Un exemple est fourni au paragraphe Exemple d’utilisation de StringVar. Noter qu’il n’y a pas d’option text valide pour un widget Entry.

Une Entry ne permet pas d’entrer du texte sur plusieurs lignes. Utiliser plutôt le widget Text (non décrit sur mon site) ainsi que c’est indiqué par Fredrik Lundh :

The entry widget is used to enter text strings. This widget allows the user to enter one line of text, in a single font. To enter multiple lines of text, use the Text widget.

Effacer une entrée

Dans l’interface ci-dessous, on a écrit un entier dans une entrée :

../../../_images/effacer_entree.gif

Après validation de l’entrée (on appuie sur ENTER), des carrés ont été dessinés sur le canevas et l’entrée a été effacée si bien que l’utilisateur peut directement réutiliser l’entrée pour générer des carrés sur le canevas.

Pour effacer la zone d’édition d’une entrée, on utilise la méthode Entry.delete :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from tkinter import *
from random import randrange

WIDTH=200
HEIGHT=200

COTE=20

root = Tk()
cnv = Canvas(root, width=WIDTH, height=HEIGHT, background="ivory")
cnv.pack()

def dessiner(event):
    n=int(my_entry.get())
    color="#%s%s%s" %(randrange(10),randrange(10),randrange(10))
    for i in range(n):
        x=randrange(WIDTH)
        y=randrange(HEIGHT)
        cnv.create_rectangle(x, y, x+COTE, y+COTE, fill=color, outline='')
    my_entry.delete(0, END)

my_entry = Entry(root)
my_entry.pack()
my_entry.bind('<Return>', dessiner)

root.mainloop()
  • Ligne 21 : on efface l’entrée depuis le caractère d’indice 0 (le premier) jusqu’au dernier (END).

Gestion du curseur

Initialiser le placement du curseur

On peut modifier dynamiquement la position du curseur, en particulier à l’initialisation.

On reprend le code d’introduction du widget Slider :

../../../_images/slider_minimal.gif

et le code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
from tkinter import *

root = Tk()
cnv = Canvas(root, width=400, height=400)
cnv.pack()

old=None

def rayon(r):
    global old
    r=int(r)
    cnv.delete(old)
    old=cnv.create_oval(200-r,200-r,200+r, 200+r)

curseur = Scale(root, orient = "horizontal", command=rayon, from_=0, to=200)
curseur.pack()

root.mainloop()

Initialement (from_ dans la ligne 15), le curseur est placé à 0. Comment faire en sorte que le curseur soit placé conformément au rayon d’un cercle qui serait tracé au départ ? A l’exemple de la figure ci-dessous

../../../_images/curseur_initial.png

où on voit que le curseur est, à l’ouverture de la fenêtre, positionné à 42 et que le cercle a un rayon de 42.

Deux méthodes sont possibles. La plus simple consiste à utiliser la méthode set du widget Scale :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from tkinter import *
from random import randrange

root = Tk()
cnv = Canvas(root, width=400, height=400)
cnv.pack()

old=None

def rayon(r):
    global old
    r=int(r)
    cnv.delete(old)
    old=cnv.create_oval(200-r,200-r,200+r, 200+r)

r=42
rayon(r)
curseur = Scale(root, orient = "horizontal", command=rayon,
                from_=0, to=200)
curseur.set(r)

curseur.pack()

root.mainloop()
  • Ligne 18 : la position du curseur est initialisée et l’appel correspondant (avec r=42) de la fonction de commande est effectué.

Une autre méthode consiste à utiliser les variables dynamiques (ou encore appelées variables de contrôle) proposées par Tkinter. Ici, on va utiliser IntVar (le rayon est entier).

Voici le code :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from tkinter import *
from random import randrange

root = Tk()
cnv = Canvas(root, width=400, height=400)
cnv.pack()

old=None

def rayon(r):
    global old
    r=int(r)
    cnv.delete(old)
    old=cnv.create_oval(200-r,200-r,200+r, 200+r)

radius=IntVar()
r=42
radius.set(r)
rayon(r)
curseur = Scale(root, variable=radius, orient = "horizontal", command=rayon,
                from_=0, to=200)
curseur.pack()

root.mainloop()
  • Ligne 20 : La position du curseur est contrôlée par l’objet défini par l’option variable (ligne 20).
  • ligne 16 : Initialement, variable (ligne 20) pointe vers une variable dynamique radius.
  • Ligne 18 : radius est initialisée à un rayon r. La conséquence est qu’à l’ouverture, le curseur pointera vers 42.
  • Ligne 19 : L’initialisation de la variable de contrôle radius n’entraîne pas d’exécution de la fonction de commande rayon. Pour que la dimension du cercle soit en conformité avec la valeur indiquée par le curseur, le cercle est tracé.

Changer le sens de progression

Par défaut, le sens croissant du curseur est défini par défaut :

  • de la gauche vers la droite
  • du haut vers le bas.

Pour changer ce sens il suffit d’inverser les valeurs donnée à from_ et to. Ci-dessous, le générateur de cercle avec un curseur progressant vers la gauche et non vers la droite :

../../../_images/curseur_dte_gche.gif

Le code correspondant est :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from tkinter import *

root = Tk()
cnv = Canvas(root, width=400, height=400)
cnv.pack()

old=None

def rayon(r):
    global old
    r=int(r)
    cnv.delete(old)
    old=cnv.create_oval(200-r,200-r,200+r, 200+r)

curseur = Scale(root, orient = "horizontal",
                command=rayon, from_=200, to=0)
curseur.pack()

root.mainloop()

Désactiver le curseur

L’état d’un curseur est déterminé par une variable state que l’on peut modifier pour le rendre inactif. Par défaut, l’état est normal. L’état déactivé est disabled. Par exemple, dans le code ci-dessous :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
from tkinter import *

root = Tk()
cnv = Canvas(root, width=400, height=400)
cnv.pack()

old=None

def rayon(r):
    global old
    if int(r)>100:
        curseur["state"]="disabled"
    r=int(r)
    cnv.delete(old)
    old=cnv.create_oval(200-r,200-r,200+r, 200+r)

curseur = Scale(root, orient = "horizontal",
                command=rayon, from_=200, to=0)
curseur.pack()

root.mainloop()

dès que le curseur dépasse 100, il est désactivé (lignes 11-12).

Faire un menu déroulant

Voici un menu déroulant :

../../../_images/menu_deroulant.gif

Le code correspondant est le suivant

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from tkinter import *


root = Tk()

cnv=Canvas(root, width=400, height=400, bg="lavender")
cnv.pack()

def rose():
    cnv['bg']="pink"

def rouge():
    cnv['bg']="red"

def orange():
    cnv['bg']="orange"

def violet():
    cnv['bg']="purple"

# Barre de menus
mon_menu = Menu(root)
root.config(menu=mon_menu)

# Menu légumes
legumes = Menu(mon_menu, tearoff=0)
legumes.add_command(label="Radis", command=rose)
legumes.add_separator()
legumes.add_command(label="Tomate", command=rouge)
mon_menu.add_cascade(label="Légumes", menu=legumes)

# Menu fruits
fruits = Menu(mon_menu, tearoff=0)
fruits.add_command(label="Raisin", command=violet)
fruits.add_command(label="Orange", command=orange)
mon_menu.add_cascade(label="Fruits", menu=fruits)

root.mainloop()

Prise de focus dans un canevas

Si un canevas est une surface devant capturer des événements du clavier, il faut lui donner le focus pour qu’il puisse réagir aux touches de clavier.

Soit l’application suivante qui à l’ouverture se présente ainsi :

../../../_images/focus_canevas_defaut.png

Comme le montre le liseré noir autour du canevas, le canevas a le focus : il est en mesure de réceptionner des événements du clavier. En particulier ici, si l’utilisateur appuie sur n’importe quelle touche, cette action dessine un carré rouge aléatoire sur le canevas :

../../../_images/focus_canevas_clavier.png

Voici le code correspondant :

from tkinter import Tk, Canvas
from random import randrange

SIDE=200

root = Tk()
cnv = Canvas(root, width=SIDE, height=SIDE, bg='ivory')
cnv.pack(padx=10, pady=10)
cnv.focus_set()

def dessiner(event):
    a=randrange(SIDE)
    b=randrange(SIDE)
    cnv. create_rectangle(a, b, a+20, b+20, fill="red")

cnv.bind('<Key>', dessiner)

root.mainloop()

Pour modifier (voire faire disparaître) le liséré noir autour du canevas, il faut modifier une option du canevas nommé highlightthickness. Par défaut, il est de 1 pixel et de couleur noire.

Témoin de prise de focus

Les interfaces graphiques permettent de visualiser la partie de l’interface qui a le focus. Sous Tkinter, le widget qui a le focus est entouré d’une bande rectangulaire ;

Ci-dessous une interface composée de deux widgets, un canevas et un bouton, est présentée :

../../../_images/alterne_focus.gif

Le widget qui a le focus est entouré d’une bande rouge ; quand il perd le focus, cette bande devient rose. Si on appuie sur la touche TAB, le focus passe des widgets à l’autre et les couleurs rose et rouge sont échangées à chaque changement de focus.

Le code de l’interface précédente montre qu’on peut contrôler les couleurs et la dimension de la bande de témoin de focus :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from tkinter import Tk, Canvas, Button
from random import randrange

W=H=200
root=Tk()

cnv=Canvas(root, width=W, height=H, bg="ivory",
           highlightthickness=20,
           highlightbackground="pink",
           highlightcolor="red",
           takefocus=1)
cnv.pack(side="left")

Button(root, text='Coucou !',
       highlightthickness=5,
       highlightbackground="pink",
       highlightcolor="red").pack()

def dessiner(event):
    a=randrange(W)
    b=randrange(W)
    cnv.create_rectangle(a, b, a+10, b+10, fill="blue", outline='')

cnv.bind('<Return>', dessiner)

root.mainloop()
  • Lignes 8 et 15 : highlightthickness est la largeur en pixels de la bande de focus. La largeur par défaut est de deux pixels.
  • Lignes 9 et 16 : highlightbackground est la couleur de la bande lorsque le widget n’a pas le focus, ici pink ;
  • Lignes 10 et 17 : highlightcolor est la couleur de la bande lorsque le widget a le focus, ici red.

Dans l’interface précédente, lorsque le canevas a le focus, l’appui sur la touche ENTRÉE (cf. ligne 24) dessine un carré aléatoire orange sur le canevas (cf. lignes 18-21). Si le canevas n’a pas le focus, rien n’est dessiné.

Clavier et focus

Soit un jeu contenant plusieurs widgets, par exemple un canevas et une entrée (un widget où on peut entrer du texte au clavier). On veut que le canevas réagisse à certains événements du clavier, par exemple, si on appuie sur la barre d’espace, le joueur saute. Comme le jeu contient plusieurs widgets, chaque widget a sa propre façon d’écouter le clavier, un appui sur ESPACE dans le widget entrée n’aura pas même signification que pour le canevas. Il faut donc qu’il y ait une manière de choisir l’interlocuteur entre le clavier et les widgets. C’est ce qu’on appelle le focus.

Le focus est acquis en général par appuis successifs sur la touche TAB ou par clic de souris (si vous cliquez sur un champ d’entrée, le champ prend le focus, comme dans un formulaire d’une page web). Pour faire en sorte qu’un widget prenne automatiquement le focus, on utilise la méthode focus_set du widget.

Voici un exemple avec un canevas :

../../../_images/prendre_focus.png

quand on appuie sur la touche ENTRÉE, un carré aléatoire est dessiné sur le canevas. Voici le code correspondant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from tkinter import *
from random import randrange


WIDTH=400
HEIGHT=300

root = Tk()
cnv = Canvas(root, width=WIDTH, height=HEIGHT, background="ivory")
cnv.pack()

COTE= 20
cnv.focus_set()

def f(event):
    a=randrange(WIDTH)
    b=randrange(HEIGHT)
    color="#%s%s%s" %(randrange(10),randrange(10),randrange(10))
    cnv.create_rectangle(a, b, a+COTE, b+COTE, fill=color)

cnv.bind('<Return>', f)

root.mainloop()
  • Ligne 21 : l’appui sur la touche ENTRÉE (si le canevas a le focus) déclenche l’exécution de la fonction f.
  • Ligne 13 : la canevas prend le focus; il pourra réagir au événement du clavier. Sans cette ligne, le canevas serait inerte, il faudrait lui donner le focus manuellement, soit en appuyant sur la touche TAB soit en cliquant dans le clavier.

Si la fenêtre contient le canevas comme seul widget, il est plus simple de lier la fonction f à la fenêtre tout entière au lieu du seul canevas. Il suffit pour cela de changer la ligne 21 en :

1
root.bind('<Return>', f)

et on peut alors supprimer la ligne 13 de prise de focus : quand on appuiera sur la touche ENTREE, automatiquement, le canevas capturera l’appui sur la touche.

Image dans un label

Un label peut aussi porter une image au lieu de texte :

../../../_images/label_image.png

Pour cela, on convertit d’abord avec le fichier image avec PhotoImage :

1
2
3
4
5
6
7
from tkinter import *

root = Tk()
logo = PhotoImage(file="python.gif")
Label(root, image=logo).pack(padx=15, pady=15)

root.mainloop()

et on utilise l’option image (ligne 5) de Label.

Si l’option image est utilisée, l’option text sera sans effet.

Centrer un texte dans un label

Un label admet une option d’alignement (option justify) permettant le centrage :

../../../_images/centrer_texte.png
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from tkinter import Tk, Label, CENTER

racine=Tk()

mon_texte="""
Je suis un texte long
qui souhaiterait
être centré sur
plusieurs lignes"""
annonce=Label(height=5, width= 50, text=mon_texte,
              justify=CENTER, bg='ivory')
annonce.pack()
racine.mainloop())
  • Ligne 11 : l’option justify est placée à CENTER ce qui centre le texte ligne par ligne.
  • ligne 11 : pour rendre le texte plus visible, une couleur de fond a été utilisée dans le widget (option bg pour background).

Utilisation d’une variable de contrôle dans un label

Soit l’interface suivante :

../../../_images/label_strvar.gif

Quand l’utilisateur clique sur le bouton, un label augmente le compteur d’une unité.

On peut effectuer la mise à jour du label en utilisant une variable de contrôle de Tkinter, ici StringVar :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from tkinter import *

def plus():
    cpt.set(int(cpt.get())+1)

root=Tk()
cpt=StringVar()
cpt.set('0')
lbl=Label(root, width='10', textvariable=cpt, font='Arial 20 bold')
lbl.pack(side=TOP, padx=50, pady=10)

bouton=Button(root, text="+", command=plus)
bouton.pack(side=TOP, padx=50, pady=10)

root.mainloop()
  • Ligne 9 : le texte du label est couplé à une variable de contrôle cpt définie antérieurement (ligne 7).
  • Lignes 12 et 3-4 : quand le bouton reçoit un clic, la fonction sans paramètre plus est appelée (ligne 3-4) et son code est exécuté. Ce code récupère la valeur de cpt avec la méthode get ; cette valeur est une chaîne représentant un entier. On convertit cette chaîne en entier, on incrémente et on met à jour la variable de contrôle cpt. Cela a pour effet de mettre à jour le label.

On peut aussi se passer de StrVar et modifier directement la valeur du label :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from tkinter import *

def plus():
    entree['text']=entree['text']+1

root=Tk()

entree=Label(root, width='10', text=0, font='Arial 20 bold',)
entree.pack(side=TOP, padx=50, pady=10)

bouton=Button(root, text="+", command=plus)
bouton.pack(side=TOP, padx=50, pady=10)

root.mainloop()
  • Ligne 8 : l’option text accepte une entrée numérique (qui n’est pas une chaîne de caractères).
  • Lignes 8 et 3-4 : la fonction de rappel plus met directement à jour le contenu de l’entrée (ligne 4).

Le widget Frame

Le widget Frame (cadre en français) est un widget qui sert juste de conteneur, un peu comme une fenêtre Tk. Ce type de widget est très utile pour regrouper et organiser des interfaces contenant beaucoup de widgets.

Voici un exemple d’utilisation :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from tkinter import *

SIDE = 400
root = Tk()
frame = Frame(root, background="lavender")
frame.pack()
cnv = Canvas(frame, width=SIDE, height=SIDE, bg='ivory')
cnv.pack(padx=20, pady=20)

btn = Button(frame, text="Coucou !")
btn.pack(pady=20)

root.mainloop()
  • ligne 6 : frame est un widget comme un autre et il doit donc être placé avec un gestionnaire de géométrie, ici pack.
  • ligne 5 : on peut donner une couleur de fond à un frame (on ne peut pas pour un fenêtre Tk).
  • lignes 7 et 10 : un frame est un conteneur et ici, il est le premier argument des constructeurs Canvas et Button.

qui affiche :

../../../_images/frame.png

On peut donner une largeur et une hauteur à un frame mais la plupart du temps, ces dimensions sont ignorées (sauf dans certains cas où on inhibe la propagation des dimensions des widgets).

Boutons radio

Typiquement, on utilise des boutons radio dans des situations de choix conduisant à une unique réponse comme entre un nombre strictement positif, strictement négatif ou nul:

../../../_images/boutonradio_exemple.png

Voici le code correspondant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
from tkinter import *

root = Tk()

pad = 30

mineur = Radiobutton(
    root, variable=StringVar(), text="Positif", font="arial 20")
mineur.pack(anchor="w", padx=pad, pady=pad)

majeur = Radiobutton(
    root, variable=StringVar(), text="Négatif", font="arial 20")
majeur.pack(anchor="w", padx=pad, pady=pad)

a = Radiobutton(root, variable=StringVar(), text="Nul", font="arial 20")
a.pack(anchor="w", padx=pad, pady=pad)

root.mainloop()
  • Lignes 7-9 : un bouton radio est un widget comme un autre ; on le crée avec un constructeur, ici RadioButton et on le place comme n’importe quel widget, ici avec la méthode pack (et des options d’alignement pour que la sortie ne soit pas trop disgrâcieuse).
  • Lignes 11-13 et 15-16 : on crée et on place de même deux autres boutons radio.
  • Pour chaque bouton, on dispose d’une option de texte descriptif (text) et de police pour le texte (font).

Dans l’exemple ci-dessus, les boutons radio sont purement factices et rien n’est prévu pour écouter s’ils sont cochés ou pas.

Un exemple avec interaction

Les boutons radio fonctionnent, en principe, par groupes. Par exemple, dans un QCM de 10 questions, il y aura 10 groupes de boutons radio. Toutefois, le programmeur va définir autant de boutons radio qu’il a besoin (sans tenir compte des groupes) et c’est ensuite lui qui définera les groupes (expliqué plus loin).

Dans ce qui suit, on va examiner une interface qui montre comment le programme peut récupérer les informations fournies par un groupe de boutons radio.

L’interface contient cinq widgets :

../../../_images/boutonsradio_nature.gif
  • trois boutons radio qui référencent des fruits,
  • un label qui va montrer leur couleur,
  • un bouton Couleur qui lorsqu’on le clique, colorie le label avec la couleur du fruit.

Au départ, aucune couleur n’apparaît et, par défaut, c’est le bouton radio de la cerise qui est activée. Voici le code :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from tkinter import *

root = Tk()


def colorer():
    val = fruit.get()
    if val == "cerise":
        lbl_fruit["bg"] = "red"
    elif val == "orange":
        lbl_fruit["bg"] = "orange"
    else:
        lbl_fruit["bg"] = "yellow"


fruit = StringVar()
fruit.set("cerise")

cerise = Radiobutton(
    root,
    text="Cerise",
    variable=fruit,
    value="cerise",
    font="arial 20")
cerise.pack(anchor="w")

orange = Radiobutton(
    root,
    text="Orange",
    variable=fruit,
    value="orange",
    font="arial 20")
orange.pack(anchor="w")

banane = Radiobutton(
    root,
    text="Banane",
    variable=fruit,
    value="banane",
    font="arial 20")
banane.pack(anchor="w")

lbl_fruit = Label(root, bg="white", width=5)
lbl_fruit.pack(padx=50, pady=50)

btn = Button(root, text="Couleur", command=colorer)
btn.pack(padx=50, pady=50)

root.mainloop()
  • ligne 16 : une variable de contrôle (spécificité de Tkinter) initialisée à la chaîne cerise. Elle initialise les options variable des trois boutons radio.
  • Lignes 22 et 23 : les boutons radio sont munis de 2 nouvelles options : variable et value. Si le bouton radio est activé (donc, on a cliqué dessus), la valeur de la variable de contrôle sera value.
  • Le point qui suit est essentiel : les trois boutons fonctionnent comme un groupe (un seul des trois doit être coché) ; pour le faire comprendre à Tkinter, il faut que leur option variable pointe vers la même variable de contrôle (lignes 22, 30 et 38), ici appelée fruit (ligne 16).
  • Ligne 7 : pour savoir quel bouton est coché, il suffit de regarder le contenu de la variable de contrôle et de le comparer aux différentes value des boutons.

Si les boutons radio sont nombreux, on utilisera plutôt une boucle for pour les créer et les placer. A noter que si votre interface a une certaine complexité et contient des boutons radio, il peut être approprié d’utiliser le placement des widgets en grid.

Cas de plusieurs groupes

Si vous avez plusieurs groupements de boutons radio, il faut associer à chaque groupement sa propre variable de contrôle.

Par exemple, si on avait aussi un groupe de boutons radio pour des légumes :

../../../_images/boutonsradio_legumes.png
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
from tkinter import *

root = Tk()


def colorer():
    if fruit.get() == "cerise":
        lbl_fruit["bg"] = "red"
    else:
        lbl_fruit["bg"] = "orange"
    if legume.get() == "aubergine":
        lbl_legume["bg"] = "purple"
    else:
        lbl_legume["bg"] = "green"


fruit = StringVar()
fruit.set("cerise")
legume = StringVar()
legume.set("aubergine")

cerise = Radiobutton(
    root,
    text="Cerise",
    variable=fruit,
    value="cerise",
    font="arial 20")
cerise.pack(anchor="w")

orange = Radiobutton(
    root,
    text="Orange",
    variable=fruit,
    value="orange",
    font="arial 20")
orange.pack(anchor="w")

lbl_fruit = Label(root, bg="white", width=5)
lbl_fruit.pack()

aubergine = Radiobutton(
    root,
    text="Aubergine",
    variable=legume,
    value="aubergine",
    font="arial 20")
aubergine.pack()

haricot = Radiobutton(
    root,
    text="Haricot",
    variable=legume,
    value="haricot",
    font="arial 20")
haricot.pack(anchor="w")

lbl_legume = Label(root, bg="white", width=5)
lbl_legume.pack()

btn = Button(root, text="Couleur", command=colorer)
btn.pack(padx=20, pady=20)

root.mainloop()
  • Lignes 17 : une première variable de contrôle pour les fruits (cf. lignes 25 et 33).
  • Lignes 19 : une seconde variable de contrôle pour les légumes (cf. lignes 44 et 52).

Déclencher une action

Comme pour d’autres widgets (bouton, entrée), un bouton radio dispose d’une option permettant de générer une action (appel d’une fonction) lorsqu’un bouton est modifié. Par exemple, on peut modifier le code ci-dessus pour que, lorsque l’utilisateur clique sur un bouton radio, la couleur du fruit sélectionné apparaise dans le label :

../../../_images/radio_action.gif
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from tkinter import *

root = Tk()


def colorer():
    val = fruit.get()
    if val == "cerise":
        lbl_fruit["bg"] = "red"
    elif val == "orange":
        lbl_fruit["bg"] = "orange"
    else:
        lbl_fruit["bg"] = "yellow"


fruit = StringVar()
fruit.set("cerise")

cerise = Radiobutton(
    root,
    text="Cerise",
    variable=fruit,
    value="cerise",
    font="arial 20",
    command=colorer)
cerise.pack(anchor="w")

orange = Radiobutton(
    root,
    text="Orange",
    variable=fruit,
    value="orange",
    font="arial 20",
    command=colorer)
orange.pack(anchor="w")

banane = Radiobutton(
    root,
    text="Banane",
    variable=fruit,
    value="banane",
    font="arial 20",
    command=colorer)
banane.pack(anchor="w")

lbl_fruit = Label(root, bg="white", width=5)
lbl_fruit.pack(padx=50, pady=50)

root.mainloop()
  • chaque boution radio (par exemple ligne 25) contient une option command pointant vers la fonction colorer (sans argument, comme l’impose Tkinter)
  • Lorsqu’un bouton est cliqué, la valeur commune de la variable de contrôle fruit (ligne 16) est examinée et la coloration du label est déclenchée (lignes 9-13).

Le widget case à cocher

Tkinter met à disposition le widget Checkbutton montrant du texte et une case à cocher :

../../../_images/checkbutton_demo.gif

Dans l’application ci-dessus, la label affiche dynamiquement la somme des valeurs des cases cochées.

Les widgets Checkbutton vont souvent par groupes mais il faut créer autant de widgets Checkbutton que de cases à cocher. A la différence des boutons-radio dont un seul est activable, on peut cocher plusieurs cases.

Ci-dessous, on crée 3 simples cases à cocher :

../../../_images/checkbutton_static.png
from tkinter import *

root=Tk()

check1=Checkbutton(root, text=2, font='arial 30')
check1.grid()

check2=Checkbutton(root, text=3, font='arial 30')
check2.grid(row=1)

check3=Checkbutton(root, text=4, font='arial 30')
check3.grid(row=2)

root.mainloop()

La question qui se pose maintenant est de savoir si une case d’un Checkbutton check a été cochée. Pour cela, il suffit d’examiner l’option variable ; c’est une variable de contrôle Tkinter qui renvoie l’entier 1 si la case est cochée et 0 sinon. En réalité, il semble qu’il n’y ait pas d’autre façon d’avoir accès à cette information.

Utiliser un callback par case

Par ailleurs, on peut définir, pour chaque case à cocher, une fonction référencée par l’option command et qui sera appelée chaque fois que la case sera modifiée. Cela fournit une première méthode pour réaliser l’application présentée tout au début :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from tkinter import *

root = Tk()


def f(i):
    def g():
        s = sum(check["text"] * v.get() for (check, v) in checks)
        lbl["text"] = "Somme :  %s" % s

    return g


checks = []

for i in range(3):
    v = IntVar()
    check = Checkbutton(
        root, text=i + 2, font='arial 30', command=f(i), variable=v)
    check.grid(row=i)
    checks.append((check, v))

lbl = Label(root, text="Somme :  0", font='arial 30')
lbl.grid(row=1, column=1, padx=100)

root.mainloop()
  • Lignes 16-21 : on crée les 3 cases à cocher ; l’état de la case (cochée ou pas) est contrôlé par la variable de contrôle v (c’est indispensable ici). Le fonctionnement des cases à cocher défini par Tkinter stipule que le contenu de cette variable est l’entier 1 si la case est cochée, 0 sinon.

  • Ligne 19, option text : le texte de la case à l’indice i est i+2.

  • Ligne 20 : on utilise la méthode grid pour placer les cases.

  • Lignes 14 et 21 : les 3 widgets ainsi que la variable de contrôle correspondante sont placés en tuple dans une liste.

  • Ligne 19, option command : chaque bouton réagit au clic sur la case. Si le bouton est d’indice i, la fonction qui est appelée est g=f(i) (ligne 11). Noter que f est une fonction qui renvoie une autre fonction.

  • Lorsqu’une case à cocher est modifiée, sa fonction de rappel g (ligne 7), qui dispose de l’indice i de la case modifiée va exécuter les actions suivantes :

    • elle recalcule (ligne 8) la nouvelle somme : en effet, si une case est décochée, v.get() vaut 0 et donc la valeur du texte n’est pas comptée et sinon, v.get() vaut 1 et donc la valeur du texte est comptée ;
    • elle met alors le label à jour (ligne 9).

N’utiliser que des variables de contrôle Tkinter

Une autre façon de mettre à jour le label est d’appeler une fonction chaque fois qu’une des variables contrôlant chaque case à cocher est modifiée. Cela se fait en utilisant la méthode trace d’une variable de contrôle. Le code suivant montre le principe de la méthode trace :

def f(*args):
    print(v.get())


v=IntVar()

# Autre code où v est modifiée en écriture

v.trace("w", f)

Chaque fois que la variable de contrôle v est modifiée ailleurs dans le programme, la fonction f est automatiquement appelée ; ici, cette fonction affiche la valeur contenue dans v. L’argument "w" de trace signifie write (modification en écriture de v).

Voici le code complet de l’application :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
from tkinter import *

root=Tk()

def update(*args):
    s=sum(check["text"]*v.get() for (check,v) in checks)
    lbl["text"]="Somme :  %s" %s

checks=[]

for i in range(3):
    v=IntVar()
    check=Checkbutton(root, text=i+2, font='arial 30', variable=v)
    check.grid(row=i)
    v.trace("w", update)
    checks.append((check,v))

lbl=Label(root, text="Somme :  0", font='arial 30')
lbl.grid(row=1, column=1, padx=100)

root.mainloop()
  • ligne 15 : chaque variable de contrôle v appelera la fonction update lorsque la case correspondante sera modifiée.
  • Lignes 5-7 : la fonction update calcule la somme et met à jour le texte du label.

Le widget Listbox

Le widget Listbox propose une liste dont les éléments sont sélectionnables à la souris ou avec les flèches Haut et Bas du clavier :

../../../_images/listbox.gif

On crée ce widget avec le constructeur Listbox (une seule majuscule). Voici le code correspondant à la figure ci-dessus :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from tkinter import *

fruits = ["Litchie", "Kiwi", "Orange", "Raisin", "Citron", "Cerise"]
n = len(fruits)

master = Tk()

lbox = Listbox(
    master,
    width=8,
    height=n,
    font="Verdana 30 bold",
    selectbackground="blue")
lbox.pack(padx=50, pady=50)

for item in fruits:
    lbox.insert(END, item)

mainloop()
  • Ligne 3 : la liste des chaînes qui vont apparaître dans le widget.

  • Lignes 8-14 : construction et placement du widget

  • Lignes 9-13. Certaines options du widget :

    • width est le nombre de caractères (pas de pixels)
    • height est le nombre d’entrées devant apparaître dans la liste
    • font : la police utilisée
    • selectbackground : couleur du fond d’une cellule sélectionnée.
  • Lignes 16-17 : insertion des éléments dans la liste du widget ; END signifie qu’on place l’item courant après le dernier placé.

En français, une listbox s’appelle une liste de sélection. On ne peut pas placer le texte d’un item sur plusieurs lignes, comme expliqué par Bryan Oakley.

Il existe de nombreuses autres options et ce widget dispose d’un nombre important de méthodes, en particulier de modification des items et dont je ne parlerai pas. Il est possible de faire de la multi-sélection, voir la documentation de selectmode. Signalons par exemple la possibilité de :

../../../_images/listbox_plus.png
  • placer le focus avec la méthode focus_set (et qui n’est pas propre à ce widget) : sans toucher à la souris, on peut se déplacer dans la liste avec la souris ;
  • sélectionner une cellule par son indice (à partir de 0)
  • modifier la couleur de certaines cellules avec la méthode itemconfigure.

Voici un code qui utilise ces méthodes :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from tkinter import *

fruits = ["Litchie", "Kiwi", "Orange", "Raisin", "Citron", "Cerise"]
n = len(fruits)

master = Tk()

lbox = Listbox(
    master,
    width=8,
    height=n,
    font="Verdana 20 bold",
    selectbackground="blue")
lbox.pack(padx=20, pady=20)

for item in fruits:
    lbox.insert(END, item)

lbox.focus_set()
lbox.selection_set(2)

for i in range(0, len(fruits), 2):
    lbox.itemconfigure(i, background='#f0f0ff')
for i in range(1, len(fruits), 2):
    lbox.itemconfigure(i, background='#fff')

mainloop()
  • Lignes 19-20 : placement du focus et sélection du 3e item.
  • Lignes 22-25 : on a colorié avec des couleurs alternées les lignes successives de la liste.

Il est également possible de définir les entrées de la liste comme des variables dynamiques Tkinter, voir le paragraphe Modification des items d’une listbox.

Action associée

Bien entendu, le déplacement et la sélection d’éléments dans la liste sont souvent associés à une action. Par exemple, dans l’exemple ci-dessous, le déplacement dans la liste affiche dans un canevas une couleur adaptée à la sélection :

../../../_images/listbox_canevas.gif

Voici le code correspondant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from tkinter import *

fruits = ["Litchie", "Kiwi", "Orange", "Raisin", "Citron", "Cerise"]
couleurs = ["pink", "lightgreen", "orange", "purple", "yellow", "red"]
n = len(fruits)

master = Tk()

lbox = Listbox(
    master,
    width=8,
    height=n,
    font="Verdana 20 bold",
    selectbackground="blue")
lbox.pack(padx=20, pady=20, side=LEFT)

for item in fruits:
    lbox.insert(END, item)

lbox.focus_set()
pos = 1
lbox.activate(pos)
lbox.selection_set(pos)

for i in range(0, len(fruits), 2):
    lbox.itemconfigure(i, background='#f0f0ff')
for i in range(1, len(fruits), 2):
    lbox.itemconfigure(i, background='#fff')


def show(event):
    index = lbox.curselection()[0]
    cnv["bg"] = couleurs[index]


lbox.bind('<<ListboxSelect>>', show)

cnv = Canvas(master, width=200, height=200, bg="ivory")
cnv.pack(padx=5, pady=5, side=RIGHT)
cnv["bg"] = couleurs[pos]

mainloop()
  • Ligne 4 : on crée, en respectant l’ordre, une liste des couleurs associées aux différents fruits.
  • Ligne 38 : on crée un canevas qui va afficher la couleur de l’élément sélectionné
  • Ligne 36 : le point essentiel : l’événement virtuel <<ListboxSelect>> qui réagit chaque fois qu’une cellule est sélectionnée en appelant une fonction, ici la fonction show qui va placer la bonne couleur sur le canevas.
  • Ligne 32 : la méthode curselection de Listbox permet de récupérer l’indice de la cellule sélectionnée (rappel : début à l’indice 0).

Modification des items d’une listbox

On peut aussi modifier le contenu du texte d’une ligne d’une listbox. Dans l’exemple ci-dessous :

../../../_images/listbox_changer_text.gif

initialement le texte est en minuscule et au bout de deux secondes, il est placé en majuscule. Le code correspondant est :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from tkinter import *

root=Tk()
MOIS=['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet',
      'août', 'septembre', 'octobre', 'novembre', 'décembre']
z=StringVar(value=MOIS)
lb=Listbox(root, width=16, fg='orange', listvar =z,
           selectbackground='pink', font="Arial 16 bold")
lb.pack(side=LEFT, padx=10, pady=10)


def modif():
    for i in range(len(MOIS)):
        MOIS[i]=MOIS[i].upper()
    z.set(MOIS)

root.after(2000, modif)

root.mainloop()
  • Ligne 17 : au bout de 2,5 secondes après l’ouverture de la fenêtre, la casse des lignes de la listbox est changée.
  • Ligne 6 : le texte de chaque ligne est initialement placé dans une variable dynamique Tkinter.
  • Ligne 15 : la méthode set de la variable dynamique permet une mise à jour automatique.

Une Listbox et une barre de défilement

Si une Listbox contient beaucoup d’items, on peut parcourir son contenu avec une barre de défilement :

../../../_images/listbox_defil.gif

Voici le code correspondant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from tkinter import *
from tkinter import ttk

fruits = ["Litchie", "Kiwi", "Orange", "Raisin", "Citron", "Cerise"] * 5

master = Tk()

lbox = Listbox(
    master,
    width=8,
    height=5,
    font="Verdana 30 bold",
    selectbackground="blue")
lbox.pack(side=LEFT, padx=40, pady=40)

for item in fruits:
    lbox.insert(END, item)

sbar = ttk.Scrollbar(master, command=lbox.yview)
sbar.pack(side=LEFT, expand=True, fill=Y)
lbox.config(yscrollcommand=sbar.set)

mainloop()
  • Ligne 1-2 : pour des raisons d’esthétique, j’ai choisi d’utiliser la barre de Ttk.
  • Ligne 4 : la liste contient 30 items.
  • Ligne 19 : on place une barre (par défaut verticale) ; sa commande pointe vers le déplacement vertical des items de la liste lbox.
  • Ligne 20 : on place soigneusement la barre pour qu’elle recouvre tout le côté de la liste.
  • Ligne 21 : on apparie cette fois la listbox à la barre.

Le widget spinbox

Un spinbox est un widget hybride :

../../../_images/spinbox_exemple.gif

Il dispose d’une zone de texte et deux boutons de défilement. A partir d’une succession d’éléments textuels, dans l’exemple les nombres de 2019 à 2024, ce widget permet de faire défiler ces élements dans la zone de texte en progressant dans un sens ou dans l’autre selon les boutons qui vont recevoir un clic. Voici le code d’une version minimaliste :

spinbox_minimal.py

from tkinter import *
root=Tk()

sp= Spinbox(root, from_=2019, to=2024)
sp.pack(padx=10, pady=10)

root.mainloop()

qui produit :

../../../_images/spinbox_minimal.png

On peut aussi parcourir une liste de chaînes dans la zone de texte :

spinbox_minimal_texte.py

1
2
3
4
5
6
7
8
9
from tkinter import *
root=Tk()

fruits = ["Litchie", "Kiwi", "Orange", "Raisin", "Citron", "Cerise"]

sp= Spinbox(root, values=fruits)
sp.pack(padx=10, pady=10)

root.mainloop()

qui produit :

../../../_images/spinbox_minimal_texte.png

L’intérêt de ce widget est la compacité : il permet de circuler relativement facilement entre tous les éléments d’une succession tout en prenant le minimum de place.

Ce widget contient de nombreuses options. Voici un exemple de l’usage de quelques unes :

../../../_images/spinbox_simple_texte.gif

spinbox_simple_texte.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from tkinter import *
root = Tk()

fruits = ["Litchie", "Kiwi", "Orange", "Raisin", "Citron", "Cerise"]

sp = Spinbox(
    root,
    values=fruits,
    width=8,
    bg="white",
    justify=CENTER,
    wrap=True,
    font="times 30")
sp.pack(padx=10, pady=10)

root.mainloop()
  • Ligne 9 : l’option width est le nombre de caractères visibles dans la zone de texte.
  • Ligne 10 : bg est la couleur de fond.
  • Ligne 11 : justify permet de disposer le texte dans la zone
  • Ligne 13 : font permet de modifier la police et la taille du texte.
  • Ligne 12 : par défaut, quand le défilement arrive en bout de succession, le défilement est bloqué. L’option wrap permet d’aller en un clic à l’autre extrémité de la succession.

La zone de texte est en fait une entrée (widget de la classe Entry) et est donc modifiable. Toutefois, comme une liste de chaînes à afficher est en général prédéfinie, l’intérêt de pouvoir modifier directement la zone de texte n’est pas immédiat. Sans compter la possibilité d’écraser accidentellement une valeur. Et justement, il est possible de faire en sorte que la zone de texte soit non modifiable. Il suffit pour cela d’utiliser l’option state="readonly". Toutefois, ce changement d’état modifie la couleur de fond de la zone de texte pour bien montrer qu’elle est désactivée, ce qui n’est pas forcément souhaité. On peut y remédier en définissant une option readonlybackground. Voici un exemple :

from tkinter import *
root = Tk()

fruits = ["Litchie", "Kiwi", "Orange", "Raisin", "Citron", "Cerise"]

sp = Spinbox(
    root,
    values=fruits,
    width=8,
    justify=CENTER,
    wrap=True,
    font="times 30",
    state="readonly",
    readonlybackground="white")
sp.pack(padx=10, pady=10)

root.mainloop()

La sortie est analogue à la sortie précédente.

Exécution d’actions

On peut faire en sorte qu’une action soit exécutée chaque fois que la zone de texte est modifiée par appui sur un des deux boutons. Pour cela, comme pour la plupart des widgets de Tkinter, on utilise l’option command. Pour faire simple, on va parcourir des nombres et on va afficher dans la console le nombre placé dans la zone de texte du spinbox après clic :

../../../_images/spinbox_command.gif

Voici le code correspondant :

spinbox_command.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from tkinter import *


def f():
    print(sp.get())


root = Tk()

fruits = ["Litchie", "Kiwi", "Orange", "Raisin", "Citron", "Cerise"]

sp = Spinbox(
    root,
    values=fruits,
    width=8,
    bg="white",
    justify=CENTER,
    wrap=True,
    font="times 30",
    command=f)
sp.pack(padx=10, pady=10)

root.mainloop()
  • Ligne 20 : l’option command qui pointe vers une fonction f
  • Ligne 4 : la fonction f utilisée lorsque un des deux boutons du spinbox est pressé.

Si on veut différencier le bouton sur lequel un clic a été reçu (le haut ou le bas), il faut procéder autrement (trouvé sur SO): on définit toujours une option command mais la commande indiquée va être encapsulée avec la méthode register. Avec un exemple, ce sera plus simple à comprendre :

../../../_images/spinbox_command_plus.gif

spinbox_command_plus.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from tkinter import *

def f(drn):
    print(drn)


root = Tk()

g=root.register(f)

fruits = ["Litchie", "Kiwi", "Orange", "Raisin", "Citron", "Cerise"]

sp = Spinbox(
    root,
    values=fruits,
    width=8,
    bg="white",
    justify=CENTER,
    wrap=True,
    font="times 30",
    command=(g, "%d"))
sp.pack(padx=10, pady=10)

root.mainloop()
  • Ligne 22 : la commande est fournie sous forme de tuple
  • Ligne 22 : le premier élément du tuple est le retour d’un appel à la méthode register, cf. ligne 9. Cette dernière encapsule une fonction, ici f (ligne 3).
  • Ligne 22 : les éléments suivants du tuple représentent le type des paramètres que la fonction encapsulée va recevoir (ici, un seul paramètre). Ces types sont conventionnelement définis par Tkinter. Ici par exemple, le type %d représente une chaîne de caractère valant up si la flèche haut a été pressée et down si c’est la flèche bas.

Bien qu’on parcoure une liste ou une succession, le widget ne semble pas donner la possibilité d’accéder à l’indice courant dans la succession de la zone de texte visible.

Il n’existe pas de version ttk du widget spinbox.

En français, la widget spinbox devrait se dire saisie rotative.

Barre de progression Ttk

Le module standard Ttk propose une barre de progression. Voici un exemple minimal :

../../../_images/progress_bar.gif
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from tkinter import Tk, ttk

root=Tk()

progress = ttk.Progressbar(root, length=400,  maximum= 300)
progress.pack(pady=30)

progress.start(10)

root.mainloop()
  • Ligne 1 : il faut impérativement importer ttk explicitement de tkinter. Une importation de la forme from tkinter import * ne donnera pas accès à Ttk.
  • Lignes 5-6 : une barre de progression se crée comme tout autre widget : construction et placement.
  • Ligne 5 : on peut définir la longueur de la barre avec l’option length. L’option maximum sera expliquée ci-dessous.
  • Ligne 8 : lancement de la progression dans la barre.

Par défaut, une barre de progression est horizontale (il existe une option pour la placer verticale). On peut changer la couleur par défaut en créant un style.

Principe de fonctionnnement d’une barre de progression Ttk

Il existe deux modes de fonctionnement d’une barre de progression : l’option indeterminate et l’option determinate qui est l’option par défaut. La première option ne prend pas en compte la proportion de progression d’une tâche, elle sert à indiquer que, par exemple, une tâche est en cours d’exécution mais pour une durée indéterminée. Cette option ne sera pas examinée.

Quand une barre de progression évolue, une valeur interne à la barre parcourt, à un certain rythme, des valeurs décimales entre 0 et une valeur maximum que l’on indique en option. Lorsque cette valeur est atteinte, la barre arrive visuellement à la fin de son rectangle de progression. Mais, curieusement, au lieu de s’arrêter, la progression reprend automatiquement de zéro. Ce comportement n’est pas modifiable sans utiliser la méthode after d’un widget.

On peut contrôler la valeur en cours de progression avec l’option "value", accessible par progress["value"]progress est le nom de la barre de progression.

Enfin, si on lance la barre de progression par progress.start(10) (cf. ligne 8), cela signifie que la valeur de référence de la barre va évoluer d’une unité toutes les 10 ms. Dans l’exemple ci-dessus, comme la valeur maximale est à 300, cela signifie que la barre se remplit en 3 secondes.

On peut d’ailleurs visualiser la duréee de remplissage avec un label :

../../../_images/progress_bar_label.gif
from tkinter import Tk, ttk, StringVar, Label

root = Tk()

progress = ttk.Progressbar(root, length=400, maximum=300)
progress.pack(pady=30)

progress.start(10)

message = StringVar()
w = Label(root, textvariable=message, font='Arial 30 bold')
w.pack()

seconds = 0
delay = 1000


def anim(ms):
    message.set(str(ms // 1000))
    root.after(delay, anim, ms + delay)


anim(0)

root.mainloop()

Un appel de start sans paramètre effectue une progression toutes les 50 ms.

Barre de progression qui s’arrête

Lorsque la barre de progression est complètement remplie, on souhaite que la progression ne reparte pas à zero.

../../../_images/progressbar_stop.gif

Pour cela, il faut utiliser after et surveiller l’évolution de la variable interne value jusqu’au moment où elle atteint la valeur de maximum. Voici un code possible :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
from tkinter import *
from tkinter import ttk

root = Tk()

progress = ttk.Progressbar(root, maximum=300, length=400)
progress.pack(pady=30)

unit = 10
progress.start(10)


def anim(value):
    if progress["value"] < value:
        progress.stop()
        progress["value"] = progress["maximum"]
    root.after(10, anim, progress["value"])


anim(0)

root.mainloop()
  • Ligne 14 : on surveille la variable value de la barre. Si la barre redémarre à zéro, c’est que la valeur courante de value diminue.
  • Ligne 17 : on redémarre l’animation avec l’ancienne value.
  • Lignes 15-16 : on arrête la barre de progression et pour qu’elle remplisse la zone, value est affecté à la valeur de remplissage maximum.

Modifier les options d’un widget

Ce qui suit est essentiel pour faire fonctionner une interface graphique sous Tkinter. Il suppose que vous avez un minimum de familiarité avec les widgets.

Pour illustrer, considérons un jeu du Pendu en version très simplifiée : le joueur choisit des lettres en cliquant sur des boutons à la recherche de lettres cachées dans une zone de texte.

../../../_images/modifier_widget.gif

On souhaite que :

  • lorsqu’un clic découvre une lettre présente dans le mot inconnu, la lettre soit rendue visible et donc que l’astérisque qui couvre la lettre disparaisse ;
  • tout clic (gagnant ou perdant) sur une lettre jamais encore choisie provoque la désactivation du bouton sur lequel le joueur a cliqué (c’est assez naturel puisqu’il n’y a aucune raison que le joueur re-clique sur le même bouton).

Chaque lettre de la zone de texte est un label. On veut donc qu’à chaque clic :

  • le texte du label bascule de * vers la lettre trouvée ;
  • le bouton soit modifié (et désactivé).

On doit donc changer l’état des deux widgets. Tous les widgets d’une interface Tkinter ont des options, souvent très nombreuses. Par exemple, un bouton a une option state qui peut prendre trois valeurs selon l’état de réceptivité du widget. De même, un label a une option text qui indique le contenu du texte qu’on lit sur le label ou encore une option bg pour la couleur de fond du label. L’état d’un widget est déterminé par l’état de ses options. Ce qui anime une interface graphique est que ses widgets changent d’état.

Il existe essentiellement deux syntaxes pour changer une option (il existe une 3e façon moins usuelle qui ne sera que très brièvement évoquée). Ainsi, dans le jeu du Pendu, pour chager l’astérisque d’un label appelé disons lbl en, par exemple, la lettre E, on pourra écrire :

lbl["text"] = "E"

C’est la syntaxe la plus simple, sous forme de dictionnaire. Bien noter que lbl est ici une référence vers le widget, le nom de l’option text est placé entre guillement pour obtenir une chaîne de caractère. Et "E" est la nouvelle valeur de l’option du widget.

Dans l’exemple du jeu du Pendu, pour désactiver un bouton nommé btn, on écrira par exemple :

btn["state"]=DISABLED

Il existe une 2e syntaxe qui consiste à utiliser la méthode configure de chaque widget :

lbl.configure(text="E")

Notons que le nom de l’option est placé en argument nommé, sans guillemet. Cette syntaxe est plus indiquée lorsqu’il y a beaucoup d’options à changer simultanément. Par exemple, supposons que nous voulions changer la lettre en E mais aussi la police et la couleur de fond. On pourrait alors écrire :

lbl.configure(text="E", bg="red", font="Times 20 Bold")

Enfin, il est aussi possible de changer l’état d’un widget de manière indirecte et automatique en faisant en sorte qu’une option de ce widget soit au départ enregistrée sous la forme d’une variable de contrôle Tkinter telle que StringVar. Une telle variable est mise à jour automatiquement, sans qu’on réaffecte comme ci-dessus. Pour voir une situation typique, consulter Exemple d’utilisation de StringVar. Cette utilisation toutefois peut être évitée dans de nombreuses situations.