Qt Model-View 设置 QHeaderView 的 setSectionsMovable 为 true 后可以拖拽表头移动行、列位置。但有时也需要拽拽内容区域进行交换(也有和组件外进行拖拽交互的,本文不涉及)。要完成这个功能,除了对 View 进行几个设置,重头戏在于 Model 的定制。无论是继承 QAbstractTableModel ,或是 QStandardItemModel 都是可以实现拖拽交换的,在我的 Demo 中两个都进行了测试。不过我的 Demo 只做了 单元格交换,可以自行修改为行、列交换,只需要 View 设置下 setSelectionBehavior ,然后在 Model 中交换时交换整行、整列。(如果只想移动而不是交换行列的话,可以 takeItem ,然后根据 drop 时坐标进行插入)
效果图 GIF:
首先是 QTableView 的设置:
view->setSelectionMode(QAbstractItemView::SingleSelection); //不是必要的
//可以配合行/列选中,需要在Model中做相应处理
//view->setSelectionBehavior(QAbstractItemView::SelectRows);
view->setDragEnabled(true);
view->setDefaultDropAction(Qt::MoveAction); //不是必要的
view->setDragDropMode(QAbstractItemView::InternalMove);
接下来需要对 Model 额外实现 4 个接口:
// 允许的操作,加上drag drop
Qt::ItemFlags flags(const QModelIndex &index) const override;
// 允许move
Qt::DropActions supportedDropActions() const override;
// drag时携带的信息
QMimeData *mimeData(const QModelIndexList &indexes) const override;
// drop时根据drag携带的信息进行处理
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
flags 和 supportedDropActions 是为了支持拖拽功能,mimeData 则用来处理拖拽时对应的信息,比如我们可以把行列信息放进去,在 drop 时对两个 index 的数据进行交换。下面是我在 QStandardItemModel 派生类中的实现:
Qt::ItemFlags MyStandardItemModel::flags(const QModelIndex &index) const
{
if (index.isValid())
return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | QStandardItemModel::flags(index);
return QStandardItemModel::flags(index);
}
Qt::DropActions MyStandardItemModel::supportedDropActions() const
{
return Qt::MoveAction | QStandardItemModel::supportedDropActions();
}
QMimeData *MyStandardItemModel::mimeData(const QModelIndexList &indexes) const
{
QMimeData *data=QStandardItemModel::mimeData(indexes);
if(data){
// parent mimeData中已判断indexes有效性,无效的会返回nullptr
// 也可以把信息放到model的mutable成员中
data->setData("row",QByteArray::number(indexes.at(0).row()));
data->setData("col",QByteArray::number(indexes.at(0).column()));
}
return data;
}
bool MyStandardItemModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
if(!data||action!=Qt::MoveAction)
return false;
//这里没有判断toint ok(数据转换有效性)
const QModelIndex old_index=index(data->data("row").toInt(),
data->data("col").toInt());
const QModelIndex current_index=parent;
//可以先对index有效性进行判断,无效返回false,此处略过
QStandardItem *old_item=takeItem(old_index.row(),old_index.column());
QStandardItem *current_item=takeItem(current_index.row(),current_index.column());
//交换两个item
setItem(old_index.row(),old_index.column(),current_item);
setItem(current_index.row(),current_index.column(),old_item);
return true;
}
(不用担心 QMimeData 没释放,貌似它是通过对应 event 对象一并释放的)
参考 Qt 文档:https://doc.qt.io/qt-5/model-view-programming.html
完整代码链接 GitHub:https://github.com/gongjianbo/MyTestCode/tree/master/Qt/QTableViewMoveAction
完整代码链接 CSDN:https://download.csdn.net/download/gongjianbo1992/12547136