Is there a definitive attribute of Tk that I can check to see if either the mainloop has stopped running or if the root window has been destroyed?
The minimal code below shows a problem resulting from Tk’s apparent failure to propagate python exceptions. To see the problem in action, click on the root window button, “Start The Child Window Dialog”. Next, close the root window using its close window button (Red X).
import sys
import tkinter as tk
class ProgramIsEnding(Exception):
pass
class UnrecognizedButtonException(Exception):
pass
class MainWindow(tk.Tk):
def __init__(self):
super().__init__()
self.title('Root Window')
button = tk.Button(text='Start The Child Window Dialog')
button.configure(command=self.run_dialog)
button.grid()
self.protocol('WM_DELETE_WINDOW', self.delete_window_callback)
def delete_window_callback(self):
self.destroy()
print('Root has been destroyed')
raise ProgramIsEnding
def run_dialog(self):
try:
button = YesNoDialog(self)()
except ProgramIsEnding:
print('Doing end of program stuff.')
return
print(f"Button returned is '{button}'")
if button == 'yes':
print("'Yes' button clicked")
elif button == 'no':
print("'No' button clicked")
else:
msg = f"button '{button}'"
raise UnrecognizedButtonException(msg)
class YesNoDialog:
window: tk.Toplevel = None
button_clicked = None
def __init__(self, parent):
self.parent = parent
def __call__(self):
self.create_window()
return self.button_clicked
def create_window(self):
self.window = tk.Toplevel(self.parent)
yes = tk.Button(self.window, text='Yes', command=self.yes_command)
yes.pack(side='left')
no = tk.Button(self.window, text='No', command=self.no_command)
no.pack(side='left')
self.window.wait_window()
def yes_command(self):
self.button_clicked = 'yes'
self.window.destroy()
def no_command(self):
self.button_clicked = 'no'
self.window.destroy()
def main():
tkroot = MainWindow()
tkroot.mainloop()
if __name__ == '__main__':
sys.exit(main())
If the code was working as intended it would terminate correctly having caught the exception, “ProgramIsEnding”. Instead the program terminates with an unhandled “UnrecognizedButtonException”. The full error message follows. Note that the “ProgramIsEnding” exception has been reported via stdout even though it was not available to the try/except handler after control has been handed back to python from Tk.
Root has been destroyed
Exception in Tkinter callback
Traceback (most recent call last):
File "[…]/python3.7/tkinter/__init__.py", line 1702, in __call__
return self.func(*args)
File "[…]/wmdeletedemo.py", line 25, in delete_window_callback
raise ProgramIsEnding
ProgramIsEnding
Exception in Tkinter callback
Traceback (most recent call last):
File "[…]/python3.7/tkinter/__init__.py", line 1702, in __call__
return self.func(*args)
File "[…]/wmdeletedemo.py", line 41, in run_dialog
raise UnrecognizedButtonException(msg)
UnrecognizedButtonException: button 'None'
Button returned is 'None'
An obvious workaround is to check if the button value is None and, if so, return. For me, though, good practice suggests that I should check the primary event and neither rely on its secondary effects nor on setting flags.
So, is there some other attribute of Tk or tkinter which records the ending of mainloop or if the root window has been destroyed?