Tkinter:优化画布对象的移动

时间:2017-10-29 13:31:59

标签: python optimization tkinter

我写了这个非常简单的代码,通过屏幕移动一个椭圆形:

from tkinter import *

""" GLOBAL VARS """
H, W = 400, 600  # default height / width
DELAY = 30  # frame rate in milliseconds
R = 20  # radius of the ball


class App:
    def __init__(self, parent):
        """ app init """
        self.root = parent
        self.canvas = Canvas(self.root, bg="white", height=H, width=W)
        self.canvas.pack()

        self.pos = [W / 2, H / 2]  # starting position
        self.speed = [2, 2]  # x / y speed

        self.canvas.create_oval(self.pos[0] - 20, self.pos[1] - 20, self.pos[0] + 20, self.pos[1] + 20, fill="blue")

        self.loop()


    def loop(self):
        """ main loop """
        coords = self.canvas.coords(1)[:2]  # actual oval coordinates

        if not 0 < coords[0] < W - 20:  # the oval bounce off the window
            self.speed[0] *= -1
        if not 0 < coords[1] < H - 20:
            self.speed[1] *= -1

        self.canvas.move(1, *self.speed)

        self.root.after(DELAY, self.loop)


""" GUI SETUP """
root = Tk()
App(root)
root.mainloop()

问题在于,当我尝试增加帧速率时,椭圆形的一半消失了(我试图拍摄一些截图,但是当我这样做时,应用程序会冻结一帧,并且在屏幕截图中椭圆形显示正确,顺便说一下可以通过运行上面的代码来尝试自己。)

我的问题是:有没有办法优化动作,还是我必须接受Tkinter无法处理高fps的事实?

1 个答案:

答案 0 :(得分:3)

我认为这是一个记录不足的Tkinter performance issue。你看到椭圆形的一半消失的原因是,当检测到画布上要更新的内容时,它会获得需要更新的bbox,在你的情况下是椭圆形,然后只刷新那部分画布(出于优化原因)。

但是,当你快速地在画布上移动椭圆时,它会使画布的一部分更新,这意味着椭圆的一部分将被切断,如你所见和描述的那样。

这是Damage/Repair model

在我看来,处理这个并修复它的最简单方法是围绕你想要更新的内容创建一个更大的,不可见的(对用户)对象。这意味着Tkinter会检测到更大的对象,并更新其周围的小部件(画布)部分。诀窍是让新的更新部分包含你想要出现的部分,椭圆形。

您可以通过tag system轻松移动较大的不可见对象和椭圆。

要实现此功能,您可以将tag "circle"添加到椭圆形,然后创建一个具有相同标记的新椭圆,但坐标覆盖整个椭圆。指定fill=""outline=""也很重要,这样您就无法看到新对象。

这看起来像:

    self.canvas.create_oval(self.pos[0] - 20, self.pos[1] - 20,
                            self.pos[0] + 20, self.pos[1] + 20, fill="blue", tag="circle")
    self.canvas.create_oval(self.pos[0] - 40, self.pos[1] - 40, self.pos[0] + 40,
                            self.pos[1] + 40, fill="", outline="", tag="circle")

第一个创建的椭圆形是当前椭圆形,现在带有标记,第二个椭圆形是不可见的大椭圆形。

移动椭圆时,您可以移动1

,而不是移动"circle"(此时您的椭圆的ID,因为您先创建它)
    self.canvas.move("circle", *self.speed)

实施此操作后,您的椭圆形不应显示任何截止值。如果你仍然看到截止,那么你可以增加不可见椭圆的大小,例如偏移60而不是40