python tclerror_解决python - "tkinter.TclError: invalid command name" error after calling root.destroy...

I am in the process of learning tkinter on Python 3.X. I am writing a simple program which will get one or more balls (tkinter ovals) bouncing round a rectangular court (tkinter root window with a canvas and rectangle drawn on it).

I want to be able to terminate the program cleanly by pressing the q key, and have managed to bind the key to the root and fire the callback function when a key is pressed, which then calls root.destroy().

However, I'm still getting errors of the form _tkinter.TclError: invalid command name ".140625086752360" when I do so. This is driving me crazy. What am I doing wrong?

from tkinter import *

import time

import numpy

class Ball:

def bates():

"""

Generator for the sequential index number used in order to

identify the various balls.

"""

k = 0

while True:

yield k

k += 1

index = bates()

def __init__(self, parent, x, y, v=0.0, angle=0.0, accel=0.0, radius=10, border=2):

self.parent = parent # The parent Canvas widget

self.index = next(Ball.index) # Fortunately, I have all my feathers individually numbered, for just such an eventuality

self.x = x # X-coordinate (-1.0 .. 1.0)

self.y = y # Y-coordinate (-1.0 .. 1.0)

self.radius = radius # Radius (0.0 .. 1.0)

self.v = v # Velocity

self.theta = angle # Angle

self.accel = accel # Acceleration per tick

self.border = border # Border thickness (integer)

self.widget = self.parent.canvas.create_oval(

self.px() - self.pr(), self.py() - self.pr(),

self.px() + self.pr(), self.py() + self.pr(),

fill = "red", width=self.border, outline="black")

def __repr__(self):

return "[{}] x={:.4f} y={:.4f} v={:.4f} a={:.4f} r={:.4f} t={}, px={} py={} pr={}".format(

self.index, self.x, self.y, self.v, self.theta,

self.radius, self.border, self.px(), self.py(), self.pr())

def pr(self):

"""

Converts a radius from the range 0.0 .. 1.0 to window coordinates

based on the width and height of the window

"""

assert self.radius > 0.0 and self.radius <= 1.0

return int(min(self.parent.height, self.parent.width)*self.radius/2.0)

def px(self):

"""

Converts an X-coordinate in the range -1.0 .. +1.0 to a position

within the window based on its width

"""

assert self.x >= -1.0 and self.x <= 1.0

return int((1.0 + self.x) * self.parent.width / 2.0 + self.parent.border)

def py(self):

"""

Converts a Y-coordinate in the range -1.0 .. +1.0 to a position

within the window based on its height

"""

assert self.y >= -1.0 and self.y <= 1.0

return int((1.0 - self.y) * self.parent.height / 2.0 + self.parent.border)

def Move(self, x, y):

"""

Moves ball to absolute position (x, y) where x and y are both -1.0 .. 1.0

"""

oldx = self.px()

oldy = self.py()

self.x = x

self.y = y

deltax = self.px() - oldx

deltay = self.py() - oldy

if oldx != 0 or oldy != 0:

self.parent.canvas.move(self.widget, deltax, deltay)

def HandleWallCollision(self):

"""

Detects if a ball collides with the wall of the rectangular

Court.

"""

pass

class Court:

"""

A 2D rectangular enclosure containing a centred, rectagular

grid of balls (instances of the Ball class).

"""

def __init__(self,

width=1000, # Width of the canvas in pixels

height=750, # Height of the canvas in pixels

border=5, # Width of the border around the canvas in pixels

rows=1, # Number of rows of balls

cols=1, # Number of columns of balls

radius=0.05, # Ball radius

ballborder=1, # Width of the border around the balls in pixels

cycles=1000, # Number of animation cycles

tick=0.01): # Animation tick length (sec)

self.root = Tk()

self.height = height

self.width = width

self.border = border

self.cycles = cycles

self.tick = tick

self.canvas = Canvas(self.root, width=width+2*border, height=height+2*border)

self.rectangle = self.canvas.create_rectangle(border, border, width+border, height+border, outline="black", fill="white", width=border)

self.root.bind('', self.key)

self.CreateGrid(rows, cols, radius, ballborder)

self.canvas.pack()

self.afterid = self.root.after(0, self.Animate)

self.root.mainloop()

def __repr__(self):

s = "width={} height={} border={} balls={}\n".format(self.width,

self.height,

self.border,

len(self.balls))

for b in self.balls:

s += "> {}\n".format(b)

return s

def key(self, event):

print("Got key '{}'".format(event.char))

if event.char == 'q':

print("Bye!")

self.root.after_cancel(self.afterid)

self.root.destroy()

def CreateGrid(self, rows, cols, radius, border):

"""

Creates a rectangular rows x cols grid of balls of

the specified radius and border thickness

"""

self.balls = []

for r in range(1, rows+1):

y = 1.0-2.0*r/(rows+1)

for c in range(1, cols+1):

x = 2.0*c/(cols+1) - 1.0

self.balls.append(Ball(self, x, y, 0.001,

numpy.pi/6.0, 0.0, radius, border))

def Animate(self):

"""

Animates the movement of the various balls

"""

for c in range(self.cycles):

for b in self.balls:

b.v += b.accel

b.Move(b.x + b.v * numpy.cos(b.theta),

b.y + b.v * numpy.sin(b.theta))

self.canvas.update()

time.sleep(self.tick)

self.root.destroy()

I've included the full listing for completeness, but I'm fairly sure that the problem lies in the Court class. I presume it's some sort of callback or similar firing but I seem to be beating my head against a wall trying to fix it.

python

tkinter

tkinter-canvas

edited Oct 22 '15 at 8:51

Jonah Fleming 771 1 5 20 asked Oct 21 '15 at 12:06

TimGJ 365 3 15 1   It's not exactly the same. The answer suggested as a duplicate leaves after events running. This is slightly different in that the after events are closed but we have effectively two mainloops as the

Animate function is miswritten to be a mainloop equivalent. The correct fix here is to change the Animate function to call itself using

after after moving the balls. You should never be using

time.sleep in a Tkinter program. That's what after is for. –

patthoyts Oct 21 '15 at 12:20      Have you tried with 'try' statement to see if it works properly? (Debugging purposes only) –

Mark Zampedroni Oct 21 '15 at 12:20

|

1 Answers

1

解决方法

You have effectively got two mainloops. In your Court.__init__ method you use after to start the Animate method and then start the Tk mainloop which will process events until you destroy the main Tk window.

However the Animate method basically replicates this mainloop by calling update to process events then time.sleep to waste some time and repeating this. When you handle the keypress and terminate your window, the Animate method is still running and attempts to update the canvas which no longer exists.

The correct way to handle this is to rewrite the Animate method to perform a single round of moving the balls and then schedule another call of Animate using after and provide the necessary delay as the after parameter. This way the event system will call your animation function at the correct intervals while still processing all other window system events promptly.

answered Oct 21 '15 at 12:30

patthoyts 17.1k 2 29 57

|

你可能感兴趣的:(python,tclerror)