tkwait的wait_variable/wait_window/wait_visibility是否存在问题?

8

最近我开始偶尔使用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()

上面的代码完全正常,符合预期:

  1. for循环调用函数
  2. 函数运行并阻塞x秒钟的代码
  3. 窗口被销毁后,for循环继续执行

但在更多的事件驱动环境中,这些 tkwait 调用会变得棘手。文档中引用了以下说明:

如果事件处理程序再次调用 tkwait,则嵌套调用 tkwait 必须完成后,外部调用才能完成

>>5 >>2 >>10 的输出不同,你会得到 >>10 >>2 >>5,因为嵌套调用会阻塞内部调用,而外部调用会阻塞内部调用。我怀疑一个嵌套的 事件循环 或类似于 mainloop 的 正常方式处理事件

使用此功能是否有问题?因为如果你仔细思考一下,几乎所有的 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()

使用wait_something存在另一个问题,如果wait_something从未释放,它将阻止您的进程结束

相关链接:https://stackoverflow.com/a/66658574/13629335 - Thingamabobs
相关链接:https://stackoverflow.com/a/66658574/13629335 - undefined
1个回答

8

如果你正在使用内部事件循环,需要格外小心,因为:

  1. 只有当内部事件循环完成后,才会检查终止外部事件循环的条件。
  2. 很容易意外地递归进入内部事件循环。

递归进入问题通常可以通过禁用进入事件循环的路径来处理。通常有一种明显的方法来做到这一点,比如禁用您要单击的按钮。

条件处理则比较困难。在Tcl中,您可以通过稍微重组结构并使用coroutine来处理它,使看起来像内部事件循环的东西实际上不是内部事件循环,而只是将事物“停放”直至满足条件。由于Python语言实现并非完全非递归(我也不确定Tkinter是否设置好以处理异步函数代码的混乱),因此在Python中执行此操作可能更加困难。幸运的是,只要您小心谨慎,就不会太难。

如果你知道wait_window正在等待一个目标窗口为toplevel(而不是任何内部组件)的<Destroy>事件,那么它会有所帮助。简而言之,只要避免重新进入,您就可以很好地处理它。在等待期间,只需安排被单击的按钮被禁用即可;从UX角度来看,这也是好的(用户无法执行操作,因此不要提供他们可以执行操作的视觉提示)。

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

这里省略了一些小事情,如错误处理。

4
确认一下:嵌套事件循环会在C堆栈上形成嵌套。当内部事件循环正在进行时,您无法完成外部事件循环(尽管可以通过执行适当的触发事件来安排其终止)。 - Donal Fellows
实际上,Tcl_Canceled 没有被调用,这是 Python 实现错误吗? - Thingamabobs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接