Why does holding down the 'X' button in a toplevel stop execution of main window in tkinter? Why does holding down the 'X' button in a toplevel stop execution of main window in tkinter? tkinter tkinter

Why does holding down the 'X' button in a toplevel stop execution of main window in tkinter?


This issue would happen on Windows. Your code works fine on Linux.(I've tested it)

A possible reason is here:

What is happening here (simplyfied a lot) is that as soon as Windows detects a button-down event on the non-client area it stops sending update messages, gets a snapshot of the window and gets ready to start drawing all those nice effects for window-moving, -resizing, etc. The window then stays frozen until the corresponding mouse-up ends the impasse.

This post also mentioned another solution: use thread.

Due to tkinter is single-threaded and those features are packaged, it seems using thread doesn't work in tkinter.

The cause is how operate system handle those "holding down" events on the title bar.

An easy solution is just hiding your title bar, and custom these buttons by yourself.(Avoid OS handling those events.) Like:

from tkinter import Tk, Toplevel, Scaleimport tkinter as tkclass CustomToplevel(Toplevel):    def __init__(self, *args, **kwargs):        super().__init__(*args, **kwargs)        self.__offset_x = 100        self.__offset_y = 100        self.window_width = 100        self.window_height = 100        self.overrideredirect(True)        self.title_bar_frame = tk.Frame(self, bg="grey")        self.title_bar_frame.pack(fill="x")        self.title_bar_frame.bind('<Button-1>', self.__click)        self.title_bar_frame.bind('<B1-Motion>',self.__drag)        self.close_button = tk.Button(self.title_bar_frame, text="X", bg="red", font=("", 15),                                      command=self.destroy)        self.close_button.pack(side="right", fill="y")        self.geometry(f"{self.window_width}x{self.window_height}+{self.winfo_pointerx() - self.__offset_x}+{self.winfo_pointery() - self.__offset_y}")    def __click(self, event):        self.__offset_x = event.x        self.__offset_y = event.y    def __drag(self, event):        self.geometry(f"{self.window_width}x{self.window_height}+{self.winfo_pointerx() - self.__offset_x}+{self.winfo_pointery() - self.__offset_y}")root = Tk()slider = Scale(root, orient='horizontal')slider.pack()num = 0def main():    global num    slider.set(num)    num += 1    slider.after(500, main)def toplevel():    win = CustomToplevel()root.bind('<space>', lambda x: [main(), toplevel()])root.mainloop()

Binding some events or using some nice color makes your UI prettier.


In short, that is a "feature", at least on windows, the menu buttons are not expected to support the action of holding it. This happens because mainloop is just asking to updateits own instance from the same place, the global _default_root, a workaround would be to create a new Tk on a detached process.Note that this does not happen on every gui library, for example wxWidgets works fine.

As you can see on this example, regular buttons are unaffected.

import tkinter as tkclass Top_Window(tk.Toplevel):    @staticmethod    def button_release(_):        print('Button released')    def __init__(self, name, **kwargs):        tk.Toplevel.__init__(self, **kwargs)        self.protocol('WM_DELETE_WINDOW', self.quit_button)        self.geometry('300x200+300+300')        self.title = name        self.button = tk.Button(self, text='Button')        self.button.bind('<ButtonRelease>', self.button_release)        self.button.pack()    def quit_button(self):        print('Top window destroyed')        self.destroy()class Main_Window(tk.Tk):    num = 0    def after_loop(self):        self.num += 1        self.slider.set(self.num)        self.after(500, self.after_loop)    def __init__(self):        tk.Tk.__init__(self)        self.geometry('300x200+100+100')        self.slider = tk.Scale(self, orient='horizontal')        self.slider.pack()        self.bind('<space>', self.spawn_top_level)        self.after(500, self.after_loop)    def spawn_top_level(self, _):        Top_Window('Top', master=self)if __name__ == '__main__':    app = Main_Window()    app.mainloop()