JavaEar 专注于收集分享传播有价值的技术资料

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

I have the following code that worked fine until I added the while loop at the end of the program, which basically seeks to keep the loop running forever (updating the screen) until the window is closed.

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

On adding the above code to the existing code, the program runs, but on exit ...comes up with this error:

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

I have seen a similar question with this error on SO, but none for this specific problem and none of the answers could help for my specific case.

The whole code is below:

Question/Problem: What is causing this error and how can I fix it?

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)

Explanation update:

It is also worth explaining that:

the main loop is the central part of a program and IDLE already has a main loop - BUT if you run this program OUTSIDE of IDLE, the canvas will appear and then disappear after a split second. To stop the window from closing we are in need of an animation loop - hence the while 1: ..... otherwise, as a user commented below, we don't need the while 1: as IDLE already has this in place (it works fine in IDLE without the use of while 1:..etc)

3个回答

    最佳答案
  1. You are effectively trying to implement your own mainloop instead of using it.

    To understand the mainloop, you can read about it here. You can think of it as a syntactical abstraction of the new code you added; your while loop. You are almost trying to recreate the wheel by making your own loop to "update the screen", when one already exists!

    When exiting the program, you could avoid errors by using sys.exit().

    Edit:

    Replace the following code:

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

    With this:

    tk.mainloop()
    

    Your error is because, as it says, you cannot update the window if it has been destroyed. The mainloop should handle this.

  2. 参考答案2
  3. When you exit your app, the next time you call update_idletasks the window object is destroyed. You then try to call update on a window that doesn't exist.

    You need to remove all four lines starting with while and replace them with the single line tk.mainloop() which properly handles the destruction of the window.

    Also, in case you are tempted to keep your original code, there is no reason to call both update_idletasks and update. The former is a subset of the latter.

  4. 参考答案3
  5. I agree with the others that you should be using mainloop() here however if you would like to keep the original code the way I would do this is keep track of a boolean and do while x == True instead. This way we can update the value of x to equal False and this should keep the error from happening.

    We can use the protocol() method to update our boolean when the app closes.

    If we add this to your code:

    x = True
    
    def update_x():
        global x
        x = False
    
    tk.protocol("WM_DELETE_WINDOW", update_x)
    

    And change your while statement to:

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

    So your full code might look like this:

    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)
    

    This will fix your problem.

    To reiterate what others have said all you really need is the mainloop() here and not your while i: statement.

    The mainloop() method is used to be the reset on the loop for your instance of Tk() Once the code reaches the line that says tk.mainloop() then it will being the next loop of your code.

    The proper way to write your code is to just use mainloop() as it does all the updating for a tkinter instance.

    See below code using 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()