Powrót do strony głównej

Tkinter ToDo: filtry wyszukiwania eksport CSV

Artykuł opisuje rozszerzenie Tkinter ToDo: dodanie Combobox dla priorytetów, przycisków radiowych filtrowania, wyszukiwania real-time i eksportu do CSV. Zawiera pełny kod metod z obsługą UTF-8 i sortowaniem.

Filtry i eksport danych w aplikacji Tkinter ToDo
Advertisement 728x90

Tkinter ToDo: implementacja filtrów, wyszukiwania i eksportu danych

Integracja wyboru priorytetu bezpośrednio w panelu wprowadzania przyspiesza tworzenie zadań. Zastępujemy okno modalne ttk.Combobox z opcjami high, medium, low. Aktywacja dodawania przez Enter upraszcza workflow.

W create_widgets dodajemy ramkę z Entry, Combobox i przyciskiem:

from tkinter import ttk

top_frame = tk.Frame(self.root)
top_frame.pack(pady=10, padx=10, fill=tk.X)

self.entry = tk.Entry(top_frame, font=("Arial", 12))
self.entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))
self.entry.bind("<Return>", lambda event: self.add_task())

tk.Label(top_frame, text="Priorytet:").pack(side=tk.LEFT, padx=(5, 2))
self.priority_var = tk.StringVar(value="medium")
priority_combo = ttk.Combobox(
    top_frame, 
    textvariable=self.priority_var, 
    values=["high", "medium", "low"],
    state="readonly",
    width=8
)
priority_combo.pack(side=tk.LEFT, padx=(0, 5))
priority_combo.set("medium")

self.add_button = tk.Button(top_frame, text="➕ Dodaj", command=self.add_task, bg="light blue")
self.add_button.pack(side=tk.RIGHT)

Metoda add_task upraszcza się: pobiera dane z self.entry i self.priority_var.get(), zapisuje zadanie, czyści pole, aktualizuje listę.

Google AdInline article slot

Filtrowanie według statusu zadań

Przyciski opcji przełączają wyświetlanie: wszystkie zadania, tylko aktywne lub zakończone. Logika filtrowania integruje się w get_sorted_tasks.

W __init__ dodajemy self.filter_mode = "all". W create_widgets — panel z Radiobutton:

filter_frame = tk.Frame(self.root)
filter_frame.pack(pady=5, padx=10, fill=tk.X)

tk.Label(filter_frame, text="Filtr:").pack(side=tk.LEFT, padx=(0, 10))
self.filter_var = tk.StringVar(value="all")

tk.Radiobutton(filter_frame, text="Wszystkie", variable=self.filter_var, 
               value="all", command=self.refresh_task_list).pack(side=tk.LEFT, padx=5)
tk.Radiobutton(filter_frame, text="Aktywne", variable=self.filter_var, 
               value="active", command=self.refresh_task_list).pack(side=tk.LEFT, padx=5)
tk.Radiobutton(filter_frame, text="Zakończone", variable=self.filter_var, 
               value="completed", command=self.refresh_task_list).pack(side=tk.LEFT, padx=5)

W refresh_task_list: self.filter_mode = self.filter_var.get().

Google AdInline article slot

get_sorted_tasks najpierw filtruje według statusu, następnie sortuje aktywne po ID u góry, zakończone na dole:

def get_sorted_tasks(self):
    if self.filter_mode == "active":
        filtered_tasks = [task for task in self.tasks if not task["completed"]]
    elif self.filter_mode == "completed":
        filtered_tasks = [task for task in self.tasks if task["completed"]]
    else:
        filtered_tasks = self.tasks.copy()
    
    active_tasks = [task for task in filtered_tasks if not task["completed"]]
    completed_tasks = [task for task in filtered_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

Dynamiczne wyszukiwanie według tekstu zadań

Pole wyszukiwania z bind na <KeyRelease> zapewnia filtrowanie w czasie rzeczywistym na obecnym filtrze statusu. Przycisk czyszczenia resetuje zapytanie.

W create_widgets dodajemy search_frame:

Google AdInline article slot
search_frame = tk.Frame(self.root)
search_frame.pack(pady=(0, 5), padx=10, fill=tk.X)

tk.Label(search_frame, text="Szukaj:").pack(side=tk.LEFT, padx=(0, 5))
self.search_entry = tk.Entry(search_frame, font=("Arial", 10))
self.search_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
self.search_entry.bind("<KeyRelease>", lambda event: self.refresh_task_list())

clear_btn = tk.Button(search_frame, text="✖", command=self.clear_search, width=3)
clear_btn.pack(side=tk.RIGHT, padx=(5, 0))

Metod clear_search czyści self.search_entry i wywołuje refresh_task_list.

get_sorted_tasks rozszerza się o drugi filtr dla search_text = self.search_entry.get().strip().lower():

search_text = self.search_entry.get().strip().lower()
if search_text:
    filtered_tasks = [task for task in filtered_tasks if search_text in task["text"].lower()]

Wyszukiwanie jest niewrażliwe na wielkość liter, stosowane po filtrze statusu.

  • Zalety wyszukiwania w czasie rzeczywistym: Minimalne opóźnienia dzięki list comprehension.
  • Ograniczenia: Wyszukiwanie liniowe O(n), odpowiednie dla <1000 zadań.
  • Optymalizacja: Dla większych list rozważ bisect lub zewnętrzny indeks.

Eksport przefiltrowanych danych do CSV

Eksport przechwytuje aktualny widok listy (po wszystkich filtrach/wyszukiwaniu). Używa csv.writer z UTF-8 BOM dla kompatybilności z Excel. Separator ';', nagłówki: ID, Tekst, Status, Priorytet, Data utworzenia.

Przycisk w bottom_frame:

self.export_button = tk.Button(bottom_frame, text="📎 Eksport do CSV", command=self.export_to_csv, bg="light cyan")
self.export_button.pack(side=tk.LEFT, padx=5)

export_to_csv:

import csv
from tkinter import filedialog

 def export_to_csv(self):
    tasks_to_export = self.get_sorted_tasks()
    if not tasks_to_export:
        messagebox.showinfo("Informacja", "Brak zadań do eksportu")
        return
    
    filename = filedialog.asksaveasfilename(
        defaultextension=".csv",
        filetypes=[("Pliki CSV", "*.csv"), ("Wszystkie pliki", "*.*")],
        title="Zapisz jako CSV"
    )
    if not filename:
        return
    
    try:
        with open(filename, 'w', encoding='utf-8-sig', newline='') as f:
            writer = csv.writer(f, delimiter=';')
            writer.writerow(['ID', 'Tekst', 'Status', 'Priorytet', 'Data utworzenia'])
            
            for task in tasks_to_export:
                status = "Zakończona" if task["completed"] else "Aktywna"
                try:
                    created = datetime.fromisoformat(task["created_at"])
                    date_str = created.strftime("%d.%m.%Y %H:%M:%S")
                except:
                    date_str = task["created_at"]
                priority_map = {"high": "Wysoki", "medium": "Średni", "low": "Niski"}
                priority_display = priority_map.get(task["priority"], task["priority"])
                
                writer.writerow([task["id"], task["text"], status, priority_display, date_str])
        
        messagebox.showinfo("Sukces", f"Zadania zostały wyeksportowane do pliku:\n{filename}")
    except Exception as e:
        messagebox.showerror("Błąd", f"Nie udało się zapisać pliku: {e}")

Co jest ważne

  • Integracja Combobox dla priorytetów upraszcza UX bez okien modalnych.
  • Kombinowane filtrowanie (status + wyszukiwanie) używa łańcucha list comprehension dla wydajności.
  • Eksport CSV z UTF-8-sig i ';' zapewnia kompatybilność z Excel na Windows.
  • Aktualizacje w czasie rzeczywistym poprzez bind na <KeyRelease> i command w Radiobutton.
  • Sortowanie zawsze umieszcza aktywne zadania na górze dla skupienia się na priorytetach.

— Editorial Team

Advertisement 728x90

Czytaj dalej