Interfaces graphiques avec Python et Tkinter #
Introduction #
Ce document traite de la réalisation d’applications graphiques (avec
fenêtres, boutons etc..) en Python. La création d’applications
graphiques nécessite le choix d’un toolkit particulier. Le toolkit
utilisé est tkinter
. Il a l’avantage d’être généralement distribué
avec Python.
L’objectif de ce document est de guider le lecteur pas à pas dans la réalisation de programmes comportant une interface graphique.
Test de l’installation #
Le programme suivant ouvre une fenêtre contenant le texte Hello World. :
import tkinter as tk
root = tk.Tk()
lbl = tk.Label(root, text="Hello World.", width=20)
lbl.pack(padx=20, pady=20)
root.mainloop()
Ce programme doit pouvoir être exécuté :
- en ligne de commande :
python3 testtkinter.py
- sous Windows, en cliquant sur le fichier
.py
ou en ouvrant ce dernier avec l’interpréteur. - depuis l’environnement de développement (Pyzo, en ayant pris soin de régler la boucle des événements sur tkinter.
Accès à la documentation #
Il y a de nombreuses documentations sur tkinter. Certaines sont référencées sur la page principale 2A : Informatique & Programmation Python - Diplôme Énergie
Apprentissage par l’exemple #
L’utilisation de tkinter peut se faire sous l’angle de la programmation orientée objets (POO) ou non. Pour réutiliser du code, il peut donc être nécessaire d’avoir des notions de POO. Toutefois, pour réaliser de petites interfaces en partant de 0, on peut généralement se passer de POO, ce que nous ferons ici.
Création d’un widget et lancement d’une application #
Une application graphique doit contenir au moins un objet (les éléments graphiques des applications sont appelés widgets).
Une interface graphique contient généralement un objet de base de type Tk (souvent la fenêtre de base). Dans cet objet sont placés d’autres objets, comme des cadres (Frame), des labels (Label), des boutons (Button), des champs de saisie (Entry), ….
Notez aussi la présence de la ligne mainloop
. Il s’agit de la
fondamentale boucle des événements. Une fois les objets graphiques
créés et affichés, le programme entre dans cette boucle et gère les
événements clavier, souris, provoqués par l’utilisateur, ainsi que le
réaffichage des objets (si la fenêtre est occultée, redimensionnée
etc…).
Voici un petit programme qui illustre ces concepts :
import tkinter as tk # Prenez l'habitude de renommer le module
root = tk.Tk()
root.title("Example")
root.geometry("640x480+100+100")
frame = tk.Frame(root) # Création d'un cadre (invisible) dans root.
frame.pack()
root.mainloop()
Ajout de Widgets à l’intérieur de la fenêtre #
Une application réelle se compose de nombreux widgets, savamment disposés, et qui interagissent.
Nous n’allons utiliser que la disposition des widgets en mode absolu c’est à dire que nous indiquerons les coordonnées et les tailles précises de chaque objet. Gardons cependant à l’esprit que c’est une simple étape. Une véritable application graphique a des objets disposés selon un ou plusieurs layouts. C’est ce qui permet à l’application d’être redimensionnée correctement et de s’adapter à plusieurs tailles d’écran.
Nous allons ajouter 2 widgets à notre fenêtre. Un Bouton et un Label. Puis, nous connecterons ces deux objets de telle manière que l’appui sur le bouton ait une action sur le label : en cliquant sur le bouton, le texte du label changera.
Voici la première version, contenant uniquement le squelette graphique :
import tkinter as tk # Prenez l'habitude de renommer le module
root = tk.Tk()
root.title("Example")
root.geometry("120x90")
frame = tk.Frame(root)
frame.pack(fill=tk.BOTH, expand=1)
label = tk.Label(frame, text="Salut", fg="blue", bg="yellow")
label.place(x=10, y=10, width=100, height=30)
bouton = tk.Button(frame, text="Hit me!")
bouton.place(x=10, y=50, width=100, height=30)
root.mainloop()
Notez dans le code qui précède :
- lors de la création d’un widget B dans un widget A, on indique
que B a le widget A pour parent (au sens de l’interface
graphique, pas au sens de la POO) :
label=tk.Label(frame,...)
crée un label dont le parent estframe
, c’est à dire le cadre de la fenêtre principale. C’est pour cette raison que le label apparaît dans la fenêtre. - la position et la taille d’un widget sont réglées par la méthode
place
qui permet de préciser les coordonnes du coin supérieur gauche du widget, sa largeur et sa hauteur. L’origine du repère est le coin supérieur gauche du widget parent. L’axe des ordonnées est dirigé vers le bas. - l’aspect graphique des widgets peut être modifié lors de la création
par un jeu de paramètres optionnels assez fourni (
fg
etbg
dans l’exemple).
Testez l’application et voyez que chaque objet apparaît effectivement. Le bouton est fonctionnel (il s’enfonce), mais ne provoque aucune action particulière.
Nous allons maintenant associer une action au bouton. Pour cela, on indique le nom d’un callable (généralement une fonction) à exécuter lorsque le bouton sera cliqué.
import tkinter as tk
compteur = 0
def unkilometre():
global compteur
compteur += 1
label['text'] = "{} km(s) à pieds...".format(compteur)
root = tk.Tk()
root.title("Example")
root.geometry("140x90")
frame = tk.Frame(root)
frame.pack(fill=tk.BOTH, expand=1)
label = tk.Label(frame, text="Salut", fg="blue", bg="yellow")
label.place(x=10, y=10, width=120, height=30)
bouton = tk.Button(frame, text="Hit me!", command=unkilometre)
bouton.place(x=10, y=50, width=120, height=30)
root.mainloop()
Dans la méthode unkilometre
, on incrémente un compteur; et on affiche
ce compteur dans label
.
Résumé
- Une application graphique est crée généralement en écrivant une hiérarchie de Widgets.
- Des actions sur certains Widgets vont déclencher des commandes dont l’action sera généralement visible dans l’interface.
Convertisseur Euros / Dollars #
Nous allons réaliser une application de conversion d’une unité en une autre. Nous reprendrons l’exemple classique du convertisseur Euros / Dollars.
Le convertisseur qui suit comporte trois widgets : le bouton de conversion, le label qui affiche le résultat et le champ Entry qui permet d’entrer la valeur à convertir :
# convertisseur Euros/Dollars
import tkinter as tk
def conversion():
print(entry.get())
text= entry.get()
label['text'] = text
root = tk.Tk()
root.title("Convertisseur")
root.geometry("140x130")
frame = tk.Frame(root)
frame.pack(fill=tk.BOTH, expand=1)
entry = tk.Entry(frame, text="")
entry.place(x=10, y=10, width=120, height=30)
bouton = tk.Button(frame, text="Conversion", command=conversion)
bouton.place(x=10, y=50, width=120, height=30)
label = tk.Label(frame, text="Salut", fg="blue", bg="yellow")
label.place(x=10, y=90, width=120, height=30)
root.mainloop()
Résumé
- On récupère le texte inscrit dans un champ
Entry
en faisant :entry.get()
.- Les chaînes de caractères récupérées dans les champs peuvent être converties en utilisant les méthodes standard de python (
int(...)
pour convertir en entier oufloat()
pour convertir en nombre à virgule).- Le mécanisme d’exception permet de prévenir les erreurs de saisie. Il n’est pas nécessaire de l’utiliser, mais il rend le programme plus clair, plus sûr, et plus facile à débuguer (non fait ici…)
Exercices #
- Terminer le programme pour qu’il convertisse des euros en dollars.
- Améliorer le programme pour qu’il fasse la conversion simultanément dans plusieurs devises.
- Vérifier ce qui se passe lorsque vous entrez une valeur non numérique. Comment régler le problème ?
Compléments #
Synchroniser un widget et une variable tkinter
Il est possible de synchroniser des widgets avec des variables : on
peut créer une variable (ici valeur
qui sera le reflet du label.
Changer valeur
modifiera l’affichage du label.
# convertisseur Euros/Dollars
import tkinter as tk
def conversion():
text= entry.get()
valeur.set(text)
root = tk.Tk()
root.title("Convertisseur")
root.geometry("140x130")
frame = tk.Frame(root)
frame.pack(fill=tk.BOTH, expand=1)
entry = tk.Entry(frame, text="")
entry.place(x=10, y=10, width=120, height=30)
bouton = tk.Button(frame, text="Conversion", command=conversion)
bouton.place(x=10, y=50, width=120, height=30)
valeur = tk.StringVar() # <<<=====
label = tk.Label(frame, text="Salut", fg="blue", bg="yellow", textvariable=valeur) # <<<<==
label.place(x=10, y=90, width=120, height=30)
root.mainloop()
Gestion des clics souris dans un Canvas
#
Le programme de départ suivant crée une fenêtre contenant un canvas. Des callbacks sont associées au canvas pour réagir aux mouvements de la souris.
import tkinter as tk
glob_data = {'canvas': None}
def click(event):
print("clic souris : ", event.x, event.y)
def drag(event):
print("drag souris : ", event.x, event.y)
def gui(parent):
parent.title("Programme de dessin")
frame = tk.Frame(parent)
frame.pack(fill='both', expand=1)
glob_data['canvas'] = tk.Canvas(frame, width=400, height=400, background="grey")
glob_data['canvas'].pack(padx=8, pady=8)
glob_data['canvas'].bind("<Button-1>", click)
glob_data['canvas'].bind("<B1-Motion>", drag)
# Script de création de l'application et lancement
# de la boucle des événements
root = tk.Tk()
gui(root)
root.mainloop()
Tester ce programme et regarder ce qui s’affiche dans le terminal lorsque la souris est utilisée. La différence entre les deux callbacksclick
etdrag
doit être parfaitement claire.
Par ailleurs, noter de quelle façon les variables globales (il y en a une seule ici) ont été regoupées dans le dictionnaire
glob_data
. Ceci évite l’utilisation de noms inappropriés par inadvertance et permet de mieux contrôles les variables globales utilisées.
- Compléter ce code pour pouvoir dessiner à la souris (voir la fonction
create_line
de tkinter)- Modifier le code pour que le dessin soit multicolore
- Réaliser une symétrie (vertical, horizontale ou autre) : chaque tracé que vous faites sera démultiplié (demandez une démo si vous n’imaginez pas le résultat)