pythondrag-and-dropwxpythonwxwidgetslistctrl

wxpython drag and drop: drop only if the cursor is inside the target listctrl object


I am trying to create a small app using wxpython where a user can drag some text from a listctrl object (the source) and drop it in another listctrl object (the target).

I would like to write this app in such a way that an text string is dropped into the target listctrl object only if the cursor is in the target listctrl area. My code (shown below) right now will drop a text string even if the cursor never moves into the target area. Any pointer would be greatly appreciated!

import wx
from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin

class TextDropTargetListCtrl(wx.TextDropTarget):
    def __init__(self, object):
        wx.TextDropTarget.__init__(self)
        self.object = object        
    def OnDropText(self, x, y, data):
        self.object.InsertStringItem(0, data)        
    def OnDragOver(self, x, y, d):
        return wx.DragCopy

class AutoWidthListCtrl(wx.ListCtrl, ListCtrlAutoWidthMixin):
    def __init__(self, parent, style):
        wx.ListCtrl.__init__(self, parent, -1, style=style)
        ListCtrlAutoWidthMixin.__init__(self)


class MainApp(wx.Frame):   
    def __init__(self):
        wx.Frame.__init__(self, None, title="",  size=(500, 800))
        self.SetBackgroundColour('white')                                                                                                                                         
        self.GridBagSizer = wx.GridBagSizer()

        self.listctrl_left = AutoWidthListCtrl(self, style = wx.LC_REPORT|wx.LC_VRULES)        
        self.listctrl_left.InsertColumn(0, "Source")
        self.listctrl_left.InsertStringItem(0, "apple")
        self.listctrl_left.InsertStringItem(1, "pear")
        self.listctrl_left.InsertStringItem(2, "watermelon")

        self.listctrl_right = AutoWidthListCtrl(self, style = wx.LC_REPORT)
        self.listctrl_right.InsertColumn(0, "Destination")        
        self.GridBagSizer.Add(self.listctrl_left, pos=(0, 0),span = (1, 1), 
                              flag = wx.EXPAND|wx.ALL, border = 15)
        self.GridBagSizer.Add(self.listctrl_right, pos=(0, 1),span = (1, 1), 
                              flag = wx.EXPAND|wx.ALL, border = 15)

        self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.OnDragInit)
        self.DropTarget = TextDropTargetListCtrl(self.listctrl_right)

        self.GridBagSizer.AddGrowableCol(0)
        self.GridBagSizer.AddGrowableCol(1)
        self.GridBagSizer.AddGrowableRow(0)        
        self.SetSizer(self.GridBagSizer)                                        


    def OnDragInit(self, evt):
        text = self.listctrl_left.GetItemText(evt.GetIndex())
        tdo = wx.TextDataObject(text)
        tds = wx.DropSource(self.listctrl_left)
        tds.SetData(tdo)
        tds.DoDragDrop(True)

if __name__ == "__main__":
    app = wx.App()
    MainFrame = MainApp()
    MainFrame.Show()
    MainFrame.Centre()
    app.MainLoop()

Solution

  • import wx
    from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin
    
    class TextDropTargetListCtrl(wx.TextDropTarget):
        def __init__(self, object, parent):
            wx.TextDropTarget.__init__(self)
            self.object = object        
            self.parent = parent
        def OnDropText(self, x, y, data):
            _x,_y = self.parent.listctrl_right.GetPosition()
            _w,_h = self.parent.listctrl_right.GetSize()
            # print _x, _w
            # if self.parent.hovering:
            if _x < x < _x+_w:
                self.object.InsertStringItem(0, data)        
        def OnDragOver(self, x, y, d):
            return wx.DragCopy
    
    class AutoWidthListCtrl(wx.ListCtrl, ListCtrlAutoWidthMixin):
        def __init__(self, parent, style):
            wx.ListCtrl.__init__(self, parent, -1, style=style)
            ListCtrlAutoWidthMixin.__init__(self)
    
    
    class MainApp(wx.Frame):   
        def __init__(self):
            wx.Frame.__init__(self, None, title="",  size=(500, 800))
            self.SetBackgroundColour('white')                                                                                                                                         
            self.GridBagSizer = wx.GridBagSizer()
    
            self.listctrl_left = AutoWidthListCtrl(self, style = wx.LC_REPORT|wx.LC_VRULES)        
            self.listctrl_left.InsertColumn(0, "Source")
            self.listctrl_left.InsertStringItem(0, "apple")
            self.listctrl_left.InsertStringItem(1, "pear")
            self.listctrl_left.InsertStringItem(2, "watermelon")
    
            self.listctrl_right = AutoWidthListCtrl(self, style = wx.LC_REPORT)
            self.listctrl_right.InsertColumn(0, "Destination")        
    
            # self.listctrl_right.Bind(wx.EVT_ENTER_WINDOW, self.OnHoverEnter)
            # self.listctrl_right.Bind(wx.EVT_LEAVE_WINDOW, self.OnHoverLeave)
            # self.hovering = False
    
            self.GridBagSizer.Add(self.listctrl_left, pos=(0, 0),span = (1, 1), 
                                  flag = wx.EXPAND|wx.ALL, border = 15)
            self.GridBagSizer.Add(self.listctrl_right, pos=(0, 1),span = (1, 1), 
                                  flag = wx.EXPAND|wx.ALL, border = 15)
    
            self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.OnDragInit)
            self.DropTarget = TextDropTargetListCtrl(self.listctrl_right, self)
    
            self.GridBagSizer.AddGrowableCol(0)
            self.GridBagSizer.AddGrowableCol(1)
            self.GridBagSizer.AddGrowableRow(0)        
            self.SetSizer(self.GridBagSizer)                                        
    
        # def OnHoverEnter(self, event):
        #     self.hovering = True
        # def OnHoverLeave(self, event):
        #     self.hovering = False
    
        def OnDragInit(self, evt):
            text = self.listctrl_left.GetItemText(evt.GetIndex())
            tdo = wx.TextDataObject(text)
            tds = wx.DropSource(self.listctrl_left)
            tds.SetData(tdo)
            tds.DoDragDrop(True)
            # evt.Skip()
    
    if __name__ == "__main__":
        app = wx.App()
        MainFrame = MainApp()
        MainFrame.Show()
        MainFrame.Centre()
        app.MainLoop()
    

    This is very hackish but does what you want... Initially I tried EVT_ENTER_WINDOW and EVT_LEAVE_WINDOW events, which worked ok on listctrl but not while dragging. So, I just checked if the x coordinate of drop event falls within the right listctrl widget or not.