Free Ebook cover Desktop Apps with Tkinter: A Beginner’s Guide to Python GUIs

Desktop Apps with Tkinter: A Beginner’s Guide to Python GUIs

New course

10 pages

Dialogs and File Operations with Tkinter (Open/Save, Messages, Color/Font)

Capítulo 7

Estimated reading time: 7 minutes

+ Exercise

Why dialogs matter in desktop apps

Dialogs are small OS-integrated windows that help your app communicate with the user or access system resources. Tkinter provides ready-made dialog modules so you do not need to build these windows yourself. In this chapter you will use: tkinter.messagebox for notifications and questions, tkinter.filedialog for Open/Save interactions, and optionally tkinter.colorchooser for picking colors. For fonts, Tkinter has no built-in “font picker” dialog, but you can implement a simple selection pattern with standard widgets.

Message dialogs with messagebox

Message dialogs are for feedback and confirmation. They are modal: the user must respond before continuing. Import them like this:

from tkinter import messagebox

Information, warning, and error

Use these when you want to notify the user. They return None (no decision needed).

  • messagebox.showinfo(title, message) for normal feedback
  • messagebox.showwarning(title, message) for non-fatal issues
  • messagebox.showerror(title, message) for failures that block the action
messagebox.showinfo("Saved", "Your file was saved successfully.")
messagebox.showwarning("Unsaved", "You have unsaved changes.")
messagebox.showerror("Open failed", "Could not read the selected file.")

Questions: askyesno (and friends)

Use question dialogs when you need a decision. askyesno returns a Boolean (True/False).

ok = messagebox.askyesno("Confirm", "Discard unsaved changes?")
if ok:
    # proceed
    pass

Common alternatives include askokcancel, askretrycancel, and askyesnocancel (which can return None for Cancel). Choose the one that matches your decision flow.

Continue in our app.

You can listen to the audiobook with the screen off, receive a free certificate for this course, and also have access to 5,000 other free online courses.

Or continue reading below...
Download App

Download the app

Open/Save dialogs with filedialog

File dialogs let the user pick a path using the operating system’s file picker. Import:

from tkinter import filedialog

Open: askopenfilename

This returns a string path, or an empty string if the user cancels. Typical options:

  • filetypes: filters shown in the dialog
  • defaultextension: extension appended if missing (more relevant for Save)
  • initialdir: starting folder
  • title: dialog title
path = filedialog.askopenfilename(
    title="Open text file",
    filetypes=[
        ("Text files", "*.txt"),
        ("Markdown", "*.md"),
        ("All files", "*.*"),
    ],
)
if not path:
    return  # user cancelled

Save: asksaveasfilename

This returns a path the user chose, or an empty string on cancel. Use defaultextension so saving “notes” becomes “notes.txt” automatically.

path = filedialog.asksaveasfilename(
    title="Save file as",
    defaultextension=".txt",
    filetypes=[
        ("Text files", "*.txt"),
        ("All files", "*.*"),
    ],
)
if not path:
    return

Important: file dialogs do not read or write the file; they only provide a path. Your code must handle I/O and errors (permissions, encoding, missing files).

Optional: choosing colors with colorchooser

colorchooser.askcolor() opens a system color picker. It returns a tuple ((r, g, b), "#rrggbb") or (None, None) if cancelled.

from tkinter import colorchooser

(rgb, hex_color) = colorchooser.askcolor(title="Pick a text color")
if hex_color:
    text_widget.configure(fg=hex_color)

Optional: simple font selection patterns

Tkinter does not ship a standard font dialog, but you can build a small “Font…” window using common widgets and apply a selected font to a Text widget. A practical pattern is:

  • Use tkinter.font.families() to list available font families
  • Provide a size selector (e.g., Spinbox or Entry)
  • Optionally add style toggles (bold/italic)
  • Apply the chosen font via text.configure(font=(family, size, style...))
import tkinter.font as tkfont

families = sorted(tkfont.families())
# Example application:
text_widget.configure(font=("Arial", 12))

Structured example: a small note editor (Open/Save, unsaved warning, exit confirm)

This example focuses on dialogs and file operations. It uses a Text widget as the editor, tracks whether the document is “dirty” (modified), and uses dialogs to open/save and confirm risky actions.

Step 1: define state and helpers

You typically track:

  • current_path: the file currently associated with the editor (or None)
  • dirty: whether there are unsaved changes

You also want small helper functions:

  • mark_dirty() when the user edits
  • confirm_discard_if_dirty() before opening a new file or exiting
  • load_file(path) and save_file(path) with error handling

Step 2: implement open/save flows with dialogs

Open flow:

  • If dirty, ask whether to discard changes
  • Show askopenfilename with file type filters
  • Read the file (handle exceptions)
  • Insert into the editor and clear dirty flag

Save flow:

  • If no current path, use asksaveasfilename
  • Write the editor content to disk (handle exceptions)
  • Clear dirty flag

Step 3: confirm before exiting

Bind the window close protocol (WM_DELETE_WINDOW) to a function that:

  • If dirty, asks whether to exit without saving (or you can offer to save first)
  • If confirmed, closes the app

Complete example code

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

class NoteEditor(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Note Editor")
        self.geometry("700x450")

        self.current_path = None
        self.dirty = False

        self.text = tk.Text(self, wrap="word", undo=True)
        self.text.pack(fill="both", expand=True)

        # Track modifications: the Text widget can fire <<Modified>>
        self.text.bind("<<Modified>>", self._on_text_modified)

        # Minimal menu for the example (focus is dialogs + file ops)
        menubar = tk.Menu(self)
        file_menu = tk.Menu(menubar, tearoff=False)
        file_menu.add_command(label="Open...", command=self.open_file)
        file_menu.add_command(label="Save", command=self.save)
        file_menu.add_command(label="Save As...", command=self.save_as)
        file_menu.add_separator()
        file_menu.add_command(label="Exit", command=self.on_exit)
        menubar.add_cascade(label="File", menu=file_menu)

        format_menu = tk.Menu(menubar, tearoff=False)
        format_menu.add_command(label="Text Color...", command=self.pick_text_color)
        menubar.add_cascade(label="Format", menu=format_menu)

        self.config(menu=menubar)

        # Intercept window close button
        self.protocol("WM_DELETE_WINDOW", self.on_exit)

        self._update_title()

    def _update_title(self):
        name = self.current_path if self.current_path else "Untitled"
        star = " *" if self.dirty else ""
        self.title(f"Note Editor - {name}{star}")

    def _set_dirty(self, value: bool):
        self.dirty = value
        self._update_title()

    def _on_text_modified(self, event=None):
        # Tkinter sets a modified flag internally; reset it to keep receiving events
        if self.text.edit_modified():
            self._set_dirty(True)
            self.text.edit_modified(False)

    def confirm_discard_if_dirty(self) -> bool:
        if not self.dirty:
            return True
        return messagebox.askyesno(
            "Unsaved changes",
            "You have unsaved changes. Discard them?",
        )

    def load_file(self, path: str):
        try:
            with open(path, "r", encoding="utf-8") as f:
                data = f.read()
        except Exception as e:
            messagebox.showerror("Open failed", f"Could not open file:\n{e}")
            return

        self.text.delete("1.0", "end")
        self.text.insert("1.0", data)
        self.current_path = path
        self._set_dirty(False)

    def save_file(self, path: str) -> bool:
        data = self.text.get("1.0", "end-1c")
        try:
            with open(path, "w", encoding="utf-8") as f:
                f.write(data)
        except Exception as e:
            messagebox.showerror("Save failed", f"Could not save file:\n{e}")
            return False

        self.current_path = path
        self._set_dirty(False)
        messagebox.showinfo("Saved", "File saved successfully.")
        return True

    def open_file(self):
        if not self.confirm_discard_if_dirty():
            return

        path = filedialog.askopenfilename(
            title="Open",
            filetypes=[
                ("Text files", "*.txt"),
                ("Markdown", "*.md"),
                ("All files", "*.*"),
            ],
        )
        if not path:
            return

        self.load_file(path)

    def save(self):
        if self.current_path:
            self.save_file(self.current_path)
        else:
            self.save_as()

    def save_as(self):
        path = filedialog.asksaveasfilename(
            title="Save As",
            defaultextension=".txt",
            filetypes=[
                ("Text files", "*.txt"),
                ("Markdown", "*.md"),
                ("All files", "*.*"),
            ],
        )
        if not path:
            return

        self.save_file(path)

    def pick_text_color(self):
        (rgb, hex_color) = colorchooser.askcolor(title="Pick text color")
        if hex_color:
            self.text.configure(fg=hex_color)

    def on_exit(self):
        if self.dirty:
            # A common alternative is askyesnocancel and offer saving.
            ok = messagebox.askyesno(
                "Exit",
                "You have unsaved changes. Exit without saving?",
            )
            if not ok:
                return
        self.destroy()

if __name__ == "__main__":
    app = NoteEditor()
    app.mainloop()

Practical notes and variations

  • Offer “Save before closing”: replace askyesno with askyesnocancel and implement: Yes => save, No => exit, Cancel => do nothing.

  • More precise dirty tracking: you can set dirty to False after saving and also after loading. The <<Modified>> approach is convenient, but be careful to reset edit_modified(False) after programmatic inserts.

  • File encodings: using UTF-8 is a good default. If you need to support arbitrary encodings, you can catch UnicodeDecodeError and show a helpful error dialog.

  • File type filters: filters guide the user but do not enforce correctness. Always handle errors when reading/writing.

Now answer the exercise about the content:

In a Tkinter note editor, what is the correct way to handle the Save As action when the user may cancel the dialog?

You are right! Congratulations, now go to the next page

You missed! Try again.

asksaveasfilename returns a chosen path or an empty string if canceled. The dialog only provides the path, so your code must check for cancel and then perform the actual write (often with defaultextension).

Next chapter

Input Validation and User Feedback in Tkinter Forms

Arrow Right Icon
Download the app to earn free Certification and listen to the courses in the background, even with the screen off.