同时移动多个 tkinter.canvas 个图形

Move multiple tkinter.canvas figures simultaneously

我想制作多个圆圈同时在给定的轨迹上移动。只有一个人影移动时一切正常。每当我添加另一个数字时,它就会开始加速,直到它开始明显冻结。无论我使用什么线程或 canvas.move() 和 canvas.after() 方法,都会发生这种情况。

实际上这也很奇怪,因为我的完整版代码在添加更多图形后开始变慢。我发送的那个被简化了,以显示移动的问题。也许会发生这种情况,因为我用我自己的方法画了很多小方块的轨迹线,但这不是重点。

如何才能同时以相同的速度移动图形而不会有那么大的延迟?我想尝试使用 procceses 而不是线程,但并不真正理解它们是如何工作的,我怀疑这会不会有什么显着的改变。

也许您还可以就如何在 canvas 中处理单个像素而不绘制宽度为一个像素的矩形提出建议?

编辑: 忘了说了。出于某种原因,即使只有一个人影在移动,如果我移动鼠标,人影的移动速度也会减慢,直到我停止触摸鼠标。我想原因是 tkinter 开始注册事件,但我需要它来绘制轨迹,所以删除并不是一个真正的选择。如何解决这个问题?

import math
import tkinter as tk
from enum import Enum
import threading

class Trajectory:
    # This trajectory should do the same trick both for circles, squares and maybe images
    # x0, y0 - start point, x1, y1 - end point
    def __init__(self, x0, y0, x1, y1, id, diameter):
        print('aaaa', id)
        self.x0 = x0
        self.y0 = y0
        self.x1 = x1
        self.y1 = y1
        self.id = id
        self.sign = 1
        self.diameter = diameter
        self.dir = self.get_normalized_dir()

    def has_arrived(self, x, y):
        return math.sqrt((self.x1 - (x + self.diameter // 2)) * (self.x1 - (x + self.diameter // 2)) +
                         (self.y1 - (y + self.diameter // 2)) * (self.y1 - (y + self.diameter // 2))) < 1

    def get_normalized_dir(self):
        L = math.sqrt((self.x1 - self.x0) * (self.x1 - self.x0) + (self.y1 - self.y0) * (self.y1 - self.y0))
        return (self.x1 - self.x0) / L, (self.y1 - self.y0) / L

    def swap_points(self):
        self.x0, self.y0, self.x1, self.y1 = self.x1, self.y1, self.x0, self.y0

    def __str__(self):
        return f'x0 {self.x0} y0 {self.y0} x1 {self.x1} y1 {self.y1} id {self.id} sign {self.sign}'


class App(tk.Tk):
    def __init__(self):
        # action_33 was intented as an Easter egg to smth (at least I think so). However,
        # I forgot what it meant :(
        super().__init__()
        self.bind('<Motion>', self.on_mouse)
        self.geometry('400x400')
        self.resizable(False, False)
        self.canvas = tk.Canvas(self, bg='white', width=400, height=400)
        self.canvas.pack(fill="both", expand=True)
        self.start_point = []
        self.end_point = []
        self.is_drawing = False
        self.OUTLINE = 'black'
        self.canvas.bind("<Button-1>", self.callback)
        self.title('Object trajetory')
        self.bg_line = None
        self.figure_color = 'green'
        self.figures = []  # will store only trajectory class
        self.diameter = 40

    def move_figures(self):
        # if not self.is_drawing:
        for figure in self.figures:
            self.canvas.move(figure.id, figure.dir[0] * 0.1 * figure.sign, figure.dir[1] * 0.1 * figure.sign)
            if figure.has_arrived(self.canvas.coords(figure.id)[0], self.canvas.coords(figure.id)[1]):
                figure.sign = -figure.sign
                figure.swap_points()
        self.canvas.after(1, self.move_figures)

    def move_for_thread(self, figure):
        while True:
            self.canvas.move(figure.id, figure.dir[0] * 0.1 * figure.sign, figure.dir[1] * 0.1 * figure.sign)
            if figure.has_arrived(self.canvas.coords(figure.id)[0], self.canvas.coords(figure.id)[1]):
                figure.sign = -figure.sign
                figure.swap_points()

    def delete_shadow_line(self):
        if self.bg_line is not None:
            self.canvas.delete(self.bg_line)

    def on_mouse(self, event):
        if self.is_drawing:
            self.delete_shadow_line()
            self.bg_line = self.canvas.create_line(self.start_point[0], self.start_point[1], event.x, event.y)

    def callback(self, event):
        if not self.is_drawing:
            self.start_point = [event.x, event.y]
            self.is_drawing = True
        else:
            self.is_drawing = False
            self.bg_line = None
            self.end_point = [event.x, event.y]
            fig = self.canvas.create_oval(self.start_point[0] - self.diameter // 2,
                                          self.start_point[1] - self.diameter // 2,
                                          self.start_point[0] + self.diameter // 2,
                                          self.start_point[1] + self.diameter // 2,
                                          fill=self.figure_color, outline='')
            # self.figures.append(
            # Trajectory(self.start_point[0], self.start_point[1], self.end_point[0], self.end_point[1], fig,
            # self.diameter))

            # self.move_figures()
            traj = Trajectory(self.start_point[0], self.start_point[1], self.end_point[0], self.end_point[1], fig, self.diameter)
            t = threading.Thread(target=self.move_for_thread, args=(traj,))
            t.start()


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

使用线程会增加不必要的复杂性。使用 after 的代码很容易制作动画。

但是,你有一个严重的缺陷。每次画线时都会调用 move_figures(),每次调用都会启动 另一个 动画循环。由于动画循环会移动所有内容,因此第一个对象每秒移动 1000 次。添加两个元素时,每个元素每秒移动 1000 次两次。三个元素,每秒移动1000次三次等等

因此,首先删除线程代码。然后,恰好调用 move_figures() 一次,并且在其中,您不应该使用 after(1, ...) 调用它,因为它试图每秒对其进行 1000 次动画处理。相反,您可以使用 after(10, ...) 或其他大于 1 的数字来减少 90% 的负载。

您可以通过从 App.__init__ 而不是在 App.callback.

中调用函数来仅调用一次该函数