- Drag and drop support within wxPython is a conversation between a "drop source" and a "drop target". The conversation is accomplished by sharing a "data object" which includes certain pieces of metadata (primarily the data types available from the drop source) as well as the actual shared data.
What Objects are Involved
- Source window -- Interprets an event as the beginning of a new drag-and-drop request. Instantiates a new drop source
and triggers the drag-and-drop sequence by calling DoDragDrop on the drop source object. Responds to the result code of the drag-and-drop sequence (move/copy/cancel/fail).
wxDropSource -- Provides common functionality for drag-and-drop source windows (UI interactions, data object storage)
- Destination window -- Advertises acceptable data types by
calling SetDropTarget with a configured drop target object.
wxDropTarget -- Responds to various "pseudo-events" during the drag-and-drop process, most importantly the OnData "event" which indicates that an object has been dropped on the associated window.
wxDataObject -- Provides storage and metadata relating to the data being transferred during a drag-and-drop operation. May support one of the built-in data types (text, bitmap, file), a single declared data type, or multiple data types. Includes: wxTextDataObject, wxBitmapDataObject, wxFileDataObject, wxDataObjectSimple, wxDataObjectComposite, wxDataObject
Create or choose a wxDataObject subclass appropriate to the data being shared
Provide OnData method to handle incoming data and update your application with the results of the drag-and-drop operation.
- If you are not using a built-in data type, choose a type specifier (a unique string used to identify the datatype). Only targets whose type specifiers include one of the current data source specifiers will be eligible for drops.
Create a wxPyDropTarget subclass (and instance)
Provide an OnData method to handle a drop "event"
Call self.GetData() to transfer data to the target's wxDataObject instance
Call wxDataObject.GetData() to retrieve the actual shared data
- Update your application to reflect drag-and-drop operation (in many cases you will need a pointer to the target window within the drop target instance to accomplish this).
Instantiate an instance of your wxDataObject class
Bind a wxDataObject to the drop target by calling SetDataObject. You will also want to keep a pointer to the wxDataObject in the drop target instance, so that you can call wxDataObject.GetData() within the OnData method.
Bind the drop target to the target window using wxWindow.SetDropTarget
- Handle an event on the source window which signals the start of the drag-and-drop cycle
Create a wxDropSource
Set the data object of the drop source to another instance of your wxDataObject class
Call the drop source DoDragDrop() method to begin the drag-and-drop operation
Respond to the result code of the DoDragDrop method as appropriate to your application
- Discuss the various concerns relating to the data object. Python pickling options Multiple formats -- currently only works for source in Python (as far as I can see) Format specifiers, do these need to be discussed? Special Data Types
- Simple drag-and-drop
1 # A very simple Drag and Drop Example 2 # provided with no warranty whatsoever for any purpose, ever 3 4 # A very simple Drag and Drop Example 5 # provided with no warranty whatsoever for any purpose, ever 6 7 # This code creates a Text Control from which Text can be dragged, 8 # a Text Control to which Text can be dragged (from the first Text Control or from other applications), 9 # and a Text Control to which Files can be dragged from outside this application. 10 # While the later two windows can receive data from outside the application, the first window 11 # does not appear to be able to provide text to other applications. Please feel free to fix 12 # this if you know how, as I think that would be more useful as an example. 13 14 # It is designed to demonstrate the fundamentals of very simple drag-and-drop operations. 15 16 """ This mini-app is designed to demonstrate simple Drag and Drop functioning in wx.Python """ 17 18 __author__ = 'David Woods, Wisconsin Center for Education Research <email@example.com>' 19 20 # Import wx.Python 21 import wx 22 23 # Declare GUI Constants 24 MENU_FILE_EXIT = wx.NewId() 25 DRAG_SOURCE = wx.NewId() 26 27 # Define Text Drop Target class 28 class TextDropTarget(wx.TextDropTarget): 29 """ This object implements Drop Target functionality for Text """ 30 def __init__(self, obj): 31 """ Initialize the Drop Target, passing in the Object Reference to 32 indicate what should receive the dropped text """ 33 # Initialize the wx.TextDropTarget Object 34 wx.TextDropTarget.__init__(self) 35 # Store the Object Reference for dropped text 36 self.obj = obj 37 38 def OnDropText(self, x, y, data): 39 """ Implement Text Drop """ 40 # When text is dropped, write it into the object specified 41 self.obj.WriteText(data + '\n\n') 42 43 # Define File Drop Target class 44 class FileDropTarget(wx.FileDropTarget): 45 """ This object implements Drop Target functionality for Files """ 46 def __init__(self, obj): 47 """ Initialize the Drop Target, passing in the Object Reference to 48 indicate what should receive the dropped files """ 49 # Initialize the wxFileDropTarget Object 50 wx.FileDropTarget.__init__(self) 51 # Store the Object Reference for dropped files 52 self.obj = obj 53 54 def OnDropFiles(self, x, y, filenames): 55 """ Implement File Drop """ 56 # For Demo purposes, this function appends a list of the files dropped at the end of the widget's text 57 # Move Insertion Point to the end of the widget's text 58 self.obj.SetInsertionPointEnd() 59 # append a list of the file names dropped 60 self.obj.WriteText("%d file(s) dropped at %d, %d:\n" % (len(filenames), x, y)) 61 for file in filenames: 62 self.obj.WriteText(file + '\n') 63 self.obj.WriteText('\n') 64 65 66 67 class MainWindow(wx.Frame): 68 """ This window displays the GUI Widgets. """ 69 def __init__(self,parent,id,title): 70 wx.Frame.__init__(self,parent, wx.ID_ANY, title, size = (800,600), style=wx.DEFAULT_FRAME_STYLE|wx.NO_FULL_REPAINT_ON_RESIZE) 71 self.SetBackgroundColour(wx.WHITE) 72 73 # Menu Bar 74 # Create a MenuBar 75 menuBar = wx.MenuBar() 76 # Build a Menu Object to go into the Menu Bar 77 menu1 = wx.Menu() 78 menu1.Append(MENU_FILE_EXIT, "E&xit", "Quit Application") 79 # Place the Menu Item in the Menu Bar 80 menuBar.Append(menu1, "&File") 81 # Place the Menu Bar on the ap 82 self.SetMenuBar(menuBar) 83 #Define Events for the Menu Items 84 wx.EVT_MENU(self, MENU_FILE_EXIT, self.CloseWindow) 85 86 # GUI Widgets 87 # Define a Text Control from which Text can be dragged for dropping 88 # Label the control 89 wx.StaticText(self, -1, "Text Drag Source (left-click to select, right-click to drag)", (10, 1)) 90 # Create a Text Control 91 self.text = wx.TextCtrl(self, DRAG_SOURCE, "", pos=(10,15), size=(350,500), style = wx.TE_MULTILINE|wx.HSCROLL) 92 # Make this control a Text Drop Target 93 # Create a Text Drop Target object 94 dt1 = TextDropTarget(self.text) 95 # Link the Drop Target Object to the Text Control 96 self.text.SetDropTarget(dt1) 97 # Put some text in the control as a starting place to have something to copy 98 for x in range(20): 99 self.text.WriteText("This is line %d of some text to drag.\n" % x) 100 # Define Right-Click as start of Drag 101 wx.EVT_RIGHT_DOWN(self.text, self.OnDragInit) 102 103 # Define a Text Control to recieve Dropped Text 104 # Label the control 105 wx.StaticText(self, -1, "Text Drop Target", (370, 1)) 106 # Create a read-only Text Control 107 self.text2 = wx.TextCtrl(self, -1, "", pos=(370,15), size=(410,235), style = wx.TE_MULTILINE|wx.HSCROLL|wx.TE_READONLY) 108 # Make this control a Text Drop Target 109 # Create a Text Drop Target object 110 dt2 = TextDropTarget(self.text2) 111 # Link the Drop Target Object to the Text Control 112 self.text2.SetDropTarget(dt2) 113 114 # Define a Text Control to receive Dropped Files 115 # Label the control 116 wx.StaticText(self, -1, "File Drop Target (from off application only)", (370, 261)) 117 # Create a read-only Text Control 118 self.text3 = wx.TextCtrl(self, -1, "", pos=(370,275), size=(410,235), style = wx.TE_MULTILINE|wx.HSCROLL|wx.TE_READONLY) 119 # Make this control a File Drop Target 120 # Create a File Drop Target object 121 dt3 = FileDropTarget(self.text3) 122 # Link the Drop Target Object to the Text Control 123 self.text3.SetDropTarget(dt3) 124 125 # Display the Window 126 self.Show(True) 127 128 129 def CloseWindow(self, event): 130 """ Close the Window """ 131 self.Close() 132 133 def OnDragInit(self, event): 134 """ Begin a Drag Operation """ 135 # Create a Text Data Object, which holds the text that is to be dragged 136 tdo = wx.PyTextDataObject(self.text.GetStringSelection()) 137 # Create a Drop Source Object, which enables the Drag operation 138 tds = wx.DropSource(self.text) 139 # Associate the Data to be dragged with the Drop Source Object 140 tds.SetData(tdo) 141 # Initiate the Drag Operation 142 tds.DoDragDrop(True) 143 144 145 146 class MyApp(wx.App): 147 """ Define the Drag and Drop Example Application """ 148 def OnInit(self): 149 """ Initialize the Application """ 150 # Declare the Main Application Window 151 frame = MainWindow(None, -1, "Drag and Drop Example") 152 # Show the Application as the top window 153 self.SetTopWindow(frame) 154 return True 155 156 157 # Declare the Application and start the Main Loop 158 app = MyApp(0) 159 app.MainLoop()
- David K. Woods, Wisconsin Center for Education Research, University of Wisconsin, Madison
(If you like this example, there's another, more complex example of Drag and Drop behavior in the TreeControls section.)
This class is used when you wish to be able to deal with multiple data formats. The sample code should demonstrate this I've left out any processing, and just show you how to "get" the required data.
1 class YourDropTarget(wx.PyDropTarget): 2 """Implements drop target functionality to receive files, bitmaps and text""" 3 def __init__(self): 4 wx.PyDropTarget.__init__(self) 5 self.do = wx.DataObjectComposite() # the dataobject that gets filled with the appropriate data 6 self.filedo = wx.FileDataObject() 7 self.textdo = wx.TextDataObject() 8 self.bmpdo = wx.BitmapDataObject() 9 self.do.Add(self.filedo) 10 self.do.Add(self.bmpdo) 11 self.do.Add(self.textdo) 12 self.SetDataObject(self.do) 13 14 15 def OnData(self, x, y, d): 16 """ 17 Handles drag/dropping files/text or a bitmap 18 """ 19 if self.GetData(): 20 df = self.do.GetReceivedFormat().GetType() 21 22 if df in [wx.DF_UNICODETEXT, wx.DF_TEXT]: 23 24 text = self.textdo.GetText() 25 26 elif df == wx.DF_FILENAME: 27 for name in self.filedo.GetFilenames(): 28 print name 29 30 elif df == wx.DF_BITMAP: 31 bmp = self.bmpdo.GetBitmap() 32 33 return d # you must return this
Accepting Thunderbird drag and drops
Here's an example of how to use wx.DataObjectComposite to accept Thunderbird drag and drops as well as text and filenames.
1 import wx 2 3 4 class DropTarget(wx.DropTarget): 5 def __init__(self, textCtrl, *args, **kwargs): 6 super(DropTarget, self).__init__(*args, **kwargs) 7 self.textCtrl = textCtrl 8 self.composite = wx.DataObjectComposite() 9 self.textDropData = wx.TextDataObject() 10 self.fileDropData = wx.FileDataObject() 11 self.thunderbirdDropData = wx.CustomDataObject('text/x-moz-message') 12 self.composite.Add(self.thunderbirdDropData) 13 self.composite.Add(self.textDropData) 14 self.composite.Add(self.fileDropData) 15 self.SetDataObject(self.composite) 16 17 def OnDrop(self, x, y): 18 return True 19 20 def OnData(self, x, y, result): 21 self.GetData() 22 formatType, formatId = self.GetReceivedFormatAndId() 23 if formatId == 'text/x-moz-message': 24 return self.OnThunderbirdDrop() 25 elif formatType in (wx.DF_TEXT, wx.DF_UNICODETEXT): 26 return self.OnTextDrop() 27 elif formatType == wx.DF_FILENAME: 28 return self.OnFileDrop() 29 30 def GetReceivedFormatAndId(self): 31 format = self.composite.GetReceivedFormat() 32 formatType = format.GetType() 33 try: 34 formatId = format.GetId() # May throw exception on unknown formats 35 except: 36 formatId = None 37 return formatType, formatId 38 39 def OnThunderbirdDrop(self): 40 self.textCtrl.AppendText(self.thunderbirdDropData.GetData().decode('utf-16')) 41 self.textCtrl.AppendText('\n') 42 return wx.DragCopy 43 44 def OnTextDrop(self): 45 self.textCtrl.AppendText(self.textDropData.GetText() + '\n') 46 return wx.DragCopy 47 48 def OnFileDrop(self): 49 for filename in self.fileDropData.GetFilenames(): 50 self.textCtrl.AppendText(filename + '\n') 51 return wx.DragCopy 52 53 54 class Frame(wx.Frame): 55 def __init__(self, *args, **kwargs): 56 super(Frame, self).__init__(*args, **kwargs) 57 textCtrl = wx.TextCtrl(self, style=wx.TE_MULTILINE) 58 textCtrl.SetDropTarget(DropTarget(textCtrl)) 59 60 61 app = wx.App(False) 62 frame = Frame(None) 63 frame.Show() 64 app.MainLoop()
I will add a example for dragging items in a wxTreeCtrl and what is more interesting how to drag a branch in a wxTreeCtrl. if added it will be found under ListAndTreeControls.
Files-and-Folders drop target Classes
The following are reusable classes based on wx.TextCtrl and wx.ListCtrl, each which receive file and folder names dropped from the filesystem GUI. These classes uses the file drag-and-drop technique shown above. When each drop is made to any of these controls, a "drop data" dictionary is created for each drop event that holds the dropFile data processed into more directly useful forms, i.e., the common parent path, a list of basenames and leaf folders, etc.
To be able to get and process the dropped files' data dictionary without altering the base classes a callback-function reference is passed in when instantiating any of the classes. The drop handler simply calls the main app's callback function with the drop-data dictionary as the only call parameter. This way the app can define a custom callback function that can process the file and folder data uniquely to each instantiated control. Since the drop controls are classes, multiple controls based on these classes may be individual drop targets in the same app having completely separate file/folder drop processing.For instance (no pun intended), Both the multi-files drop control and the multi-folder/directory controls are both derived from the [ FilesDirsDropCtrl.py ] class. One simply filters the drop dictionary for only files and links while the other accepts only folders.
When adding files and folders, duplicate filenames and folders are ignored. Each control displays a simple message in the form of a pseudo (false) row data item. On the first drop this message is replaced by row(s) of true file and folder path info. The multi-line controls have the additional capability in which the user can delete line items using a context (right-click) menu. Deleting all the listed dropped files causes the help message to be redisplayed.More context menu items can easily be added, such as "delete all row data", "save row data to file", "add file contents to row data", etc.
When files and folders are dropped on the multi-line controls, the first column widths undergo true autosizing of both the row data entries and the column headers (whichever is wider) which, unfortunately, is lacking in tha basic wx.ListCtrl column autosizing method.
Ray Pasco 2012-06-05