Étendre une application ToDo Tkinter : Édition, couleurs, tri et dates
Étendre les fonctionnalités nécessite de mettre à jour la structure des tâches. Chaque tâche inclut désormais les champs : id, text, completed, created_at (chaîne ISO) et priority (high/medium/low). Pour la compatibilité ascendante, une migration des anciennes données avec des valeurs par défaut est implémentée.
Les fonctions de chargement et de sauvegarde assurent une intégration transparente :
import json
import os
from datetime import datetime
TASKS_FILE = "tasks.json"
def load_tasks():
"""Charge les tâches depuis un fichier JSON avec compatibilité ascendante"""
if not os.path.exists(TASKS_FILE):
return []
with open(TASKS_FILE, "r", encoding="utf-8") as f:
try:
tasks = json.load(f)
# Migrer les anciennes tâches (ajouter created_at et priority si manquants)
for task in tasks:
if "created_at" not in task:
task["created_at"] = datetime.now().isoformat()
if "priority" not in task:
task["priority"] = "medium"
return tasks
except json.JSONDecodeError:
return []
def save_tasks(tasks):
"""Sauvegarde les tâches dans un fichier JSON"""
with open(TASKS_FILE, "w", encoding="utf-8") as f:
json.dump(tasks, f, ensure_ascii=False, indent=2)
def get_next_id(tasks):
"""Retourne le prochain ID disponible pour une nouvelle tâche"""
if not tasks:
return 1
return max(task["id"] for task in tasks) + 1
datetime.now().isoformat() génère une chaîne triable au format 2025-03-22T15:30:45.123456.
Tri et indicateurs visuels
Le tri est implémenté sans altérer l'ordre original des tâches dans self.tasks. La méthode get_sorted_tasks retourne une copie :
- D'abord, les tâches incomplètes (
completed == False) - Ensuite, les tâches complétées
- Au sein des groupes — par
id
class TodoApp:
def get_sorted_tasks(self):
"""Retourne les tâches triées pour l'affichage : actives d'abord, puis complétées"""
active_tasks = [task for task in self.tasks if not task["completed"]]
completed_tasks = [task for task in self.tasks if task["completed"]]
active_tasks.sort(key=lambda x: x["id"])
completed_tasks.sort(key=lambda x: x["id"])
return active_tasks + completed_tasks
def refresh_task_list(self):
"""Met à jour l'affichage de la liste des tâches avec tri et indicateurs de couleur"""
self.task_listbox.delete(0, tk.END)
sorted_tasks = self.get_sorted_tasks()
for task in sorted_tasks:
status = "✓" if task["completed"] else "○"
try:
created = datetime.fromisoformat(task["created_at"])
date_str = created.strftime("%d/%m/%y %H:%M")
except:
date_str = "date inconnue"
display_text = f"{status} [{date_str}] {task['id']}. {task['text']}"
self.task_listbox.insert(tk.END, display_text)
bg_color = "white"
fg_color = "black"
if task["completed"]:
fg_color = "green"
bg_color = "light gray"
else:
if task["priority"] == "high":
fg_color = "red"
bg_color = "#ffe6e6"
elif task["priority"] == "medium":
fg_color = "orange"
bg_color = "#fff0e6"
index = tk.END
self.task_listbox.itemconfig(tk.END, bg=bg_color, fg=fg_color)
Techniques clés :
itemconfig(index, bg=..., fg=...)pour styliser les lignes de Listboxstrftime("%d/%m/%y %H:%M")pour le format de date localisé- Tri paresseux uniquement pendant
refresh_task_list
Fenêtre d'édition modale
L'édition ouvre une fenêtre Toplevel avec grab_set() pour la modalité. La logique trouve la tâche par id depuis la liste triée :
class TodoApp:
def edit_task(self):
selection = self.task_listbox.curselection()
if not selection:
messagebox.showwarning("Avertissement", "Sélectionnez une tâche à éditer")
return
selected_index = selection[0]
sorted_tasks = self.get_sorted_tasks()
selected_task = sorted_tasks[selected_index]
task_id = selected_task["id"]
task_to_edit = None
task_index = None
for i, task in enumerate(self.tasks):
if task["id"] == task_id:
task_to_edit = task
task_index = i
break
if not task_to_edit:
return
edit_window = tk.Toplevel(self.root)
edit_window.title(f"Édition de la tâche #{task_id}")
edit_window.geometry("450x250")
edit_window.resizable(False, False)
edit_window.grab_set()
tk.Label(edit_window, text="Texte de la tâche :").pack(pady=(10, 0), anchor=tk.W, padx=10)
text_entry = tk.Text(edit_window, height=4, width=50, font=("Arial", 10))
text_entry.pack(pady=5, padx=10, fill=tk.BOTH, expand=True)
text_entry.insert("1.0", task_to_edit["text"])
tk.Label(edit_window, text="Priorité :").pack(pady=(5, 0), anchor=tk.W, padx=10)
priority_var = tk.StringVar(value=task_to_edit["priority"])
priority_frame = tk.Frame(edit_window)
priority_frame.pack(pady=5, padx=10, anchor=tk.W)
tk.Radiobutton(priority_frame, text="Haute", variable=priority_var, value="high", fg="red").pack(side=tk.LEFT, padx=5)
tk.Radiobutton(priority_frame, text="Moyenne", variable=priority_var, value="medium", fg="orange").pack(side=tk.LEFT, padx=5)
tk.Radiobutton(priority_frame, text="Basse", variable=priority_var, value="low").pack(side=tk.LEFT, padx=5)
button_frame = tk.Frame(edit_window)
button_frame.pack(pady=15)
def save_edit():
new_text = text_entry.get("1.0", "end-1c").strip()
if not new_text:
messagebox.showwarning("Avertissement", "Le texte de la tâche ne peut pas être vide", parent=edit_window)
return
task_to_edit["text"] = new_text
task_to_edit["priority"] = priority_var.get()
save_tasks(self.tasks)
self.refresh_task_list()
edit_window.destroy()
messagebox.showinfo("Succès", f"Tâche #{task_id} mise à jour")
tk.Button(button_frame, text="Sauvegarder", command=save_edit, bg="light green", width=10).pack(side=tk.LEFT, padx=5)
tk.Button(button_frame, text="Annuler", command=edit_window.destroy, bg="light gray", width=10).pack(side=tk.LEFT, padx=5)
Intégration : Ajouter self.edit_button = tk.Button(bottom_frame, text="✏ Éditer", command=self.edit_task) à create_widgets.
Dialogue d'ajout de tâche avec priorité
La méthode add_task utilise une structure Toplevel similaire avec focus sur Entry et boutons radio :
text_entry.focus()pour l'UX- Validation avec
strip()et vérification de vide - Génération automatique de
id,created_at,completed=False
Le code complet de la méthode assure la cohérence avec l'édition.
Points clés
- Compatibilité ascendante : Migration automatique de
created_atetpriorityau chargement - Performance : Tri d'une copie de la liste, laissant les données persistantes intactes
- Stylisation Listbox :
itemconfig(tk.END, bg=..., fg=...)pour les couleurs dynamiques - Fenêtres modales :
Toplevel+grab_set()+resizable(False, False) - Formatage des dates :
strftimepour un affichage lisible des chaînes ISO
— Editorial Team
Aucun commentaire pour le moment.