列表、表格和树中的项目可以在视图中被拖拽,数据作为MIME编码的数据被导入和导出。标准视图可以自动支持内部的拖放。
默认视图的拖放功能并没有被启用,如果要进行项目的拖动,就需要进行一些属性的设置。
如果在一个新的模型中启用拖放功能,还需要重新实现一些函数。
三个便捷类QListWidget、QTreeWidget、QTableWidget中每一种类型的项目都默认配置了一组不同的标志。
每一个QListWidgetItem和QTreeWidgetItem被初始化为可用的、可检查的、可选择的,也可以用作拖放的源。每一个QTableWidgetItem可以被编辑和用作拖放操作的目标。
一般还需要在视图中设置一些属性来使它启用对拖放操作的内建支持:
dragEnable
属性设置为true;viewport()
的acceptDrops
属性为true;showDropIndicator
属性; ///
/// 在视图项目中启用拖放功能
///
listWidget.setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection); // 设置单选模式
listWidget.setDragEnabled(true); // 启用拖拽
listWidget.viewport()->setAcceptDrops(true); // 设置接受拖放
listWidget.setDropIndicatorShown(true); // 设置显示将要被放置的位置
listWidget.setDragDropMode(QListWidget::InternalMove); // 设置拖放模式为移动项目,如果不设置,默认为复制项目
treeWidget.setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection); // 设置单选模式
treeWidget.setDragEnabled(true); // 启用拖拽
treeWidget.viewport()->setAcceptDrops(true); // 设置接受拖放
treeWidget.setDropIndicatorShown(true); // 设置显示将要被放置的位置
treeWidget.setDragDropMode(QTreeWidget::InternalMove); // 设置拖放模式为移动项目,如果不设置,默认为复制项目
tableWidget.setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection); // 设置单选模式
tableWidget.setDragEnabled(true); // 启用拖拽
tableWidget.viewport()->setAcceptDrops(true); // 设置接受拖放
tableWidget.setDropIndicatorShown(true); // 设置显示将要被放置的位置
tableWidget.setDragDropMode(QTableWidget::InternalMove); // 设置拖放模式为移动项目,如果不设置,默认为复制项目
在视图中启用拖放功能与在便捷类中的设置相似。如下:
///
/// 模型/视图中启用拖放功能
///
listView->setSelectionMode(QAbstractItemView::SingleSelection); // 设置单选模式
listView->setDragEnabled(true); // 启用拖放功能
listView->setAcceptDrops(true); // 接受拖放
listView->setDropIndicatorShown(true); // 显示要被放置的位置
自定义模型中的flags()函数要提供对于拖放操作的支持,即需要增加Qt::ItemIsDragEnabled
和Qt::ItemIsDropEnabled
标志。
由于视图中显示的数据是由模型控制的,也要为使用的模型提供拖放操作的支持。需要重新实现一些必要的函数。如下:
// 设置支持的拖拽动作
Qt::DropActions supportedDropActions() const override;
// 设置在拖放操作中导出的条目的数据的编码类型
QStringList mimeTypes() const override;
// 将拖放的数据放入QMimeData中
QMimeData *mimeData(const QModelIndexList &indexes) const override;
// 将拖放操作的数据放入模型中
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
这些函数的实现代码如下:
/*设置支持使用拖放进行复制和移动两种操作*/
Qt::DropActions StringListModel::supportedDropActions() const
{
return Qt::CopyAction | Qt::MoveAction; // 设置模型支持拖拽时的移动和复制操作
/*
* 要允许Qt::MoveAction,模型需要实现removeRows()函数
*/
}
/*在拖放操作中的数据项从模型中导出时,要被编码为合适的格式来对应一个或多个MIME类型,
如下自定义了一个类型,仅支持纯文本类型*/
QStringList StringListModel::mimeTypes() const
{
QStringList types;
// application/vnd.text.list为自定义的类型,后续需要保持一致
types << "application/vnd.text.list";
return types;
}
/*进行拖放操作之前,需要将数据放入到一个QMimeData类型的对象中,
如下使用自定义的格式,将所有要拖拽的数据都放入一个QMimeData对象中*/
QMimeData *StringListModel::mimeData(const QModelIndexList &indexes) const
{
QMimeData *mimeData = new QMimeData;
QByteArray encodedData;
QDataStream stream(&encodedData, QDataStream::WriteOnly);
//根据传入的indexes获取到所有的文本数据
foreach (const QModelIndex &index, indexes) {
if(index.isValid()){
QString text = data(index, Qt::DisplayRole).toString();
stream << text;
}
}
// 将数据放入QMimeData中
mimeData->setData("application/vnd.text.list", encodedData);
return mimeData;
}
bool StringListModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
// 如果是 Qt::IgnorAction,直接返回true
if(action == Qt::IgnoreAction)
return true;
// 如果数据不是指定的格式,返回false
if(!data->hasFormat("application/vnd.text.list"))
return false;
// 该模型是列表模型,只有一列,判断列是否正确
if(column>0)
return false;
// 设置开始插入的行
int beginRow=0;
if(row != -1)
beginRow = row;
else if(parent.isValid())
beginRow = parent.row();
else
beginRow = rowCount(QModelIndex());
// 从data中读取数据
QByteArray encodedData = data->data("application/vnd.text.list");
QDataStream stream(&encodedData, QDataStream::ReadOnly);
QStringList newItem;
int rows=0;
while (!stream.atEnd()) {
QString text;
stream >> text;
newItem << text;
++rows;
}
insertRows(beginRow, rows, QModelIndex());
foreach (const QString &text, newItem) {
QModelIndex idx = index(beginRow, 0, QModelIndex());
setData(idx, text);
beginRow++;
}
return true;
}
任何给定的模型处理放入数据的方式都依赖于它们的类型和向用户展现的方式。
一般应该使用最适合模型底层数据存储的方式来容纳放入的数据。不同类型的模型会使用不同的方式来处理放入的数据。
列表和表格模型只提供了一个平面结构来存储数据项,结果是可能会在当数据放入一个视图中的已经存在的项目时插入新的行或列,或者会使用提供的数据来覆盖已经存在的项目的内容。
树模型一般会在底层数据存储中添加包含新的数据的子项。
最后还需要更新flags()函数,用于提供合适的标志向视图表明哪些项目是可以被拖拽的、哪些项目是可以接受放入的。
在自定义模型中实现视图中的项的拖放代码参考:https://github.com/Innern/Qt/tree/master/ModelView/Examples/01_AddressBooks,该项目参考了官方示例Address Books Example,但是增加了对视图中的项的拖放的支持。
如下:
需要注意的是:
- mimeData()函数的参数indexes包含所有选择拖放的项的索引。比如,
- 如果是简单的列表视图,只有一列,选择拖放时只选择了一行,那么indexes中包含一个索引,即这一行的索引。
- 如果是表格视图,有两列,选择拖放时选择一行,那么indexes中包含两个索引,即这一行的每一列的索引。