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 Q!ListBox 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 Q!ListBox 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.

DragAndDropWithPyQt (last edited 2009-04-14 07:27:24 by localhost)