在调用root.destroy()之前清理tkinter canvas

时间:2015-10-22 11:49:30

标签: python tkinter

我正在学习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)

0 个答案:

没有答案