【问题标题】:Unable to cancel infinite Tkinter (nested) after loop循环后无法取消无限 Tkinter(嵌套)
【发布时间】: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_1self.run_id_2,以便在需要时调用after_cancel()
  • @BryanOakley 感谢您的帮助 :) 在尝试避免 lambda 时,我唯一的问题是有两个可选参数。如果我想说只提供其中的第二个,我可以使用 lambda 输入类似“prev_run_id = “SPAM”的内容并忽略前面的可选参数。但是,当转向非 lambda 方式时,我不能这样做这个;有什么办法可以避免这个问题?

标签: python loops tkinter infinite-loop cancellation


【解决方案1】:

每当您再次调用该函数时,您都需要更新该 ID,以便 - 正如您所怀疑的那样 - 您有一个可以传递给 after_cancel 的引用。您已经将该引用存储为 self.run_id_1,但您没有更新。

最简单的方法是添加一个检查if color == 'red',然后设置self.run_id_1 = self.after(...)'green'self.run_id_2 也是如此。

但是,我应该注意您的moving_square 函数的结构并不理想。您可能发现很难找到要更改的内容的一个原因是它已经过度参数化了。您应该将其工作稍微划分,分成更具体的各种功能。或者,您可以创建一个数据表来存储滚动行的相关状态和 ID,以便简化逻辑。

【讨论】:

  • 感谢您的帮助 :) 我认为这可能是问题所在,但我想不出在 if 语句中将什么作为变量/参数放入以便更新正确的 ID。根据您的回复,我意识到我可以将这些存储在对象属性中的字典中;然后我可以解析一个字符串,该字符串指向它需要更新/使用的 ID。但是,您提到使用数据表?我不确定你在这里指的是什么?我也不确定你所说的过度参数化是什么意思?你的意思是我必须有很多函数参数吗?
  • @Steve 是的,是的。我所说的表格是指一个字典,而过度参数化是指太多的参数。通常,当您发现自己使用“标志”参数时,例如color,其中给定的字符串决定了行为,这表明您应该拆分函数或采用面向对象的多态方法。 (否则你会看到使用两种以上的颜色会变得非常复杂。)
  • 这真是太棒了;非常感谢您帮助解决我的问题(使用 dict 至少包含后面的 ID),也感谢您指出我将变量解析为函数的方式可以(并且应该) 更加精简(我肯定会在这方面进行相关更改)。谢谢你:)
猜你喜欢
  • 1970-01-01
  • 2018-07-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多