DoDragDrop problem

I'm working on a window that is a drag source. But it does not provide
data for dragging. Instead it tires to send itself for the drag drop
handler. The idea is that there are dragging operations between
similar widgets. These widgets are connected to the same data model.
The drag operations are ending in method calls of the data model, and
they change the state of the data model. After any state change, many
or all of the widgets needs to be updated. E.g. there is no "data" to
be dragged.

So I created this function, that stores the "drag source" widget in a
global variable:

def start_drag(ident,source):
    global _dragged
    _dragged = weakref.ref(source)
    # Create custom data object with your format.
    format = ident_to_format(ident)
    data = wx.CustomDataObject(format)
    data.SetData("1")
    return data

There can only be one drag operation in progress, so the same _dragged
global variable can be used for all "dataless" dragging operations.

The widgets connected to the data model are wx.Window descendants, and
they are drawn completely from Python. From a widget like this, here
are some code snippets:

# Event for dragging, should be similar to EVT_LIST_BEGIN_DRAG but it
is a custom widget so I guess I need a custom event?
DimensionDragEvent, EVT_DIMENSION_DRAG = wx.lib.newevent.NewEvent()

class DimensionPanem(wx.Window):
   def __init__(self,parent,id,model):
       wx.Window.__init__(self,parent,id)
       self.model = model
       self._src_ident = model.get_ident() # Identifies the model,
this is a string
       self.Bind(wx.EVT_PAINT, self.OnPaint)
       self.Bind(wx.EVT_LEFT_DOWN,self.LeftDown,self)
       self.Bind(EVT_DIMENSION_DRAG ,self.datalessStartDrag,self)
       # .... more code here ...

    def OnPaint(self,event):
       # .... display state/view of self.model here!

    def OnLeftDown(self,event):
            # Start dragging operation when the user presses left
mouse button
            evt = DimensionDragEvent(x=event.GetX(),y=event.GetY())
            wx.PostEvent(self, evt)

    def datalessStartDrag(self, evt):
        # Called when we need to start dragging.
        # Create a tricky data object for this data model + source
widget
        data = start_drag(self._src_ident,self)
        # Create drop source and begin drag-and-drop.
        dropSource = wx.DropSource(self)
        dropSource.SetData(data)
        res = dropSource.DoDragDrop(flags=wx.Drag_DefaultMove) # <---
this is where the problem is!
        print "DoDragDrop returned",res # Print result of the drag
operation

So I try to start the drag operation when the user presses left mouse
button. My problem is that when I left click on the source widget, the
DoDragDrop returns immediately. E.g. after left clicking on the source
widget, the application prints:

DoDragDrop returned 1

I don't understand WHEN the DoDragDrop call is supposed to return? How
do I control this from my program? I would like to end the drag
operation in the following cases:

* user releases left mouse button over a drop target that is capable
of accepting the drag source. (This can be done with a PyDropTarget,
that check if the source widget is connected to the same model or
not). In this case, the data model needs to be changed/method called.
* user releases left mouse button over something else, in this case
the drag operation should be cancelled.

How can I do the second part? I was reading this wiki:
http://wiki.wxpython.org/DragAndDrop but It does not tell WHEN and HOW
DoDragDrop returns? It only tells that it will return, eventually...

Thanks,

   Laszlo

Here is a complete example:

import weakref

import wx
import wx.lib.newevent
import uuid

APPID = uuid.uuid4().hex
# Dragged object. We suppose there can only be one dragged object
# at a time.
_dragged = None

def ident_to_format(ident):
    """Convert type identifier to computer-global unique
identifier."""
    global APPID
    return "dnd_dataless|%s|%s"%(ident,APPID)

def start_drag(ident,source):
    global _dragged
    _dragged = weakref.ref(source)
    # Create custom data object with your format.
    format = ident_to_format(ident)
    data = wx.CustomDataObject(format)
    data.SetData("1")
    return data

DimensionDragEvent, EVT_DIMENSION_DRAG = wx.lib.newevent.NewEvent()

class Model(object):
    def get_ident(self):
        return "model_01"

class DimensionPanel(wx.Window):
    def __init__(self,parent,id,model):
       wx.Window.__init__(self,parent,id)
       self.model = model
       self._src_ident = model.get_ident() # Identifies the model,
this is a string
       self.Bind(wx.EVT_PAINT, self.OnPaint)
       self.Bind(wx.EVT_LEFT_DOWN,self.OnLeftDown)
       self.Bind(EVT_DIMENSION_DRAG ,self.datalessStartDrag)
       # .... more code here ...

    def OnPaint(self,event):
       event.Skip()

    def OnLeftDown(self,event):
        # Start dragging operation when the user presses left mouse
button
        evt = DimensionDragEvent(x=event.GetX(),y=event.GetY())
        wx.PostEvent(self, evt)

    def datalessStartDrag(self, evt):
        # Called when we need to start dragging.
        # Create a tricky data object for this data model + source
widget
        data = start_drag(self._src_ident,self)
        # Create drop source and begin drag-and-drop.
        dropSource = wx.DropSource(self)
        dropSource.SetData(data)
        print "Start drag"
        res = dropSource.DoDragDrop(flags=wx.Drag_DefaultMove)
        print "DoDragDrop returned",res # Print result of the drag

class MyFrame(wx.Frame):
    def __init__(self):
        self.m = Model()

        wx.Frame.__init__(self,None)

        self.pnl1 = DimensionPanel(self,-1,self.m)
        self.pnl2 = DimensionPanel(self,-1,self.m)

        self.pnl1.SetBackgroundColour(wx.Colour(255,255,0))
        self.pnl2.SetBackgroundColour(wx.Colour(0,255,255))

        s = wx.BoxSizer(wx.HORIZONTAL)
        s.Add(self.pnl1,flag=wx.EXPAND,proportion=1)
        s.Add(self.pnl2,flag=wx.EXPAND,proportion=1)
        self.SetSizer(s)

        self.Show()

if __name__ == '__main__':
    app = wx.App()
    MyFrame()
    app.MainLoop()

To demonstrate the problem, press down left mouse button on one of the
DimensionPanel instances.

Sorry, I realized that the problem was using wx.PostEvent. If I
directly call the datalessStartDrag() method, then it works. Now the
question is, how can I call an event handler by its event id?
Something like PostEvent, but I want to call it and wait for the
result. Is it possible?

I was looking at wx.App.ProcessEvent, but I guess this is not what I
need?

Thanks,

   Laslzo

Sorry, I realized that the problem was using wx.PostEvent. If I
directly call the datalessStartDrag() method, then it works. Now the
question is, how can I call an event handler by its event id?
Something like PostEvent, but I want to call it and wait for the
result. Is it possible?

Yes, but see below. self.GetEventHandler.ProcessEvent(event) will wait for the event to be handled before returning.

I was looking at wx.App.ProcessEvent, but I guess this is not what I
need?

But why use an event at all? Just do the DnD from directly from the mouse event handler (or directly call another method as you've already tried.) That will end up being the same as waiting for ProcessEvent to complete, as the DoDragDrop will not return until the DnD is finished.

···

On 7/17/11 7:01 AM, nagylzs wrote:

--
Robin Dunn
Software Craftsman