Powrót do strony głównej

Tkinter ToDo: edycja, kolory i sortowanie zadań

Artykuł opisuje rozszerzenie aplikacji Tkinter ToDo o nowe funkcje: edycję zadań przez modale okna, kolorową indykację priorytetów, sortowanie niewykonanych zadań i przechowywanie dat utworzenia. Podano pełny kod z kompatybilnością wsteczną danych JSON i technikami stylizacji Listbox.

Tkinter ToDo: kolory, sortowanie i modale okna edytowania
Advertisement 728x90

Rozszerzenie aplikacji ToDo w Tkinter: edycja, kolory, sortowanie i daty

Rozszerzenie funkcjonalności wymaga aktualizacji struktury zadań. Każde zadanie zawiera teraz pola: id, text, completed, created_at (ciąg w formacie ISO) i priority (high/medium/low). Dla zachowania kompatybilności wstecznej zaimplementowano migrację starych danych z wartościami domyślnymi.

Funkcje ładowania i zapisywania zapewniają płynną integrację:

import json
import os
from datetime import datetime

TASKS_FILE = "tasks.json"

def load_tasks():
    """Ładuje zadania z pliku JSON z zachowaniem kompatybilności wstecznej"""
    if not os.path.exists(TASKS_FILE):
        return []
    with open(TASKS_FILE, "r", encoding="utf-8") as f:
        try:
            tasks = json.load(f)
            # Migracja starych zadań (dodajemy created_at i priority, jeśli ich brak)
            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):
    """Zapisuje zadania do pliku 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):
    """Zwraca następny dostępny ID dla nowego zadania"""
    if not tasks:
        return 1
    return max(task["id"] for task in tasks) + 1

datetime.now().isoformat() generuje sortowalny ciąg w formacie 2025-03-22T15:30:45.123456.

Google AdInline article slot

Sortowanie i wizualna wskazówka

Sortowanie zaimplementowano bez zmiany oryginalnej kolejności zadań w self.tasks. Metoda get_sorted_tasks zwraca kopię:

  • Najpierw niezakończone (completed == False)
  • Następnie zakończone
  • Wewnątrz grup — według id
class TodoApp:
    
    def get_sorted_tasks(self):
        """Zwraca zadania posortowane do wyświetlenia: najpierw aktywne, potem zakończone"""
        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):
        """Odświeża wyświetlanie listy zadań z uwzględnieniem sortowania i kolorowej wskazówki"""
        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 = "data nieznana"

            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)

Kluczowe techniki:

  • itemconfig(index, bg=..., fg=...) do stylizacji wierszy Listbox
  • strftime("%d.%m.%y %H:%M") dla zlokalizowanego formatu daty
  • Leniwe sortowanie tylko przy refresh_task_list

Okno modalne edycji

Edycja otwiera Toplevel z grab_set() dla modalności. Logika znajduje zadanie po id z posortowanej listy:

Google AdInline article slot
class TodoApp:
   def edit_task(self):
        selection = self.task_listbox.curselection()
        if not selection:
            messagebox.showwarning("Ostrzeżenie", "Wybierz zadanie do edycji")
            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"Edycja zadania #{task_id}")
        edit_window.geometry("450x250")
        edit_window.resizable(False, False)
        edit_window.grab_set()

        tk.Label(edit_window, text="Tekst zadania:").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="Priorytet:").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="Wysoki", variable=priority_var, value="high", fg="red").pack(side=tk.LEFT, padx=5)
        tk.Radiobutton(priority_frame, text="Średni", variable=priority_var, value="medium", fg="orange").pack(side=tk.LEFT, padx=5)
        tk.Radiobutton(priority_frame, text="Niski", 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("Ostrzeżenie", "Tekst zadania nie może być pusty", 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("Sukces", f"Zadanie #{task_id} zaktualizowane")

        tk.Button(button_frame, text="Zapisz", command=save_edit, bg="light green", width=10).pack(side=tk.LEFT, padx=5)
        tk.Button(button_frame, text="Anuluj", command=edit_window.destroy, bg="light gray", width=10).pack(side=tk.LEFT, padx=5)

Integracja: Dodać self.edit_button = tk.Button(bottom_frame, text="✏ Edytuj", command=self.edit_task) w create_widgets.

Okno dialogowe dodawania z priorytetem

Metoda add_task używa podobnej struktury Toplevel z fokusem na Entry i przyciskami opcji:

  • text_entry.focus() dla UX
  • Walidacja strip() i pustości
  • Autogeneracja id, created_at, completed=False

Pełny kod metody zapewnia spójność z edycją.

Google AdInline article slot

Co jest ważne

  • Kompatybilność wsteczna: Automatyczna migracja created_at i priority przy ładowaniu
  • Wydajność: Sortowanie kopii listy, nie dotyka danych trwałych
  • Stylizacja Listbox: itemconfig(tk.END, bg=..., fg=...) dla dynamicznych kolorów
  • Okna modalne: Toplevel + grab_set() + resizable(False, False)
  • Formatowanie daty: strftime dla czytelnego wyświetlania ciągów ISO

— Editorial Team

Advertisement 728x90

Czytaj dalej