【问题标题】:Using tkinter after to produce an animation之后使用 tkinter 制作动画
【发布时间】:2018-12-16 02:26:07
【问题描述】:

背景信息 - 我正在尝试使用 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 的宽度。 任何解决方案将不胜感激!

【问题讨论】:

    标签: python tkinter


    【解决方案1】:

    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 当您反转事物时相反。不同之处在于,我们无法访问魔法forwhilewhile 没有那么神奇,因此更容易反转。)

    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)
    

    【讨论】:

    • 代替`app.after(5000, lambda: stepAnim(iterwidth)`,您可以通过这种方式避免使用lambdaapp.after(5000, stepAnim, iterwidth)
    • @BryanOakley 对,我忘了。自 Python 2.2 左右以来它是新的,所以我花了一段时间才习惯它。 :)
    猜你喜欢
    • 2021-07-26
    • 1970-01-01
    • 2020-09-02
    • 1970-01-01
    • 1970-01-01
    • 2015-04-16
    • 1970-01-01
    • 1970-01-01
    • 2017-12-29
    相关资源
    最近更新 更多