简体   繁体   English

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

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

I have a program that needs to open Toplevel windows except the main Tk() window in tkinter.我有一个程序需要打开顶层Toplevel除了 tkinter 中的主要Tk() window 之外。 In the main window I have a Scale widget which is updated every 100 miliseconds with the after call.在主要的 window 中,我有一个Scale小部件,它每 100 毫秒after调用后更新一次。 However in a state where the Toplevel window is open and the scale is updated when I press down the 'X' button in the Toplevel window the Scale stops moving.然而,在 state 中,顶层 window 处于打开状态,当我按下顶层Toplevel中的“X”按钮时,天平会更新, Scale停止移动。

在此处输入图像描述

This is my code:这是我的代码:

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

When I stop pressing the 'X' button the Scale jumps to the point it should be当我停止按下“X”按钮时,Scale 会跳到应有的位置在此处输入图像描述

How can I keep the slider/scale flowing normally even when I hold down the 'X' button?即使按住“X”按钮,如何保持滑块/刻度正常流动?
And also why does this happen?还有为什么会这样?

Thanks in advance!提前致谢!

This issue would happen on Windows.这个问题会发生在 Windows 上。 Your code works fine on Linux.(I've tested it)您的代码在 Linux 上运行良好。(我已经测试过了)

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.这里发生的事情(简化了很多)是,一旦 Windows 在非客户区检测到按下按钮事件,它就会停止发送更新消息,获取 window 的快照并准备开始绘制所有这些漂亮的效果窗口移动、调整大小等。然后 window 将保持冻结状态,直到相应的鼠标向上结束僵局。

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.由于 tkinter 是单线程的并且这些功能已打包,因此使用线程似乎在 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, 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()

Binding some events or using some nice color makes your UI prettier.绑定一些事件或使用一些漂亮的颜色会让你的 UI 更漂亮。

I have a program that needs to open Toplevel windows except the main Tk() window in tkinter.我有一个程序需要打开顶层Toplevel除了 tkinter 中的主要Tk() window 之外。 In the main window I have a Scale widget which is updated every 100 miliseconds with the after call.在主要的 window 中,我有一个Scale小部件,它每 100 毫秒after调用后更新一次。 However in a state where the Toplevel window is open and the scale is updated when I press down the 'X' button in the Toplevel window the Scale stops moving.然而,在 state 中,顶层 window 处于打开状态,当我按下顶层Toplevel中的“X”按钮时,天平会更新, Scale停止移动。

在此处输入图像描述

This is my code:这是我的代码:

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()
    win.mainloop()


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

root.mainloop()

When I stop pressing the 'X' button the Scale jumps to the point it should be当我停止按下“X”按钮时,Scale 会跳到应有的位置在此处输入图像描述

How can I keep the slider/scale flowing normally even when I hold down the 'X' button?即使按住“X”按钮,如何保持滑块/刻度正常流动?
And also why does this happen?还有为什么会这样?

Thanks in advance!提前致谢!

In short, that is a " feature ", at least on windows, the menu buttons are not expected to support the action of holding it.简而言之,那是一个“功能”,至少在windows上,菜单按钮预计不会支持按住它的动作。 This happens because mainloop is just asking to update its own instance from the same place, the global _default_root , a workaround would be to create a new Tk on a detached process.发生这种情况是因为mainloop只是要求从同一个地方更新自己的实例,即全局_default_root ,解决方法是在分离的进程上创建一个新的Tk Note that this does not happen on every gui library, for example wxWidgets works fine.请注意,并非每个 gui 库都会发生这种情况,例如 wxWidgets 可以正常工作。

As you can see on this example, regular buttons are unaffected.正如您在此示例中看到的,常规按钮不受影响。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM