我正在学习tkinter,我正在编写一个简单的Python3程序,它生成一个根窗口并添加一个矩形和多个椭圆。使用在after()
函数中注册的回调对椭圆进行动画处理。我也绑定了键盘,当按下q
键时,根窗口被破坏。如果我从命令行运行该程序,它工作正常。如果我从一个iPython(spyder)控制台运行它,它第一次运行正常,但在第二次尝试时它会产生一个错误,这似乎是某种残余的回调触发,当它不应该。如果我关闭我正在使用的控制台并打开另一个,程序在第一次调用时再次运行OK,但此后给了我完全相同的错误。
我怀疑在第一次调用程序结束后我自己没有清理过。我假设调用after_cancel()
后跟root_destroy()
会清除所有内容。
在调用key
之前,我已尝试单独删除各种椭圆作为root.destroy()
回调的一部分,但这没有效果。
Python 3.4.3 (default, Jul 28 2015, 18:20:59)
Type "copyright", "credits" or "license" for more information.
IPython 1.2.1 -- An enhanced Interactive Python.
? -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help -> Python's own help system.
object? -> Details about 'object', use 'object??' for extra details.
%guiref -> A brief reference about the graphical user interface.
In [1]: runfile('/home/tim/metatron/Scratch/gr3.py', wdir='/home/tim/metatron/Scratch')
Got key 'q'
Bye!
In [2]: runfile('/home/tim/metatron/Scratch/gr3.py', wdir='/home/tim/metatron/Scratch')
Got key 'q'
Bye!
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib/python3.4/tkinter/__init__.py", line 1536, in __call__
return self.func(*args)
File "/usr/lib/python3.4/tkinter/__init__.py", line 585, in callit
func(*args)
File "/home/tim/metatron/Scratch/gr3.py", line 165, in Animate
b.Move(b.position + b.velocity)
File "/home/tim/metatron/Scratch/gr3.py", line 77, in Move
self.parent.canvas.move(self.widget, deltax, deltay)
File "/usr/lib/python3.4/tkinter/__init__.py", line 2432, in move
self.tk.call((self._w, 'move') + args)
_tkinter.TclError: invalid command name ".139666802660408"
In [3]:
任何想法我做错了什么? (我已经包含了完整性的完整来源)。
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, speed=0.0, angle=0.0, accel=0.0, radius=0.0, 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.radius = radius # Radius
self.border = border # Border thickness (integer)
self.position = numpy.complex(x, y)
self.velocity = numpy.complex(speed*numpy.cos(angle),
speed*numpy.sin(angle))
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} vx={:.4f} vy={:.4f} r={:.4f} t={}, px={} py={} pr={}".format(
self.index, self.position.real, self.position.imag,
self.velocity.real, self.velocity.imag,
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
"""
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
"""
return int((1.0 + self.position.real) * 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
"""
return int((1.0 - self.position.imag) * self.parent.height / 2.0 + self.parent.border)
def Move(self, z):
"""
Moves ball to absolute position z where z.real and z.imag are both -1.0 .. 1.0
"""
oldx = self.px()
oldy = self.py()
self.position = z
deltax = self.px() - oldx
deltay = self.py() - oldy
if oldx != 0 or oldy != 0:
self.parent.canvas.move(self.widget, deltax, deltay)
self.HandleWallCollision()
def HandleWallCollision(self):
"""
Detects if a ball collides with the walls of the rectangular
Court.
"""
# Check impact with top and invert vy if it occurs
if self.py() < self.pr() + self.parent.border:
self.velocity = numpy.complex(self.velocity.real, -self.velocity.imag)
# Check bottom impact
if self.py() + self.pr() > self.parent.height + self.parent.border:
self.velocity = numpy.complex(self.velocity.real, -self.velocity.imag)
# Left impact
if self.px() - self.pr() < self.parent.border:
self.velocity = numpy.complex(-self.velocity.real, self.velocity.imag)
# Right impact
if self.px() + self.pr() > self.parent.width + self.parent.border:
self.velocity = numpy.complex(-self.velocity.real, self.velocity.imag)
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=10, # Number of rows of balls
cols=10, # 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): # Animation tick length (mS)
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('<Key>', 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':
self.root.after_cancel(self.afterid)
self.root.destroy()
print("Bye!")
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,
numpy.random.random()*0.005,
numpy.random.random()*numpy.pi*2,
0.0, radius, border))
def Animate(self):
"""
Animates the movement of the various balls
"""
for b in self.balls:
b.Move(b.position + b.velocity)
self.canvas.update()
self.afterid=self.root.after(self.tick, self.Animate)