Python To-Do List App – Console and GUI Project for Beginners

← Back to Projects

📝 Python Console To-Do List App


Project Overview

The Python To-Do List App is a beginner-friendly project that demonstrates how task management applications work in real life. This article covers three versions of the same application, helping learners understand how Python programs can evolve from simple console-based tools to full graphical user interface (GUI) applications.

By building this project, beginners learn how to manage data, handle user input, and persist information using files.



What You Will Learn From This Project

  • How to create a menu-driven console application
  • How to store and manage tasks using Python lists and dictionaries
  • How to validate user input
  • How to save and load data using files
  • How to build a GUI To-Do app using Tkinter
  • How to structure a Python project logically


Project Versions Explained

This article includes three different implementations of the To-Do List application. Each version builds upon the previous one to gradually introduce new concepts.

  • Console To-Do List: A basic menu-driven application.
  • Console To-Do List with File Saving: Tasks are stored permanently.
  • GUI To-Do List with File Saving: A complete desktop application.


How the To-Do List Application Works

The application maintains a list of tasks where each task has a description and a completion status. Users can add new tasks, mark existing tasks as completed, or delete tasks.

In the file-saving versions, tasks are stored in a text file so that the data remains available even after the program is closed and restarted.

Copy and paste this code into your Python editor or terminal to use:


todo_list = []

def show_menu():
    print("\n📝 To-Do List Menu")
    print("1. View Tasks")
    print("2. Add Task")
    print("3. Mark Task as Completed")
    print("4. Delete Task")
    print("5. Exit")

def view_tasks():
    if not todo_list:
        print("📭 No tasks in the list.")
    else:
        print("\nYour To-Do List:")
        for idx, task in enumerate(todo_list, start=1):
            status = "✅" if task['done'] else "❌"
            print(f"{idx}. {task['task']} [{status}]")

def add_task():
    task = input("✍️  Enter the task: ").strip()
    if task:
        todo_list.append({'task': task, 'done': False})
        print("✅ Task added!")
    else:
        print("⚠️  Empty task cannot be added.")

def mark_completed():
    view_tasks()
    if todo_list:
        try:
            task_num = int(input("✔️  Enter the task number to mark as completed: "))
            if 1 <= task_num <= len(todo_list):
                todo_list[task_num - 1]['done'] = True
                print("🎉 Task marked as completed!")
            else:
                print("❌ Invalid task number.")
        except ValueError:
            print("⚠️  Please enter a valid number.")

def delete_task():
    view_tasks()
    if todo_list:
        try:
            task_num = int(input("🗑️  Enter the task number to delete: "))
            if 1 <= task_num <= len(todo_list):
                removed = todo_list.pop(task_num - 1)
                print(f"🗑️  Deleted task: {removed['task']}")
            else:
                print("❌ Invalid task number.")
        except ValueError:
            print("⚠️  Please enter a valid number.")

# Main loop
while True:
    show_menu()
    choice = input("👉 Enter your choice (1-5): ").strip()

    if choice == '1':
        view_tasks()
    elif choice == '2':
        add_task()
    elif choice == '3':
        mark_completed()
    elif choice == '4':
        delete_task()
    elif choice == '5':
        print("👋 Exiting To-Do List. Have a productive day!")
        break
    else:
        print("❌ Invalid choice. Please enter a number from 1 to 5.")



Code Walkthrough (Core Concepts)

Task Storage

Tasks are stored using a list of dictionaries, where each dictionary represents a task and its completion status.

User Input Handling

The program validates user input to prevent invalid choices and runtime errors.

Menu-Based Navigation

A loop keeps the application running until the user chooses to exit.



📋 Python To-Do List (Console) with File Saving

This version will save your tasks to todo.txt and loads them when you restart the app:


import os

FILE_NAME = "todo.txt"
todo_list = []

def load_tasks():
    if not os.path.exists(FILE_NAME):
        return
    with open(FILE_NAME, "r", encoding="utf-8") as file:
        for line in file:
            parts = line.strip().split("|")
            if len(parts) == 2:
                task, done = parts
                todo_list.append({'task': task, 'done': done == "1"})

def save_tasks():
    with open(FILE_NAME, "w", encoding="utf-8") as file:
        for item in todo_list:
            file.write(f"{item['task']}|{'1' if item['done'] else '0'}\n")

def show_menu():
    print("\n📝 To-Do List Menu")
    print("1. View Tasks")
    print("2. Add Task")
    print("3. Mark Task as Completed")
    print("4. Delete Task")
    print("5. Exit")

def view_tasks():
    if not todo_list:
        print("📭 No tasks in the list.")
    else:
        print("\nYour To-Do List:")
        for idx, task in enumerate(todo_list, start=1):
            status = "✅" if task['done'] else "❌"
            print(f"{idx}. {task['task']} [{status}]")

def add_task():
    task = input("✍️  Enter the task: ").strip()
    if task:
        todo_list.append({'task': task, 'done': False})
        save_tasks()
        print("✅ Task added!")
    else:
        print("⚠️  Empty task cannot be added.")

def mark_completed():
    view_tasks()
    if todo_list:
        try:
            task_num = int(input("✔️  Enter task number to mark completed: "))
            if 1 <= task_num <= len(todo_list):
                todo_list[task_num - 1]['done'] = True
                save_tasks()
                print("🎉 Task marked as completed!")
            else:
                print("❌ Invalid task number.")
        except ValueError:
            print("⚠️  Please enter a valid number.")

def delete_task():
    view_tasks()
    if todo_list:
        try:
            task_num = int(input("🗑️  Enter task number to delete: "))
            if 1 <= task_num <= len(todo_list):
                removed = todo_list.pop(task_num - 1)
                save_tasks()
                print(f"🗑️  Deleted task: {removed['task']}")
            else:
                print("❌ Invalid task number.")
        except ValueError:
            print("⚠️  Please enter a valid number.")

# 📦 Load tasks at startup
load_tasks()

# Main loop
while True:
    show_menu()
    choice = input("👉 Enter your choice (1-5): ").strip()

    if choice == '1':
        view_tasks()
    elif choice == '2':
        add_task()
    elif choice == '3':
        mark_completed()
    elif choice == '4':
        delete_task()
    elif choice == '5':
        print("👋 Exiting To-Do List. All changes saved!")
        break
    else:
        print("❌ Invalid choice. Please enter a number from 1 to 5.")


How File Saving Works

In the file-saving versions, tasks are written to a text file using a simple delimiter-based format. Each task is stored along with its completion status.

When the application starts, it reads the file and loads the saved tasks into memory, allowing users to continue where they left off.



📋 Python GUI To-Do List with File Saving

This app lets users manage tasks in a GUI with automatic file saving to todo.txt:


import tkinter as tk
from tkinter import messagebox
import os

FILE_NAME = "todo.txt"

class ToDoApp:
    def __init__(self, root):
        self.root = root
        self.root.title("📋 To-Do List with File Saving")
        self.root.geometry("400x500")
        self.root.resizable(False, False)

        self.tasks = []

        self.entry = tk.Entry(root, font=("Arial", 14))
        self.entry.pack(padx=10, pady=10, fill=tk.X)

        self.add_button = tk.Button(root, text="➕ Add Task", font=("Arial", 12), bg="#4CAF50", fg="white", command=self.add_task)
        self.add_button.pack(padx=10, pady=5, fill=tk.X)

        self.task_listbox = tk.Listbox(root, font=("Arial", 12), selectmode=tk.SINGLE)
        self.task_listbox.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)

        self.complete_button = tk.Button(root, text="✅ Mark Completed", font=("Arial", 12), bg="#2196F3", fg="white", command=self.mark_completed)
        self.complete_button.pack(padx=10, pady=5, fill=tk.X)

        self.delete_button = tk.Button(root, text="🗑️ Delete Task", font=("Arial", 12), bg="#f44336", fg="white", command=self.delete_task)
        self.delete_button.pack(padx=10, pady=5, fill=tk.X)

        self.load_tasks()

    def load_tasks(self):
        if not os.path.exists(FILE_NAME):
            return
        with open(FILE_NAME, "r", encoding="utf-8") as file:
            for line in file:
                task_text, done = line.strip().split("|")
                self.tasks.append({'task': task_text, 'done': done == "1"})
        self.refresh_list()

    def save_tasks(self):
        with open(FILE_NAME, "w", encoding="utf-8") as file:
            for task in self.tasks:
                file.write(f"{task['task']}|{'1' if task['done'] else '0'}\n")

    def refresh_list(self):
        self.task_listbox.delete(0, tk.END)
        for task in self.tasks:
            status = "✅" if task['done'] else "❌"
            self.task_listbox.insert(tk.END, f"{task['task']} [{status}]")

    def add_task(self):
        task_text = self.entry.get().strip()
        if not task_text:
            messagebox.showwarning("Empty Input", "Please enter a task.")
            return
        self.tasks.append({'task': task_text, 'done': False})
        self.entry.delete(0, tk.END)
        self.refresh_list()
        self.save_tasks()

    def mark_completed(self):
        index = self.task_listbox.curselection()
        if not index:
            messagebox.showwarning("No Selection", "Please select a task.")
            return
        self.tasks[index[0]]['done'] = True
        self.refresh_list()
        self.save_tasks()

    def delete_task(self):
        index = self.task_listbox.curselection()
        if not index:
            messagebox.showwarning("No Selection", "Please select a task.")
            return
        del self.tasks[index[0]]
        self.refresh_list()
        self.save_tasks()

# Start the app
if __name__ == "__main__":
    root = tk.Tk()
    app = ToDoApp(root)
    root.mainloop()


GUI Version Explanation

The GUI version uses Python’s tkinter library to create a user-friendly interface. Users can interact with buttons, input fields, and lists instead of typing commands.

This version demonstrates how real desktop productivity applications are built using Python.



Real-World Applications

  • Personal task management tools
  • Productivity and reminder apps
  • Learning project for CRUD-based systems
  • Foundation for project management software


Enhancement Ideas

  • Add due dates and priorities
  • Use a database instead of a text file
  • Add search and filtering options
  • Improve the GUI with themes
  • Convert the app into an executable file


Frequently Asked Questions

Is this project suitable for beginners?

Yes, this project is ideal for beginners learning Python fundamentals and application structure.

Which version should I start with?

Beginners should start with the console version and gradually move to the GUI version.

Do I need external libraries?

No, all versions use Python’s built-in libraries.



Conclusion

The Python To-Do List App project helps learners understand how Python applications are designed, improved, and extended. By exploring console and GUI versions, beginners gain confidence in building real-world applications.

Next Project: Dice Roller Project in python