4

我有一个程序需要打开除 tkinterToplevel中的主Tk()窗口以外的窗口。在主窗口中,我有一个Scale小部件,它每 100 毫秒通过after调用更新一次。但是,在 Toplevel 窗口打开并且当我按下Toplevel窗口中的“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”按钮时,Scale 会跳到应有的位置 在此处输入图像描述

即使按住“X”按钮,如何保持滑块/刻度正常流动?
还有为什么会这样?

提前致谢!

4

2 回答 2

5

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

一个可能的原因在这里:

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

这篇文章还提到了另一个解决方案:使用线程。

由于 tkinter 是单线程的并且这些功能是打包的,因此使用线程似乎在 tkinter 中不起作用。

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

一个简单的解决方案就是隐藏您的标题栏,并自己自定义这些按钮。(避免操作系统处理这些事件。)例如:

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 更漂亮。

于 2021-05-04T18:16:41.343 回答
1

简而言之,那是一个“功能”,至少在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()
于 2021-05-04T18:28:07.357 回答