我的顶级ttk::progressbar
(唯一的.
)我通过以下函数更新:
proc progress {x} {
global prog
set prog [expr fmod(($prog +$x),100)]
update idletasks
}
prog
是通过-variable
选项绑定到进度条的变量。
如果我将注意力集中在窗户上,一切都会正常工作。如果我切换到另一个窗口,进度条将停止更新,即使我切换回应用程序也不会重新启动。
我在Windows 7上使用tcl / tk 8.6(以防它可能相关)。
这种行为的原因是什么?我是否遗漏了有关如何更新进度条的内容?
修改
似乎有一个完整的update
可以解决问题,所以问题就是为什么会出现这种情况,以及是否有办法避免完全更新刷新。
答案 0 :(得分:3)
当您更新进度条小部件的值(或者,通常是任何小部件的值或配置)时,Tk会创建一个内部空闲 * 事件来执行重绘;我的想法是,如果你连续更新多个事物以响应一系列事件(一个非常常见的事情!)那么你只能重新绘制一个小部件。这在大多数情况下都能很好地工作,但是在你做某种繁忙的动画循环时却不行。
执行update idletasks
会使任何计划的空闲事件立即发生;内部生成的重绘在此时进行处理。
但这不是全部。还有外部生成的重绘来自外部世界(即主机窗口系统)通过真实事件:在Windows上,WM_PAINT
,在X11上,Expose
,在OSX上,......,它是在OSX上很复杂。 (为了争论而忽略那个平台。)因为这些外部事件来自外部世界,它们只被事件循环的完整运行所注意到 - 低级事件处理系统不知道它们是请求重绘,直到收到它们为止 - 这意味着使用主事件循环或由vwait
启动的辅助事件循环,完整update
或tkwait
。 (事实上,处理这些外部重绘事件的方式是通过在空闲事件中安排重绘,因为可能还有其他事件,例如小部件调整大小或焦点更改,也需要同时处理也需要重绘。)
建议的处理方法是拆分正在进行长时间运行处理的代码,以便定期返回事件循环并允许它处理任何挂起的外部事件。在8.5及之前,您可以不时使用after $aFewMilliseconds doTheNextBit
来执行此操作;这需要构造你的代码,以便它以延续传递的方式工作,这可能有点痛苦。另一方面,在8.6中,您可以将长时间运行的代码放在协程中,并使用after $aFewMilliseconds [info coroutine];yield
进行一次停止,而不会显着破坏代码流。
建议不要撒上update
。这有一个问题,它启动一个辅助事件循环,如果你试图重新进入任何长时间运行的处理,这可能会导致堆栈耗尽问题。可以通过适当的互锁来实现这项工作(例如,在您进行长时间处理时禁用可能导致问题的按钮),但这确实更棘手。您还可以考虑将处理转移到单独的非GUI 线程 ** ,它只是不时地将消息发送回主GUI线程以导致进度条改变。 (Tcl将线程间消息视为事件,BTW。)在您的情况下,多线程可能会或可能没有意义。
*您可以使用after idle
制作自己的空闲活动,但通常不需要它们
**真正的多线程GUI非常疯狂,而且无论如何Tk都不会这样。每个带有Tk的线程都有自己的完整副本。