python - 调用 root.destroy() 后出现 "tkinter.TclError: invalid command name"错误

标签 python tkinter tkinter-canvas

我正在学习 Python 3.X 上的 tkinter。我正在编写一个简单的程序,它将让一个或多个球(tkinter 椭圆形)围绕矩形球场弹跳(tkinter 根窗口上绘制有 Canvas 和矩形)。

我希望能够通过按q键干净地终止程序,并设法将键绑定(bind)到根并在按下键时触发回调函数,然后调用root.destroy().

但是,当我这样做时,我仍然收到 _tkinter.TclError: invalid command name ".140625086752360" 形式的错误。这真让我抓狂。我做错了什么?

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 类中。我认为这是某种回调或类似的触发,但我似乎正在用头撞墙试图修复它。

最佳答案

您实际上已经有了两个主循环。在您的 Court.__init__ 方法中,您使用 after 启动 Animate 方法,然后启动 Tk 主循环,它将处理事件,直到您销毁主循环Tk 窗口。

但是,Animate 方法基本上通过调用 update 来处理事件,然后调用 time.sleep 来重复此主循环,以浪费一些时间并重复此操作。当您处理按键并终止窗口时,Animate 方法仍在运行并尝试更新不再存在的 Canvas 。

处理此问题的正确方法是重写 Animate 方法以执行单轮移动球,然后使用 after 安排另一次 Animate 调用 并提供必要的延迟作为 after 参数。这样,事件系统将以正确的时间间隔调用您的动画函数,同时仍然及时处理所有其他窗口系统事件。

关于python - 调用 root.destroy() 后出现 "tkinter.TclError: invalid command name"错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33258866/

相关文章:

python : Why is it said that variables that are only referenced are implicitly global?

python - 验证导致错误

python - 在 Python tkinter Canvas 上删除线条

python - tkinter - 通过鼠标移动在 Canvas 图像上显示图像叠加

Python -> 标签无法显示到框架

python - Tkinter Canvas 不显示图像

python - TypeError : unbound method __init__() . ...在重新打包后的单元测试期间

python - Pyplot 的刻度值可以被自动间隔整除吗?

python - "cannot find vcvarsall.bat"错误后 pip 的 MinGW 编译器,仍然无法正常工作

python - 如何一次将 3 个键绑定(bind)到一个事件?