QTableView + QAbstractTableModel:通过拖放移动行
QTableView + QAbstractTableModel: Move rows via drag'n'drop
我有一个简单的基于 QAbstractTableModel
的模型和一个 QTableView
。
我的目标也很简单:允许通过拖放 move/reorder 行。备注:
- D'n'd
QTableView
内部的变化应该反映在我的模型中;
- D'n'd 应该是内部的 - 移动应该只在我的视图内执行,没有外部 MIME 导出;
- 我想拖放 整 行。应不拖放单独的项目;
- 水平拖动 header 对我来说不是 suitable 解决方案,因为我想隐藏 headers 并且因为我想让用户在 [=38] 处抓取行=]任意处拖动即可;
我真的很接近我的目标。但它仍然没有像我预期的那样工作。现在我可以拖动行了,但似乎 任何 单元格都可以接受拖放,尽管我指定 Qt::ItemIsDropEnabled
仅用于全局 table 的 [=71] =] 并且不要为实际的 table 项指定此标志,因为我不想删除它们,我想以某种方式删除 "between the rows",只是为了执行行移动。因为 table 项目出于某种原因可以接受掉落,所以我得到了奇怪的行为:如果掉落到任何行的 first 单元格,我完全实现了我想要的:我的行正确移动。但是,如果我放到任何行的 nonfirst 单元格,那就完全错了。但最好展示一下这里发生的事情的图片:
我的代码(完全符合我的问题的最小样本):
main.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);
}
您应该将 dropMimeData
方法添加到您的模型中并正确实施它。如果第一列的下降对你来说很好,你可以简单地从模型的 dropMimeData
内部调用 QAbstractItemModel::dropMimeData
并且 column
参数等于 0 而不管实际下降的是哪一列上。
挑剔:这两行不是必须的:
t.setAcceptDrops(true);
t.viewport()->setAcceptDrops(true);
我有一个简单的基于 QAbstractTableModel
的模型和一个 QTableView
。
我的目标也很简单:允许通过拖放 move/reorder 行。备注:
- D'n'd
QTableView
内部的变化应该反映在我的模型中; - D'n'd 应该是内部的 - 移动应该只在我的视图内执行,没有外部 MIME 导出;
- 我想拖放 整 行。应不拖放单独的项目;
- 水平拖动 header 对我来说不是 suitable 解决方案,因为我想隐藏 headers 并且因为我想让用户在 [=38] 处抓取行=]任意处拖动即可;
我真的很接近我的目标。但它仍然没有像我预期的那样工作。现在我可以拖动行了,但似乎 任何 单元格都可以接受拖放,尽管我指定 Qt::ItemIsDropEnabled
仅用于全局 table 的 [=71] =] 并且不要为实际的 table 项指定此标志,因为我不想删除它们,我想以某种方式删除 "between the rows",只是为了执行行移动。因为 table 项目出于某种原因可以接受掉落,所以我得到了奇怪的行为:如果掉落到任何行的 first 单元格,我完全实现了我想要的:我的行正确移动。但是,如果我放到任何行的 nonfirst 单元格,那就完全错了。但最好展示一下这里发生的事情的图片:
我的代码(完全符合我的问题的最小样本):
main.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);
}
您应该将 dropMimeData
方法添加到您的模型中并正确实施它。如果第一列的下降对你来说很好,你可以简单地从模型的 dropMimeData
内部调用 QAbstractItemModel::dropMimeData
并且 column
参数等于 0 而不管实际下降的是哪一列上。
挑剔:这两行不是必须的:
t.setAcceptDrops(true);
t.viewport()->setAcceptDrops(true);