Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Unable to get drag-and-drop working #67

Open
ohshitgorillas opened this issue May 28, 2024 · 2 comments
Open

Unable to get drag-and-drop working #67

ohshitgorillas opened this issue May 28, 2024 · 2 comments
Labels
enhancement New feature or request

Comments

@ohshitgorillas
Copy link

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()
@Akascape
Copy link
Owner

@ohshitgorillas Drag and drop is not implemented in this ctk listbox, but it can be achieved if you modify the list button by binding different events.
I will try to add this feature in next version.

@Akascape Akascape added the enhancement New feature or request label May 28, 2024
@ohshitgorillas
Copy link
Author

As a related enhancement, can you also please add .nearest()?

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants