首页 > 解决方案 > Python Tkinter - 在不创建新实例的情况下在屏幕上绘制形状

问题描述

我正在尝试通过单击鼠标时每帧创建椭圆来创建一个用于在屏幕上绘制的基本程序。然而,随着程序运行一段时间,它开始变得非常不稳定,并且圆圈停止形成有凝聚力的线条,因为代码运行速度不够快,无法处理精确的鼠标移动。

这是我的代码 -

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() 做类似的事情,但我找不到任何适合我的情况。

标签: pythonpython-3.xtkintertkinter-canvas

解决方案


我不明白你为什么创建循环并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()

推荐阅读