pythonkivy

Kivy drag and drop from ListView


I'm attempting to set up a ListView so that

1) When selection is made, that selection is deleted from the ListView

2) A Scatter is created holding a label with the text of the selection

3) Using the same click, the Scatter is dragged across the screen

4) The Scatter is deleted once the click is released.

I did this in tkinter, and I'm trying to transition this to Kivy. For the most part, this is pretty straightforward, but I've encountered a couple of issues. The first issue I have is getting the selection of the ListView. The on_touch_down event of the ListView gets fired before the on_selection_change event of the ListView's adapter, so if I bind to on_touch_down, I get what the previous selection was, not the current one. The second issue is dragging the Scatter. The goal is for the user, in 1 click, to make a selection from the ListView, have a Scatter appear and drag it across the screen, and then have the Scatter be removed when the click is released. I've tried to use touch.grab() in the following method that was bound to the ListView's on_touch_down

def onPress(self, view, touch):
    if view.collide_point(touch.x, touch.y):
        self.floatLayout.add_widget(self.scatter)
        touch.grab(self.scatter)

But when I click on the ListView I get a TypeError: cannot create weak reference to 'weakproxy' object error, despite having keyScatter: keyScatter.__self__ in my .kv file, and keyScatter is the id for self.scatter.

Is there a good fix for either issue?


Solution

  • To anyone who attempts to do drag and drop from a ListView, there is good news: a solution exists. To solve this problem, I used methods bound to the on_selection_change of the ListView's adapter, the on_touch_down of the ListView, and the on_touch_up of the Scatter that I was being used for the drag and drop, as in the following code block.

    self.listview.bind(on_touch_down=self.press)
    self.scatter.bind(on_touch_up=self.release)
    self.adapter.bind(on_selection_change=self.selectionChange)
    
    def selectionChange(self, adapter):
        if adapter.selection: #Sometimes the selection was [], so a check doesn't hurt 
            names = adapter.data
            self.scatter.children[0].text = adapter.selection[0].text #My scatter has a label as it's first and only child. Here, I'm changing the label's text
            for j in adapter.data:
                if j == adapter.selection[0].text:
                    break
            names.pop(names.index(j))
            self.listview.adapter.data = names
            if(hasattr(self.listview, '_reset_spopulate')): #This is used to reset the ListView
                self.listview._reset_spopulate()
    
    def press(self, view, touch):
        if view.collide_point(touch.x, touch.y) and not touch.is_mouse_scrolling:
            self.scatter.center = touch.pos
            self.floatLayout.add_widget(self.scatter) #The scatter appears on the click
            self.scatter.on_touch_down(touch) #Needs to be called to get the scatter to be dragged
    
    def release(self, scatter, touch):
        if scatter.collide_point(touch.x, touch.y) and touch.grab_current: #Because Kivy's on_touch_up doesn't work like I think it does
    
            #Do whatever you want on the release of the scatter
    
            self.floatLayout.remove_widget(self.scatter) #Remove the scatter on release