问题
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.
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.
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)
回答1:
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:
- On drag we serialize some info about the dragged items
- 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.
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()
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.
来源:https://stackoverflow.com/questions/40250771/editable-reorderable-by-drag-and-drop-qt5-qtreeview-example