循环后无法取消无限 Tkinter(嵌套)
Unable to cancel infinite Tkinter (nested) after loop
TL;DR: 我在 Tkinter 中有一个 after
循环,我想无限期地 运行,但希望能够杀死如果我想用新的起始输入重新启动循环。随着循环的每次迭代再次调用 after
函数,我无法弄清楚如何在 after_cancel
函数中获得正确的后 ID 以在启动新循环时终止旧循环。
更详细post:
所以我试图通过 Tkinter 生成一个(点阵)滚动文本栏,用于虚拟出发板。为此,我需要一段给定的文本来无限期地滚动 GUI 的相关部分,直到更新的文本准备好替换它。我已经很好地管理了无限期滚动的部分;但是我在停止旧卷轴准备好让新卷轴取代它时遇到问题。
我觉得我的问题是,一旦其中一个循环开始运行,我就无法轻易 'communicate' 使用它;我的新循环 'tell' 我的旧循环也无法停止。为了解决这个问题,我尝试在 GUI 对象中设置几个切换属性,但这些尝试都失败了(并不是说这不可能;只是我无法管理它)。进一步寻找解决方案让我觉得 Tkinter 函数 after_cancel
是可能的解决方案,但我一直无法实现它,因为我循环文本的方式涉及使用 after
函数再次 Tkinter(因此 after ID 在循环中不断变化)。我无法解决这个问题,老实说,我不确定是我没有完全掌握 after
and/or after_cancel
功能,还是我更多m 以糟糕的方式循环我的函数(即我是否不应该使用 after
函数来实现此目的)。
任何 help/advice 将不胜感激;如果需要任何其他信息,我非常高兴。
下面是我的最小可重现示例代码。我很快把它放在一起,它非常像我的主要代码的一个更简单的版本(这里我移动的是一个正方形而不是点阵显示器上的文本);这以与我的真实代码相同的方式循环函数,并且在其中存在相同的问题。
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()
每当您再次调用该函数时,您都需要更新该 ID,以便——正如您所怀疑的那样——您有一个可以传递给 after_cancel
的引用。您已经将该引用存储为 self.run_id_1
,但您没有更新一个。
最简单的方法是添加检查 if color == 'red'
,然后设置 self.run_id_1 = self.after(...)
,'green'
和 self.run_id_2
.
也是如此
不过,我要注意你的moving_square
函数的结构并不理想。您可能发现很难找到要更改的内容的原因之一是它已经过度参数化了。您应该将它的工作稍微分解一下,分成更具体的各种功能。或者您可以创建一个 table 数据来存储滚动行的相关状态和 ID,这样您就可以简化逻辑。
TL;DR: 我在 Tkinter 中有一个 after
循环,我想无限期地 运行,但希望能够杀死如果我想用新的起始输入重新启动循环。随着循环的每次迭代再次调用 after
函数,我无法弄清楚如何在 after_cancel
函数中获得正确的后 ID 以在启动新循环时终止旧循环。
更详细post: 所以我试图通过 Tkinter 生成一个(点阵)滚动文本栏,用于虚拟出发板。为此,我需要一段给定的文本来无限期地滚动 GUI 的相关部分,直到更新的文本准备好替换它。我已经很好地管理了无限期滚动的部分;但是我在停止旧卷轴准备好让新卷轴取代它时遇到问题。
我觉得我的问题是,一旦其中一个循环开始运行,我就无法轻易 'communicate' 使用它;我的新循环 'tell' 我的旧循环也无法停止。为了解决这个问题,我尝试在 GUI 对象中设置几个切换属性,但这些尝试都失败了(并不是说这不可能;只是我无法管理它)。进一步寻找解决方案让我觉得 Tkinter 函数 after_cancel
是可能的解决方案,但我一直无法实现它,因为我循环文本的方式涉及使用 after
函数再次 Tkinter(因此 after ID 在循环中不断变化)。我无法解决这个问题,老实说,我不确定是我没有完全掌握 after
and/or after_cancel
功能,还是我更多m 以糟糕的方式循环我的函数(即我是否不应该使用 after
函数来实现此目的)。
任何 help/advice 将不胜感激;如果需要任何其他信息,我非常高兴。
下面是我的最小可重现示例代码。我很快把它放在一起,它非常像我的主要代码的一个更简单的版本(这里我移动的是一个正方形而不是点阵显示器上的文本);这以与我的真实代码相同的方式循环函数,并且在其中存在相同的问题。
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()
每当您再次调用该函数时,您都需要更新该 ID,以便——正如您所怀疑的那样——您有一个可以传递给 after_cancel
的引用。您已经将该引用存储为 self.run_id_1
,但您没有更新一个。
最简单的方法是添加检查 if color == 'red'
,然后设置 self.run_id_1 = self.after(...)
,'green'
和 self.run_id_2
.
不过,我要注意你的moving_square
函数的结构并不理想。您可能发现很难找到要更改的内容的原因之一是它已经过度参数化了。您应该将它的工作稍微分解一下,分成更具体的各种功能。或者您可以创建一个 table 数据来存储滚动行的相关状态和 ID,这样您就可以简化逻辑。