QTableView拖拽交換行、列、單元格

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章