可编辑的可重新排序(通过拖放)Qt5 QTreeView示例

时间:2016-10-25 22:27:25

标签: c++ qt5 pyqt5 qtreeview qabstractitemmodel

在无法为Qt5的QTreeView找到合适的通用分层可重新排序拖放示例之后,我尝试相应地转换可编辑树模型示例代码。

在以下位置记录了相关问题: QTreeView with drag and drop support in PyQt,但是虽然它的PyQt4本身并不是问题(我还是要将它转换为PyQt;)),树视图+抽象模型并没有。工作正常。至少,它不会对此处的任何项目进行重新排序。

此示例代码也不起作用:它允许移动项目,但删除它们会导致空行,但条目不会被移动。

diff -up editabletreemodel.orig/mainwindow.cpp editabletreemodel/mainwindow.cpp
--- editabletreemodel.orig/mainwindow.cpp   2016-06-10 08:48:56.000000000 +0200
+++ editabletreemodel/mainwindow.cpp        2016-10-25 23:20:09.909671875 +0200
@@ -67,6 +67,7 @@ MainWindow::MainWindow(QWidget *parent)
     file.close();

     view->setModel(model);
+    view->setDragDropMode(QAbstractItemView::InternalMove);
     for (int column = 0; column < model->columnCount(); ++column)
         view->resizeColumnToContents(column);

diff -up editabletreemodel.orig/treemodel.cpp editabletreemodel/treemodel.cpp
--- editabletreemodel.orig/treemodel.cpp    2016-06-10 08:48:56.000000000 +0200
+++ editabletreemodel/treemodel.cpp 2016-10-25 23:23:47.408024344 +0200
@@ -96,10 +96,12 @@ QVariant TreeModel::data(const QModelInd
 //! [3]
 Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
 {
-    if (!index.isValid())
-        return 0;
+    Qt::ItemFlags defaultFlags = Qt::ItemIsEditable | QAbstractItemModel::flags(index);

-    return Qt::ItemIsEditable | QAbstractItemModel::flags(index);
+    if (index.isValid())
+   return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
+    else
+        return Qt::ItemIsDropEnabled | defaultFlags;
 }
 //! [3]

@@ -295,3 +297,8 @@ void TreeModel::setupModelData(const QSt
         ++number;
     }
 }
+
+Qt::DropActions TreeModel::supportedDropActions() const
+{
+    return Qt::MoveAction;
+}
diff -up editabletreemodel.orig/treemodel.h editabletreemodel/treemodel.h
--- editabletreemodel.orig/treemodel.h      2016-06-10 08:48:56.000000000 +0200
+++ editabletreemodel/treemodel.h   2016-10-25 23:19:18.884870266 +0200
@@ -95,6 +95,7 @@ public:
                     const QModelIndex &parent = QModelIndex()) Q_DECL_OVERRIDE;
     bool removeRows(int position, int rows,
                     const QModelIndex &parent = QModelIndex()) Q_DECL_OVERRIDE;
+    Qt::DropActions supportedDropActions() const Q_DECL_OVERRIDE;

 private:
     void setupModelData(const QStringList &lines, TreeItem *parent);

从理论上讲,这就是所有能够重新排序物品所需要的。

这是PyQt5版本:     --- editabletreemodel.py.orig 2015-07-17 13:39:33.000000000 +0200     +++ editabletreemodel.py 2016-10-26 00:24:51.857176297 +0200     @@ -44,7 +44,7 @@

 from PyQt5.QtCore import (QAbstractItemModel, QFile, QIODevice,
         QItemSelectionModel, QModelIndex, Qt)
-from PyQt5.QtWidgets import QApplication, QMainWindow
+from PyQt5.QtWidgets import QApplication, QMainWindow, QAbstractItemView

 import editabletreemodel_rc
 from ui_mainwindow import Ui_MainWindow
@@ -151,10 +151,12 @@ class TreeModel(QAbstractItemModel):
         return item.data(index.column())

     def flags(self, index):
-        if not index.isValid():
-            return 0
+        defaultFlags = Qt.ItemIsEditable | super(TreeModel, self).flags(index)

-        return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable
+        if index.isValid():
+            return defaultFlags | Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled
+        else:
+            return defaultFlags | Qt.ItemIsDropEnabled

     def getItem(self, index):
         if index.isValid():
@@ -296,6 +298,9 @@ class TreeModel(QAbstractItemModel):

             number += 1

+    def supportedDropActions(self):
+        return Qt.MoveAction
+

 class MainWindow(QMainWindow, Ui_MainWindow):
     def __init__(self, parent=None):
@@ -311,6 +316,7 @@ class MainWindow(QMainWindow, Ui_MainWin
         file.close()

         self.view.setModel(model)
+        self.view.setDragDropMode(QAbstractItemView.InternalMove)
         for column in range(model.columnCount()):
             self.view.resizeColumnToContents(column)

1 个答案:

答案 0 :(得分:1)

我设法从引用的问题扩展answer,以便通过拖放操作重新定位模型项。但是,我必须指出,引用的示例和我的答案实际上处理的模型类似于 list 模型而不是,因为drag-n-drop不会#39; t影响模型项之间的父子关系,而实现drop处理以始终执行重新排序的项目。

只要drag-n-drop需要序列化有关正在重新定位的项目的某些数据,就可以通过以下方式实现通过拖放重新排序:

  1. 在拖动时,我们会序列化有关拖动项目的一些信息
  2. 下降我们:
    • 将此信息反序列化
    • 使用此信息查找每个拖动项目在模型中的原始位置
    • 从模型中删除已定位的项目,从而导致其余项目移位;如果模型正确实现removeRows方法,Qt会为我们做。
    • 将删除的项目重新插入到模型中,但这次是在之前放置它的项目或之后它的位置。前一个选项对于放到第一个项目很有用,后一个选项对其他情况很有用。
  3. 这里是PyQt4解决方案的完整代码:

    import sys
    from PyQt4 import QtGui, QtCore
    
    class TreeModel(QtCore.QAbstractItemModel):
        def __init__(self):
            QtCore.QAbstractItemModel.__init__(self)
            self.nodes = ['node0', 'node1', 'node2', 'node3', 'node4', 'node5']
    
        def index(self, row, column, parent):
            if row < 0 or row >= len(self.nodes):
                return QtCore.QModelIndex()
            return self.createIndex(row, column, self.nodes[row])
    
        def parent(self, index):
            return QtCore.QModelIndex()
    
        def rowCount(self, index):
            if index.isValid():
                return 0
            if index.internalPointer() in self.nodes:
                return 0
            return len(self.nodes)
    
        def columnCount(self, index):
            if index.isValid():
                return 0
            return 1
    
        def data(self, index, role):
            if not index.isValid():
                return None
            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 insertRows(self, row, count, index):
            if index.isValid():
                return False
            if count <= 0:
                return False
            # inserting 'count' empty rows starting at 'row'
            self.beginInsertRows(QtCore.QModelIndex(), row, row + count - 1)
            for i in range(0, count):
                self.nodes.insert(row + i, '')
            self.endInsertRows()
            return True
    
        def removeRows(self, row, count, index):
            if index.isValid():
                return False
            if count <= 0:
                return False
            num_rows = self.rowCount(QtCore.QModelIndex())
            self.beginRemoveRows(QtCore.QModelIndex(), row, row + count - 1)
            for i in range(count, 0, -1):
                self.nodes.pop(row - i + 1)
            self.endRemoveRows()
            return True
    
        def setData(self, index, value, role):
            if not index.isValid():
                return False
            if index.row() < 0 or index.row() > len(self.nodes):
                return False
            self.nodes[index.row()] = str(value)
            self.dataChanged.emit(index, index)
    
        def mimeTypes(self):
            return ['application/vnd.treeviewdragdrop.list']
    
        def mimeData(self, indexes):
            mimedata = QtCore.QMimeData()
            encoded_data = QtCore.QByteArray()
            stream = QtCore.QDataStream(encoded_data, QtCore.QIODevice.WriteOnly)
            for index in indexes:
                if index.isValid():
                    text = self.data(index, 0)
                    stream << QtCore.QString(text)
            mimedata.setData('application/vnd.treeviewdragdrop.list', encoded_data)
            return mimedata
    
        def dropMimeData(self, data, action, row, column, parent):
            if action == QtCore.Qt.IgnoreAction:
                return True
            if not data.hasFormat('application/vnd.treeviewdragdrop.list'):
                return False
            if column > 0:
                return False
    
            num_rows = self.rowCount(QtCore.QModelIndex())
    
            begin_row = 0
            if row != -1:
                begin_row = row
            elif parent.isValid():
                begin_row = parent.row()
            else:
                begin_row = num_rows
    
            if begin_row != num_rows and begin_row != 0:
                begin_row += 1
    
            encoded_data = data.data('application/vnd.treeviewdragdrop.list')
            stream = QtCore.QDataStream(encoded_data, QtCore.QIODevice.ReadOnly)
            new_items = []
            rows = 0
            while not stream.atEnd():
                text = QtCore.QString()
                stream >> text
                new_items.append(text)
                rows += 1
    
            # insert the new rows for the dropped items and set the data to these items appropriately
            self.insertRows(begin_row, rows, QtCore.QModelIndex())
            for text in new_items:
                idx = self.index(begin_row, 0, QtCore.QModelIndex())
                self.setData(idx, text, 0)
                self.dataChanged.emit(idx, idx)
                begin_row += 1
    
            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()
    

    更新。:为了防止有人感兴趣,我已经扩展了这个演示,以便在拖放后正确处理选择,PyQt5和Python 3的代码可以是找到here