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 :
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
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’optionfont
. - 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 :
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 :
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 fonctiondessiner
). 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 :
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()
|
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()
|
- 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 :
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 constructeurButton
:command
référence une fonction sans paramètre, ici la fonctionshow
, 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 :
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 verticalecommand
: la fonction qui est appelée quand on déplace le curseur (ici, la fonctionrayon
)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 terminefrom_
est utilisé car on ne peut pas utiliser la variablefrom
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 :
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 fonctionprint
. - 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 :
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 debind
(dans notre exemple la touche ENTRÉE référencée par<Return>
), une fonction de commande est exécutée (ici la fonctiondessiner
) - 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 fonctiondessiner
. L’objetmy_entry
dispose d’une méthodeget
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 fonctionint
(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
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 :
Deux méthodes sont ici utilisées :
- Ligne 8 : on accède à l’option du label
L
en utilisantL
comme un dictionnaire avec pour clé le nom de l’option, donc ici, on modofoeL['text']
; - Ligne 11 : on accède à l’option du label
L
en utilisant la méthodeconfigure
du label et l’argument nommé correspondant à l’option que l’on veut changer, icitext
.
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):
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 :
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 :
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 :
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
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 dynamiqueradius
. - Ligne 18 :
radius
est initialisée à un rayonr
. 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 commanderayon
. 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 :
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).
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 :
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 :
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 :
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, icipink
; - Lignes 10 et 17 :
highlightcolor
est la couleur de la bande lorsque le widget a le focus, icired
.
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 :
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 fonctionf
. - 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 :
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 :
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 :
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éthodeget
; 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ôlecpt
. 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, icipack
. - 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
etButton
.
qui affiche :
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:
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éthodepack
(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 :
- 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 optionsvariable
des trois boutons radio. - Lignes 22 et 23 : les boutons radio sont munis de 2 nouvelles options :
variable
etvalue
. Si le bouton radio est activé (donc, on a cliqué dessus), la valeur de la variable de contrôle seravalue
. - 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éefruit
(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 :
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 :
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 fonctioncolorer
(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 :
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 :
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’indicei
esti+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’indicei
, la fonction qui est appelée estg=f(i)
(ligne 11). Noter quef
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’indicei
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).
- elle recalcule (ligne 8) la nouvelle somme : en effet, si une case est décochée,
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 fonctionupdate
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 :
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 listefont
: la police utiliséeselectbackground
: 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 :
- 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 :
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 fonctionshow
qui va placer la bonne couleur sur le canevas. - Ligne 32 : la méthode
curselection
deListbox
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 fonctionf
- 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 :
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, icif
(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 valantup
si la flèche haut a été pressée etdown
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 :
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 detkinter
. Une importation de la formefrom 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’optionmaximum
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"]
où 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 :
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.
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 devalue
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.
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.