为什么在顶层按住 'X' 按钮会停止执行 tkinter 中的 main window?

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

我有一个程序需要在 tkinter 中打开 Toplevel windows 除了主要的 Tk() window。 在主 window 中,我有一个 Scale 小部件,它每 100 毫秒通过 after 调用更新一次。但是,在 Toplevel window 打开的状态下,当我按下 Toplevel window 中的 'X' 按钮时,比例会更新 Scale 停止移动.

这是我的代码:

from tkinter import Tk, Toplevel, Scale

root = Tk()

slider = Scale(root, orient='horizontal')
slider.pack()
num = 0


def main():
    global num
    slider.set(num)
    num += 1
    slider.after(500, main)


def toplevel():
    win = Toplevel()


root.bind('<space>', lambda x: [main(), toplevel()])

root.mainloop()

当我停止按下 'X' 按钮时,比例会跳到它应该的位置

如何在按住 'X' 按钮的情况下保持 slider/scale 正常流动?
还有为什么会这样?

提前致谢!

这个问题会在 Windows 上发生。您的代码在 Linux 上运行良好。(我已经测试过了)

A possible reason 在这里:

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.

这个post也提到了另一种解决方法:使用线程。

由于 tkinter 是 single-threaded 并且这些功能被打包,似乎使用线程在 tkinter 中不起作用。

原因在于操作系统如何处理标题栏上的“按住”事件。

一个简单的解决方案就是隐藏您的标题栏,然后自己自定义这些按钮。(避免 OS 处理这些事件。)喜欢:

from tkinter import Tk, Toplevel, Scale
import tkinter as tk


class 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 = 0


def 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()

绑定一些事件或使用一些漂亮的颜色会让你的UI更漂亮。

简而言之,那是一个“特性”,至少在windows上,菜单按钮预计不会支持按住它的动作。发生这种情况是因为 mainloop 只是要求更新 它自己的实例来自同一个地方,全局 _default_root,解决方法是在分离进程上创建一个新的 Tk。 请注意,这不会发生在每个 gui 库上,例如 wxWidgets 工作正常。

如您在此示例中所见,常规按钮不受影响。

import tkinter as tk

class 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()