Open
Description
I'm trying to create a drag-and-drop GUI interface featuring several CTkListboxes, however, I can't get the drag-drop functionality to work. I've bound the items to <ButtonPress-1>
but the items never register as being clicked. I get no output from "on_click" print statements unless the click is out of bounds and the response is "Nearest index: None"
This works perfectly with a regular tk listbox (and the code is much neater due to the presence of .nearest()
), but I'm not sure why the CTkListbox doesn't work.
Note that on_drop is probably broken, haven't gotten to the point of being able to debug it yet.
import customtkinter as ctk
from CTkListbox import *
class DragDropGUI(ctk.CTk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Create the list of objects
objects = ["Object 1", "Object 2", "Object 3", "Object 4"]
ctk.set_appearance_mode('light')
# Create the listbox for objects
self.object_listbox = CTkListbox(self, width=200, height=400)
for obj in objects:
self.object_listbox.insert(ctk.END, obj)
self.object_listbox.pack(side=ctk.LEFT, padx=10, pady=10)
# Create the bin listboxes
self.bin1_listbox = CTkListbox(self, width=200, height=400)
self.bin1_listbox.pack(side=ctk.LEFT, padx=10, pady=10)
self.bin2_listbox = CTkListbox(self, width=200, height=400)
self.bin2_listbox.pack(side=ctk.LEFT, padx=10, pady=10)
self.bin3_listbox = CTkListbox(self, width=200, height=400)
self.bin3_listbox.pack(side=ctk.LEFT, padx=10, pady=10)
self.bin4_listbox = CTkListbox(self, width=200, height=400)
self.bin4_listbox.pack(side=ctk.LEFT, padx=10, pady=10)
# Enable drag and drop functionality
self.object_listbox.bind("<ButtonPress-1>", self.on_start_drag)
self.bin1_listbox.bind("<ButtonPress-1>", self.on_start_drag)
self.bin2_listbox.bind("<ButtonPress-1>", self.on_start_drag)
self.bin3_listbox.bind("<ButtonPress-1>", self.on_start_drag)
self.bin4_listbox.bind("<ButtonPress-1>", self.on_start_drag)
self.object_listbox.bind("<B1-Motion>", self.on_drag_motion)
self.bin1_listbox.bind("<B1-Motion>", self.on_drag_motion)
self.bin2_listbox.bind("<B1-Motion>", self.on_drag_motion)
self.bin3_listbox.bind("<B1-Motion>", self.on_drag_motion)
self.bin4_listbox.bind("<B1-Motion>", self.on_drag_motion)
self.object_listbox.bind("<ButtonRelease-1>", self.on_drop)
self.bin1_listbox.bind("<ButtonRelease-1>", self.on_drop)
self.bin2_listbox.bind("<ButtonRelease-1>", self.on_drop)
self.bin3_listbox.bind("<ButtonRelease-1>", self.on_drop)
self.bin4_listbox.bind("<ButtonRelease-1>", self.on_drop)
def on_start_drag(self, event):
# Get the selected item and its index
widget = event.widget
y = event.y
nearest_index = None
min_distance = float("inf")
asdf, height = widget.size()
for i in range(height):
item_y, item_height = widget.bbox(i)[1:3]
item_center_y = item_y + item_height / 2
distance = abs(y - item_center_y)
if distance < min_distance:
min_distance = distance
nearest_index = i
print("Nearest index:", nearest_index)
if nearest_index is not None:
try:
self.drag_data = {"widget": widget, "index": nearest_index, "text": widget.get(nearest_index)}
print("Drag data:", self.drag_data)
print('item grabbed')
except AttributeError: # clicks between objects
self.drag_data = None
else:
self.drag_data = None
def on_drag_motion(self, event):
# Change the cursor to a hand symbol
event.widget.config(cursor="hand2")
def on_drop(self, event):
# Get the widget we dropped on
target_widget = self.winfo_containing(event.x_root, event.y_root)
# Check if the target widget is a listbox and the y-coordinate is within the target widget
if isinstance(target_widget, CTkListbox) and 0 <= event.y < target_widget.winfo_height():
# Get the position where the item was dropped
# Calculate the drop index manually
drop_index = None
asdf, height = target_widget.size()
y = event.y - target_widget.winfo_rooty() - target_widget.winfo_y()
for i in range(height):
item_y = target_widget.bbox(i)[1]
if y < item_y:
drop_index = i
break
if drop_index is None:
drop_index = target_widget.size()
print("Drop index:", drop_index)
# Add the dragged item to the target listbox
target_widget.insert(drop_index, self.drag_data["text"])
print('item dropped')
# Remove the item from the original listbox
self.drag_data["widget"].delete(self.drag_data["index"])
# force the target listbox to update
target_widget.activate(drop_index)
# Reset the drag_data
self.drag_data = {"widget": None, "index": None, "text": None}
if __name__ == "__main__":
app = DragDropGUI()
app.mainloop()