Title: Make a custom dialog with Python and tkinter
Recently I needed a dialog for an example (which I'll post shortly), so I went online looking for solutions. I found some posts that build general-purpose customizable dialogs, but they seemed needlessly complex, so I decided to make my own. This dialog does just what I need and nothing more. It's not particularly customizable, but it should be easy to modify to make other dialogs.
It's not too long, but to make it easier to digest, I'll describe it in parts.
Constructor
The dialog class is called SettingsDialog and it inherits from tkinter's Toplevel class. That class is used to create top-level windows.
The following code shows the SettingsDialog constructor.
'''The custom class.'''
import tkinter as tk
from tkinter import messagebox
class SettingsDialog(tk.Toplevel):
def __init__(self, master, num_rows=5, num_columns=5):
super().__init__(master)
self.transient(master) # Keep dialog above master.
self.minsize(300, 200)
self.title('Settings')
self.num_rows = None
self.num_cols = None
# Make a frame to hold the dialog's main body.
main_frame = tk.main_frame(self)
main_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=10, pady=10)
# Build the dialog's widgets.
num_rows_label = tk.Label(main_frame, text='# Rows:')
num_rows_label.grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
self.num_rows_entry = tk.Entry(main_frame)
self.num_rows_entry.insert(0, f'{num_rows}')
self.num_rows_entry.selection_range(0, tk.END)
self.num_rows_entry.grid(row=0, column=1, sticky=tk.W, padx=5, pady=5)
num_cols_label = tk.Label(main_frame, text='# Columns:')
num_cols_label.grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
self.num_cols_entry = tk.Entry(main_frame)
self.num_cols_entry.insert(0, f'{num_columns}')
self.num_cols_entry.selection_range(0, tk.END)
self.num_cols_entry.grid(row=1, column=1, sticky=tk.W, padx=5, pady=5)
# Make OK and Cancel buttons.
button_frame = tk.button_frame(self, height=5)
button_frame.pack(side=tk.RIGHT, fill=tk.X, expand=False, padx=10, pady=10)
ok_button = tk.Button(button_frame, text='OK', width=7,
command=self.ok_click)
ok_button.pack(side=tk.LEFT, padx=10, pady=10)
self.bind('<Return>', lambda event: self.ok_click())
cancel_button = tk.Button(button_frame, text='Cancel', width=7,
command=self.cancel_click)
cancel_button.pack(side=tk.LEFT, padx=10, pady=10)
self.bind('<Escape>', lambda event: self.cancel_click())
self.num_rows_entry.focus_set()
# Grab events for this dialog.
self.grab_set()
The constructor calls the constructor provided by the superclass (Toplevel). It then calls transient to make the dialog's window remain above its master window. If you don't do that, it tends to slip behind the master (for some reason) when it displays message boxes.
The code gives the dialog a comfortable minimum size and sets its title. It then initializes the dialog's two values: num_rows and num_cols.
The code then builds the dialog's widgets. It makes the main_frame to hold the dialog's labels, text boxes, and whatever. You'll probably want to the contents of main_frame to suit your needs.
Next, the program makes another frame to hold the OK and Cancel buttons. It makes the buttons and binds them to the Enter and Escape keys, respectively.
The code sets the input focus to the num_rows_entry text box and then calls grab_set. That makes future events go to the dialog rather than the window that launched the dialog.
At this point, the dialog is ready to go.
Button Event Handlers
The following code shows the dialog's OK and Cancel button event handlers.
def cancel_click(self):
'''Close the dialog.'''
self.destroy()
def ok_click(self):
'''Validate the user's entries and close the dialog.'''
if not self.validate(): return
# Close the dialog.
self.withdraw()
self.update_idletasks()
self.cancel_click()
If the user clicks Cancel or presses the Escape key, the cancel_click method executes. That method uses simply destroys the dialog's window. The num_rows and num_cols parameters have their initial values None.
If the user clicks OK or presses Enter, the ok_click method executes. That method calls the validate method described shortly to validate the user's inputs. If that method returns False, the ok_click event handler just returns.
If the user's data is valid, the ok_click event handler calls withdraw to hide the dialog. It calls update_idletasks to handle any pending events and then invokes the cancel_click method to destroy the dialog's window.
Validating User Input
The following code shows the dialog's validate function.
def validate(self):
'''
Validate the user's entries.
Display an error message if appropriate.
Return True if the entries are valid, False otherwise.
'''
try:
num_rows = int(self.num_rows_entry.get())
except:
num_rows = 0
if num_rows <= 0:
messagebox.showerror('Error',
'The number of rows should be a positive integer.')
self.num_rows_entry.focus_set()
return False
try:
num_cols = int(self.num_cols_entry.get())
except:
num_cols = 0
if num_cols <= 0:
messagebox.showerror('Error',
'The number of columns should be a positive integer.')
self.num_cols_entry.focus_set()
return False
# Save the validated values.
self.num_rows = num_rows
self.num_cols = num_cols
return True
This method parses the user's entries and verifies that they are both integers greater than zero. If either entry is invalid, the method displays an error message, sets focus back to the invalid text box, and returns False. If both fields hold valid values, the method returns True.
show
At this point, the main program could display the dialog and get information off of it, but we can add a show method to the dialog to make it even easier to use.
def show(self):
'''Display the dialog, wait for it to end, and return its results.'''
self.wait_window()
return self.num_rows, self.num_cols
This method waits for the window to pause the main program until the dialog closes. It then returns the dialog's num_rows and num_cols values.
Main Program
When you open the main program's Configuration menu and select Settings, the following code executes.
def mnu_settings(self):
'''Display the SettingsDialog and show the returned results.'''
print(SettingsDialog(self.window, 5, 8).show())
This code creates a SettingsDialog and calls its show method. If the user enters valid values and accepts the dialog, show returns the user's value. If the user cancels the dialog, method returns (None, None).
That's all there is to using the dialog! Download the example to experiment and to see additional details.
|