之后使用tkinter制作动画

时间:2018-07-08 00:38:35

标签: python tkinter

背景信息-我正尝试使用TKinter使用以下代码为帧对象创建一些动画: 从tkinter导入框架,Tk,标签,按钮 导入时间

def runAnim():
    for width in range(0, 200):
        app.after(5000, lambda width = width: test_label.config(width=width))

app = Tk()
app.geometry("500x500")
test_label = Frame(bg="#222", width=0)
test_label.pack(side="left", fill="y")
test_button = Button(text="toggle", command=lambda: runAnim() )
test_button.pack(side="right")

问题是,这没有产生预期的行为。我的理解是,这应该每5秒逐渐增加宽度,但是0-200范围似乎在这5秒内完成,而不是每5秒增加1宽度。 任何解决方案将不胜感激!

1 个答案:

答案 0 :(得分:3)

after(5000, …)的意思是现在正被称为after的时间是之后的5秒,而不是tkinter只能通过阅读您的头脑。

因此,您仅创建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相反。区别是在这里,我们无法访问forwhile的魔力,并且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)