tkinter - How to drag and drop widgets?

15,534

Solution 1

The behavior you're observing is caused by the fact that the event's coordinates are relative to the dragged widget. Updating the widget's position (in absolute coordinates) with relative coordinates obviously results in chaos.

To fix this, I've used the .winfo_x() and .winfo_y() functions (which allow to turn the relative coordinates into absolute ones), and the Button-1 event to determine the cursor's location on the widget when the drag starts.

Here's a function that makes a widget draggable:

def make_draggable(widget):
    widget.bind("<Button-1>", on_drag_start)
    widget.bind("<B1-Motion>", on_drag_motion)

def on_drag_start(event):
    widget = event.widget
    widget._drag_start_x = event.x
    widget._drag_start_y = event.y

def on_drag_motion(event):
    widget = event.widget
    x = widget.winfo_x() - widget._drag_start_x + event.x
    y = widget.winfo_y() - widget._drag_start_y + event.y
    widget.place(x=x, y=y)

It can be used like so:

main = tk.Tk()

frame = tk.Frame(main, bd=4, bg="grey")
frame.place(x=10, y=10)
make_draggable(frame)

notes = tk.Text(frame)
notes.pack()

If you want to take a more object-oriented approach, you can write a mixin that makes all instances of a class draggable:

class DragDropMixin:
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        make_draggable(self)

Usage:

# As always when it comes to mixins, make sure to
# inherit from DragDropMixin FIRST!
class DnDFrame(DragDropMixin, tk.Frame):
    pass

# This wouldn't work:
# class DnDFrame(tk.Frame, DragDropMixin):
#     pass

main = tk.Tk()

frame = DnDFrame(main, bd=4, bg="grey")
frame.place(x=10, y=10)

notes = tk.Text(frame)
notes.pack()

Solution 2

Tkinter has a module for this, documented in the module docstring. It was expected that it would be replaced by a tk dnd module, but this has not happened. I have never tried it. Searching SO for [tkinter] dnd returns this page. Below is the beginning of the docstring.

>>> from tkinter import dnd
>>> help(dnd)
Help on module tkinter.dnd in tkinter:

NAME
    tkinter.dnd - Drag-and-drop support for Tkinter.

DESCRIPTION
    This is very preliminary.  I currently only support dnd *within* one
    application, between different windows (or within the same window).
[snip]
Share:
15,534
Admin
Author by

Admin

Updated on June 04, 2022

Comments

  • Admin
    Admin almost 2 years

    I am trying to make a Python program in which you can move around widgets.

    This is my code:

    import tkinter as tk 
    main = tk.Tk()
    notesFrame = tk.Frame(main, bd = 4, bg = "a6a6a6")
    notesFrame.place(x=10,y=10)
    notes = tk.Text(notesFrame)
    notes.pack()
    notesFrame.bind("<B1-Motion>", lambda event: notesFrame.place(x = event.x, y = event.y)
    

    But, this gets super glitchy and the widget jumps back and forth.

  • Chris P
    Chris P almost 4 years
    Motion 1: Do nothing, Motion 2: change place. That's a little bug i think.
  • martineau
    martineau almost 3 years
    The entire docstring can be viewed in the source code.
  • Andres Manuel Diaz
    Andres Manuel Diaz almost 2 years
    Thanks you very much @Aran-Fey. This helped me to drag my main window from a child widget that was a label.