Retour à l'accueil

Tkinter ToDo : édition, couleurs et tri des tâches

L'article décrit l'extension de l'application Tkinter ToDo avec de nouvelles fonctionnalités : édition des tâches via fenêtres modales, indication des couleurs pour les priorités, tri des tâches non terminées et stockage des dates de création. Code complet avec compatibilité arrière des données JSON et techniques de stylisation Listbox fourni.

Tkinter ToDo : couleurs, tri et fenêtres modales d'édition
Advertisement 728x90

É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.

Google AdInline article slot

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 Listbox
  • strftime("%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 :

Google AdInline article slot
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.

Google AdInline article slot

Points clés

  • Compatibilité ascendante : Migration automatique de created_at et priority au 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 : strftime pour un affichage lisible des chaînes ISO

— Editorial Team

Advertisement 728x90

Lire ensuite