Create a Notepad in Python | Beginner-Friendly Guide

Faraz

By Faraz - July 02, 2024

Learn how to create a simple notepad in Python using tkinter with our step-by-step guide. Perfect for beginners looking to build a text editor from scratch.


create-a-notepad-in-python-using-tkinter-beginner-friendly-guide.webp

Table of Contents

  1. Setting Up the Environment
  2. Step-by-Step Guide
  3. Full Notepad Source Code
  4. Final Thoughts

Building a simple notepad application in Python is a great way to get started with GUI programming. In this tutorial, we'll create a basic notepad application using the Tkinter library. Our notepad will have essential features like creating, opening, saving files, cut/copy/paste functionality, find and replace text, font customization, word wrap, dark mode, and a status bar to display the current line and column number.

Basic Features:

  • New File: Create a new file, clearing the current text area.
  • Open File: Open an existing text file, loading its content into the text area.
  • Save File: Save the current content of the text area to a text file.
  • Cut, Copy, Paste: Standard text editing operations for cutting, copying, and pasting text.

Advanced Features:

  1. Find Text:
    • Opens a popup to search for a specific text within the document.
    • Highlights all occurrences of the searched text.
  2. Replace Text:
    • Opens a popup to search for and replace text within the document.
    • Allows replacing all occurrences of a specific text with another text.
  3. Font Options:
    • Opens a popup to customize the font family, size, and color of the text.
    • Applies the selected font settings to the text area.
  4. Word Wrap:
    • Toggles word wrapping in the text area.
    • When enabled, long lines are wrapped within the window instead of scrolling horizontally.
  5. Dark Mode:
    • Toggles between light and dark themes.
    • Changes the background and text color for a dark or light appearance.
  6. Status Bar:
    • Displays the current line and column number of the cursor position.
    • Updates dynamically as the user types or moves the cursor.

Additional Features:

  1. Exit Application:
    • Prompts the user to save changes before closing the application.
    • Ensures that no unsaved work is lost when exiting the application.

Requirements

Before we start, ensure you have Python installed on your computer. We will use the Tkinter library, which is included with Python's standard library. You'll also need a code editor like Visual Studio Code or PyCharm to write and test your code.

Setting Up the Environment

First, open your code editor and create a new Python file. Import the Tkinter library at the beginning of your script:

import tkinter as tk

This will allow us to use Tkinter’s widgets. Initialize the main application window:

root = tk.Tk()
root.title("Python Notepad")
root.geometry("800x600")

This code creates a window with a specified title and size.

Step-by-Step Guide

1. Import Required Modules

We'll start by importing the necessary modules from Tkinter:

import tkinter as tk
from tkinter import filedialog, messagebox, font, colorchooser

2. Define Functions for File Operations

We'll create functions for creating a new file, opening an existing file, and saving the current file.

def new_file():
    if len(text_area.get("1.0", tk.END)) > 1:
        if messagebox.askyesno("Save", "Do you want to save the current file?"):
            save_file()
    text_area.delete("1.0", tk.END)


def open_file():
    file_path = filedialog.askopenfilename(defaultextension=".txt",
                                           filetypes=[("Text files", "*.txt"), ("All files", "*.*")])
    if file_path:
        text_area.delete("1.0", tk.END)
        with open(file_path, "r") as file:
            text_area.insert(tk.END, file.read())


def save_file():
    file_path = filedialog.asksaveasfilename(defaultextension=".txt",
                                             filetypes=[("Text files", "*.txt"), ("All files", "*.*")])
    if file_path:
        with open(file_path, "w") as file:
            file.write(text_area.get("1.0", tk.END))

3. Define Functions for Editing Operations

We will add functions for cut, copy, and paste operations.

def cut():
    text_area.event_generate("<<Cut>>")

def copy():
    text_area.event_generate("<<Copy>>")

def paste():
    text_area.event_generate("<<Paste>>")

4. Add Find and Replace Functionality

We'll create a function to find text within the notepad and another function to replace text.

def find_text():
    find_popup = tk.Toplevel(root)
    find_popup.title("Find")
    find_popup.geometry("300x100")

    tk.Label(find_popup, text="Find:").grid(row=0, column=0, padx=10, pady=10)
    find_entry = tk.Entry(find_popup)
    find_entry.grid(row=0, column=1, padx=10, pady=10)

    def find():
        text_area.tag_remove("found", "1.0", tk.END)
        find_str = find_entry.get()
        if find_str:
            start_idx = "1.0"
            while True:
                start_idx = text_area.search(find_str, start_idx, nocase=1, stopindex=tk.END)
                if not start_idx:
                    break
                end_idx = f"{start_idx}+{len(find_str)}c"
                text_area.tag_add("found", start_idx, end_idx)
                start_idx = end_idx
            text_area.tag_config("found", background="yellow")

    tk.Button(find_popup, text="Find", command=find).grid(row=1, column=0, columnspan=2, pady=10)


def replace_text():
    replace_popup = tk.Toplevel(root)
    replace_popup.title("Replace")
    replace_popup.geometry("400x150")

    tk.Label(replace_popup, text="Find:").grid(row=0, column=0, padx=10, pady=10)
    find_entry = tk.Entry(replace_popup)
    find_entry.grid(row=0, column=1, padx=10, pady=10)

    tk.Label(replace_popup, text="Replace:").grid(row=1, column=0, padx=10, pady=10)
    replace_entry = tk.Entry(replace_popup)
    replace_entry.grid(row=1, column=1, padx=10, pady=10)

    def replace():
        find_str = find_entry.get()
        replace_str = replace_entry.get()
        content = text_area.get("1.0", tk.END)
        new_content = content.replace(find_str, replace_str)
        text_area.delete("1.0", tk.END)
        text_area.insert("1.0", new_content)

    tk.Button(replace_popup, text="Replace", command=replace).grid(row=2, column=0, columnspan=2, pady=10)

5. Customize Font Options

We can allow the user to customize the font family, size, and color.

def font_options():
    font_popup = tk.Toplevel(root)
    font_popup.title("Font Options")
    font_popup.geometry("400x250")

    tk.Label(font_popup, text="Font Family:").grid(row=0, column=0, padx=10, pady=10)
    font_family = tk.StringVar(value="Arial")
    font_family_dropdown = tk.OptionMenu(font_popup, font_family, *font.families())
    font_family_dropdown.grid(row=0, column=1, padx=10, pady=10)

    tk.Label(font_popup, text="Font Size:").grid(row=1, column=0, padx=10, pady=10)
    font_size = tk.IntVar(value=12)
    font_size_spinbox = tk.Spinbox(font_popup, from_=8, to=72, textvariable=font_size)
    font_size_spinbox.grid(row=1, column=1, padx=10, pady=10)

    tk.Label(font_popup, text="Font Color:").grid(row=2, column=0, padx=10, pady=10)
    font_color = tk.StringVar(value="#000000")
    font_color_button = tk.Button(font_popup, text="Choose Color", command=lambda: choose_color(font_color))
    font_color_button.grid(row=2, column=1, padx=10, pady=10)

    def apply_font():
        new_font = font.Font(family=font_family.get(), size=font_size.get())
        text_area.config(font=new_font, fg=font_color.get())

    tk.Button(font_popup, text="Apply", command=apply_font).grid(row=3, column=0, columnspan=2, pady=10)


def choose_color(color_var):
    color = colorchooser.askcolor()[1]
    if color:
        color_var.set(color)

6. Toggle Word Wrap and Dark Mode

We can add options to toggle word wrap and dark mode.

def toggle_word_wrap():
    current_wrap = text_area.cget("wrap")
    text_area.config(wrap=tk.WORD if current_wrap == tk.NONE else tk.NONE)


def toggle_dark_mode():
    bg_color = "#2E2E2E" if dark_mode.get() else "#FFFFFF"
    fg_color = "#FFFFFF" if dark_mode.get() else "#000000"
    text_area.config(bg=bg_color, fg=fg_color)

7. Add a Status Bar

We will add a status bar to show the current line and column number.

def update_status_bar(event=None):
    row, col = text_area.index(tk.INSERT).split(".")
    status_bar.config(text=f"Line: {int(row)}, Column: {int(col) + 1}")

8. Exit the Application

Finally, we need a function to exit the application safely.

def exit_app():
    if len(text_area.get("1.0", tk.END)) > 1:
        if messagebox.askyesno("Save", "Do you want to save the current file?"):
            save_file()
    root.destroy()

9. Setting Up the Main Application Window

Now, let's set up the main application window, add the text area, and create the menu bar.

root = tk.Tk()
root.title("Python Notepad")
root.geometry("800x600")

text_area = tk.Text(root, wrap='word', undo=True)
text_area.pack(expand=True, fill='both')
text_area.bind("<KeyRelease>", update_status_bar)

menu_bar = tk.Menu(root)
root.config(menu=menu_bar)

file_menu = tk.Menu(menu_bar, tearoff=0)
menu_bar.add_cascade(label="File", menu=file_menu)
file_menu.add_command(label="New", command=new_file)
file_menu.add_command(label="Open", command=open_file)
file_menu.add_command(label="Save", command=save_file)
file_menu.add_separator()
file_menu.add_command(label="Exit", command=exit_app)

edit_menu = tk.Menu(menu_bar, tearoff=0)
menu_bar.add_cascade(label="Edit", menu=edit_menu)
edit_menu.add_command(label="Cut", command=cut)
edit_menu.add_command(label="Copy", command=copy)
edit_menu.add_command(label="Paste", command=paste)
edit_menu.add_separator()
edit_menu.add_command(label="Find", command=find_text)
edit_menu.add_command(label="Replace", command=replace_text)

format_menu = tk.Menu(menu_bar, tearoff=0)
menu_bar.add_cascade(label="Format", menu=format_menu)
format_menu.add_command(label="Font", command=font_options)
format_menu.add_command(label="Word Wrap", command=toggle_word_wrap)

view_menu = tk.Menu(menu_bar, tearoff=0)
menu_bar.add_cascade(label="View", menu=view_menu)
dark_mode = tk.BooleanVar()
view_menu.add_checkbutton(label="Dark Mode", onvalue=True, offvalue=False, variable=dark_mode, command=toggle_dark_mode)

status_bar = tk.Label(root, text="Line: 1, Column: 1", anchor="e")
status_bar.pack(fill="x")

root.mainloop()

Full Notepad Source Code

import tkinter as tk
from tkinter import filedialog, messagebox, font, colorchooser


def new_file():
    if len(text_area.get("1.0", tk.END)) > 1:
        if messagebox.askyesno("Save", "Do you want to save the current file?"):
            save_file()
    text_area.delete("1.0", tk.END)


def open_file():
    file_path = filedialog.askopenfilename(defaultextension=".txt",
                                           filetypes=[("Text files", "*.txt"), ("All files", "*.*")])
    if file_path:
        text_area.delete("1.0", tk.END)
        with open(file_path, "r") as file:
            text_area.insert(tk.END, file.read())


def save_file():
    file_path = filedialog.asksaveasfilename(defaultextension=".txt",
                                             filetypes=[("Text files", "*.txt"), ("All files", "*.*")])
    if file_path:
        with open(file_path, "w") as file:
            file.write(text_area.get("1.0", tk.END))


def cut():
    text_area.event_generate("<<Cut>>")


def copy():
    text_area.event_generate("<<Copy>>")


def paste():
    text_area.event_generate("<<Paste>>")


def find_text():
    find_popup = tk.Toplevel(root)
    find_popup.title("Find")
    find_popup.geometry("300x100")

    tk.Label(find_popup, text="Find:").grid(row=0, column=0, padx=10, pady=10)
    find_entry = tk.Entry(find_popup)
    find_entry.grid(row=0, column=1, padx=10, pady=10)

    def find():
        text_area.tag_remove("found", "1.0", tk.END)
        find_str = find_entry.get()
        if find_str:
            start_idx = "1.0"
            while True:
                start_idx = text_area.search(find_str, start_idx, nocase=1, stopindex=tk.END)
                if not start_idx:
                    break
                end_idx = f"{start_idx}+{len(find_str)}c"
                text_area.tag_add("found", start_idx, end_idx)
                start_idx = end_idx
            text_area.tag_config("found", background="yellow")

    tk.Button(find_popup, text="Find", command=find).grid(row=1, column=0, columnspan=2, pady=10)


def replace_text():
    replace_popup = tk.Toplevel(root)
    replace_popup.title("Replace")
    replace_popup.geometry("400x150")

    tk.Label(replace_popup, text="Find:").grid(row=0, column=0, padx=10, pady=10)
    find_entry = tk.Entry(replace_popup)
    find_entry.grid(row=0, column=1, padx=10, pady=10)

    tk.Label(replace_popup, text="Replace:").grid(row=1, column=0, padx=10, pady=10)
    replace_entry = tk.Entry(replace_popup)
    replace_entry.grid(row=1, column=1, padx=10, pady=10)

    def replace():
        find_str = find_entry.get()
        replace_str = replace_entry.get()
        content = text_area.get("1.0", tk.END)
        new_content = content.replace(find_str, replace_str)
        text_area.delete("1.0", tk.END)
        text_area.insert("1.0", new_content)

    tk.Button(replace_popup, text="Replace", command=replace).grid(row=2, column=0, columnspan=2, pady=10)


def font_options():
    font_popup = tk.Toplevel(root)
    font_popup.title("Font Options")
    font_popup.geometry("400x250")

    tk.Label(font_popup, text="Font Family:").grid(row=0, column=0, padx=10, pady=10)
    font_family = tk.StringVar(value="Arial")
    font_family_dropdown = tk.OptionMenu(font_popup, font_family, *font.families())
    font_family_dropdown.grid(row=0, column=1, padx=10, pady=10)

    tk.Label(font_popup, text="Font Size:").grid(row=1, column=0, padx=10, pady=10)
    font_size = tk.IntVar(value=12)
    font_size_spinbox = tk.Spinbox(font_popup, from_=8, to=72, textvariable=font_size)
    font_size_spinbox.grid(row=1, column=1, padx=10, pady=10)

    tk.Label(font_popup, text="Font Color:").grid(row=2, column=0, padx=10, pady=10)
    font_color = tk.StringVar(value="#000000")
    font_color_button = tk.Button(font_popup, text="Choose Color", command=lambda: choose_color(font_color))
    font_color_button.grid(row=2, column=1, padx=10, pady=10)

    def apply_font():
        new_font = font.Font(family=font_family.get(), size=font_size.get())
        text_area.config(font=new_font, fg=font_color.get())

    tk.Button(font_popup, text="Apply", command=apply_font).grid(row=3, column=0, columnspan=2, pady=10)


def choose_color(color_var):
    color = colorchooser.askcolor()[1]
    if color:
        color_var.set(color)


def toggle_word_wrap():
    current_wrap = text_area.cget("wrap")
    text_area.config(wrap=tk.WORD if current_wrap == tk.NONE else tk.NONE)


def toggle_dark_mode():
    bg_color = "#2E2E2E" if dark_mode.get() else "#FFFFFF"
    fg_color = "#FFFFFF" if dark_mode.get() else "#000000"
    text_area.config(bg=bg_color, fg=fg_color)


def update_status_bar(event=None):
    row, col = text_area.index(tk.INSERT).split(".")
    status_bar.config(text=f"Line: {int(row)}, Column: {int(col) + 1}")


def exit_app():
    if len(text_area.get("1.0", tk.END)) > 1:
        if messagebox.askyesno("Save", "Do you want to save the current file?"):
            save_file()
    root.destroy()


root = tk.Tk()
root.title("Python Notepad")
root.geometry("800x600")

text_area = tk.Text(root, wrap='word', undo=True)
text_area.pack(expand=True, fill='both')
text_area.bind("<KeyRelease>", update_status_bar)

menu_bar = tk.Menu(root)
root.config(menu=menu_bar)

file_menu = tk.Menu(menu_bar, tearoff=0)
menu_bar.add_cascade(label="File", menu=file_menu)
file_menu.add_command(label="New", command=new_file)
file_menu.add_command(label="Open", command=open_file)
file_menu.add_command(label="Save", command=save_file)
file_menu.add_separator()
file_menu.add_command(label="Exit", command=exit_app)

edit_menu = tk.Menu(menu_bar, tearoff=0)
menu_bar.add_cascade(label="Edit", menu=edit_menu)
edit_menu.add_command(label="Cut", command=cut)
edit_menu.add_command(label="Copy", command=copy)
edit_menu.add_command(label="Paste", command=paste)
edit_menu.add_separator()
edit_menu.add_command(label="Find", command=find_text)
edit_menu.add_command(label="Replace", command=replace_text)

format_menu = tk.Menu(menu_bar, tearoff=0)
menu_bar.add_cascade(label="Format", menu=format_menu)
format_menu.add_command(label="Font", command=font_options)
format_menu.add_command(label="Word Wrap", command=toggle_word_wrap)

view_menu = tk.Menu(menu_bar, tearoff=0)
menu_bar.add_cascade(label="View", menu=view_menu)
dark_mode = tk.BooleanVar()
view_menu.add_checkbutton(label="Dark Mode", onvalue=True, offvalue=False, variable=dark_mode, command=toggle_dark_mode)

status_bar = tk.Label(root, text="Line: 1, Column: 1", anchor="e")
status_bar.pack(fill="x")

root.mainloop()

Final Thoughts

In this tutorial, we created a simple notepad application using Python's Tkinter library. We covered file operations, editing operations, find and replace functionality, font customization, word wrap, dark mode, and a status bar. This notepad application is a great starting point for learning more about GUI programming with Tkinter. You can further enhance this application by adding more features like undo/redo, spell check, and syntax highlighting.

That’s a wrap!

I hope you enjoyed this article

Did you like it? Let me know in the comments below 🔥 and you can support me by buying me a coffee.

And don’t forget to sign up to our email newsletter so you can get useful content like this sent right to your inbox!

Thanks!
Faraz 😊

End of the article

Subscribe to my Newsletter

Get the latest posts delivered right to your inbox


Latest Post