QTableView + QAbstractTableModel:通过拖放移动行

Posted

技术标签:

【中文标题】QTableView + QAbstractTableModel:通过拖放移动行【英文标题】:QTableView + QAbstractTableModel: Move rows via drag'n'drop 【发布时间】:2017-12-18 01:37:56 【问题描述】:

我有一个简单的基于QAbstractTableModel 的模型和一个QTableView

我的目标也很简单:允许通过拖放移动/重新排列行。备注:

QTableView 内部的更改应该反映在我的模型中; 不应该是内部的 - 移动应该只在我的视图内执行,没有外部 MIME 导出; 我想拖放整个行。 不应拖放单独的项目; 拖动水平标题对我来说不是一个合适的解决方案,因为我希望标题被隐藏并且因为我想让用户在 任何 位置抓取行来拖动它;

我已经非常接近我的目标了。但它仍然没有像我预期的那样工作。现在我可以拖动行,但似乎 any 单元格可以接受放置,尽管我只为全局表的父级指定了 Qt::ItemIsDropEnabled 并且没有为实际表项指定此标志,因为我这样做了不想丢给他们,我想以某种方式丢“行之间”,只是为了执行行移动。因为表格项目由于某种原因可以接受下降,所以我得到了奇怪的行为:如果下降到任何行的 first 单元格,我完全实现了我想要的:我的行正确移动。但是,如果我放到任何行的 nonfirst 单元格,那就完全错误了。但最好显示这里发生的事情的图片:

我的代码(正是我的问题的最小示例):

ma​​in.cpp

void setupView(QTableView &t)

    t.verticalHeader()->hide();
    t.horizontalHeader()->hide();
    t.horizontalHeader()->setStretchLastSection(true);

    t.setSelectionBehavior(QAbstractItemView::SelectRows);
    t.setSelectionMode(QAbstractItemView::SingleSelection);

    t.setDragEnabled(true);
    t.setDropIndicatorShown(true);
    t.setAcceptDrops(true);
    t.viewport()->setAcceptDrops(true);
    t.setDefaultDropAction(Qt::MoveAction);
    t.setDragDropMode(QTableView::InternalMove);
    t.setDragDropOverwriteMode(false);


int main(int argc, char *argv[])

    QApplication a(argc, argv);
    QMainWindow w;

    QTableView *table = new QTableView(&w);
    setupView(*table);
    table->setModel(new TableModel);

    w.setCentralWidget(table);
    w.show();

    return a.exec();

tablemodel.cpp

#include "tablemodel.h"

TableModel::TableModel()

    // m_data is a QList<QStringList>
    m_data = 
        "Name", "Kelly",
        "Age", "19",
        "Gender", "Female",
    ;


int TableModel::rowCount(const QModelIndex &parent) const 
    return m_data.size();


int TableModel::columnCount(const QModelIndex &parent) const 
    return 2;


QVariant TableModel::data(const QModelIndex &i, int r) const

    return (r == Qt::DisplayRole) ? m_data[i.row()][i.column()] : QVariant();


QVariant TableModel::headerData(int section, Qt::Orientation orientation, int r) const

    return QVariant();


Qt::ItemFlags TableModel::flags(const QModelIndex &index) const

    Qt::ItemFlags f = Qt::ItemIsEnabled | Qt::ItemIsSelectable
                    | Qt::ItemIsEditable | Qt::ItemIsDragEnabled;

    if(!index.isValid()) 
        f |= Qt::ItemIsDropEnabled;
    

    return f;


Qt::DropActions TableModel::supportedDropActions() const

    return Qt::MoveAction | Qt::CopyAction;


bool TableModel::setData(const QModelIndex &i, const QVariant &v, int r)

    if(r == Qt::EditRole || r == Qt::DisplayRole) 
        m_data[i.row()][i.column()] = v.toString();
        return true;
    
    return false;


bool TableModel::setItemData(const QModelIndex &i, const QMap<int, QVariant> &roles)

    if(!roles.contains(Qt::EditRole) && !roles.contains(Qt::DisplayRole)) 
        return false;
    

    m_data[i.row()][i.column()] = roles[Qt::DisplayRole].toString();
    return true;


bool TableModel::insertRows(int row, int count, const QModelIndex &parent)

    beginInsertRows(QModelIndex(), row, row + count - 1);
    for(int i = 0; i<count; ++i) 
        m_data.insert(row, QStringList("", ""));
    
    endInsertRows();
    return true;


bool TableModel::removeRows(int row, int count, const QModelIndex &parent)

    beginRemoveRows(QModelIndex(), row, row + count - 1);
    for(int i = 0; i<count; ++i) 
        m_data.removeAt(row);
    
    endRemoveRows();
    return true;


bool TableModel::moveRows(const QModelIndex &srcParent, int srcRow, int count,
                          const QModelIndex &dstParent, int dstChild)

    beginMoveRows(QModelIndex(), srcRow, srcRow + count - 1, QModelIndex(), dstChild);
    for(int i = 0; i<count; ++i) 
        m_data.insert(dstChild + i, m_data[srcRow]);
        int removeIndex = dstChild > srcRow ? srcRow : srcRow+1;
        m_data.removeAt(removeIndex);
    
    endMoveRows();
    return true;

请给我一些提示,现在模型或视图设置有什么问题。

UPD

对于那些对解决方案感兴趣的人:

bool TableModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)

    Q_UNUSED(parent);
    Q_UNUSED(column);

    if(row == -1) 
        row = rowCount();
    

    return QAbstractTableModel::dropMimeData(data, action, row, 0, parent);

【问题讨论】:

@Dmitry 是的,这正是解决方案!它解决了我的问题。将您的评论复制到答案中,我会接受。 @NikolaiShalakin,我也有同样的问题,想问一下是否可以通过dropMimeData的实现发布整个解决方案 @Emanuele 当然,请查看,我已将其添加到问题底部 您的解决方案很棒,但它不属于问题。请编辑问题,从那里删除解决方案,然后将其作为您对问题的答案发布。 【参考方案1】:

您应该在模型中添加dropMimeData 方法并正确实现它。如果第一列的拖放对您来说效果很好,您可能只需从模型的 dropMimeData 内部调用 QAbstractItemModel::dropMimeData 并使用等于 0 的 column 参数,而不管实际在哪一列进行拖放。

【讨论】:

【参考方案2】:

Nitpick:这两行不是必需的:

t.setAcceptDrops(true);
t.viewport()->setAcceptDrops(true);

【讨论】:

以上是关于QTableView + QAbstractTableModel:通过拖放移动行的主要内容,如果未能解决你的问题,请参考以下文章

如何更改 QTableView 边框颜色?

QTableView - 排序标题

QTableView如何设置行高?

PyQt5 组件之QTableView

如何在调整 QTableView 大小时动态更改列数?

QTableView - 没有得到选择改变信号