A differen "Drag and Drop" images in Tkinter (Python 3.x)
To drag an image between two canvases without removing the image from the first canvas.
The idea is the following:
When the user clicks on the canvas
can1
(click1
function):- get the item the user clicked on with
can1.find_closest
- get its image with
can1.itemcget
- create a toplevel containing the image
- bind mouse motion to drag along the toplevel: for that you need to use the
event.x_root
andevent.y_root
to change the toplevel's geometry.
- get the item the user clicked on with
When the user releases the left mouse button (
release
function):- unbind mouse motion
- if the toplevel is inside the canvas
can2
, create the image incan2
at the mouse position - destroy the toplevel
This way, there can be only one toplevel because each time the button is released, the toplevel is destroyed.
Here is the code:
import tkinter as tkclass DragToplevel(tk.Toplevel): def __init__(self, master, image, x, y): tk.Toplevel.__init__(self, master) self.overrideredirect(True) self.geometry('+%i+%i' % (x, y)) self.image = image self.label = tk.Label(self, image=image, bg='red') self.label.pack() def move(self, x, y): self.geometry('+%i+%i' % (x, y))root = tk.Tk()can1 = tk.Canvas(root, width=300, height=300, bg='white')can2 = tk.Canvas(root, width=300, height=300, bg='white')can1.pack(side='left')can2.pack(side='right')root.geometry('800x800')im = tk.PhotoImage('tux', master=root, file='/home/juliette/Images/tux_mini.png')drag_id = ''dragged = Nonecan1.create_image(100, 200, image=im)def click1(event): global drag_id, dragged items = can1.find_closest(event.x, event.y) if items: image = can1.itemcget(items[0], 'image') dragged = DragToplevel(root, image, event.x_root, event.y_root) drag_id = root.bind('<Motion>', lambda e: dragged.move(e.x_root, e.y_root))def release(event): global drag_id, dragged root.unbind('<Motion>', drag_id) drag_id = "" xr, yr = event.x_root, event.y_root x2, y2 = can2.winfo_rootx(), can2.winfo_rooty() w2, h2 = can2.winfo_width(), can2.winfo_height() if dragged and xr >= x2 and xr < x2 + w2 and yr >= y2 and yr < y2 + h2: can2.create_image(xr - x2, yr - y2, image=dragged.image, anchor='nw') if dragged: dragged.destroy() dragged = Nonecan1.bind('<ButtonPress-1>', click1)root.bind('<ButtonRelease-1>', release)root.mainloop()