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.
- Listen to the audio with the screen off.
- Earn a certificate upon completion.
- Over 5000 courses for you to explore!
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.