Python GUI: Drag-and-Drop with Tkinter

Drag and Drop in Tkinter GUI

Introduction: Why Drag and Drop Matters

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.

1️⃣ Understanding Drag-and-Drop in Tkinter

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.

2️⃣ Example 1: Dragging a Label

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

🔍 Explanation of Variables and Objects in 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.

🔍 Explanation of Variables and Objects in 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.

🛠 How the Drag-and-Drop Works

  1. When the user presses the mouse on the label, start_drag(event) stores the initial cursor position inside the widget.
  2. As the user moves the mouse, on_drag(event) calculates the new position based on the cursor movement.
  3. The widget is repositioned dynamically using widget.place(x, y).

✅ Key Takeaways

  • event.widget is the widget being dragged.
  • winfo_x() & winfo_y() get the widget’s current position.
  • Position updates dynamically as the mouse moves.

3️⃣ Drag-and-Drop Between Two Frames

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

Why the Drag-and-Drop Stays in One Frame?

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).

Explanation:

  • Frames (frame1 & frame2) act as independent containers.
    • The label is created inside frame1, so its place() coordinates are calculated relative to frame1, not root.
    • Even when on_drag() updates its position, the label cannot move outside frame1.
  • place() only works within the parent widget.
    • Since label is placed inside frame1, its movement is limited to frame1’s boundaries.
    • It cannot be positioned inside frame2 or root because place() does not allow widgets to "jump" between different parents.

How to Allow Drag-and-Drop Across Frames?

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`
    

Why Does This Work?

  • Now, 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.

Final Thoughts

  • Issue: label is inside frame1, restricting its movement.
  • Fix: Change 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! 🚀

4️⃣ Drag-and-Drop for Listbox Items

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

🚀 Implementing Drag-and-Drop with Floating Labels in Tkinter

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:

  • ✔ Drag-and-drop between multiple Listboxes.
  • ✔ Floating label follows the cursor for better user experience.
  • ✔ Items are only moved when dropped into another Listbox.
  • ✔ Works smoothly with different screen resolutions.

📌 Code Explanation:

  • start_drag(event): This function is triggered when an item is selected for dragging. It stores the index and text of the selected item and creates a floating label that follows the cursor.
  • on_drag(event): Moves the floating label in sync with the mouse pointer to provide a visual indication of the dragging process.
  • on_drop(event):
    • Hides the floating label temporarily to detect the correct target Listbox.
    • Transfers the selected item from the source Listbox to the target Listbox.
    • Removes the floating label after the item is dropped.
  • Main Window & Listboxes: The script initializes a Tkinter window and creates three Listboxes, each capable of receiving dragged items.
  • Event Bindings:
    • "<ButtonPress-1>": Detects when an item is clicked for dragging.
    • "<B1-Motion>": Moves the floating label while dragging.
    • "<ButtonRelease-1>": Identifies the drop location and moves the item.

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()

5️⃣ Advanced Drag-and-Drop with 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()

🛠 Features:

  • ✔ Drag files from Explorer/Finder into the Tkinter app.
  • DND_FILES allows detecting file paths.
  • dnd_bind("<<Drop>>", on_drop) handles the file drop event.

6️⃣ Enhancements & Best Practices

🔹 Smooth Dragging:

  • Implement animations for better UX.
  • Use after() to add a delay before updating widget position.

🔹 Boundary Restriction:

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()))

🔹 Snapping Effect:

Automatically align dragged widgets to grid positions.

x = round(x / 50) * 50
y = round(y / 50) * 50

🔹 Multi-Widget Dragging:

Store all widgets in a list and allow group dragging.

7️⃣ Conclusion: What You Learned

  • ✅ Basics of dragging and dropping widgets
  • ✅ Handling mouse events for smooth movement
  • ✅ Moving items inside a Listbox
  • ✅ Implementing file drag-and-drop using tkinterdnd2
  • ✅ Enhancing UX with snapping, boundaries, and smooth movement

Would you like multi-selection dragging or grid-based snapping as a next tutorial? 🚀 Let me know!

Conclusion: Expanding Your GUI Possibilities

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
Subscribe to our YouTube Channel here


Subscribe

* indicates required
Subscribe to plus2net

    plus2net.com







    Python Video Tutorials
    Python SQLite Video Tutorials
    Python MySQL Video Tutorials
    Python Tkinter Video Tutorials
    We use cookies to improve your browsing experience. . Learn more
    HTML MySQL PHP JavaScript ASP Photoshop Articles FORUM . Contact us
    ©2000-2024 plus2net.com All rights reserved worldwide Privacy Policy Disclaimer