ToDoMate πŸ“: Build a Modern Tkinter To-Do List App in Python

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • MyrinNew
    Senior Member
    • Feb 2024
    • 5175

    #1

    ToDoMate πŸ“: Build a Modern Tkinter To-Do List App in Python

    Managing tasks efficiently is key to productivity. In this tutorial, we’ll build ToDoMate, a modern Python Tkinter to-do list app with features like priorities, due dates, filters, sorting, and exporting. By the end, you'll have a full-featured desktop app that stores tasks locally in CSV format.


    Screenshot of the ToDoMate app with dashboard and tasks highlighted.





    Features


    ToDoMate comes with:


    πŸ—‚οΈ Two-tab interface: Dashboard & To-Do List


    βœ… Add, remove, and mark tasks as done


    πŸ“… Priority levels (High, Medium, Low) and due dates with color coding


    πŸ” Filters: Today, Overdue, High-priority


    πŸ”Ž Search tasks by title


    ↕ Sort tasks by due date or priority


    πŸ’Ύ Export tasks to CSV or TXT


    Requirements


    You only need Python 3 (tested with 3.9+) and the built-in libraries:


    pip install tk


    Tkinter usually comes pre-installed with Python.


    Project Structure

    ToDoMate/

    β”œβ”€β”€ todo_list.csv # Tasks storage file (auto-created)

    └── todomate.py # Main Python app


    Full Source Code


    Here’s the full Python script for ToDoMate:






    import sys
    import os
    import csv
    from datetime import datetime, date
    import tkinter as tk
    from tkinter import ttk, messagebox, filedialog

    # =========================
    # Helpers
    # =========================
    def resource_path(file_name):
    base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
    return os.path.join(base_path, file_name)

    TODO_FILE = resource_path("todo_list.csv")
    tasks = []

    def save_tasks():
    try:
    with open(TODO_FILE, "w", newline="") as f:
    writer = csv.writer(f)
    for task in tasks:
    writer.writerow([task["title"], task["done"], task["priority"], task["due_date"]])
    except Exception as e:
    messagebox.showerror("Error", f"Saving tasks failed: {e}")

    def load_tasks():
    if not os.path.exists(TODO_FILE):
    return
    try:
    with open(TODO_FILE, "r") as f:
    reader = csv.reader(f)
    for row in reader:
    if len(row) == 4:
    due_date = row[3].strip()
    if due_date:
    try:
    datetime.strptime(due_date, "%Y-%m-%d")
    except:
    try:
    dt = datetime.strptime(due_date, "%m/%d/%Y")
    due_date = dt.strftime("%Y-%m-%d")
    except:
    due_date = ""
    tasks.append({
    "title": row[0],
    "done": row[1] == "True",
    "priority": row[2],
    "due_date": due_date
    })
    except Exception as e:
    messagebox.showerror("Error", f"Loading tasks failed: {e}")

    def get_filtered_sorted_tasks(filter_type=None, sort_by=None, search_text=""):
    filtered = tasks
    today_str = date.today().strftime("%Y-%m-%d")
    if filter_type == "today":
    filtered = [t for t in filtered if t["due_date"] == today_str]
    elif filter_type == "overdue":
    filtered = [t for t in filtered if t["due_date"] and t["due_date"] < today_str and not t["done"]]
    elif filter_type == "high":
    filtered = [t for t in filtered if t["priority"] == "High"]

    if search_text:
    filtered = [t for t in filtered if search_text.lower() in t["title"].lower()]

    if sort_by == "due":
    filtered.sort(key=lambda x: x["due_date"] or "9999-99-99")
    elif sort_by == "priority":
    order = {"High": 0, "Medium": 1, "Low": 2}
    filtered.sort(key=lambda x: order.get(x["priority"], 3))

    return filtered

    # =========================
    # GUI Functions
    # =========================
    def refresh_treeview(*args):
    for row in tree.get_children():
    tree.delete(row)
    filter_type = filter_var.get()
    sort_by = sort_var.get()
    search_text = search_var.get()
    for task in get_filtered_sorted_tasks(filter_type, sort_by, search_text):
    due_display = task["due_date"] if task["due_date"] else "β€”"
    tree.insert("", "end", values=(
    task["title"],
    "βœ…" if task["done"] else "❌",
    task["priority"],
    due_display
    ))
    tags = []
    if task["done"]:
    tags.append("done")
    elif task["priority"] == "High":
    tags.append("high")
    elif task["priority"] == "Medium":
    tags.append("medium")
    elif task["priority"] == "Low":
    tags.append("low")
    if task["due_date"]:
    due_dt = datetime.strptime(task["due_date"], "%Y-%m-%d").date()
    if due_dt < date.today() and not task["done"]:
    tags.append("overdue")
    tree.item(tree.get_children()[-1], tags=tags)

    def add_task():
    title = title_entry.get().strip()
    if not title:
    messagebox.showwarning("Input Error", "Task title cannot be empty")
    return
    priority = priority_combo.get()
    due_date = due_entry.get().strip()
    if due_date:
    try:
    datetime.strptime(due_date, "%Y-%m-%d")
    except:
    messagebox.showwarning("Input Error", "Invalid due date format. Use YYYY-MM-DD")
    return
    tasks.append({"title": title, "done": False, "priority": priority, "due_date": due_date})
    save_tasks()
    refresh_treeview()
    title_entry.delete(0, tk.END)
    due_entry.delete(0, tk.END)

    def remove_task():
    selected = tree.selection()
    if not selected: return
    idx = tree.index(selected[0])
    removed = tasks.pop(idx)
    save_tasks()
    refresh_treeview()
    messagebox.showinfo("πŸ—‘οΈ Removed", f"Removed task: {removed['title']}")

    def mark_done():
    selected = tree.selection()
    if not selected: return
    idx = tree.index(selected[0])
    tasks[idx]["done"] = True
    save_tasks()
    refresh_treeview()

    def clear_all_tasks():
    if messagebox.askyesno("⚠️ Clear All", "Are you sure you want to remove all tasks?"):
    tasks.clear()
    save_tasks()
    refresh_treeview()

    def export_tasks():
    file_path = filedialog.asksaveasfilename(defaultextension=".cs v", filetypes=[("CSV file","*.csv"),("Text file","*.txt")])
    if not file_path: return
    try:
    if file_path.endswith(".csv"):
    with open(file_path,"w",newline="") as f:
    writer = csv.writer(f)
    for task in tasks:
    writer.writerow([task["title"], task["done"], task["priority"], task["due_date"]])
    else:
    with open(file_path,"w") as f:
    for task in tasks:
    f.write(f"{task['title']} | {'Done' if task['done'] else 'Pending'} | {task['priority']} | {task['due_date'] or 'β€”'}\n")
    messagebox.showinfo("πŸ’Ύ Exported", f"Tasks exported to {file_path}")
    except Exception as e:
    messagebox.showerror("Error", f"Export failed: {e}")

    # =========================
    # GUI Setup
    # =========================
    root = tk.Tk()
    root.title("ToDoMate πŸ“")
    root.geometry("950x600")
    root.configure(bg="#f0f4f8")

    # Notebook
    notebook = ttk.Notebook(root)
    notebook.pack(fill="both", expand=True)

    # ... (Dashboard & To-Do Tab code continues as in original)

    load_tasks()
    refresh_treeview()
    root.mainloop()








    How It Works


    Tasks are stored in todo_list.csv.


    Filters and sorting allow users to view tasks by due date, priority, or today’s tasks.


    Color-coded treeview highlights priorities and overdue tasks.


    Easy export to CSV or TXT ensures your tasks are portable.




    More...
Working...