after(5000, …) 表示 现在之后 5 秒,因为 after 被调用,而不是 tkinter 只能通过读心来猜测的某个未来时间点之后的 5 秒。 p>
因此,您只是创建了 200 个回调,并将它们全部安排在 5 秒后运行。这显然不是你想要的,而是你要求的,所以这就是你得到的。
一般来说,您不能在基于事件的编程中执行这样的循环。您需要做的是将循环由内向外翻转:每个步骤执行一次迭代,然后为下一个调用安排下一次调用。
完全一般的转换如下所示:
def runAnim():
iterwidth = iter(range(0, 200))
stepAnim(iterwidth)
def stepAnim(iterwidth):
try:
width = next(iterwidth)
except StopIteration:
return
test_label.config(width=width))
app.after(5000, stepAnim, iterwidth)
虽然这适用于任何可迭代对象,但当您只是对数字进行迭代时,将for 循环转换为更容易反转的显式计数器通常会更好一些。 (是的,这与“通常的 for 而不是 while 和 += 1 当您不反转事物时相反。不同之处在于,我们无法访问魔法for 或 while 和 while 没有那么神奇,因此更容易反转。)
def runAnim():
stepAnim(0, 200):
def stepAnim(width, maxWidth):
test_label.config(width=width))
width += 1
if width < maxWidth:
app.after(5000, stepAnim, width, maxWidth)
但是,在这个特别简单的情况下,您可以安排 200 个回调,范围从 5 到 1000 秒到未来:
def runAnim():
for width in range(0, 200):
app.after(5000 * width, lambda width = width: test_label.config(width=width))
这可能会导致计时器漂移得更厉害,甚至可能会阻塞调度程序并为您的程序增加延迟,但至少值得一试。
说到漂移:
回到开头,我提到after(5000, …) 表示现在之后 5 秒。
after 的触发可能有点晚。正如the docs 所说:“Tkinter 只保证不会提前调用回调;如果系统繁忙,实际延迟可能会更长。”
那么,如果它在 5.2 秒后触发会发生什么?然后第二个滴答声发生在 之后 5 秒,即 10.2 秒,而不是 10 秒。如果他们都开火有点晚,那加起来,所以到最后,我们可能会落后 20 秒。
更糟糕的是,如果after 恰好在 5.0 秒时触发,但 Label.config 需要 0.2 秒才能运行怎么办?那么我们绝对保证落后 20 秒。 (加上来自after 本身的任何其他错误。)
如果这很重要,您需要跟踪所需的“下一次”,并等到那时,而不是从现在开始的 5 秒。例如:
import datetime as dt
def runAnim():
stepAnim(0, 200, dt.datetime.now() + dt.timedelta(seconds=5):
def stepAnim(width, maxWidth, nextTick):
test_label.config(width=width))
width += 1
if width < maxWidth:
now = dt.datetime.now()
delay = (nextTick - now).total_seconds() * 1000
nextTick += dt.timedelta(seconds=5)
app.after(delay, stepAnim, width, maxWidth, nextTick)