Tkinter

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 est frame, 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 et bg 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 ou float() 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 #

  1. Terminer le programme pour qu’il convertisse des euros en dollars.
  2. Améliorer le programme pour qu’il fasse la conversion simultanément dans plusieurs devises.
  3. 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 callbacks click et drag 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)