【发布时间】:2021-10-02 19:57:08
【问题描述】:
TL;DR: 我在 Tkinter 中有一个 after 循环,我想无限期地运行它,但如果我想用新的起始输入重新启动循环,我希望能够杀死它.由于循环的每次迭代都会再次调用after 函数,我无法弄清楚如何在after_cancel 函数之后获得正确的ID,以在启动新循环时终止旧循环。
更详细的帖子: 所以我试图通过 Tkinter 生成一个(点阵)滚动文本栏,用于虚拟出发板。为此,我需要一个给定的文本位来无限期地滚动 GUI 的相关部分,直到准备好更新的文本位来替换它。我已经很好地管理了无限期滚动部分;但是我在停止旧卷轴以供新卷轴取而代之时遇到问题。
我觉得我的问题是,一旦其中一个循环开始,我就不能轻易地与之“交流”;我的新循环也不能“告诉”我的旧循环停止。在尝试解决这个问题时,我尝试在 GUI 对象中设置几个切换属性,但这些尝试都失败了(并不是说这种方式不可能;只是我无法管理它)。进一步寻找解决方案让我觉得 Tkinter 函数 after_cancel 是可能的解决方案,但我无法实现这一点,因为我循环文本的方式涉及再次使用 Tkinter 的 after 函数(因此after ID 在循环中不断变化)。我无法解决这个问题,老实说,我不确定是我没有完全获得 after 和/或 after_cancel 函数,还是更多的是我正在循环我的函数糟糕的方式(即我是否不应该使用after 函数来实现这一点)。
任何帮助/建议将不胜感激;如果需要,我很乐意提供任何其他信息。
以下是我的最小可重现示例代码。我很快把它放在一起,它很像我的主要代码的一个更简单的版本(这里我移动的是一个正方形而不是点阵显示器上的文本);这以与我的真实代码相同的方式循环函数,并且其中存在相同的问题。
import tkinter as tk
class App(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.background = "black"
self.width = 600
self.height = 150
self.canvas = tk.Canvas(self, width=self.width, height=self.height, borderwidth=0, \
highlightthickness=0, background = self.background)
self.canvas.pack(side="top", fill="both", expand="true")
self.run_id_1 = self.after( 100, lambda: self.moving_square( "red", 50, 10 ) )
self.run_id_2 = self.after( 5000, lambda: self.moving_square( "green", 200, 90, \
prev_run_id = self.run_id_1 ) )
def moving_square( self, color, x_top_left, y_top_left, last_square = None, prev_run_id = None ):
if not prev_run_id == None:
self.after_cancel( prev_run_id )
if not last_square == None:
self.canvas.delete( last_square )
last_square = self.canvas.create_rectangle(x_top_left, y_top_left, x_top_left + 50, \
y_top_left + 50, outline=color, fill=color)
if x_top_left + 50 < 0:
self.after( 500, lambda: self.moving_square( color, self.width, y_top_left, last_square ) )
else:
self.after( 500, lambda: self.moving_square( color, x_top_left - 10, y_top_left, last_square ) )
if __name__ == "__main__":
app = App( )
app.mainloop()
【问题讨论】:
-
FWIW,您不需要使用
lambda和 after。你可以self.after(500, self.moving_square, color, self.width, y_top_left, last_square)。没有lambda,它会变得更容易阅读。 -
每次调用
after()时,将其返回的ID值分配给self.run_id_1或self.run_id_2,以便在需要时调用after_cancel()。 -
@BryanOakley 感谢您的帮助 :) 在尝试避免 lambda 时,我唯一的问题是有两个可选参数。如果我想说只提供其中的第二个,我可以使用 lambda 输入类似“prev_run_id = “SPAM”的内容并忽略前面的可选参数。但是,当转向非 lambda 方式时,我不能这样做这个;有什么办法可以避免这个问题?
标签: python loops tkinter infinite-loop cancellation