Tkinter 循环永远不会干净地退出

Tkinter Loop Never Exits Cleanly

Python 2.7

我已经为我的 Tkinter GUI 编写了一个 run 方法,而不是使用标准 mainloop,并且当我关闭 window 时它总是因错误而退出,即使在实施之后SO 上其他地方建议的 WM_DELETE_WINDOW 协议。我尝试在协议回调中调用 exit 并从循环中调用 return,但 Python 总是最后一次通过循环。这是为什么?

class FrameApp(object):
    def __init__(self):
        ...
        self.rootWin.protocol("WM_DELETE_WINDOW", self.callback_destroy)
        self.winRunning = False

    def callback_destroy(self):
        self.winRunning = False
        self.rootWin.destroy() # go away, window
        exit() # GET OUT

这是 运行 循环:

    def run(self):
        last = -infty
        self.winRunning = True
        ...
        while self.winRunning:
            # 4.a. Calc geometry
            self.calcFunc( self.get_sliders_as_list() )
            # 4.b. Send new coords to segments
            self.simFrame.transform_contents()
            # 4.d. Wait remainder of 40ms
            elapsed = time.time() * 1000 - last
            if elapsed < 40:
                time.sleep( (40 - elapsed) / 1000.0 )
            # 4.e. Mark beginning of next loop
            last = time.time() * 1000
            # 4.f. Update window
            if not self.winRunning: # This does not solve the problem 
                return # still tries to call 'update', 
                #        and never exits cleanly
            self.canvas.update() 
            # don't know how to prevent these from being called 
            # again after the window is destroyed
            self.rootWin.update_idletasks()

结果:

File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 972, in update_idletasks self.tk.call('update', 'idletasks') _tkinter.TclError: can't invoke "update" command: application has been destroyed

没有主循环,tkinter 无法获得 WM_DELETE_WINDOW 消息来调用您的退出函数。 (或者更确切地说,它只能在 update_idletasks 调用的~毫秒期间捕获任何内容,因为它不会排队,因为 tkinter 没有事件循环(因此没有队列),因为你从未开始过。)如果无法与 Window 管理器(系统)通信,则无法捕获信号,如果不循环,则无法通信。

要解决它,只需使用event/main循环。让你的 run 函数保存它需要的任何状态,并在你希望的任何时间间隔调用自己 after


另一方面,不要将 time.sleep 与 tkinter 一起使用 - 它会阻止它做任何事情(而且剩余的 40 毫秒睡眠时间可能比循环的其余部分长,所以你会41 毫秒的等待时间和 0.5 毫秒的可点击性)。相反,只需仔细配置您的 root.after 语句(您也可以计算其中的内容)