logo oujood
🔍

after() et threads Tkinter

after() planifie une action future sans bloquer l'interface. Les threads gèrent les tâches longues en arrière-plan. Les deux ensemble évitent les interfaces figées.

OUJOOD.COM

Le problème le plus frustrant avec les interfaces graphiques : un calcul long bloque l'interface et la fenêtre se fige. L'utilisateur ne peut plus cliquer, déplacer la fenêtre, ni voir de retour visuel. Deux outils Tkinter résolvent ce problème : after() pour les tâches courtes planifiées, et threading pour les opérations vraiment longues.

after() — planifier une action future

after(délai_ms, fonction) planifie un appel dans la boucle principale après un délai en millisecondes. La fenêtre reste réactive pendant l'attente :

📋 Copier le code

import tkinter as tk

fenetre = tk.Tk()
fenetre.title("after() — exemple")
fenetre.geometry("340x160")

def message_temporaire():
    label.config(text="Message envoyé !", fg="#2e7d32")
    # Effacer le message après 2 secondes
    fenetre.after(2000, lambda: label.config(text="", fg="black"))

tk.Button(fenetre, text="Envoyer", command=message_temporaire).pack(pady=30)
label = tk.Label(fenetre, text="", font=("Arial", 11))
label.pack()

fenetre.mainloop()

after() retourne un identifiant qu'on peut passer à after_cancel(id) pour annuler le callback avant son déclenchement — utile pour les tooltips ou les délais annulables.

Animation avec after() récursif

En appelant after() depuis le callback lui-même, on crée une boucle d'animation qui tourne dans la boucle principale sans bloquer :

📋 Copier le code

import tkinter as tk

fenetre = tk.Tk()
fenetre.title("Horloge en temps réel")
fenetre.geometry("300x130")

from datetime import datetime

label_heure = tk.Label(fenetre, font=("Arial", 36, "bold"), fg="#1565c0")
label_heure.pack(expand=True)

job_id = [None]

def mettre_a_jour():
    heure = datetime.now().strftime("%H:%M:%S")
    label_heure.config(text=heure)
    # Planifie le prochain appel dans 1 seconde
    job_id[0] = fenetre.after(1000, mettre_a_jour)

def arreter():
    if job_id[0]:
        fenetre.after_cancel(job_id[0])
        job_id[0] = None

mettre_a_jour()

fenetre.protocol("WM_DELETE_WINDOW", lambda: (arreter(), fenetre.destroy()))
fenetre.mainloop()

On stocke l'identifiant retourné par after() pour pouvoir annuler la boucle proprement à la fermeture. Sans ça, Tkinter peut lever une erreur si un callback est déclenché après la destruction de la fenêtre.

Threading pour les tâches longues

Pour les opérations qui prennent plusieurs secondes — téléchargement, lecture de gros fichiers, calcul intensif — threading est indispensable. La règle absolue : ne jamais modifier des widgets depuis un thread secondaire :

📋 Copier le code

import tkinter as tk
from tkinter import ttk
import threading
import queue
import time

fenetre = tk.Tk()
fenetre.title("Thread + Queue")
fenetre.geometry("360x180")

file_msgs = queue.Queue()  # canal de communication thread → interface

barre = ttk.Progressbar(fenetre, length=300, mode="determinate", maximum=100)
barre.pack(pady=20)
label = tk.Label(fenetre, text="Prêt")
label.pack()

def tache_longue():
    for i in range(1, 11):
        time.sleep(0.4)                      # simule un traitement
        file_msgs.put(("progression", i*10)) # envoie un message à l'interface
    file_msgs.put(("termine", None))

def verifier_queue():
    try:
        while True:
            type_msg, valeur = file_msgs.get_nowait()
            if type_msg == "progression":
                barre["value"] = valeur
                label.config(text=f"Traitement... {valeur} %")
            elif type_msg == "termine":
                label.config(text="Terminé !")
                btn.config(state="normal")
    except queue.Empty:
        pass
    fenetre.after(100, verifier_queue)  # vérifie la queue toutes les 100 ms

def lancer():
    btn.config(state="disabled")
    barre["value"] = 0
    t = threading.Thread(target=tache_longue, daemon=True)
    t.start()

btn = tk.Button(fenetre, text="Lancer la tâche", command=lancer)
btn.pack(pady=5)

verifier_queue()
fenetre.mainloop()

Le schéma Queue est le pattern recommandé : le thread secondaire écrit dans la queue, la boucle principale lit la queue via after() régulier. La fenêtre reste réactive, et on ne touche jamais aux widgets depuis le thread. Le paramètre daemon=True garantit que le thread s'arrête automatiquement à la fermeture de l'application, sans qu'on ait besoin de l'arrêter manuellement. Ce mécanisme est particulièrement utile quand on charge des données avec Pandas ou qu'on effectue des calculs NumPy en arrière-plan.

Par carabde | Mis à jour le 30 avril 2025