问题描述
限时送ChatGPT账号..我正在 Python 3.X 上学习 tkinter.我正在编写一个简单的程序,它可以让一个或多个球(tkinter 椭圆形)在矩形球场(tkinter 根窗口,上面画有画布和矩形)周围弹跳.
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).
我希望能够通过按 q
键干净地终止程序,并设法将键绑定到根并在按下键时触发回调函数,然后调用root.destroy()
.
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()
.
但是,当我这样做时,我仍然收到 _tkinter.TclError: invalid command name ".140625086752360"
形式的错误.这真让我抓狂.我做错了什么?
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('<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':
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()
为了完整性,我已经包含了完整列表,但我相当确定问题出在 Court
类中.我认为这是某种回调或类似的触发,但我似乎在试图修复它.
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.
推荐答案
您已经有效地获得了两个主循环.在您的 Court.__init__
方法中,您使用 after
启动 Animate
方法,然后启动将处理事件的 Tk 主循环,直到您销毁主Tk 窗口.
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.
然而,Animate
方法基本上通过调用 update
来处理事件然后 time.sleep
浪费一些时间并重复这个来复制这个主循环.当您处理按键并终止窗口时,Animate 方法仍在运行并尝试更新不再存在的画布.
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.
处理这个问题的正确方法是重写Animate
方法来执行一轮移动球,然后使用after 安排另一个
并提供必要的延迟作为 Animate
调用after
参数.这样,事件系统会以正确的时间间隔调用您的动画函数,同时仍能及时处理所有其他窗口系统事件.
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.
这篇关于“tkinter.TclError:无效的命令名称"调用 root.destroy() 后出错的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
更多推荐
[db:关键词]
发布评论