Skip to main content

5. Dialogs and Message Boxes

app with buttons that open different dialogs own modal dialog with Delete and Cancel buttons

CTkInputDialog​

The CTkInputDialog asks the user for a text. get_input() opens the dialog, waits until it is closed and returns the text. If the user clicks Cancel or closes the dialog, it returns None:

def ask_name(self):
self.input_dialog = customtkinter.CTkInputDialog(title="Name", text="What is your name?")
name = self.input_dialog.get_input() # None if the dialog was cancelled or closed
if name:
self.result_label.configure(text=f"Hello, {name}!")
else:
self.result_label.configure(text="No name entered.")

Message boxes​

For messages and simple questions you can use the messagebox module of tkinter. These dialogs look like the native dialogs of the operating system and not like CustomTkinter, but they work well and the user knows them. The functions wait until the dialog is closed:

from tkinter import messagebox

messagebox.showinfo("Saved", "The file was saved.", parent=self)
messagebox.showwarning("Warning", "The file is very large.", parent=self)
messagebox.showerror("Error", "The file could not be opened.", parent=self)

if messagebox.askyesno("Question", "Do you want to continue?", parent=self):
print("yes")

askyesno and askokcancel return True or False, askyesnocancel returns None for Cancel. With parent=self the dialog is shown above the window of your app.

File dialogs​

To open or save files, use the file dialogs of tkinter. They are also available as customtkinter.filedialog. If the user cancels the dialog, an empty string is returned:

path = customtkinter.filedialog.askopenfilename(parent=self, title="Open a text file",
filetypes=[("Text files", "*.txt"), ("All files", "*.*")])
if path:
print("open", path)

folder = customtkinter.filedialog.askdirectory(parent=self, title="Choose a folder")
save_path = customtkinter.filedialog.asksaveasfilename(parent=self, defaultextension=".txt")

Your own dialog​

If a dialog should look like the rest of the app or needs more than one or two buttons, you can create it yourself as a CTkToplevel. The following MessageDialog shows a text and any number of buttons, and get() returns the text of the button that was clicked:

class MessageDialog(customtkinter.CTkToplevel):
"""A modal dialog in CustomTkinter style, get() returns the text of the clicked button."""

def __init__(self, master, title, message, buttons=("OK",)):
super().__init__(master)
self.title(title)
self.resizable(False, False)
self.transient(master) # keep the dialog above the main window
self.result = None
...
self.protocol("WM_DELETE_WINDOW", lambda: self.answer(None)) # closing the window counts as no answer
self.center_over(master)
self.after(100, self.make_modal)

Three things make the dialog behave like a dialog:

  1. transient(master) keeps the dialog above the main window. On Windows it also removes the dialog from the taskbar.
  2. grab_set() makes the dialog modal, which means that clicks on the other windows of the app are ignored until the dialog is closed. On Linux, grab_set() fails if the window is not visible yet, so make_modal tries again after 100 ms in this case.
  3. wait_window() in the get method waits until the dialog is destroyed. The app keeps running in the meantime, so the windows are still redrawn.
def make_modal(self):
try:
self.grab_set() # clicks on other windows of the app are ignored until the dialog is closed
except tkinter.TclError:
self.after(100, self.make_modal) # the window is not visible yet, try again
return
self.focus()

def answer(self, value):
self.result = value
self.grab_release()
self.destroy()

def get(self):
self.master.wait_window(self) # wait until the dialog is closed
return self.result

The dialog is then used like the CTkInputDialog:

answer = MessageDialog(self, "Delete file", "Do you really want to delete the file?",
buttons=("Delete", "Cancel")).get()

Full code​

dialogs.py
import os
import tkinter
from tkinter import messagebox
import customtkinter


class MessageDialog(customtkinter.CTkToplevel):
"""A modal dialog in CustomTkinter style, get() returns the text of the clicked button."""

def __init__(self, master, title, message, buttons=("OK",)):
super().__init__(master)
self.title(title)
self.resizable(False, False)
self.transient(master) # keep the dialog above the main window
self.result = None

self.grid_columnconfigure(tuple(range(len(buttons))), weight=1)
self.label = customtkinter.CTkLabel(self, text=message, wraplength=280, justify="left")
self.label.grid(row=0, column=0, columnspan=len(buttons), padx=20, pady=20, sticky="w")
for column, text in enumerate(buttons):
button = customtkinter.CTkButton(self, text=text, width=100, command=lambda t=text: self.answer(t))
button.grid(row=1, column=column, padx=10, pady=(0, 20))

self.protocol("WM_DELETE_WINDOW", lambda: self.answer(None)) # closing the window counts as no answer
self.center_over(master)
self.after(100, self.make_modal)

def center_over(self, master):
self.update_idletasks()
x = master.winfo_rootx() + (master.winfo_width() - self.winfo_reqwidth()) // 2
y = master.winfo_rooty() + (master.winfo_height() - self.winfo_reqheight()) // 2
self.geometry(f"+{x}+{y}")

def make_modal(self):
try:
self.grab_set() # clicks on other windows of the app are ignored until the dialog is closed
except tkinter.TclError:
self.after(100, self.make_modal) # the window is not visible yet, try again
return
self.focus()

def answer(self, value):
self.result = value
self.grab_release()
self.destroy()

def get(self):
self.master.wait_window(self) # wait until the dialog is closed
return self.result


class App(customtkinter.CTk):
def __init__(self):
super().__init__()

self.title("Dialogs")
self.geometry("380x340")
self.grid_columnconfigure(0, weight=1)

actions = [
("Ask for a name (CTkInputDialog)", self.ask_name),
("Show information (messagebox)", self.show_info),
("Ask yes or no (messagebox)", self.ask_yes_no),
("Open a file (filedialog)", self.open_file),
("Choose a folder (filedialog)", self.choose_folder),
("Delete something (own dialog)", self.ask_delete),
]
for row, (text, command) in enumerate(actions):
button = customtkinter.CTkButton(self, text=text, command=command)
button.grid(row=row, column=0, padx=20, pady=(20 if row == 0 else 10, 0), sticky="ew")

self.result_label = customtkinter.CTkLabel(self, text="", wraplength=340)
self.result_label.grid(row=len(actions), column=0, padx=20, pady=20)

def ask_name(self):
self.input_dialog = customtkinter.CTkInputDialog(title="Name", text="What is your name?")
name = self.input_dialog.get_input() # None if the dialog was cancelled or closed
if name:
self.result_label.configure(text=f"Hello, {name}!")
else:
self.result_label.configure(text="No name entered.")

def show_info(self):
messagebox.showinfo("Saved", "The file was saved.", parent=self)

def ask_yes_no(self):
if messagebox.askyesno("Question", "Do you want to continue?", parent=self):
self.result_label.configure(text="You chose yes.")
else:
self.result_label.configure(text="You chose no.")

def open_file(self):
path = customtkinter.filedialog.askopenfilename(parent=self, title="Open a text file",
filetypes=[("Text files", "*.txt"), ("All files", "*.*")])
if path: # empty if the user cancelled
self.result_label.configure(text=f"File: {os.path.basename(path)}")

def choose_folder(self):
folder = customtkinter.filedialog.askdirectory(parent=self, title="Choose a folder")
if folder:
self.result_label.configure(text=f"Folder: {folder}")

def ask_delete(self):
self.dialog = MessageDialog(self, "Delete file", "Do you really want to delete the file? "
"This cannot be undone.", buttons=("Delete", "Cancel"))
answer = self.dialog.get()
self.result_label.configure(text=f"Answer: {answer}")


app = App()
app.mainloop()

There is also a community widget CTkMessagebox, which is not part of CustomTkinter, if you need message boxes with icons in CustomTkinter style.

Common errors​

_tkinter.TclError: grab failed: window not viewable

grab_set() was called before the window was visible, which happens on Linux. Call it with a delay and try again like in make_modal above.

The dialog opens behind the main window

Call transient(master) for the dialog, and lift() if it is still hidden. Message boxes and file dialogs should always get the parent argument.