问题描述
我有一个程序需要在 tkinter 中打开除主 Toplevel
窗口之外的 Tk()
窗口。
在主窗口中,我有一个 Scale
小部件,它通过 after
调用每 100 毫秒更新一次。但是,在 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”按钮,如何保持滑块/刻度正常流动?
还有为什么会发生这种情况?
提前致谢!
解决方法
此问题会在 Windows 上发生。你的代码在 Linux 上运行良好。(我已经测试过了)
A possible reason 在这里:
这里发生的事情(简化了很多)是,一旦 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 更漂亮。
,简而言之,这是一个“功能”,至少在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()