OUJOOD.COM
Ce deuxième projet est plus orienté données que la calculatrice. Il introduit la persistance (sauvegarde JSON), le filtrage d'une liste, et l'organisation en classes avec plusieurs responsabilités bien séparées. Les concepts utilisés : Entry, Listbox, StringVar, messagebox et bind().
Fonctionnalités
L'application gère des tâches avec un statut "fait / à faire". Elle permet d'ajouter une tâche, de la marquer comme faite d'un double-clic, de la supprimer et de filtrer l'affichage. Les tâches sont sauvegardées dans un fichier JSON au même endroit que le script.
Code complet
import tkinter as tk
from tkinter import messagebox
import json
import os
FICHIER = "taches.json"
class GestionnaireTaches(tk.Tk):
def __init__(self):
super().__init__()
self.title("Gestionnaire de tâches")
self.geometry("480x420")
self.resizable(False, False)
self.protocol("WM_DELETE_WINDOW", self._quitter)
self._taches = [] # [{"texte": str, "faite": bool}]
self._filtre = tk.StringVar(value="toutes")
self._charger()
self._creer_interface()
self._rafraichir_liste()
def _creer_interface(self):
# Barre de saisie
frame_top = tk.Frame(self, bg="#f5f5f5")
frame_top.pack(fill="x", padx=10, pady=10)
self._champ = tk.Entry(frame_top, font=("Arial", 12), width=32)
self._champ.pack(side="left", ipady=4)
self._champ.bind("", lambda e: self._ajouter())
tk.Button(frame_top, text="Ajouter", command=self._ajouter,
bg="#1565c0", fg="white", relief="flat",
padx=12, pady=4).pack(side="left", padx=6)
# Filtres
frame_filtres = tk.Frame(self, bg="#f5f5f5")
frame_filtres.pack(fill="x", padx=10)
for val, texte in [("toutes","Toutes"), ("a_faire","À faire"), ("faites","Faites")]:
tk.Radiobutton(
frame_filtres, text=texte, variable=self._filtre,
value=val, command=self._rafraichir_liste,
bg="#f5f5f5"
).pack(side="left", padx=6)
# Liste avec scrollbar
frame_liste = tk.Frame(self)
frame_liste.pack(fill="both", expand=True, padx=10, pady=8)
scrollbar = tk.Scrollbar(frame_liste)
scrollbar.pack(side="right", fill="y")
self._listbox = tk.Listbox(
frame_liste, font=("Arial", 11), height=12,
yscrollcommand=scrollbar.set,
selectbackground="#bbdefb"
)
self._listbox.pack(side="left", fill="both", expand=True)
scrollbar.config(command=self._listbox.yview)
self._listbox.bind("", lambda e: self._basculer())
# Boutons d'action
frame_btn = tk.Frame(self)
frame_btn.pack(pady=6)
tk.Button(frame_btn, text="✓ Marquer faite",
command=self._basculer, width=16).pack(side="left", padx=5)
tk.Button(frame_btn, text="✕ Supprimer",
command=self._supprimer,
bg="#ef5350", fg="white", relief="flat", width=12).pack(side="left", padx=5)
# Compteur
self._label_compteur = tk.Label(self, text="", fg="#777")
self._label_compteur.pack(pady=4)
def _ajouter(self):
texte = self._champ.get().strip()
if not texte:
return
self._taches.append({"texte": texte, "faite": False})
self._champ.delete(0, tk.END)
self._sauvegarder()
self._rafraichir_liste()
def _basculer(self):
indices = self._listbox.curselection()
if not indices:
return
idx_reel = self._indices_visibles()[indices[0]]
self._taches[idx_reel]["faite"] = not self._taches[idx_reel]["faite"]
self._sauvegarder()
self._rafraichir_liste()
def _supprimer(self):
indices = self._listbox.curselection()
if not indices:
return
idx_reel = self._indices_visibles()[indices[0]]
texte = self._taches[idx_reel]["texte"]
if messagebox.askyesno("Confirmer", f"Supprimer « {texte} » ?"):
del self._taches[idx_reel]
self._sauvegarder()
self._rafraichir_liste()
def _indices_visibles(self):
filtre = self._filtre.get()
return [
i for i, t in enumerate(self._taches)
if filtre == "toutes"
or (filtre == "faites" and t["faite"])
or (filtre == "a_faire" and not t["faite"])
]
def _rafraichir_liste(self):
self._listbox.delete(0, tk.END)
for i in self._indices_visibles():
t = self._taches[i]
prefixe = "✓ " if t["faite"] else "○ "
self._listbox.insert(tk.END, prefixe + t["texte"])
if t["faite"]:
self._listbox.itemconfig(tk.END, fg="#aaa")
total = len(self._taches)
faites = sum(1 for t in self._taches if t["faite"])
self._label_compteur.config(
text=f"{faites}/{total} tâche(s) terminée(s)"
)
def _sauvegarder(self):
with open(FICHIER, "w", encoding="utf-8") as f:
json.dump(self._taches, f, ensure_ascii=False, indent=2)
def _charger(self):
if os.path.exists(FICHIER):
try:
with open(FICHIER, "r", encoding="utf-8") as f:
self._taches = json.load(f)
except (json.JSONDecodeError, KeyError):
self._taches = []
def _quitter(self):
self._sauvegarder()
self.destroy()
if __name__ == "__main__":
app = GestionnaireTaches()
app.mainloop()
Ce que ce projet illustre
La séparation entre les données (self._taches), l'affichage (_rafraichir_liste()) et les actions (_ajouter, _supprimer) est le schéma MVC simplifié — une action modifie les données puis appelle le rafraîchissement. La liste filtrée est calculée à la volée via _indices_visibles() plutôt que stockée séparément, ce qui évite les désynchronisations. Ce type d'architecture est réutilisable pour n'importe quelle liste d'objets — y compris des données chargées depuis un CSV avec Pandas.
Par carabde | Mis à jour le 30 avril 2025