tkwait wait_variable/wait_window/wait_visibility 是否损坏?

Atl*_*435 8 python tkinter tcl event-driven wait

我最近开始随意使用tkwait并注意到某些功能仅在特殊条件下才起作用。例如:

import tkinter as tk

def w(seconds):
    dummy = tk.Toplevel(root)
    dummy.title(seconds)
    dummy.after(seconds*1000, lambda x=dummy: x.destroy())
    dummy.wait_window(dummy)
    print(seconds)

root = tk.Tk()
for i in [5,2,10]:
    w(i)
root.mainloop()
Run Code Online (Sandbox Code Playgroud)

上面的代码工作得很好并且符合预期:

  1. for循环调用函数
  2. 该函数运行并阻止代码 x 秒
  3. 窗口被销毁并且 for 循环继续

但在事件驱动的环境中,这些tkwait调用会变得很棘手。该文档指出引用:

如果事件处理程序再次调用 tkwait,则对 tkwait 的嵌套调用必须在外部调用完成之前完成

>>5 >>2 >>10您将得到的不是输出>>10 >>2 >>5,因为嵌套调用会阻塞内部调用,而外部调用会阻塞内部调用。我怀疑嵌套事件循环或等效的主循环在等待时以正常方式处理事件

我使用此功能是否做错了什么?因为如果您仔细想想,几乎所有 tkinter 对话框窗口都在使用此功能,而我以前从未读过此行为。

事件驱动的示例可能是:

import tkinter as tk

def w(seconds):
    dummy = tk.Toplevel(root)
    dummy.title(seconds)
    dummy.after(seconds*1000, lambda x=dummy: x.destroy())
    dummy.wait_window(dummy)
    print(seconds)

root = tk.Tk()
btn1 = tk.Button(
    root, command=lambda : w(5), text = '5 seconds')
btn2 = tk.Button(
    root, command=lambda : w(2), text = '2 seconds')
btn3 = tk.Button(
    root, command=lambda : w(10), text = '10 seconds')
btn1.pack()
btn2.pack()
btn3.pack()
root.mainloop()
Run Code Online (Sandbox Code Playgroud)

随之而来的另一个问题wait_something是,如果从未被释放,它将阻止您的进程完成wait_something

Don*_*ows 8

基本上,如果您使用内部事件循环,则需要非常小心,因为:

  1. 在内部事件循环完成之前,不会检查将终止外部事件循环的条件。
  2. 很容易意外地递归地进入内部事件循环。

递归进入问题通常最容易通过在内部事件循环运行时禁用进入事件循环的路径来解决。通常有一种明显的方法可以做到这一点,例如禁用您要单击的按钮。

条件处理比较困难。在 Tcl 中,您可以通过使用协程稍微重组事物来处理它,这样看起来像内部事件循环的事物就不是内部事件循环,而是只是将事物停放直到满足条件为止。这个选项在 Python 中更难实现,因为语言实现并不是完全非递归的(而且我不确定 Tkinter 是否设置为处理混乱的异步函数着色)。幸运的是,只要你小心,这并不是太困难。

如果您知道wait_window正在等待<Destroy>目标窗口是顶层(而不是内部组件之一)的事件,并且销毁主窗口将触发它,因为当您这样做时,所有其他窗口也会被销毁,这会有所帮助。简而言之,只要避免重入就可以了。您只需安排在等待期间禁用被单击的按钮即可;从用户体验的角度来看,这也很好(用户无法做到这一点,所以不要提供他们可以做到的视觉提示)。

def w(seconds, button):
    dummy = tk.Toplevel(root)
    dummy.title(seconds)
    dummy.after(seconds*1000, lambda x=dummy: x.destroy())
    button["state"] = "disabled"  # <<< This, before the wait
    dummy.wait_window(dummy)
    button["state"] = "normal"    # <<< This, after the wait
    print(seconds)

btn1 = tk.Button(root, text = '5 seconds')
# Have to set the command after creation to bind the button handle to the callback
btn1["command"] = (lambda : w(5, btn1))
Run Code Online (Sandbox Code Playgroud)

这一切都忽略了诸如错误处理之类的小事情。

  • 并确认:嵌套事件循环堆栈在 C 堆栈上。当内部事件正在进行时,您“无法”完成外部事件(尽管您可以通过执行适当的触发事件来安排其终止)。 (4认同)