我有一个自定义结构的QList
,我正在使用自定义模型类(QAbstractListModel
的子类)在一维QListView中显示这些结构。我已经重写了方法rowCount
、flags
和data
,以从结构元素构造一个显示字符串。
现在,我想启用内部拖放功能,通过在其他项目之间拖放项目来对列表中的项目进行重新排序,但这项任务似乎非常复杂。我到底需要覆盖哪些内容,需要设置哪些参数?我试了很多东西,我试过了
view->setDragEnabled( true );
view->setAcceptDrops( true );
view->setDragDropMode( QAbstractItemView::InternalMove );
view->setDefaultDropAction( Qt::MoveAction );
我试过了
Qt::DropActions supportedDropActions() const override {
return Qt::MoveAction;
}
Qt::ItemFlags flags( const QModelIndex & index ) const override{
return QAbstractItemModel::flags( index ) | Qt::ItemIsDragEnabled;
}
我尝试实现了insertRows
和removeRows
,但仍然不起作用。
我还没有找到一个代码可以完全做到这一点的例子。官方文档非常深入地介绍了视图/模型模式是如何工作的,以及如何从外部应用程序或其他小部件进行拖放,但我不想要这些。我只想简单的内部拖放手动重新排序的项目中的一个列表视图。
有人能帮帮我吗?否则我会为此发疯的。
编辑:根据请求添加insertRows/removeRows实现:
bool insertRows( int row, int count, const QModelIndex & parent ) override
{
QAbstractListModel::beginInsertRows( parent, row, row + count - 1 );
for (int i = 0; i < count; i++)
AObjectListModel<Object>::objectList.insert( row, Object() );
QAbstractListModel::endInsertRows();
return true;
}
bool removeRows( int row, int count, const QModelIndex & parent ) override
{
if (row < 0 || row + count > AObjectListModel<Object>::objectList.size())
return false;
QAbstractListModel::beginRemoveRows( parent, row, row + count - 1 );
for (int i = 0; i < count; i++)
AObjectListModel<Object>::objectList.removeAt( row );
QAbstractListModel::endRemoveRows();
return true;
}
objectList
为QList,其中Object为模板参数。
发布于 2019-06-30 06:17:25
当您想要在自定义模型中重新组织项目时,您必须实现所有需要的操作:-如何插入和删除行-如何获取和设置数据-如何序列化项目(构建mimedata) -如何取消序列化项目
使用QStringList
作为数据源的自定义模型的示例:
模型的最小实现应该是:
class CustomModel: public QAbstractListModel
{
public:
CustomModel()
{
internalData = QString("abcdefghij").split("");
}
int rowCount(const QModelIndex &parent) const
{
return internalData.length();
}
QVariant data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.parent().isValid())
return QVariant();
if (role != Qt::DisplayRole)
return QVariant();
return internalData.at(index.row());
}
private:
QStringList internalData;
};
我们必须添加插入/删除行和设置数据的方法:
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::DisplayRole)
{
if (role != Qt::DisplayRole)
return false;
internalData[index.row()] = value.toString();
return true;
}
bool insertRows(int row, int count, const QModelIndex &parent)
{
if (parent.isValid())
return false;
for (int i = 0; i != count; ++i)
internalData.insert(row + i, "");
return true;
}
bool removeRows(int row, int count, const QModelIndex &parent)
{
if (parent.isValid())
return false;
beginRemoveRows(parent, row, row + count - 1);
for (int i = 0; i != count; ++i)
internalData.removeAt(row);
endRemoveRows();
return true;
}
对于拖放部分:
首先,我们需要定义一个mime类型来定义反序列化数据的方式:
QStringList mimeTypes() const
{
QStringList types;
types << CustomModel::MimeType;
return types;
}
其中,CustomModel::MimeType
是像"application/my.custom.model"
这样的常量字符串
将使用canDropMimeData
方法检查丢弃的数据是否合法。因此,我们可以丢弃外部数据:
bool canDropMimeData(const QMimeData *data,
Qt::DropAction action, int /*row*/, int /*column*/, const QModelIndex& /*parent*/)
{
if ( action != Qt::MoveAction || !data->hasFormat(CustomModel::MimeType))
return false;
return true;
}
然后,我们可以基于内部数据创建我们的mime数据:
QMimeData* mimeData(const QModelIndexList &indexes) const
{
QMimeData* mimeData = new QMimeData;
QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
for (const QModelIndex &index : indexes) {
if (index.isValid()) {
QString text = data(index, Qt::DisplayRole).toString();
stream << text;
}
}
mimeData->setData(CustomModel::MimeType, encodedData);
return mimeData;
}
现在,我们必须处理丢弃的数据。我们必须反序列化mime数据,插入新行以将数据设置在正确的位置(对于Qt::MoveAction
,旧行将被自动删除。这就是我们必须实现removeRows
的原因):
bool dropMimeData(const QMimeData *data,
Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
if (!canDropMimeData(data, action, row, column, parent))
return false;
if (action == Qt::IgnoreAction)
return true;
else if (action != Qt::MoveAction)
return false;
QByteArray encodedData = data->data("application/my.custom.model");
QDataStream stream(&encodedData, QIODevice::ReadOnly);
QStringList newItems;
int rows = 0;
while (!stream.atEnd()) {
QString text;
stream >> text;
newItems << text;
++rows;
}
insertRows(row, rows, QModelIndex());
for (const QString &text : qAsConst(newItems))
{
QModelIndex idx = index(row, 0, QModelIndex());
setData(idx, text);
row++;
}
return true;
}
如果你想了解更多关于Qt中的拖放系统的信息,可以看看the documentation。
发布于 2019-07-01 02:42:42
除了Romha的很好的答案之外,我还想补充一些关于它是如何工作的更多细节,以及它的困惑之处。
官方的documentation says QAbstractItemModel
有mimeTypes
,mimeData
和dropMimeData
的默认实现,只要你正确地实现了data
,setData
,insertRows
和removeRows
,它们就应该可以用于内部移动和复制操作。
从某种角度来看,他们是对的。它不会覆盖mimeData
和dropMimeData
,但仅当底层数据结构仅包含单个字符串时才有效,即从data
返回并在setData
中作为DisplayRole接收的字符串。当您有一个包含多个元素的复合对象列表(如我所拥有的),其中只有一个元素用于DisplayRole时,例如
struct Elem {
QString name;
int i;
bool b;
}
QVariant data( const QModelIndex & index, int role ) const override
{
return objectList[ index.row() ].name;
}
bool setData( const QModelIndex & index, const QVariant & value, int role ) override
{
objectList[ index.row() ].name = value.toString();
}
然后,缺省实现将实际执行此操作
QVariant data = data( oldIndex, Qt::DisplayRole );
insertRows( newIndex, 1 )
setData( newIndex, data, Qt::DisplayRole )
removeRows( oldIndex, 1 )
因此,只有正确地移动名称,并保持结构的其余部分不变。这现在说得通了,但是这个系统太复杂了,我以前没有意识到。
因此,需要使用自定义mimeData
和dropMimeData
来移动结构的整个内容
发布于 2021-03-30 16:16:21
下面是一个用Python编写的明显的例子:
import sys
from PySide6 import QtCore, QtGui, QtWidgets
from PySide6.QtCore import (Qt, QStringListModel, QModelIndex,
QMimeData, QByteArray, QDataStream, QIODevice)
from PySide6.QtWidgets import (QApplication, QMainWindow, QListView, QAbstractItemView, QPushButton, QVBoxLayout, QWidget)
class DragDropListModel(QStringListModel):
def __init__(self, parent=None):
super(DragDropListModel, self).__init__(parent)
# self.myMimeTypes = 'application/vnd.text.list' # 可行
# self.myMimeTypes = "text/plain" # 可行
self.myMimeTypes = 'application/json' # 可行
def supportedDropActions(self):
# return Qt.CopyAction | Qt.MoveAction # 拖动时复制并移动相关项目
return Qt.MoveAction # 拖动时移动相关项目
def flags(self, index):
defaultFlags = QStringListModel.flags(self, index)
if index.isValid():
return Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled | defaultFlags
else:
return Qt.ItemIsDropEnabled | defaultFlags
def mimeTypes(self):
return [self.myMimeTypes]
# 直接将indexes里面对应的数据取出来,然后打包进了QMimeData()对象,并返回
def mimeData(self, indexes):
mmData = QMimeData()
encodedData = QByteArray()
stream = QDataStream(encodedData, QIODevice.WriteOnly)
for index in indexes:
if index.isValid():
text = self.data(index, Qt.DisplayRole)
stream << text # 测试,也行
# stream.writeQString(str(text)) # 原始, 可行
mmData.setData(self.myMimeTypes, encodedData)
return mmData
def canDropMimeData(self, data, action, row, column, parent):
if data.hasFormat(self.myMimeTypes) is False:
return False
if column > 0:
return False
return True
def dropMimeData(self, data, action, row, column, parent):
if self.canDropMimeData(data, action, row, column, parent) is False:
return False
if action == Qt.IgnoreAction:
return True
beginRow = -1
if row != -1: # 表示
print("case 1: ROW IS NOT -1, meaning inserting in between, above or below an existing node")
beginRow = row
elif parent.isValid():
print("case 2: PARENT IS VALID, inserting ONTO something since row was not -1, "
"beginRow becomes 0 because we want to "
"insert it at the beginning of this parents children")
beginRow = parent.row()
else:
print("case 3: PARENT IS INVALID, inserting to root, "
"can change to 0 if you want it to appear at the top")
beginRow = self.rowCount(QModelIndex())
print(f"row={row}, beginRow={beginRow}")
encodedData = data.data(self.myMimeTypes)
stream = QDataStream(encodedData, QIODevice.ReadOnly)
newItems = []
rows = 0
while stream.atEnd() is False:
text = stream.readQString()
newItems.append(str(text))
rows += 1
self.insertRows(beginRow, rows, QModelIndex()) # 先插入多行
for text in newItems: # 然后给每一行设置数值
idx = self.index(beginRow, 0, QModelIndex())
self.setData(idx, text)
beginRow += 1
return True
class DemoDragDrop(QWidget):
def __init__(self, parent=None):
super(DemoDragDrop, self).__init__(parent)
# 设置窗口标题
self.setWindowTitle('drag&drop in PySide6')
# 设置窗口大小
self.resize(480, 320)
self.initUi()
def initUi(self):
self.vLayout = QVBoxLayout(self)
self.listView = QListView(self)
self.listView.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.listView.setDragEnabled(True)
self.listView.setAcceptDrops(True)
self.listView.setDropIndicatorShown(True)
self.ddm = DragDropListModel() # 该行和下面4行的效果类似
# self.listView.setDragDropMode(QAbstractItemView.InternalMove)
# self.listView.setDefaultDropAction(Qt.MoveAction)
# self.listView.setDragDropOverwriteMode(False)
# self.ddm = QStringListModel()
self.ddm.setStringList(['Item 1', 'Item 2', 'Item 3', 'Item 4'])
self.listView.setModel(self.ddm)
self.printButton = QPushButton("Print")
self.vLayout.addWidget(self.listView)
self.vLayout.addWidget(self.printButton)
self.printButton.clicked.connect(self.printModel)
def printModel(self): # 验证移动view中项目后,背后model中数据也发生了移动
print(self.ddm.data(self.listView.currentIndex()))
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyle('fusion')
window = DemoDragDrop()
window.show()
sys.exit(app.exec_())
https://stackoverflow.com/questions/56819085
复制相似问题