Question: What do I need I am trying to implement a custom Yes / No prompt box with help of tkinter. However I don't want to use the default messagebox, because I require the following two functionalites:  a default value a countdown after which the widget destroys itself and takes the default value as answer  What are the unpredictable errors I've managed to implement these requirements with the code below, however I get some really unpredictable behaviour when using the widgets in the following sense:  Sometimes everything works as expected. When I press the buttons, the correct answer is stored, or if I let the countdown time out, the default answer is stored, or if I click the close-window it correctly applies the default value as answer. But then, at times when I click the buttons, I get some wierd errors _tkinter.TclError: invalid command name ".!ctkframe2.!ctkcanvas" (see execution log below for whole stacktrace)  I suspect it has something to do with the timer, since the errors do not always apper when the buttons are pressed. It is really driving me crazy... example code # util_gui_classes.py # -*- coding: utf-8 -*- """ Classes which serve for gui applications. """ from typing import Any import tkinter import tkinter.messagebox import customtkinter # ____________________________________________________________________________________________ customtkinter.set_appearance_mode('System') # Modes: 'System' (standard), 'Dark', 'Light' customtkinter.set_default_color_theme('blue') # Themes: 'blue' (standard), 'green', 'dark-blue' # ____________________________________________________________________________________________ class GuiPromptYesNo(customtkinter.CTk): """ Creates a yes / no gui based prompt with default value and countdown functionality. The user input will be stored in: > instance.answer """ WIDTH = 500 HEIGHT = 200 def __init__(self, question: str, default_value: str = 'no', countdown_seconds: int = 0): super().__init__() self.title('input required') self.geometry(f'{self.__class__.WIDTH}x{self.__class__.HEIGHT}') self.protocol('WM_DELETE_WINDOW', self.on_closing) # call .on_closing() when app gets closed self.resizable(False, False) self.question = question self.answer = None self.default_value = default_value self.countdown_seconds = countdown_seconds self.remaining_seconds = countdown_seconds # ============ create top-level-frames ============ # configure grid layout (4x1) self.equal_weighted_grid(self, 4, 1) self.grid_rowconfigure(0, minsize=10) self.grid_rowconfigure(3, minsize=10) self.frame_label = customtkinter.CTkFrame(master=self, corner_radius=10) self.frame_label.grid(row=1, column=0) self.frame_buttons = customtkinter.CTkFrame(master=self, corner_radius=0, fg_color=None) self.frame_buttons.grid(row=2, column=0) # ============ design frame_label ============ # configure grid layout (5x4) self.equal_weighted_grid(self.frame_label, 5, 4) self.frame_label.grid_rowconfigure(0, minsize=10) self.frame_label.grid_rowconfigure(2, minsize=10) self.frame_label.grid_rowconfigure(5, minsize=10) self.label_question = customtkinter.CTkLabel( master=self.frame_label, text=self.question, text_font=('Consolas',), ) self.label_question.grid(row=1, column=0, columnspan=4, pady=5, padx=10) self.label_default_value = customtkinter.CTkLabel( master=self.frame_label, text='default value: ', text_font=('Consolas',), ) self.label_default_value.grid(row=3, column=0, pady=5, padx=10) self.entry_default_value = customtkinter.CTkEntry( master=self.frame_label, width=40, justify='center', placeholder_text=self.default_value, state='disabled', textvariable=tkinter.StringVar(value=self.default_value), text_font=('Consolas',), ) self.entry_default_value.grid(row=3, column=1, pady=5, padx=10) if countdown_seconds > 0: self.label_timer = customtkinter.CTkLabel( master=self.frame_label, text='timer [s]: ', text_font=('Consolas',), ) self.label_timer.grid(row=3, column=2, pady=5, padx=10) self.entry_timer = customtkinter.CTkEntry( master=self.frame_label, width=40, justify='center', state='disabled', textvariable=tkinter.StringVar(value=str(self.remaining_seconds)), placeholder_text=str(self.remaining_seconds), text_font=('Consolas',), ) self.entry_timer.grid(row=3, column=3, pady=5, padx=10) # ============ design frame_buttons ============ # configure grid layout (3x2) self.equal_weighted_grid(self.frame_buttons, 3, 2) self.frame_buttons.grid_rowconfigure(0, minsize=10) self.frame_buttons.grid_rowconfigure(2, minsize=10) self.button_yes = customtkinter.CTkButton( master=self.frame_buttons, text='yes', text_font=('Consolas',), command=lambda: self.button_event('yes'), ) self.button_yes.grid(row=1, column=0, pady=5, padx=20) self.button_no = customtkinter.CTkButton( master=self.frame_buttons, text='no', text_font=('Consolas',), command=lambda: self.button_event('no'), ) self.button_no.grid(row=1, column=1, pady=5, padx=20) if self.countdown_seconds > 0: self.countdown() self.attributes('-topmost', True) self.mainloop() # __________________________________________________________ # methods @staticmethod def equal_weighted_grid(obj: Any, rows: int, cols: int): """configures the grid to be of equal cell sizes for rows and columns.""" for row in range(rows): obj.grid_rowconfigure(row, weight=1) for col in range(cols): obj.grid_columnconfigure(col, weight=1) def button_event(self, answer): """Stores the user input as instance attribute `answer`.""" self.answer = answer self.terminate() def countdown(self): """Sets the timer for the question.""" if self.answer is not None: self.terminate() elif self.remaining_seconds < 0: self.answer = self.default_value self.terminate() else: self.entry_timer.configure(textvariable=tkinter.StringVar(value=str(self.remaining_seconds))) self.remaining_seconds -= 1 self.after(1000, self.countdown) def stop_after_callbacks(self): """Stops all after callbacks on the root.""" for after_id in self.tk.eval('after info').split(): self.after_cancel(after_id) def on_closing(self, event=0): """If the user presses the window x button without providing input""" if self.answer is None and self.default_value is not None: self.answer = self.default_value self.terminate() def terminate(self): """Properly terminates the gui.""" # stop all .after callbacks to avoid error message "Invalid command ..." after destruction self.stop_after_callbacks() self.destroy() # ____________________________________________________________________________________________ if __name__ == '__main__': print('\n', 'do some python stuff before', '\n', sep='') q1 = GuiPromptYesNo(question='1. do you want to proceed?', countdown_seconds=5) print(f'>>>{q1.answer=}') print('\n', 'do some python stuff in between', '\n', sep='') q2 = GuiPromptYesNo(question='2. do you want to proceed?', countdown_seconds=5) print(f'>>>{q2.answer=}') print('\n', 'do some python stuff at the end', '\n', sep='') # ____________________________________________________________________________________________  execution log with errors The first three tests where successful (clicking buttons included), after that the errors appeared. (py311) C:\Users\user\PycharmProjects\Sandbox\gui_tools>python util_guis.py do some python stuff before q1.answer='yes' do some python stuff in between q2.answer='no' do some python stuff at the end (py311) C:\Users\user\PycharmProjects\Sandbox\gui_tools>python util_guis.py do some python stuff before q1.answer='yes' do some python stuff in between q2.answer='yes' do some python stuff at the end (py311) C:\Users\user\PycharmProjects\Sandbox\gui_tools>python util_guis.py do some python stuff before q1.answer='no' do some python stuff in between q2.answer='no' do some python stuff at the end (py311) C:\Users\user\PycharmProjects\Sandbox\gui_tools>python util_guis.py do some python stuff before >>>q1.answer='yes' do some python stuff in between Exception in Tkinter callback Traceback (most recent call last): File "C:\Program Files\Python311\Lib\tkinter\__init__.py", line 1948, in __call__ return self.func(*args) ^^^^^^^^^^^^^^^^ File "C:\Program Files\Python311\Lib\tkinter\__init__.py", line 861, in callit func(*args) File "C:\Users\user\PycharmProjects\Sandbox\gui_tools\util_guis.py", line 197, in countdown self.terminate() File "C:\Users\user\PycharmProjects\Sandbox\gui_tools\util_guis.py", line 224, in terminate child.destroy() File "C:\Users\user\python\shared_venvs\py311\Lib\site-packages\customtkinter\widgets\widget_base_class.py", line 85, in destroy super().destroy() File "C:\Program Files\Python311\Lib\tkinter\__init__.py", line 2635, in destroy for c in list(self.children.values()): c.destroy() ^^^^^^^^^^^ File "C:\Users\user\python\shared_venvs\py311\Lib\site-packages\customtkinter\widgets\widget_base_class.py", line 85, in destroy super().destroy() File "C:\Program Files\Python311\Lib\tkinter\__init__.py", line 2639, in destroy Misc.destroy(self) File "C:\Program Files\Python311\Lib\tkinter\__init__.py", line 687, in destroy self.tk.deletecommand(name) _tkinter.TclError: can't delete Tcl command >>>q2.answer='no' do some python stuff at the end (py311) C:\Users\user\PycharmProjects\Sandbox\gui_tools>python util_guis.py do some python stuff before Exception in Tkinter callback Traceback (most recent call last): File "C:\Program Files\Python311\Lib\tkinter\__init__.py", line 1948, in __call__ return self.func(*args) ^^^^^^^^^^^^^^^^ File "C:\Users\user\python\shared_venvs\py311\Lib\site-packages\customtkinter\widgets\ctk_button.py", line 377, in clicked self.command() File "C:\Users\user\PycharmProjects\Sandbox\gui_tools\util_guis.py", line 124, in <lambda> command=lambda: self.button_event('yes'), ^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\user\PycharmProjects\Sandbox\gui_tools\util_guis.py", line 156, in button_event self.terminate() File "C:\Users\user\PycharmProjects\Sandbox\gui_tools\util_guis.py", line 186, in terminate self.destroy() File "C:\Users\user\python\shared_venvs\py311\Lib\site-packages\customtkinter\windows\ctk_tk.py", line 108, in destroy super().destroy() File "C:\Program Files\Python311\Lib\tkinter\__init__.py", line 2367, in destroy for c in list(self.children.values()): c.destroy() ^^^^^^^^^^^ File "C:\Users\user\python\shared_venvs\py311\Lib\site-packages\customtkinter\widgets\widget_base_class.py", line 85, in destroy super().destroy() File "C:\Program Files\Python311\Lib\tkinter\__init__.py", line 2635, in destroy for c in list(self.children.values()): c.destroy() ^^^^^^^^^^^ File "C:\Users\user\python\shared_venvs\py311\Lib\site-packages\customtkinter\widgets\widget_base_class.py", line 85, in destroy super().destroy() File "C:\Program Files\Python311\Lib\tkinter\__init__.py", line 2639, in destroy Misc.destroy(self) File "C:\Program Files\Python311\Lib\tkinter\__init__.py", line 687, in destroy self.tk.deletecommand(name) _tkinter.TclError: can't delete Tcl command Exception in Tkinter callback Traceback (most recent call last): File "C:\Program Files\Python311\Lib\tkinter\__init__.py", line 1948, in __call__ return self.func(*args) ^^^^^^^^^^^^^^^^ File "C:\Users\user\python\shared_venvs\py311\Lib\site-packages\customtkinter\widgets\widget_base_class.py", line 142, in update_dimensions_event self.draw(no_color_updates=True) # faster drawing without color changes ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\user\python\shared_venvs\py311\Lib\site-packages\customtkinter\widgets\ctk_frame.py", line 80, in draw requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.apply_widget_scaling(self._current_width), ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\user\python\shared_venvs\py311\Lib\site-packages\customtkinter\draw_engine.py", line 88, in draw_rounded_rect_with_border return self.__draw_rounded_rect_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius, ()) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\user\python\shared_venvs\py311\Lib\site-packages\customtkinter\draw_engine.py", line 207, in __draw_rounded_rect_with_border_font_shapes self._canvas.delete("border_parts") File "C:\Program Files\Python311\Lib\tkinter\__init__.py", line 2879, in delete self.tk.call((self._w, 'delete') + args) _tkinter.TclError: invalid command name ".!ctkframe2.!ctkcanvas" >>>q1.answer='yes' do some python stuff in between Exception in Tkinter callback Traceback (most recent call last): File "C:\Program Files\Python311\Lib\tkinter\__init__.py", line 1948, in __call__ return self.func(*args) ^^^^^^^^^^^^^^^^ File "C:\Users\user\python\shared_venvs\py311\Lib\site-packages\customtkinter\widgets\ctk_button.py", line 377, in clicked self.command() super().destroy() File "C:\Program Files\Python311\Lib\tkinter\__init__.py", line 2639, in destroy Misc.destroy(self) File "C:\Program Files\Python311\Lib\tkinter\__init__.py", line 687, in destroy self.tk.deletecommand(name) _tkinter.TclError: can't delete Tcl command >>>q2.answer='no' do some python stuff at the end (py311) C:\Users\user\PycharmProjects\Sandbox\gui_tools>  requirements I am using Windows 11 as os and have a virtual python 3.11 environment with customtkinter installed. EDIT: With the help of @Thingamabobs answer I managed to achive the expected behaviour without getting the errors. Here is the final code: # util_gui_classes.py # -*- coding: utf-8 -*- """ Classes which serve for gui applications. """ from typing import Any import tkinter import tkinter.messagebox import customtkinter from _tkinter import TclError # _______________________________________________________________________ customtkinter.set_appearance_mode('System') # Modes: 'System' (standard), 'Dark', 'Light' customtkinter.set_default_color_theme('blue') # Themes: 'blue' (standard), 'green', 'dark-blue' # _______________________________________________________________________ class GuiPromptYesNo(customtkinter.CTk): """ Creates a yes / no gui based prompt with default value and countdown functionality. The user input will be stored in: >>> instance.answer """ WIDTH = 500 HEIGHT = 200 def __init__(self, question: str, default_value: str = 'no', countdown_seconds: int = 0): super().__init__() self.terminated = False self.title('input required') self.geometry(f'{self.__class__.WIDTH}x{self.__class__.HEIGHT}') self.protocol('WM_DELETE_WINDOW', self.on_closing) # call .on_closing() when app gets closed self.resizable(False, False) self.question = question self.answer = None self.default_value = default_value self.countdown_seconds = countdown_seconds self.remaining_seconds = countdown_seconds # ============ create top-level-frames ============ # configure grid layout (4x1) self.equal_weighted_grid(self, 4, 1) self.grid_rowconfigure(0, minsize=10) self.grid_rowconfigure(3, minsize=10) self.frame_label = customtkinter.CTkFrame(master=self, corner_radius=10) self.frame_label.grid(row=1, column=0) self.frame_buttons = customtkinter.CTkFrame(master=self, corner_radius=0, fg_color=None) self.frame_buttons.grid(row=2, column=0) # ============ design frame_label ============ # configure grid layout (5x4) self.equal_weighted_grid(self.frame_label, 5, 4) self.frame_label.grid_rowconfigure(0, minsize=10) self.frame_label.grid_rowconfigure(2, minsize=10) self.frame_label.grid_rowconfigure(5, minsize=10) self.label_question = customtkinter.CTkLabel( master=self.frame_label, text=self.question, text_font=('Consolas',), ) self.label_question.grid(row=1, column=0, columnspan=4, pady=5, padx=10) self.label_default_value = customtkinter.CTkLabel( master=self.frame_label, text='default value: ', text_font=('Consolas',), ) self.label_default_value.grid(row=3, column=0, pady=5, padx=10) self.entry_default_value = customtkinter.CTkEntry( master=self.frame_label, width=40, justify='center', placeholder_text=self.default_value, state='disabled', textvariable=tkinter.StringVar(value=self.default_value), text_font=('Consolas',), ) self.entry_default_value.grid(row=3, column=1, pady=5, padx=10) if countdown_seconds > 0: self.label_timer = customtkinter.CTkLabel( master=self.frame_label, text='timer [s]: ', text_font=('Consolas',), ) self.label_timer.grid(row=3, column=2, pady=5, padx=10) self.entry_timer = customtkinter.CTkEntry( master=self.frame_label, width=40, justify='center', state='disabled', textvariable=tkinter.StringVar(value=str(self.remaining_seconds)), placeholder_text=str(self.remaining_seconds), text_font=('Consolas',), ) self.entry_timer.grid(row=3, column=3, pady=5, padx=10) # ============ design frame_buttons ============ # configure grid layout (3x2) self.equal_weighted_grid(self.frame_buttons, 3, 2) self.frame_buttons.grid_rowconfigure(0, minsize=10) self.frame_buttons.grid_rowconfigure(2, minsize=10) self.button_yes = customtkinter.CTkButton( master=self.frame_buttons, text='yes', text_font=('Consolas',), command=lambda: self.button_event('yes'), ) self.button_yes.grid(row=1, column=0, pady=5, padx=20) self.button_no = customtkinter.CTkButton( master=self.frame_buttons, text='no', text_font=('Consolas',), command=lambda: self.button_event('no'), ) self.button_no.grid(row=1, column=1, pady=5, padx=20) if self.countdown_seconds > 0: self.countdown() self.attributes('-topmost', True) self.mainloop() # __________________________________________________________ # methods @staticmethod def equal_weighted_grid(obj: Any, rows: int, cols: int): """configures the grid to be of equal cell sizes for rows and columns.""" for row in range(rows): obj.grid_rowconfigure(row, weight=1) for col in range(cols): obj.grid_columnconfigure(col, weight=1) def button_event(self, answer): """Stores the user input as instance attribute `answer`.""" self.answer = answer self.terminate() def countdown(self): """Sets the timer for the question.""" if self.answer is not None: self.terminate() elif self.remaining_seconds < 0: self.answer = self.default_value self.terminate() else: self.entry_timer.configure(textvariable=tkinter.StringVar(value=str(self.remaining_seconds))) self.remaining_seconds -= 1 self.after(1000, self.countdown) def stop_after_callbacks(self): """Stops all after callbacks on the root.""" for after_id in self.tk.eval('after info').split(): self.after_cancel(after_id) def on_closing(self, event=0): """If the user presses the window x button without providing input""" if self.answer is None and self.default_value is not None: self.answer = self.default_value self.terminate() def terminate(self): """Properly terminates the gui.""" # stop all .after callbacks to avoid error message "Invalid command ..." after destruction self.stop_after_callbacks() if not self.terminated: self.terminated = True try: self.destroy() except TclError: self.destroy() # _______________________________________________________________________ if __name__ == '__main__': print('before') q1 = GuiPromptYesNo(question='1. do you want to proceed?', countdown_seconds=5) print(f'>>>{q1.answer=}') print('between') q2 = GuiPromptYesNo(question='2. do you want to proceed?', countdown_seconds=5) print(f'>>>{q2.answer=}') print('after') # _______________________________________________________________________  BTW: the class can also be found in my package utils_nm inside the module util_gui_classes.

Answer: While I don't have Ctk to give you the exact code. I can tell you exactly what is wrong and how you need to solve it. You have self repeating function via after here: def countdown(self): """Sets the timer for the question.""" if self.answer is not None: self.terminate() elif self.remaining_seconds < 0: self.answer = self.default_value self.terminate() else: self.entry_timer.configure(textvariable=tkinter.StringVar(value=str(self.remaining_seconds))) self.remaining_seconds -= 1 self.after(1000, self.countdown)  The problem that you are facing is that you try to delete the window that has already been destroyed in an after call. So depending on which event is quicker you are running into an error or not. Why does this happen? When ever you give an instruction regardless of what it is (with a few exceptions e.g update), it is placed in a event queue and scheduled in some sort of FIFO (first in, first out). So the oldest event gets processed. That means you can try to cancel an alarm but running the alarm before you actual cancel it. How to solve? There are different approaches available. The easiest and cleanest solution, in my opinion, is to set a flag and avoid trying to destroy an already destroyed Window. Something like:  def __init__(self, question: str, default_value: str = 'no', countdown_seconds: int = 0): super().__init__() self.terminated = False  and set it like: def terminate(self): """Properly terminates the gui.""" # stop all .after callbacks to avoid error message "Invalid command ..." after destruction #self.stop_after_callbacks() shouldn't be needed if not self.terminated: self.terminated = True self.destroy()  In addition to the above proposal I suggest you to set up a protocol handler for WM_DELETE_WINDOW and set the flag, since it probably also occur by destroying the window via the window manager.  A different approach is a try and except block in terminate where you catch except _tkinter.TclError:. But note the underscore, the module is not intended to be used and it can change in future and might break your app again, even if this seems unlikely.