背景信息-我正尝试使用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宽度。 任何解决方案将不胜感激!
答案 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
相反。区别是在这里,我们无法访问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)