Qt Model-View 拖拽表頭換行、列比較容易,只需要設置 QHeaderView 的 setSectionsMovable 爲 true 。但有時也需要拽拽內容區域完成交換(也有和組件外進行拖拽交互的,本文不涉及)。要完成這個功能,除了對 View 進行幾個設置,重頭戲在於 Model 的定製。無論是繼承 QAbstractTableModel ,或是 QStandardItemModel 都是可以實現拖拽交換的,在我的 Demo 中兩個都進行了測試。不過我的 Demo 只做了 單元格交換,可以自行修改爲行、列交換,只需要 View 設置下 setSelectionBehavior ,然後在 Model 中交換時交換整行、整列。
效果圖 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