同时移动多个 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
.
中调用函数来仅调用一次该函数
我想制作多个圆圈同时在给定的轨迹上移动。只有一个人影移动时一切正常。每当我添加另一个数字时,它就会开始加速,直到它开始明显冻结。无论我使用什么线程或 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
.