12

In PyQt 4 I would like to create a QTreeView with possibility to reorganize its structure with drag and drop manipulation.

I have implemented my own model(QAbstractItemModel) for QTreeView so my QTreeView properly displays the data. Now I would like to add drag and drop support for tree's nodes to be able to move a node inside the tree from one parent to another one, drag-copy and so on, but I cannot find any complete tutorial how to achieve this. I have found few tutorials and hints for QTreeWidget, but not for QTreeView with custom model. Can someone point me where to look?

Thank you.

Eli Bendersky
  • 263,248
  • 89
  • 350
  • 412
Ondrej Vencovsky
  • 3,188
  • 9
  • 28
  • 34

1 Answers1

19

You can enable drag and drop support for tree view items by setting QtGui.QAbstractItemView.InternalMove into the dragDropMode property of the treeview control. Also take a look at the documentation here Using drag & drop with item views. Below is a small example of a treeview with internal drag and drop enabled for its items.

import sys
from PyQt4 import QtGui, QtCore

class MainForm(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(MainForm, self).__init__(parent)

        self.model = QtGui.QStandardItemModel()

        for k in range(0, 4):
            parentItem = self.model.invisibleRootItem()
            for i in range(0, 4):
                item = QtGui.QStandardItem(QtCore.QString("item %0 %1").arg(k).arg(i))
                parentItem.appendRow(item)
                parentItem = item

        self.view = QtGui.QTreeView()
        self.view.setModel(self.model)
        self.view.setDragDropMode(QtGui.QAbstractItemView.InternalMove)

        self.setCentralWidget(self.view)

def main():
    app = QtGui.QApplication(sys.argv)
    form = MainForm()
    form.show()
    app.exec_()

if __name__ == '__main__':
    main()

Edit0: treeview + abstract model with drag and drop support

import sys
from PyQt4 import QtGui, QtCore

class TreeModel(QtCore.QAbstractItemModel):
    def __init__(self):
        QtCore.QAbstractItemModel.__init__(self)
        self.nodes = ['node0', 'node1', 'node2']

    def index(self, row, column, parent):
        return self.createIndex(row, column, self.nodes[row])

    def parent(self, index):
        return QtCore.QModelIndex()

    def rowCount(self, index):
        if index.internalPointer() in self.nodes:
            return 0
        return len(self.nodes)

    def columnCount(self, index):
        return 1

    def data(self, index, role):
        if role == 0: 
            return index.internalPointer()
        else:
            return None

    def supportedDropActions(self): 
        return QtCore.Qt.CopyAction | QtCore.Qt.MoveAction         

    def flags(self, index):
        if not index.isValid():
            return QtCore.Qt.ItemIsEnabled
        return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | \
               QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled        

    def mimeTypes(self):
        return ['text/xml']

    def mimeData(self, indexes):
        mimedata = QtCore.QMimeData()
        mimedata.setData('text/xml', 'mimeData')
        return mimedata

    def dropMimeData(self, data, action, row, column, parent):
        print 'dropMimeData %s %s %s %s' % (data.data('text/xml'), action, row, parent) 
        return True


class MainForm(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(MainForm, self).__init__(parent)

        self.treeModel = TreeModel()

        self.view = QtGui.QTreeView()
        self.view.setModel(self.treeModel)
        self.view.setDragDropMode(QtGui.QAbstractItemView.InternalMove)

        self.setCentralWidget(self.view)

def main():
    app = QtGui.QApplication(sys.argv)
    form = MainForm()
    form.show()
    app.exec_()

if __name__ == '__main__':
    main()

hope this helps, regards

ekhumoro
  • 115,249
  • 20
  • 229
  • 336
serge_gubenko
  • 20,186
  • 2
  • 61
  • 64
  • Thank you for your answer. But it doesn't solve my problem. I know using QStandardItemModel + QStandardItem works as expected. But I need to make it working using CUSTOM model, pure subclass from QAbstractItemModel. I think I need to implement some methods in model or use some specialized object for the tree items. Now drag doesn't show even drop indicator even if this is set to be shown... Obviously I'm just missing something. – Ondrej Vencovsky Nov 13 '10 at 10:09
  • I have one more comment. If you take an example "simpletreemodel" in PyQt standard examples: how to add drag and drop support to it? If I just add setAcceptsDrop(True), setDragEnabled(True), setDragDropMode(view.InternalMove) to view and flags ItemIsDragEnabled | ItemIsDropEnabled to model, it simply is not enough to have drag and drop functionality. – Ondrej Vencovsky Nov 13 '10 at 12:10
  • never seen any standard example for that; but remember, QT is open source so you can always take a look what's going on inside QStandardItemModel which is QAbstractItemModel descendant and supports drag and drop. I've edited my post with simple abstract model example, it should support drag and drop. – serge_gubenko Nov 13 '10 at 14:52
  • 1
    Yes, it works now! You don't have an idea how much I want to thank you. Really thanks. – Ondrej Vencovsky Nov 13 '10 at 19:52
  • I have found that QString does not exist in PyQt inside of python3. I read this article which solved it for me: http://inputvalidation.blogspot.com/2010/10/python3-pyqt4-and-missing-qstring.html – REA_ANDREW Sep 02 '11 at 11:54