Drag-and-drop enhances user interaction in Python GUI applications. Instead of manually positioning widgets, users can move elements with a simple click and drag. This feature improves usability in file management, dashboards, and interactive interfaces.
Drag-and-drop in Tkinter consists of three key events:
<ButtonPress-1>
: Detects mouse click.<B1-Motion>
: Tracks mouse movement.<ButtonRelease-1>
: Stops dragging action.This example allows a Label to be dragged inside the main window .
import tkinter as tk # Import the Tkinter library
def start_drag(event):
# Store the initial position of the widget when dragging starts
event.widget.startX = event.x # Store initial X position
event.widget.startY = event.y # Store initial Y position
def on_drag(event):
# Update widget position as it's dragged
widget = event.widget # Get reference to the dragged widget
x = widget.winfo_x() - widget.startX + event.x
y = widget.winfo_y() - widget.startY + event.y
widget.place(x=x, y=y) # Move the widget
root = tk.Tk() # Create the main window
root.title("plus2net.com Drag-and-Drop Example") # Set window title
root.geometry("400x300") # Set window size
root.configure(background='yellow') # Set window background color
# Create a label that can be dragged
label = tk.Label(root, text="Drag Me", bg="lightblue", padx=10, pady=5)
label.place(x=50, y=50) # Place the label at initial position
# Bind mouse events to the label for dragging functionality
label.bind("<ButtonPress-1>", start_drag) # Detect mouse press to start dragging
label.bind("<B1-Motion>", on_drag) # Move label while dragging
root.mainloop() # Run the Tkinter event loop
start_drag(event)
Variable / Object | Type | Description |
---|---|---|
event |
Event Object | Stores details about the mouse event, such as the widget clicked and cursor position. |
event.widget |
Widget Object | Reference to the widget that triggered the event (the label in this case). |
event.x |
Integer | X-coordinate of the cursor relative to the widget when clicked. |
event.y |
Integer | Y-coordinate of the cursor relative to the widget when clicked. |
event.widget.startX |
Custom Attribute | Stores the initial X position of the cursor inside the widget. |
event.widget.startY |
Custom Attribute | Stores the initial Y position of the cursor inside the widget. |
on_drag(event)
Variable / Object | Type | Description |
---|---|---|
event |
Event Object | Stores details about the drag event, including updated cursor position. |
widget |
Widget Object | Reference to the widget being dragged. |
widget.winfo_x() |
Integer | Returns the widget’s current X position relative to the window. |
widget.winfo_y() |
Integer | Returns the widget’s current Y position relative to the window. |
x |
Integer | New calculated X position of the widget. |
y |
Integer | New calculated Y position of the widget. |
widget.place(x=x, y=y) |
Method | Moves the widget to the new (x, y) position inside the window. |
start_drag(event)
stores the initial cursor position inside the widget.on_drag(event)
calculates the new position based on the cursor movement.widget.place(x, y)
.event.widget
is the widget being dragged.winfo_x()
& winfo_y()
get the widget’s current position.This example allows a label to be dragged between two frames.
import tkinter as tk # Import the Tkinter library
def start_drag(event):
# Store the initial position of the widget when dragging starts
event.widget.startX = event.x
event.widget.startY = event.y
def on_drag(event):
# Update widget position as it's dragged
widget = event.widget
x = widget.winfo_x() - widget.startX + event.x
y = widget.winfo_y() - widget.startY + event.y
widget.place(x=x, y=y) # Move the widget
root = tk.Tk() # Create the main window
root.geometry("500x300") # Set window size
# Create two frames inside the root window
frame1 = tk.Frame(root, bg="lightgray", width=250, height=300)
frame1.pack(side="left", fill="y") # Place frame1 on the left
frame2 = tk.Frame(root, bg="white", width=250, height=300)
frame2.pack(side="right", fill="y") # Place frame2 on the right
# Create a draggable label inside frame1
label = tk.Label(frame1, text="Drag Me", bg="lightblue", padx=10, pady=5)
label.place(x=50, y=50) # Place the label at an initial position
# Bind mouse events to the label for dragging functionality
label.bind("<ButtonPress-1>", start_drag) # Detect mouse press to start dragging
label.bind("<B1-Motion>", on_drag) # Move label while dragging
root.mainloop() # Run the Tkinter event loop
The issue is that label
is placed inside frame1
, and when dragging, its movement is
restricted to frame1 because place()
positions widgets relative to their parent container (which is frame1
in this case).
frame1
& frame2
) act as independent containers.
label
is created inside frame1
, so its place()
coordinates are calculated relative to frame1, not root
.on_drag()
updates its position, the label
cannot move outside frame1
.place()
only works within the parent widget.
label
is placed inside frame1
, its movement is limited to frame1’s boundaries.frame2
or root
because place()
does not allow widgets to "jump" between different parents.
To make label
draggable across both frames, change its parent to root
instead of frame1
:
label = tk.Label(root, text="Drag Me", bg="lightblue", padx=10, pady=5) # Change parent to `root`
label.place(x=50, y=50) # Place relative to `root`
label
is not restricted to frame1.place(x, y)
positions it relative to the entire window, allowing it to move freely across frame1
and frame2
.label
is inside frame1
, restricting its movement.label
's parent to root
so its coordinates are relative to the entire window, not just frame1
.Now, the drag-and-drop will work across both frames! 🚀
Reorder items within a Listbox effortlessly by dragging them to new positions. This intuitive feature enhances usability and organization.
import tkinter as tk # Import the Tkinter library
def start_drag(event):
# Store the index of the item being dragged
widget = event.widget
widget.dragged_item = widget.nearest(event.y)
def on_drop(event):
# Get the target index where the item will be dropped
widget = event.widget
target_index = widget.nearest(event.y)
# Swap the items if the dragged item and target index are different
if widget.dragged_item != target_index:
item = widget.get(widget.dragged_item) # Get the dragged item
widget.delete(widget.dragged_item) # Remove it from the original position
widget.insert(target_index, item) # Insert it at the new position
root = tk.Tk() # Create the main window
root.geometry("300x250") # Set window size
# Create a Listbox widget
listbox = tk.Listbox(root)
listbox.pack(pady=20, fill="both", expand=True)
# Insert items into the Listbox
for item in ["Apple", "Banana", "Cherry", "Orange", "Banana"]:
listbox.insert("end", item)
# Bind mouse events for dragging and dropping
listbox.bind("<ButtonPress-1>", start_drag) # Detect item selection
listbox.bind("<ButtonRelease-1>", on_drop) # Detect item drop
root.mainloop() # Run the Tkinter event loop
This Tkinter program enables a drag-and-drop feature where items can be moved between three Listboxes using a floating label that follows the cursor. The floating label visually represents the item being dragged before it is dropped into a target Listbox.
🔹 Key Features:
📌 Code Explanation:
With this implementation, users can effortlessly drag and drop items between Listboxes with real-time visual feedback from the floating label! 🚀
import tkinter as tk # Import Tkinter
def start_drag(event):
"""Store the selected item index when dragging starts and create a floating label."""
widget = event.widget
widget.dragged_item = widget.nearest(event.y) # Get the index of the selected item
widget.selected_item = widget.get(widget.dragged_item) # Store the selected item text
# Create a floating label to follow the cursor while dragging
global floating_label
floating_label = tk.Label(root, text=widget.selected_item, bg="yellow", relief="solid")
# Convert screen coordinates to relative window coordinates for accurate placement
x_offset = root.winfo_pointerx() - root.winfo_rootx()
y_offset = root.winfo_pointery() - root.winfo_rooty()
floating_label.place(x=x_offset, y=y_offset) # Position at the mouse location inside the window
def on_drag(event):
"""Move the floating label with the cursor, keeping it aligned with the pointer."""
if floating_label:
x_offset = root.winfo_pointerx() - root.winfo_rootx()
y_offset = root.winfo_pointery() - root.winfo_rooty()
floating_label.place(x=x_offset, y=y_offset) # Keep label aligned with mouse
def on_drop(event):
"""Move the dragged item to the Listbox where it is dropped and remove the floating label."""
global floating_label
source = event.widget # Source Listbox
target = None # Initialize the target Listbox
# Temporarily hide the floating label to detect the actual target Listbox
if floating_label:
floating_label.place_forget()
# Determine which Listbox is the target
for lstbox in [listbox1, listbox2, listbox3]:
if lstbox.winfo_containing(event.x_root, event.y_root) == lstbox:
target = lstbox
break
# If a valid target Listbox is found and item is not dropped in the same Listbox
if target and source != target:
item = source.selected_item # Get the dragged item
if item: # Check if item exists
source.delete(source.dragged_item) # Remove from source Listbox
target.insert("end", item) # Add to target Listbox
# Remove the floating label after drop
if floating_label:
floating_label.destroy()
floating_label = None
# Create main window
root = tk.Tk()
root.geometry("600x300")
root.title("Drag and Drop with Floating Label Fix")
# Create three Listboxes
listbox1 = tk.Listbox(root, selectmode="single", bg="lightgray")
listbox1.pack(side="left", padx=10, pady=20, fill="both", expand=True)
listbox2 = tk.Listbox(root, selectmode="single", bg="white")
listbox2.pack(side="left", padx=10, pady=20, fill="both", expand=True)
listbox3 = tk.Listbox(root, selectmode="single", bg="lightblue")
listbox3.pack(side="left", padx=10, pady=20, fill="both", expand=True)
# Insert items into Listbox 1
for item in ["Apple", "Banana", "Cherry", "Orange", "Grapes"]:
listbox1.insert("end", item)
# Global variable for floating label
floating_label = None
# Bind events for drag-and-drop functionality
for lstbox in [listbox1, listbox2, listbox3]:
lstbox.bind("<ButtonPress-1>", start_drag) # Detect item selection
lstbox.bind("<B1-Motion>", on_drag) # Move floating label while dragging
lstbox.bind("<ButtonRelease-1>", on_drop) # Detect item drop
# Run the Tkinter event loop
root.mainloop()
tkinterdnd2
For file dragging or moving elements across windows, we can use tkinterdnd2
.
📌 Install it first:
pip install tkinterdnd2
🚀 Example: Dragging files into a Tkinter window
import tkinter as tk
from tkinterdnd2 import DND_FILES, TkinterDnD
def on_drop(event):
label.config(text=f"Dropped File: {event.data}") # Display file path
root = TkinterDnD.Tk()
root.geometry("400x200")
label = tk.Label(root, text="Drag a file here", bg="lightgray", width=40, height=5)
label.pack(pady=50)
# Enable drag and drop
label.drop_target_register(DND_FILES)
label.dnd_bind("<<Drop>>", on_drop)
root.mainloop()
DND_FILES
allows detecting file paths.dnd_bind("<<Drop>>", on_drop)
handles the file drop event.after()
to add a delay before updating widget position.Prevent dragging outside the window using winfo_width()
and winfo_height()
.
x = max(0, min(x, root.winfo_width() - widget.winfo_width()))
y = max(0, min(y, root.winfo_height() - widget.winfo_height()))
Automatically align dragged widgets to grid positions.
x = round(x / 50) * 50
y = round(y / 50) * 50
Store all widgets in a list and allow group dragging.
tkinterdnd2
Would you like multi-selection dragging or grid-based snapping as a next tutorial? 🚀 Let me know!
Drag-and-drop is useful for **data visualization, file management, and interactive UIs**. Try combining these examples to enhance your Python GUI applications.
Moving element in Canvas