Python Tkinter - 在不创建新实例的情况下将形状绘制到屏幕

Python Tkinter - Draw Shape to Screen Without Creating New Instance

我打算创建一个基本程序,通过在单击鼠标时每帧创建椭圆来在屏幕上绘图。然而,随着程序运行一段时间,它开始变得非常不稳定,圆圈不再形成连贯的线条,这是因为代码 运行 速度不够快,无法处理精确的鼠标移动。

这是我的代码 -

import tkinter as tk

DRAW_HEIGHT = 560
DRAW_WIDTH = 560
PALETTE_HEIGHT = 40

def draw_palette(canvas):
    canvas.create_rectangle(0, 0, DRAW_WIDTH, PALETTE_HEIGHT, fill = 'light grey', width= 0)
    canvas.create_rectangle(DRAW_WIDTH/8, PALETTE_HEIGHT/5, 3*DRAW_WIDTH/8, 4*PALETTE_HEIGHT/5, fill = 'dark grey', width = 1)
    canvas.create_rectangle(5*DRAW_WIDTH/8, PALETTE_HEIGHT/5, 7*DRAW_WIDTH/8, 4*PALETTE_HEIGHT/5, fill = 'dark grey',width = 1)
    canvas.create_text(DRAW_WIDTH/4, PALETTE_HEIGHT/2, text = 'clear screen') #non-functional
    
class Brush():
    def __init__(self,stroke_size,stroke_color):
        self.size = stroke_size
        self.color = stroke_color
        self.mode = 'draw'
        self.pos = (0,0)
        self.clicked = False
        
    def render(self,canvas):
        if self.clicked:
            canvas.create_oval( self.pos.x-self.size/2, self.pos.y-self.size/2,
                                self.pos.x+self.size/2, self.pos.y+self.size/2,
                                width = 0, fill = self.color )
            
    def mouse_moved(self,event):
        self.pos = event
    
    def mouse_clicked(self,throwaway):
        self.clicked = True
    
    def mouse_released(self,throwaway):
        self.clicked = False


#set up root window and canvas
root = tk.Tk()
root.geometry('{}x{}'.format(DRAW_WIDTH,DRAW_HEIGHT+PALETTE_HEIGHT))
c = tk.Canvas(root, width = DRAW_WIDTH, height = DRAW_HEIGHT + PALETTE_HEIGHT, bg = 'white')
c.pack()

b = Brush(40,'black')

#bind actions to functions
c.bind("<Button-1>",b.mouse_clicked)
c.bind("<ButtonRelease-1>",b.mouse_released)
c.bind("<Motion>",b.mouse_moved)

#main loop
while 1:
    b.render(c)
    draw_palette(c)
    root.update()

我想我只是问是否有任何方法可以加快速度,但具体来说我想知道是否可以在不每次都使用 create_shape() 的情况下将形状绘制到屏幕上。

例如,

oval = c.create_oval()

while 1:
    canvas.draw(oval)

我知道你可以用 canvas.move() 做类似的事情,但我找不到适合我情况的东西。

我不明白为什么你创建循环 while 1 和 运行 render()draw_palette() 数百次,即使你不需要它。

我在 mouse_moved() 中画了新的圆并使用 root.mainloop() 并且它 运行 好多了并且创建了更平滑的线条。可能如果我从以前的地方画线到现在的地方,或者用一些步骤画许多椭圆,那么我会得到更好的线

编辑: 我在 mouse_click() 中画了第一个椭圆,所以即使我只点击而不移动,我也能看到第一个椭圆。

import tkinter as tk

# --- constanst ---

DRAW_HEIGHT = 560
DRAW_WIDTH = 560
PALETTE_HEIGHT = 40

# --- classes ---

class Brush():
    
    def __init__(self,stroke_size,stroke_color):
        self.size = stroke_size
        self.color = stroke_color
        self.mode = 'draw'
        self.pos = (0,0)
        self.clicked = False
        
    def draw(self):
        s = self.size/2
        c.create_oval(
            self.pos.x-s, self.pos.y-s,
            self.pos.x+s, self.pos.y+s,
            width=0, fill=self.color
        )
        
    def mouse_moved(self, event):
        if self.clicked:
            self.pos = event
            self.draw()

    def mouse_clicked(self, event):
        self.clicked = True
        self.pos = event
        self.draw()

    def mouse_released(self, event):
        self.clicked = False

# --- functions ---

def draw_palette(canvas):
    canvas.create_rectangle(0, 0, DRAW_WIDTH, PALETTE_HEIGHT, fill='light grey', width=0)
    canvas.create_rectangle(DRAW_WIDTH/8, PALETTE_HEIGHT/5, 3*DRAW_WIDTH/8, 4*PALETTE_HEIGHT/5, fill='dark grey', width=1)
    canvas.create_rectangle(5*DRAW_WIDTH/8, PALETTE_HEIGHT/5, 7*DRAW_WIDTH/8, 4*PALETTE_HEIGHT/5, fill='dark grey', width=1)
    canvas.create_text(DRAW_WIDTH/4, PALETTE_HEIGHT/2, text='clear screen') #non-functional

# --- main ---

#set up root window and canvas
root = tk.Tk()
root.geometry('{}x{}'.format(DRAW_WIDTH, DRAW_HEIGHT+PALETTE_HEIGHT))

c = tk.Canvas(root, width=DRAW_WIDTH, height=DRAW_HEIGHT+PALETTE_HEIGHT, bg='white')
c.pack()

b = Brush(40, 'black')

#bind actions to functions
c.bind("<Button-1>", b.mouse_clicked)
c.bind("<ButtonRelease-1>", b.mouse_released)
c.bind("<Motion>", b.mouse_moved)

draw_palette(c)

root.mainloop()

编辑:

我添加了一个功能,如果前一个位置和当前位置之间的距离太大并且有间隙,则添加椭圆。现在即使鼠标快速移动线条也很流畅

import tkinter as tk

# --- constanst ---

DRAW_HEIGHT = 560
DRAW_WIDTH = 560
PALETTE_HEIGHT = 40

# --- classes ---

class Brush():
    
    def __init__(self,stroke_size,stroke_color):
        self.size = stroke_size
        self.color = stroke_color
        self.mode = 'draw'
        self.pos = None
        self.prev = None
        self.clicked = False
        
    def draw_oval(self, x, y):
        r = self.size/2 # radius
        c.create_oval(x-r, y-r, x+r, y+r, width=0, fill=self.color)
        
    def draw(self):
        if self.pos:
            self.draw_oval(self.pos.x, self.pos.y)
            
        if self.prev:
            # calculate distance between ovals
            dx = self.pos.x - self.prev.x
            dy = self.pos.y - self.prev.y
            
            max_diff = max(abs(dx), abs(dy))
            
            # add ovals if distance bigger then some size of oval (tested with //4, //8, //6, //5)
            if max_diff > (self.size//6):
                
                # how many ovals to add
                parts = max_diff//(self.size//6)
                
                # distance between ovals
                step_x = dx/parts
                step_y = dy/parts

                # add ovals except first which is already on canvas 
                for i in range(1, parts):
                    x = self.pos.x - i*step_x
                    y = self.pos.y - i*step_y
                    self.draw_oval(x, y)
                        
    def mouse_moved(self, event):
        if self.clicked:
            self.prev = self.pos
            self.pos = event
            self.draw()

    def mouse_clicked(self, event):
        self.clicked = True
        self.prev = None
        self.pos = event
        self.draw()

    def mouse_released(self, event):
        self.clicked = False
        self.prev = None
        self.pos = None

# --- functions ---

def draw_palette(canvas):
    canvas.create_rectangle(0, 0, DRAW_WIDTH, PALETTE_HEIGHT, fill='light grey', width=0)
    canvas.create_rectangle(DRAW_WIDTH/8, PALETTE_HEIGHT/5, 3*DRAW_WIDTH/8, 4*PALETTE_HEIGHT/5, fill='dark grey', width=1)
    canvas.create_rectangle(5*DRAW_WIDTH/8, PALETTE_HEIGHT/5, 7*DRAW_WIDTH/8, 4*PALETTE_HEIGHT/5, fill='dark grey', width=1)
    canvas.create_text(DRAW_WIDTH/4, PALETTE_HEIGHT/2, text='clear screen') #non-functional

# --- main ---

#set up root window and canvas
root = tk.Tk()
root.geometry('{}x{}'.format(DRAW_WIDTH, DRAW_HEIGHT+PALETTE_HEIGHT))

c = tk.Canvas(root, width=DRAW_WIDTH, height=DRAW_HEIGHT+PALETTE_HEIGHT, bg='white')
c.pack()

b = Brush(40, 'black')

#bind actions to functions
c.bind("<Button-1>", b.mouse_clicked)
c.bind("<ButtonRelease-1>", b.mouse_released)
c.bind("<Motion>", b.mouse_moved)

draw_palette(c)

root.mainloop()