渲染时间随时间增加

Render time increasing with time

鉴于此代码

import tkinter

canvas = tkinter.Canvas()
canvas.pack()

i = 0
k = 1

while True:
    canvas.create_text(100, 0+i, text="Python 3.6!", fill="red")
    canvas.update()
    canvas.create_rectangle(0, 0, canvas["width"], canvas["height"], fill=canvas["background"])
    canvas.after(3)
    i = i + k

    if i==-1 or i==265:
        k = -k

我们可以看到,随着 while 循环的每次循环,文本的移动速度变慢。

现在,由于我们每秒多次重新渲染整个 canvas,难怪它很慢。但是,为什么它会随着时间的推移而变慢?是因为大量的对象文本和矩形对象堆积了吗?这可以避免吗?

不是移动文本,而是在每次迭代时创建一个新的文本项和一个矩形来隐藏前一个文本项,因此 canvas 变得拥挤并且程序变慢。

有几种方法可以避免这种情况:

1) 删除文本而不是将其隐藏在矩形后面:

   text = canvas.create_text(100, i, text="Python 3.6!", fill="red")
   ...
   canvas.delete(text)

2) 用coords更改现有项目的坐标:

import tkinter

canvas = tkinter.Canvas()
canvas.pack()

i = 0
k = 1
text = canvas.create_text(100, i, text="Python 3.6!", fill="red")

while True:
    canvas.coords(text, 100, i)  # change coordinates of the text
    canvas.update()
    canvas.after(3)
    i = i + k
    if i==-1 or i==265:
        k = -k

3) 移动现有项目 move:

import tkinter

canvas = tkinter.Canvas()
canvas.pack()

i = 0
k = 1
text = canvas.create_text(100, i, text="Python 3.6!", fill="red")

while True:
    canvas.move(text, 0, k)  # increment by k the y coordinate of the text
    canvas.update()
    canvas.after(3)
    i = i + k
    if i==-1 or i==265:
        k = -k

最后,除了while循环,您还可以使用after方法来制作动画。类似于:

import tkinter

root = tkinter.Tk()
canvas = tkinter.Canvas(root)
canvas.pack()

def motion(i, k):
    canvas.coords(text, 100, i)
    i = i + k
    if i==-1 or i==265:
        k = -k
    canvas.after(3, motion, i, k)

i = 0
k = 1
text = canvas.create_text(100, 0, text="Python 3.6!", fill="red")

motion(i, k)

root.mainloop()

琐事

你的猜测是对的。 Redraw/Render 时间增加是因为你通过堆叠对象泄漏内存(它们不会在每次迭代中消失,它们保存在内存中)并且因为 tkinter 同时重绘它们。从那时起,我认为解决方案很明确。

回答

当然可以避免。

有两个选项:

  1. 如果您只需要为一个对象设置动画 - 您可以同时移动一个(并重绘一个)对象。

考虑这个例子:

import tkinter as tk


class App(tk.Tk):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.canvas = tk.Canvas()
        self.canvas.pack()
        self.canvas.create_rectangle(0, 0, self.canvas["width"], self.canvas["height"], fill=self.canvas["background"])
        self.text = self.canvas.create_text(100, 1, text="Python 3.6!", fill="red")
        self.step = 3
        self.simulate_things()

    def simulate_things(self):
        if 0 <= self.canvas.coords(self.text)[1] + self.step <= self.canvas.winfo_height():
            # no need to change direction - still inside canvas
            pass
        else:
            # switch direction
            self.step *= -1
        
        # you can use coords method as alternative
        self.canvas.move(self.text, 0, self.step)

        # scheduling next move
        self.after(41, self.simulate_things)

if __name__ == '__main__':
    app = App()
    app.mainloop()
  1. 如果对多个对象有任何想法 - 您可以将它们的 state 切换为 hidden/disabled(这也是比删除对象更优化的方法,因为我们减少了id/item 管理到最低限度)。该选项将告诉 tkinter 不要重绘它们。 但是,您需要跟踪已创建的对象以防止内存泄漏!

考虑这个例子:

import tkinter as tk


class App(tk.Tk):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.canvas = tk.Canvas()
        self.canvas.pack()
        self.canvas.create_rectangle(0, 0, self.canvas["width"], self.canvas["height"], fill=self.canvas["background"])
        self.texts = {}
        self.i = 1
        self.step = 3
        self.simulate_things()

    def simulate_things(self):
        if self.i in self.texts:
            # hide previous object (if exists)
            self.canvas.itemconfigure(self.texts[self.i], state='hidden')

        if 0 <= self.i + self.step <= self.canvas.winfo_height():
            # no need to change direction - still inside canvas
            pass
        else:
            # switch direction
            self.step *= -1
        
        self.i += self.step
        
        if self.i in self.texts:
            # show hidden object for a step (if exists)
            self.canvas.itemconfigure(self.texts[self.i], state='normal')
        else:
            # or create a new one
            self.texts[self.i] = self.canvas.create_text(100, self.i, text="Python 3.6!", fill="red")

        # scheduling next move
        self.after(41, self.simulate_things)

if __name__ == '__main__':
    app = App()
    app.mainloop()

如您所见 - 您在管理性能方面的主要目标是减少重绘的数量(并将对象的数量保持在最低限度)。

链接