【问题标题】:_tkinter.TclError: can't invoke "update" command: application has been destroyed error_tkinter.TclError:无法调用“更新”命令:应用程序已被破坏错误
【发布时间】:2017-09-22 17:43:44
【问题描述】:

在我在程序末尾添加 while 循环之前,我的以下代码运行良好,这基本上是为了让循环永远运行(更新屏幕)直到窗口关闭。

 while 1:
        tk.update_idletasks()
        tk.update()
        time.sleep(0.01)

在将上述代码添加到现有代码时,程序运行,但在退出时...出现此错误:

_tkinter.TclError: can't invoke "update" command: application has been destroyed error

我在 SO 上看到了与此错误类似的问题,但没有针对此特定问题,并且没有任何答案对我的特定情况有帮助。

整个代码如下:

问题/问题:是什么导致了这个错误,我该如何解决?

from tkinter import *
import random
import time
tk=Tk()
tk.title("My 21st Century Pong Game")
tk.resizable(0,0)
tk.wm_attributes("-topmost",1)
canvas=Canvas(tk,bg="white",width=500,height=400,bd=0,highlightthickness=0)
canvas.pack()
tk.update()


class Ball: #create a ball class
    def __init__(self,canvas,color): #initiliased with the variables/attributes self, canvas, and color
        self.canvas=canvas #set the intiial values for the starting attributes
        self.id=canvas.create_oval(30,30,50,50,fill=color) #starting default values for the ball
        """ Note: x and y coordinates for top left corner and x and y coordinates for the bottom right corner, and finally the fill colour for the oval
        """
        self.canvas.move(self.id,0,0) #this moves the oval to the specified location

    def draw(self): #we have created the draw method but it doesn't do anything yet.
        pass 


ball1=Ball(canvas,'green') #here we are creating an object (green ball) of the class Ball

while 1:
    tk.update_idletasks()
    tk.update()
    time.sleep(0.01)

说明更新:

还有一点值得说明:

主循环是程序的中心部分,IDLE 已经有一个主循环 - 但是如果你在 IDLE 之外运行这个程序,画布会出现然后在一瞬间消失。为了阻止窗口关闭,我们需要一个动画循环 - 因此 while 1: ..... 否则,正如用户在下面评论的那样,我们不需要 while 1: 因为 IDLE 已经有了这个(它在 IDLE 中运行良好,无需使用 while 1:..etc)

【问题讨论】:

  • 刚刚修复了代码缩进 - 抱歉
  • 旁注:避免在 tkinter 中使用 sleep()wait()。这些方法不适用于 tkinter。请改用after()

标签: python tkinter invoke


【解决方案1】:

我同意其他人的观点,即您应该在这里使用mainloop(),但是如果您想按照我的方式保留原始代码,那就是跟踪布尔值并改为使用while x == True。这样我们可以将 x 的值更新为等于False,这样可以防止错误发生。

我们可以使用protocol() 方法在应用关闭时更新我们的布尔值。

如果我们将此添加到您的代码中:

x = True

def update_x():
    global x
    x = False

tk.protocol("WM_DELETE_WINDOW", update_x)

并将您的 while 语句更改为:

while x == True:
    tk.update_idletasks()
    tk.update()
    time.sleep(0.01)

所以您的完整代码可能如下所示:

from tkinter import *
import random
import time


tk=Tk()
tk.title("My 21st Century Pong Game")
tk.resizable(0,0)
tk.wm_attributes("-topmost",1)

x = True

def update_x():
    global x
    x = False

tk.protocol("WM_DELETE_WINDOW", update_x)
canvas=Canvas(tk,bg="white",width=500,height=400,bd=0,highlightthickness=0)
canvas.pack()
tk.update()

class Ball:
    def __init__(self,canvas,color):
        self.canvas=canvas
        self.id=canvas.create_oval(30,30,50,50,fill=color)
        """ Note: x and y coordinates for top left corner and x and y coordinates for the bottom right corner, and finally the fill colour for the oval
        """
        self.canvas.move(self.id,0,0)

    def draw(self):
        pass 

ball1=Ball(canvas,'green')

while x == True:
    tk.update_idletasks()
    tk.update()
    time.sleep(0.01)

这将解决您的问题。

重申其他人所说的,您真正需要的是mainloop(),而不是您的while i: 声明。

mainloop() 方法用于重置您的 Tk() 实例的循环 一旦代码到达显示 tk.mainloop() 的行,它将成为您代码的下一个循环。

编写代码的正确方法是使用mainloop(),因为它会为 tkinter 实例进行所有更新。

使用mainloop()查看下面的代码:

from tkinter import *

tk=Tk()
tk.title("My 21st Century Pong Game")
tk.resizable(0,0)
tk.wm_attributes("-topmost",1)

canvas=Canvas(tk,bg="white",width=500,height=400,bd=0,highlightthickness=0)
canvas.pack()
tk.update()

class Ball:
    def __init__(self,canvas,color):
        self.canvas=canvas
        self.id=canvas.create_oval(30,30,50,50,fill=color)
        """ Note: x and y coordinates for top left corner and x and y coordinates for the bottom right corner, and finally the fill colour for the oval
        """
        self.canvas.move(self.id,0,0)

    def draw(self):
        pass 

ball1=Ball(canvas,'green')

tk.mainloop()

【讨论】:

  • 你的解决方案需要四行低效的代码并将其变成九行,而正确的解决方案是用一行代码替换所有代码。
  • @BryanOakley 正如我在回答中所说,最好的选择是使用mainloop(),我已经说得很清楚了。我只是为 OP 提供了一种使用其原始代码的功能性方式,以便他们可以看到他们做错了什么。
【解决方案2】:

您实际上是在尝试实现自己的 mainloop 而不是使用它。

要了解mainloop,您可以阅读here。您可以将其视为您添加的新代码的语法抽象;你的while 循环。当一个已经存在时,您几乎是在尝试通过创建自己的循环来“更新屏幕”来重新创建轮子!

退出程序时,使用sys.exit()可以避免错误。

编辑:

替换以下代码:

while 1:
    tk.update_idletasks()
    tk.update()
    time.sleep(0.01)

有了这个:

tk.mainloop()

您的错误是因为,正如它所说,如果窗口已被破坏,您将无法更新它。 mainloop 应该处理这个问题。

【讨论】:

  • SneakyTurtle - 这完全没有意义。您能否发布我需要使用的代码 - 我想投票,理想情况下! :-) 用 tk.mainloop() 替换“while”是什么意思?请澄清
  • 同意-这没有帮助-除非您可以澄清或发布代码/答案。当然可以退出并避免使用 sys.exit() 但在什么情况下以及如何使用?
  • 谢谢,但这仍然不能回答问题。我知道我的主循环的实现有问题 - 这就是我的问题和问题首先要解决的问题!我需要的是一个解决方案——如果你可以发布一个——将涉及发布可以解决它的代码。正如您所建议的,我需要在我的代码中进行哪些更改才能使其正常工作?在此先感谢 :=)
  • 您是说,完全删除 while 1:,然后将代码保持原样吗?您还缺少一些重要的东西-主循环是程序的中心部分,而 IDLE 已经有一个主循环-所以是的..但是您缺少的是,如果您在 IDLE 之外运行该程序,则画布将出现并且然后在一瞬间消失。为了阻止窗口关闭,我们需要一个动画循环 - 因此 while 1: ..... 我希望能更好地解释上下文
  • @MissComputing 我建议不要使用 GitHub 的“教程”。以这种方式使用time.sleep 是非常不合常规的,应该避免。没有理由不在这里使用mainloop,而且我看不出从 Idle 之外运行这个程序会有什么不同。我自己已经用 Tkinter 重新创建了 Pong,相信我,您不需要 while 1 循环来“更新屏幕”来代替 mainloop
【解决方案3】:

当您退出应用程序时,下次调用update_idletasks 时,窗口对象将被销毁。然后,您尝试在不存在的窗口上调用 update

您需要删除以while 开头的所有四行,并将它们替换为正确处理窗口破坏的单行tk.mainloop()

此外,如果您想保留原始代码,没有理由同时调用update_idletasksupdate。前者是后者的子集。

【讨论】:

    猜你喜欢
    • 2021-06-12
    • 1970-01-01
    • 2013-10-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-03
    • 2011-09-02
    相关资源
    最近更新 更多