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 messageboxInformation, warning, and error
Use these when you want to notify the user. They return None (no decision needed).
messagebox.showinfo(title, message)for normal feedbackmessagebox.showwarning(title, message)for non-fatal issuesmessagebox.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
passCommon 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 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 filedialogOpen: askopenfilename
This returns a string path, or an empty string if the user cancels. Typical options:
filetypes: filters shown in the dialogdefaultextension: extension appended if missing (more relevant for Save)initialdir: starting foldertitle: dialog title
path = filedialog.askopenfilename(
title="Open text file",
filetypes=[
("Text files", "*.txt"),
("Markdown", "*.md"),
("All files", "*.*"),
],
)
if not path:
return # user cancelledSave: 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:
returnImportant: 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 (orNone)dirty: whether there are unsaved changes
You also want small helper functions:
mark_dirty()when the user editsconfirm_discard_if_dirty()before opening a new file or exitingload_file(path)andsave_file(path)with error handling
Step 2: implement open/save flows with dialogs
Open flow:
- If dirty, ask whether to discard changes
- Show
askopenfilenamewith 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
askyesnowithaskyesnocanceland 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 resetedit_modified(False)after programmatic inserts.File encodings: using UTF-8 is a good default. If you need to support arbitrary encodings, you can catch
UnicodeDecodeErrorand show a helpful error dialog.File type filters: filters guide the user but do not enforce correctness. Always handle errors when reading/writing.