0

I am trying to create a simple program where you can move multiple objects on canvas in tkinter by dragging them. For beginning, I started with 2 squares. However, I am facing 2 problems:

  1. anytime I click on object it gets centred to mouse pointer (pointer always in the middle of square). How can I drag the object by place of click (for example drag by corner)?

  2. once I pass with one square through another the 2nd square stays on top and can not be separated any more. The objects have same sizes.

Are there any simple solutions for this? Thank you for any help or advice.

My code:

import tkinter
c = tkinter.Canvas(width = 400, height = 300)
c.pack()

d = 25  #size of square
x, x2 = 100, 200
y, y2 = 100, 200

rect = c.create_rectangle(x-d, y-d, x+d, y+d, fill = 'blue')     #first square
rect2 = c.create_rectangle(x2-d, y2-d, x2+d, y2+d, fill = 'red')    #second square

def drag(event): #drag by holding mouse button 1
    global x, y, x2, y2, xt, yt, x2t, y2t
    xt, yt = event.x, event.y   #1st square movement coords
    x2t, y2t = event.x, event.y #2nd square movement coords
    if xt in range(x-d, x+d):
        if yt in range(y-d, y+d):
            c.coords(rect, xt-d, yt-d, xt+d, yt+d)  #coords update of 1st square
            x, y = xt, yt
    if x2t in range(x2-d, x2+d):
        if y2t in range(y2-d, y2+d):
            c.coords(rect2, x2t-d, y2t-d, x2t+d, y2t+d) #coords update of 2nd square
            x2, y2 = x2t, y2t

c.bind('<B1-Motion>', drag)

tkinter.mainloop()
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
Blacho
  • 87
  • 1
  • 1
  • 8

1 Answers1

1

There are two questions:

1- to capture an object by a corner, you need to offset the object position by half of its bounding box dimensions. (see example that captures an object by the bottom right corner; you can change the corner by changing the signs of the offsets used.)

2- In order to differentiate between the objects when they are dragged, you need to 'deactivate' the call back when an object has already been captured. The example shows how to do the housekeeping I think is necessary. (but there may be a better way, built in tkinter, IDK)

import tkinter


captured = None


def capture(event):
    global captured
    print('captured')
    if captured is None:
        captured = event.widget.find_closest(event.x, event.y)


def release(event):
    global captured
    print('released')
    captured = None


def drag(event):   # drag by holding mouse button 1
    global captured
    if captured is None:
        print(f'captured {event.widget.find_closest(event.x, event.y)}')
        captured = event.widget.find_closest(event.x, event.y)
        print(f'captured: {captured}')
    else:
        x0, y0, x1, y1 = c.bbox(captured)
        xt, yt = event.x + (x0 - x1) // 2, event.y + (y0 - y1) // 2      # 1st square movement coords
        c.coords(captured, xt-d, yt-d, xt+d, yt+d)        # coords update of 1st square


if __name__ == '__main__':

    c = tkinter.Canvas(width = 400, height = 300)
    c.pack()

    d = 25   # size of square
    x, x2 = 100, 200
    y, y2 = 100, 200

    rect1 = c.create_rectangle(x-d, y-d, x+d, y+d, fill='blue', tags='r1')        # first square
    rect2 = c.create_rectangle(x2-d, y2-d, x2+d, y2+d, fill='red', tags='r2')    # second square

    c.bind('<Button-1>', capture)
    c.bind('<ButtonRelease-1>', release)
    c.bind('<B1-Motion>', drag)

    tkinter.mainloop()
Reblochon Masque
  • 35,405
  • 10
  • 55
  • 80