Drag and drop utilization without subclassing widgets
The original idea for this comes from a blog entry of Roberto Alsina.
Often enough, you find yourself with the problem, that you created a nice UI with Qt Designer, subclassed it in PyQt and you want to add drag and drop support to some widgets of your dialog. With "standard" Qt, there is no way of getting around creating a subclass for your widget and override some functions, but, due to the dynamic nature of Python, we can avoid that inconvenience easily.
Dropping
Imagine you have a QListBox on your dialog and want to be able to drop files from your file manager into it. According to the Qt docs, we have to override dragEnterEvent(...) and dropEvent(...) at least. First, we add this functions (with a modified name) to our dialog class (assuming that there is a QListBox by the name "listbox"):
class MyDialog(MyDesignerUI):
def __init__(self, *args):
MyDesignerUI.__init__(self,*)
def lbDragEnterEvent(self, event):
event.accept(qt.QUriDrag.canDecode(event))
def lbDropEvent(self, event):
files = qt.QStringList()
if qt.QUriDrag.decodeLocalFiles(event, files):
self.listbox.insertStringList(files)
Of course, this code does not do anything. For it to work, we have to set the "acceptDrops" property of our listbox to True (do this in Designer) and add following two lines to our dialog's init function (that's the actual magic):
class MyDialog(MyDesignerUI):
def __init__(self, *args):
MyDesignerUI.__init__(self,*)
self.listbox.__class__.dragEnterEvent = self.lbDragEnterEvent
self.listbox.__class__.dropEvent = self.lbDropEvent
Now, the two functions in MyDialog are called if the are invoked by Qt. Please note, that self in these functions refers to MyDialog, not to listbox.
Dragging
For dragging, it works the same way. Say you have a listbox with filenames (of music tracks, pictures) and you want to drag them to another program (viewer, player). The docs tells us that dragObject(...) is what we want to override. So we just write this function (once again as a member of MyDialog):
def lbDragObject(self):
q = qt.QUriDrag(self)
q.setUris(filter(lambda x: x.isSeletected(), ListBoxIterator(self.listbox)))
return q
(For an explanation of ListBoxIterator, look and ListBoxAndListViewIterators.)
In the constructor, we write the following:
self.listbox.__class__.dragObject = self.lbDragObject
and we have a DnD-capable application without subclassing. By default, Qt starts a drag operation as move or copy, depending on what keys you press while starting to drag, if you want to customize that, startDrag(...) is the function you have to modify.
Comment
The QListBox class doesn't have a starDrag method. So the dropping is fine but what about the dragging ?
Outlook
Of course, you can do this for every function in every class, and if you only override a fistful of functions, it is more convenient to do it this way. If you want to reuse the customizations, you should better create your own subclass and take the hassle with Designer and custom PyQt widgets into account.
PyQt Wiki