Home > other >  <B1-Motion> binding is unresponsive in Tkinter
<B1-Motion> binding is unresponsive in Tkinter

Time:05-09

I've been working on learning more about the Canvas() widget in tkinter, so I decided to build a simple paint app just for practice.

To achieve this, I created a canvas and binded it to "<B1-Motion>", but it becomes unresponsive when I drag the mouse too fast.

Here's a code sample:

from tkinter import *


class Paint:
    def __init__(self, root):
        self.root = root

        self.current_x = None
        self.current_y = None

        self.brush_size = 10
        self.brush_color = "black"

    def create_widgets(self):
        # Creating the canvas
        self.canvas = Canvas(self.root, width=1000, height=1000)
        self.canvas.grid(row=0, column=0, sticky="nsew")

        # Setting up bindings for the canvas.
        self.canvas.bind("<Button-1>", self.setup_coords)
        self.canvas.bind("<B1-Motion>", self.drag)

    def setup_coords(self, e):
        # Reset the starting points to the current mouse position
        self.current_x = e.x
        self.current_y = e.y

    def drag(self, e):
        # Create an oval that's size is the same as brush_size
        oval = self.canvas.create_oval(self.current_x, self.current_y, self.current_x self.brush_size, self.current_y self.brush_size, fill=self.brush_color)

        # Set the variables values to the current position of the mouse, so that the oval gets drawn correctly on the next call.
        self.current_x = e.x
        self.current_y = e.y


def main():
    root = Tk()
    root.geometry("1000x1000")
    p = Paint(root)
    p.create_widgets()
    mainloop()

if __name__ == '__main__':
    main()

Here, when I drag the mouse slowly, everything works just fine: enter image description here

But as soon as I start dragging fast, the bindings don't get called on time and only a few circles get drawn: enter image description here

Am I doing something inefficiently here? Is there any way to fix this problem?

It would be great if anyone could help me out. Thanks in advance.

UPDATE:

I tried acw1668's suggestion which is drawing lines instead of circles and setting it's width to the brush size:

from tkinter import *


class Paint:
    def __init__(self, root):
        self.root = root

        self.current_x = None
        self.current_y = None

        self.brush_size = 50
        self.brush_color = "black"

    def create_widgets(self):
        # Creating the canvas
        self.canvas = Canvas(self.root, width=1000, height=1000)
        self.canvas.grid(row=0, column=0, sticky="nsew")

        # Setting up bindings for the canvas.
        self.canvas.bind("<Button-1>", self.setup_coords)
        self.canvas.bind("<B1-Motion>", self.drag)

    def setup_coords(self, e):
        # Reset the starting points to the current mouse position
        self.current_x = e.x
        self.current_y = e.y

    def drag(self, e):
        # Create an oval that's size is the same as brush_size
        oval = self.canvas.create_line(self.current_x, self.current_y, e.x, e.y, width=self.brush_size, fill=self.brush_color)

        # Set the variables values to the current position of the mouse, so that the oval gets drawn correctly on the next call.
        self.current_x = e.x
        self.current_y = e.y


def main():
    root = Tk()
    root.geometry("1000x1000")
    p = Paint(root)
    p.create_widgets()
    mainloop()

if __name__ == '__main__':
    main()

But still, there is some unwanted gap when I increase the brush size: enter image description here

Any fixes?

CodePudding user response:

For well-connected lines, set the set capstyle to round when creating lines.

Check out this canvas tutorial for more details.

Almost all drawing libraries that I'm familiar with support different line end ("caps") styles, as well as line connection ("joins") styles.

Also note that you can use create_line to draw either quadratic Bézier splines or cubic splines.

  • Related