13

When closing the python 3 program, I get a strange exception in the console.

The Python 3 code:

from tkinter import *
from random import randint

# Return a random color string in the form of #RRGGBB
def getRandomColor():
    color = "#"
    for j in range(6):
        color += toHexChar(randint(0, 15)) # Add a random digit
    return color

# Convert an integer to a single hex digit in a character
def toHexChar(hexValue):
    if 0 <= hexValue <= 9:
        return chr(hexValue + ord('0'))
    else: # 10 <= hexValue <= 15
        return chr(hexValue - 10 + ord('A'))

# Define a Ball class
class Ball:
    def __init__(self):
        self.x = 0 # Starting center position
        self.y = 0
        self.dx = 2 # Move right by default
        self.dy  = 2 # Move down by default
        self.radius = 3
        self.color = getRandomColor()

class BounceBalls:
    def __init__(self):
        self.ballList = [] # Create a list for balls

        window = Tk()
        window.title("Bouncing Balls")

        ### Create Canvas ###
        self.width = 350
        self.height = 150
        self.canvas = Canvas(window, bg = "white", width = self.width, height = self.height)
        self.canvas.pack()


        ### Create Buttons ###
        frame = Frame(window)
        frame.pack()

        btStop = Button(frame, text = "Stop", command = self.stop)
        btStop.pack(side = LEFT)

        btResume = Button(frame, text = "Resume", command = self.resume)
        btResume.pack(side = LEFT)

        btAdd = Button(frame, text = "Add", command = self.add)
        btAdd.pack(side = LEFT)

        btRemove = Button(frame, text = "Remove", command = self.remove)
        btRemove.pack(side = LEFT)

        self.sleepTime = 20
        self.isStopped = False
        self.animate()

        window.mainloop()

    def stop(self): # Stop animation
        self.isStopped = True

    def resume(self):
        self.isStopped = False
        self.animate()

    def add(self): # Add a new ball
        self.ballList.append(Ball())

    def remove(self):
        self.ballList.pop()

    def animate(self):
        while not self.isStopped:
            self.canvas.after(self.sleepTime)
            self.canvas.update()
            self.canvas.delete("ball")

            for ball in self.ballList:
                self.redisplayBall(ball)

    def redisplayBall(self, ball):
        if ball.x > self.width or ball.x < 0:
            ball.dx = -ball.dx

        if ball.y > self.height or ball.y < 0:
            ball.dy = -ball.dy

        ball.x += ball.dx
        ball.y += ball.dy
        self.canvas.create_oval(ball.x - ball.radius, ball.y - ball.radius, \
                                ball.x + ball.radius, ball.y + ball.radius, \
                                fill = ball.color, tags = "ball")

BounceBalls()

Here's the full Traceback:

/Library/Frameworks/Python.framework/Versions/3.3/bin/python3 "/Users/narek_a/Dropbox/Python/PycharmProjects/Introduction to Programming/Chapter 10.py"
Traceback (most recent call last):
  File "/Users/narek_a/Dropbox/Python/PycharmProjects/Introduction to Programming/Chapter 10.py", line 446, in <module>
    BounceBalls()
  File "/Users/narek_a/Dropbox/Python/PycharmProjects/Introduction to Programming/Chapter 10.py", line 407, in __init__
    self.animate()
  File "/Users/narek_a/Dropbox/Python/PycharmProjects/Introduction to Programming/Chapter 10.py", line 428, in animate
    self.canvas.delete("ball")
  File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/tkinter/__init__.py", line 2344, in delete
    self.tk.call((self._w, 'delete') + args)
_tkinter.TclError: invalid command name ".4302957584"

Process finished with exit code 1

Why are these exceptions caused?

Eric Leschinski
  • 146,994
  • 96
  • 417
  • 335
narzero
  • 2,199
  • 5
  • 40
  • 73
  • Did you get this code from that book? This is the wrong way to do animation in Tkinter, and is contributing to the problem. You might want to search this website for how to animate in Tkinter. – Bryan Oakley Apr 17 '13 at 13:05
  • Yes I did. The book shows the example code and you just have to type it over. – narzero Apr 17 '13 at 13:16
  • that's a pity. It's a bad example. :-\ – Bryan Oakley Apr 17 '13 at 13:43
  • possible duplicate of [Get tkinter.tclerror when closing python turtle before it finished it's draw](http://stackoverflow.com/questions/15588583/get-tkinter-tclerror-when-closing-python-turtle-before-it-finished-its-draw) – CassOnMars Apr 18 '13 at 14:24
  • It is because tkinter window closed but other processes related to it e.g. `canvas.delete('ball')` is still running. To avoid this, put **try** and **except** when calling `animate()` function. To avoid the error, `import sys` and do this whenever `animate()` is called: `try: self.animate()` `except: sys.exit(1)` – Prashantzz Jan 26 '22 at 07:15

2 Answers2

12

When you exit the program, the windows are destroyed. This destruction happens after the event loop notices the application has exited. The way you have your code structured, this happens when you call self.update(). Immediately after that call, and after the widgets have been destroyed, you are calling self.canvas.delete("ball"). Since the widget was destroyed in the previous statement, you get the error.

The reason the error is so cryptic is that Tkinter is just a wrapper around a tcl/tk interpreter. Tk widgets are named with a dot followed by some characters, and widget names are also tcl commands. When you call the delete method of the canvas, Tkinter translates the canvas reference to the internal tk widget name / tcl command. Because the widget has been destroyed, tk doesn't recognize that as a known command and throws an error.

The solution is going to require you to redo your animation logic. You should not have your own animation loop. Instead, you need to use the Tkinter eventloop (mainloop()) as your animation loop. There are a few examples on this site to show you how (for example: https://stackoverflow.com/a/11505034/7432)

Community
  • 1
  • 1
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
5

Main application loop window.mainloop will never be executed without pressing stop button. It is source of your problem. Rewrite animation loop:

def __init__(self):
    ...
    self.sleepTime = 20
    self.isStopped = False
    self.window = window
    self.window.after(self.sleepTime, self.animate)
    window.mainloop()
    ...

def animate(self):
    if not self.isStopped:
        self.canvas.update()
        self.canvas.delete("ball")

        for ball in self.ballList:
            self.redisplayBall(ball)
        self.window.after(self.sleepTime, self.animate)
kalgasnik
  • 3,149
  • 21
  • 19