0

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)

Compoot
  • 2,227
  • 6
  • 31
  • 63

3 Answers3

3

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()
Mike - SMT
  • 14,784
  • 4
  • 35
  • 79
  • Your solution takes four lines of inefficient code and turns it into nine, when the correct solution is to replace all that with one line of code. – Bryan Oakley Sep 23 '17 at 13:33
  • 2
    @BryanOakley as I said in my answer the best option is to use `mainloop()` I made that clear. I was only providing a functional way for the OP to use their original code so they could see what it is they were doing wrong. – Mike - SMT Sep 23 '17 at 14:53
0

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.

SneakyTurtle
  • 1,831
  • 1
  • 13
  • 23
  • 1
    SneakyTurtle - this makes absolutely no sense. Could you please post the code that I would need to use - I'd like to upvote, ideally! :-) What do you mean replace "while" with tk.mainloop()? Please clarify – Compoot Sep 22 '17 at 17:57
  • agreed -this does not help - unless you can clarify or post code/an answer. Of course one can exit and avoid using sys.exit() but in what context and how? –  Sep 22 '17 at 18:00
  • @MissComputing @pythoncarrot, I have edited my main post to try and explain what the `mainloop` is. – SneakyTurtle Sep 22 '17 at 18:02
  • 1
    Thank you but that still doesn't answer the question. I know there is something wrong with the implementation of my main loop - that's what my question and problem is about in the first place! What I need is a solution- if you can post one - would involve posting the code that would solve it. What do I need to change in my code to make it work correctly, as you've suggested? Thanks in advance :=) – Compoot Sep 22 '17 at 18:04
  • 1
    Are you saying, remove the while 1: altogether, and leave the code in as-is after that? You are also missing something important - the main loop is the central part of a program and IDLE already has a main loop - so yes..but what you are missing is that 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: ..... I hope that better explains the context – Compoot Sep 22 '17 at 18:05
  • @MissComputing What you are saying about running your program outside of Idle is unclear. Idle is an IDE, do you mean you are running your program from console? See my edit. – SneakyTurtle Sep 22 '17 at 18:14
  • Yes, that's it. If you try to run this program OUTSIDE of IDLE the canvas will appear for a split second and then disappear.It is for this reason (to stop the window from closing when opened outside of IDLE) that we need to add an animation loop (basically re-creating, as you said, a main loop) – Compoot Sep 22 '17 at 18:16
  • If it helps, you'll note that various tutorials make use of this: see this for example: https://gist.github.com/feelinc/5379013 ...so it isn't just a figment of our imagination. It is needed for the reasons given above – Compoot Sep 22 '17 at 18:17
  • 2
    @MissComputing I would recommend not using that GitHub 'tutorial'. Using `time.sleep` in this manner is very unpythonic, and should be avoided. There is no reason not to use the `mainloop` here whatsoever, and I fail to see how running this program from outside of Idle will have any difference whatsoever. I myself have recreated Pong with Tkinter, and trust me in that you do not need a `while 1` loop to 'update the screen` in place of the `mainloop`. – SneakyTurtle Sep 22 '17 at 18:21
0

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.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685