本文介绍了可编辑的可重排序(通过拖放)Qt5 QTreeView示例的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在找不到Qt5的QTreeView的体面通用分层可重排序的拖放示例之后,我尝试相应地转换可编辑树模型示例代码.

after being unable to find a decent generic hierarchical reorderable drag and drop example for Qt5's QTreeView, I tried to transform the Editable Tree Model example code accordingly.

有一个相关的问题记录在:在PyQt中具有拖放支持的QTreeView ,但是PyQt4本身不是问题(无论如何我都会将其转换为PyQt;)),treeview +抽象模型无法正常工作.至少,它不会在这里重新排序任何项目.

There's an related question recorded at:QTreeView with drag and drop support in PyQt, but while it's PyQt4, which isn't a problem in itself (I'm going to convert this to PyQt anyway ;)), the treeview + abstract model doesn't work properly. At least, it doesn't reorder any items here.

此示例代码也无法正常工作:它允许移动项目,但是将其删除会导致空白行,但条目不会移动.

This example code doesn't work as well: it allows moving items, but dropping them results in an empty row, but the entry isn't moved.

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);

从理论上讲,这就是全部内容,以便能够对商品进行重新排序.

In theory, this is all, what is needed to be able to reorder items.

这是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 @@

Here's the PyQt5 version: --- 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)

推荐答案

我设法将答案从引用的问题,以使模型项可以通过拖放进行重新排序.但是,我必须指出,所引用的示例和我的答案实际上都处理了类似于 list 模型而不是 tree 模型的问题,因为drag-n-drop并没有不会影响模型项之间的父子关系,而是实现了放置处理以始终执行项重新排序.

I've managed to extend the answer from the referenced question to make the model items reorderable by drag-n-drop. However, I have to point out that both the referenced example and my answer actually deal with the model resembling a list model more than a tree one because drag-n-drop doesn't affect the parent-child relationships between the model items, instead the drop handling is implemented to always perform items reordering.

只要拖放需要序列化一些与要重新放置的商品有关的数据,就可以通过以下方式实现通过拖放的重新排序:

As long as drag-n-drop requires the serialization of some data about the item being repositioned, the reordering via drag-n-drop can be implemented in the following way:

  1. 在拖动时,我们会序列化一些有关被拖动项目的信息
  2. 在下降时,我们:
    • 反序列化此信息
    • 使用此信息在模型中找到每个被拖动项目的原始位置
    • 从模型中删除定位的项目,从而导致其余项目移动其位置;如果模型正确实现removeRows方法,Qt会为我们做到这一点.
    • 将已删除的项目重新插入模型中,但这一次是将其放置在放置项目的之前之后.前一个选项可用于放置第一个项目,后一个选项可用于其他情况.
  1. On drag we serialize some info about the dragged items
  2. On drop we:
    • deserialize this info back
    • use this info to locate each dragged item's original position in the model
    • remove the located items from the model thus causing the remaining items to shift their positions; Qt would do it for us if the model properly implements removeRows method.
    • insert the removed item back into the model but this time at position either before the item onto which it has been dropped or after it. The former option is useful for dropping onto the first item and the latter option is useful for other cases.

这是PyQt4解决方案的完整代码:

Here's the full code of the solution for 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的代码此处.

Upd.: Just in case someone is interested, I've extended this demo to properly deal with the selection after drag-n-drop, the code for PyQt5 and Python 3 can be found here.

这篇关于可编辑的可重排序(通过拖放)Qt5 QTreeView示例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

05-24 21:28