views:

443

answers:

2

I have wxPython app which is running on MS Windows and I'd like it to support drag&drop between its instances (so the user opens my app 3 times and drags data from one instance to another).

The simple drag&drop in wxPython works that way:

  1. User initiates drag: The source window packs necessary data in wx.DataObject(), creates new wx.DropSource, sets its data and calls dropSource.DoDragDrop()
  2. User drops data onto target window: The drop target calls library function GetData() which transfers actual data to its wx.DataObject instance and finally - dataObject.GetData() unpacks the actual data.

I'd like to have some more sophisticated drag&drop which would allow user to choose what data is dragged after he drops.
Scenario of my dreams:

  1. User initiates drag: Only some pointer to the source window is packed (some function or object).
  2. User drops data onto target window: Nice dialog is displayed which asks user which drag&drop mode he chooses (like - dragging only song title, or song title and the artists name or whole album of the dragged artist).
  3. Users chooses drag&drop mode: Drop target calls some function on the dragged data object, which then retrieves data from the drag source and transfers it to the drop target.

The scenario of my dreams seems doable in MS Windows, but the docs for wxWidgets and wxPython are pretty complex and ambigious. Not all wx.DataObject classes are available in wxPython (only wx.PySimpleDataObject), so I'd like someone to share his experience with such approach. Can such behaviour be implemented in wxPython without having to code it directly in winAPI?

EDIT: Toni Ruža gave an answer with working drag&drop example, but that's not exactly the scenario of my dreams. His code manipulates data when it's dropped (the HandleDrop() shows popup menu), but data is prepared when drag is initiated (in On_ElementDrag()). In my application there should be three different drag&drop modes, and some of them require time-consuming data preparation. That's why I want to postpone data retrieval to the moment user drops data and chooses (potentially costly) d&d mode.

And for memory protection issue - I want to use OLE mechanisms for inter-process communication, like MS Office does. You can copy Excel diagram and paste it into MS-Word where it will behave like an image (well, sort of). Since it works I believe it can be done in winAPI. I just don't know if I can code it in wxPython.

+2  A: 

Since you can't use one of the standard data formats to store references to python objects I would recommend you use a text data format for storing the parameters you need for your method calls rather than making a new data format. And anyway, it would be no good to pass a reference to an object from one app to another as the object in question would not be accessible (remember memory protection?).

Here is a simple example for your requirements:

import wx


class TestDropTarget(wx.TextDropTarget):
    def OnDropText(self, x, y, text):
        wx.GetApp().TopWindow.HandleDrop(text)

    def OnDragOver(self, x, y, d):
        return wx.DragCopy


class Test(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None)

        self.numbers = wx.ListCtrl(self, style = wx.LC_ICON | wx.LC_AUTOARRANGE)
        self.field = wx.TextCtrl(self)

        sizer = wx.FlexGridSizer(2, 2, 5, 5)
        sizer.AddGrowableCol(1)
        sizer.AddGrowableRow(0)
        self.SetSizer(sizer)
        sizer.Add(wx.StaticText(self, label="Drag from:"))
        sizer.Add(self.numbers, flag=wx.EXPAND)
        sizer.Add(wx.StaticText(self, label="Drag to:"), flag=wx.ALIGN_CENTER_VERTICAL)
        sizer.Add(self.field)

        for i in range(100):
            self.numbers.InsertStringItem(self.numbers.GetItemCount(), str(i))

        self.numbers.Bind(wx.EVT_LIST_BEGIN_DRAG, self.On_ElementDrag)
        self.field.SetDropTarget(TestDropTarget())

        menu_id1 = wx.NewId()
        menu_id2 = wx.NewId()
        self.menu = wx.Menu()
        self.menu.AppendItem(wx.MenuItem(self.menu, menu_id1, "Simple copy"))
        self.menu.AppendItem(wx.MenuItem(self.menu, menu_id2, "Mess with it"))
        self.Bind(wx.EVT_MENU, self.On_SimpleCopy, id=menu_id1)
        self.Bind(wx.EVT_MENU, self.On_MessWithIt, id=menu_id2)

    def On_ElementDrag(self, event):
        data = wx.TextDataObject(self.numbers.GetItemText(event.Index))
        source = wx.DropSource(self.numbers)
        source.SetData(data)
        source.DoDragDrop()

    def HandleDrop(self, text):
        self._text = text
        self.PopupMenu(self.menu)

    def On_SimpleCopy(self, event):
        self.field.Value = self._text

    def On_MessWithIt(self, event):
        self.field.Value = "<-%s->" % "".join([int(c)*c for c in self._text])


app = wx.PySimpleApp()
app.TopWindow = Test()
app.TopWindow.Show()
app.MainLoop()

Methods like On_SimpleCopy and On_MessWithIt get executed after the drop so any lengthy operations you might want to do you can do there based on the textual or some other standard type of data you transfered with the drag (self._text in my case), and look... no OLE :)

Toni Ruža
Wow, you're gr8. Thanks. As for memory protection - I hoped to use Windows OLE mechanisms (as it works when pasting Excel spreadsheet in Word document) to have secure inter-process communication.
Abgan
Abgan
I understood you. I said: "I would recommend you use a text data format for storing the parameters" not the actual data. The methods SimpleCopy and MessWithIt extract the data based on the parameter after the drop.
Toni Ruža
A: 

Ok, it seems that it can't be done the way I wanted it.

Possible solutions are:

  1. Pass some parameters in d&d and do some inter-process communication on your own, after user drops data in target processes window.
  2. Use DataObjectComposite to support multiple drag&drop formats and keyboard modifiers to choose current format. Scenario:
    1. User initiates drag. State of CTRL, ALT and SHIFT is checked, and depending on it the d&d format is selected. DataObjectComposite is created, and has set data in chosen format.
    2. User drops data in target window. Drop target asks dropped DataObject for supported format and retrieves data, knowing what format it is in.

I'm choosing the solution 2., because it doesn't require hand crafting communication between processes and it allows me to avoid unnecessary data retrieval when user wants to drag only the simplest data.

Anyway - Toni, thanks for your answer! Played with it a little and it made me think of d&d and of changing my approach to the problem.

Abgan
No problem. However, I'm not really convinced that you need to have the keyboard shortcuts... maybe thats just because I don't know your problem in detail. Why can't you include the necessary input for retrieving any format and then use what is needed when the user makes the selection at the drop?
Toni Ruža
Because I want to give 3 options for user: copy just the dragged object, copy object and it's closest relatives, or copy all relatives of an object. Option 1. doesn't require fetching anything from DB, while option 3. can take up to one minute to fetch all data.
Abgan
That's why I want to avoid unnecessary data retrieval when user doesn't want to choose option 3. (Option 2. is somewhere in the middle, shouldn't take more than 1-3 secs). Sorry for not putting all the details in question at the beginning.
Abgan
You hinted it was something like this. Does each application instance have it's own database or do they share the same one? In the latter case all you need to transfer with the drag is the primary key of the object and the program that receives the drop can do any of your three use cases.
Toni Ruža
Databases shouldn't be so slow for the operations you described (even when huge amounts of data are in question), maybe you could look in to optimizing your schema.
Toni Ruža
The feature in question regards transferring data between databases. The application is a GUI client for server app, which in turn - operates on DB. And what I want to transfer is an object or "object and all accessible objects in its sub-graph".
Abgan
While getting neighbours of an object is quite quick, retrieving the full result of DFS from database takes time, when graph has 5000+ nodes... :-|
Abgan