Friday, August 31, 2018

Drag and Drop from a QTreeWidget to another QTreeWidget

Sweet mercy of mercies. I've been trying for *much* too long to figure out how the (hell!) to drag-and-drop simple MIME data from one Qt (in this case, PyQt) QTreeWidget to another QTreeWidget. After what felt like about 1000 pages of StackOverflow that I read through, I finally found a super-helpful example from someone demonstrating custom MIME types.

I just wanted a simple example of drag-and-drop from one QAbstractItemView to another, and so while that example was fantastic, I was able to simplify it much more to demonstrate just a simple-as-heck/dumb-as-anything/silly-old drag-and-drop. The documentation is shockingly bad about Drag-And-Drop in general in Qt, and I feel like it's so overly complicated when you just want to do something simple... I figured I'd repost this demo code here for the possibly-millions of other folks with the exact same question as I had: "How do I just.. like... drag and drop a SIMPLE thing from on QTreeWidget to another?!?!"

My friends, the answer is annoyingly simple, and there's nothing special here. You don't need to set a bazillion obscure flags on your QTreeWidgetItems or the other 1000 things I tried before landing on the simple way to do it. Here you are (and to my future self who is trying to remember this example... you're welcome) :)


from PyQt4 import QtCore
from PyQt4 import QtGui

class DragDropTreeWidget(QtGui.QTreeWidget):
    def __init__(self):
        super(DragDropTreeWidget, self).__init__()        
        self.setRootIsDecorated(False) # I'm using the QTreeWidget like a QTableWidget here.
        self.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
    
    def mimeTypes(self):
        mimetypes = QtGui.QTreeWidget.mimeTypes(self)
        mimetypes.append("text/plain")
        return mimetypes

    def startDrag(self, allowableActions):
        drag = QtGui.QDrag(self)
        
        # only one item is selectable at once so self.selectedItems() should have a length of 1 only
        selectedItems = self.selectedItems()
        if len(selectedItems) < 1:
            return
        # else use the first item
        selectedTreeWidgetItem = selectedItems[0]

        # for this to work, just note that you need to add your own extension to the QTreeWidgetItem
        # so you can implement this simple data getter method.
        dragAndDropName = selectedTreeWidgetItem.dragAndDropName()
        
        mimedata = QtCore.QMimeData()
        mimedata.setText(dragAndDropName)        
        drag.setMimeData(mimedata)
        drag.exec_(allowableActions)

    def dropEvent(self, event):
        if event.mimeData().hasFormat("text/plain"):
            passedData = event.mimeData().text()
            event.acceptProposedAction()
            print "passedData", passedData
            # TODO handle drop event (prob emit a signal to be caught somewhere else)


Magnificent! So that's it... amazingly. Nothing else is required. See... drag and drop in PyQt IS simple... right??! Anyone?!?

Note that to instantiate this demo you should do something like so, twice. Make two of these tree widgets and add them to your MainWindow and then you can test drag and drop between them.
=============
treeWidget = DragDropTreeWidget()        
header = QtGui.QTreeWidgetItem(["Header Name"])
treeWidget.setHeaderItem(header)

# Note this item should actually be a custom extension of QTreeWidgetItem if you want to implement the "dragAndDropName" method mentioned above.
root = QtGui.QTreeWidgetItem("Item in tree") 
treeWidget.addTopLevelItem(root)
treeWidget.setDragEnabled(True)
==============

Happy drag-and-dropping, friends!

Tuesday, August 07, 2018