[Rod Stephens Books]
Index Books Python Examples About Rod Contact
[Mastodon] [Bluesky] [Facebook]
[Build Your Own Python Action Arcade!]

[Build Your Own Ray Tracer With Python]

[Beginning Database Design Solutions, Second Edition]

[Beginning Software Engineering, Second Edition]

[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

Title: Display multiple modal dialogs safely with Python and tkinter

[This app displays a modal dialog that displays another modal dialog]

I've been working on a book lately (which is why I haven't been posting as frequently) and one of the programs uses tkinter to display a sequence of modal dialogs. The initial window displays a dialog which may display another dialog.

Unfortunately, tkinter can only keep track of one window that grabs the input. When you display one modal dialog, all is well. If that dialog displays another, tkinter forgets that the first dialog had grabbed input so, when you close the second dialog, the first dialog is no longer modal.

This example creates a ModalDialog class that automatically restores the first dialog's modal nature.

Using the Example

This section explains how to demonstrate the problem. If you already get it, skip to the next section.

When you run the program, you'll see the window at the bottom of the stack shown in the picture at the top of this post. First, leave the Regrab box unchecked and click Launch Dialog. You'll notice that the dialog is modal so you can't interact with the initial window.

Actually, you can interact with it a little. You can grab the window's title bar and drag it to a new position. However, you can't resize, minimize, maximize, or close that window. You also cannot interact with the widgets that it contains.

Click the dialog's Launch Dialog button to launch a second dialog. Again, it's modal and you can't interact (except by moving) the initial window or the first dialog.

Now if you close the top dialog, tkinter forgets about the first dialog and you can interact with the initial window. In particular, you can close that window to end the program even though the first dialog is still floating around.

Run the program again. This time check the Regrab box before you click Launch Dialog. Now when you make two dialogs and close one, the remaining dialog is modal like it should be.

Displaying Dialogs

The grab_set method makes all future input for the application go to a particular window. That's how you make a dialog modal. The way to solve this problem is to make the first dialog call grab_set again after the second dialog closes.

You can do that in the first dialog's code, but that means you need to remember to do it. The ModalDialog class does that automatically.

Here's how the class begins.

class ModalDialog(tk.Toplevel, ABC): '''A basic modal dialog class.''' # Call self.close to close the dialog. def __init__(self, title, parent, restore_grab=True): # Save parameters. self.parent = parent self.restore_grab = restore_grab # Call base class constructors. tk.Toplevel.__init__(self, parent) ABC.__init__(self) # Initialize the dialog. self.transient(parent) # Keep above parent. self.grab_set() # Grab events. self.protocol('WM_DELETE_WINDOW', self.close) self.title(title) # Watch for destruction. self.bind('', self.on_destroy) # Build the user interface. self.build_ui()

The class inherits from tk.Toplevel (so it's a window) and ABC (which stands for Abstract Base Class) so it can define abstract methods.

The constructor saves the dialog's parent window and the restore_grab flag. It then calls its base class constructors.

Next, the code calls the methods it needs to make the dialog modal and sets the window's title.

The code binds the <Destroy> event to the on_destroy method so that method is called when the dialog closes.

Finally, the constructor calls build_ui to create the dialog's widgets.

Here's the on_destroy method that executes when the dialog closes.

def on_destroy(self, event=None): '''We're being destroyed. Restore the grab.''' # Ignore events from child widgets. if event.widget == self: # Restore the parent's grab. self.grab_release() if self.restore_grab: self.parent.grab_set()

The window receives a <Destroy> event whenever it or any of its child widgets is destroyed. The on_destroy method first verifies that the window itself is dying. If the window is being destroyed, it releases the window's grab so it is no longer modal. Then, if the saved restore_grab value is True, the code calls the parent window's grab_set method to make it modal again.

That's really all there is to it! there are only two last small details.

The following code shows the build_ui method.

@abstractmethod def build_ui(self): '''Build the user interface here.''' pass

This method is marked as abstract, which means child classes must override it. If you try to instantiate this class or a child class without overriding build_ui, Python throws a temper tantrum.

Finally, here's the close method.

def close(self): '''Default close action.''' self.destroy()

This method simply destroys the window. That raises the <Destroy> event and that makes on_destroy take action.

Subclassing ModalDialog

To use the ModalDialog class, subclass it and override build_ui. Here's the class that the example program uses.

class MyModalDialog(ModalDialog): def __init__(self, parent, restore_grab): super().__init__('Modal Dialog', parent, restore_grab) self.geometry('300x200') def build_ui(self): '''Build the user interface.''' # Launch Dialog button. button = tk.Button(self, text='Launch Dialog', width=15, command=self.launch_dialog) button.place(relx=0.5, rely=0.5, anchor=tk.CENTER) def launch_dialog(self): '''Launch a dialog.''' # Display the dialog. dialog = MyModalDialog(self, self.restore_grab) dialog.wait_window()

The class inherits from ModalDialog. Its constructor calls the ModalDialog constructor and sets the window's dimensions.

The build_ui method creates the Launch Dialog button and the launch_dialog method launches a new dialog.

That's it! The dialog automatically restores the parent's grab if desired.

Conclusion

Here's the most important part of the main program's code.

def launch_dialog(self): '''Launch a dialog.''' # See whether we should restore_grab. self.restore_grab = self.restore_grab_var.get() # Display the dialog. dialog = MyModalDialog(self.window, self.restore_grab) dialog.wait_window()

This code gets the state of the Restore Grab checkbox. It creates a MyModalDialog, passing its constructor the restore_grab value and waits for it to close.

Using the ModalDialog class makes working with cascades of modal dialogs easy and safe. Download the example to experiment with it and to see additional details.

© 2025 - 2026 Rocky Mountain Computer Consulting, Inc. All rights reserved.