前言
QAbstractTableModel 继承自 QAbstractItemModel,主要用于为 QTableView 提供相关接口,我们可以子类化该抽象类并实现相关接口。本文主要讲 QAbstractTableModel 数据的加载与更新,以及行列的增删。
(请确保自己已经熟悉了 rowCount、columnCount、data 等基础的接口)
加载数据并刷新
当我们想重置 model 的数据时,可以定义一个成员函数,然后将重置的逻辑放在 beginResetModel 和 endResetModel 两个函数调用之间,这样 model 就能正常的刷新数据。调用 beginResetModel 之后,将触发modelAboutToBeReset信号,而 endResetModel 会触发 modelReset 信号。
void MyTableModel::setModelData(const QList<MyModelItem> &datas)
{
//重置model数据之前调用beginResetModel,此时会触发modelAboutToBeReset信号
beginResetModel();
//重置model中的数据
modelData=datas;
//数据设置结束后调用endResetModel,此时会触发modelReset信号
endResetModel();
//注意:reset model后,选中的item会失效,我们可以自己写保存和恢复选中项的逻辑
}
要注意的是,model 重置后选中项也失效了,我们可以自己写保存和恢复选中项的逻辑,比如在 view 中关联这两个 reset 信号。
QTableView *table=ui->tableView;
model=new MyTableModel(ui->tableView);
table->setModel(model);
//保存和恢复model选中项,因为在resetModel后会失效
connect(model,&MyTableModel::modelAboutToBeReset,this,[=]{
selectedIndex=table->currentIndex();
},Qt::DirectConnection);
connect(model,&MyTableModel::modelReset,this,[=]{
//不用担心无效的index,接口内部会处理
table->setCurrentIndex(selectedIndex);
},Qt::DirectConnection);
如果表的行列数是固定的,只是数据变更了,我们可以用 dataChanged 信号来请求刷新。
//重置model中的数据,此时行列数一致
modelData=datas;
//如果表的行列数是固定的,只是数据变更了,我们可以用 dataChanged 信号来请求刷新。
emit dataChanged(index(0,0),index(RowMax-1,ColMax-1),QVector<int>());
行列动态增删
因为我做这个表格列是固定的,所以就只用增删行来讲解,操作都是类似的。
增删行相关接口(虚函数是我们需要实现的,增删单行的其实也是回调增删多行的接口):
//插入相关接口
bool insertColumn(int column, const QModelIndex &parent = QModelIndex())
virtual bool insertColumns(int column, int count, const QModelIndex &parent = QModelIndex())
bool insertRow(int row, const QModelIndex &parent = QModelIndex())
virtual bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex())
//删除相关接口
bool removeColumn(int column, const QModelIndex &parent = QModelIndex())
virtual bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex())
bool removeRow(int row, const QModelIndex &parent = QModelIndex())
virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex())
类似 resetModel ,增删行的逻辑也要放在 beginXX 和 endXXX 的调用之间:
bool MyTableModel::insertRows(int row, int count, const QModelIndex &parent)
{
//row为0就是开始,为rowcount就在尾巴
if(row<0||count<1||row>rowCount())
return false;
//需要将操作放到beginInsertRows和endInsertRows两个函数调用之间
beginInsertRows(parent, row, row + count - 1);
for(int i=row;i<row+count;i++)
{
//在接口对应行插入空数据
modelData.insert(i,MyModelItem());
}
endInsertRows();
return true;
}
bool MyTableModel::removeRows(int row, int count, const QModelIndex &parent)
{
if(row<0||count<1||row+count>rowCount())
return false;
//需要将操作放到beginRemoveRows和endRemoveRows两个函数调用之间
beginRemoveRows(parent, row, row + count - 1);
for(int i=row+count-1;i>=row;i--)
{
//移除该行数据
modelData.removeAt(i);
}
endRemoveRows();
return true;
}
注意,插入行接口是不带数据参数的,所以只能填充默认数据。
然后我们就可以调用这两个接口增删行:
void DemoTableModel::addRow()
{
//在选中行和下一行之间插入
//model->insertRows(ui->tableView->currentIndex().row()+1,1);
model->insertRow(ui->tableView->currentIndex().row()+1);
}
void DemoTableModel::delRow()
{
//删除选中行
//model->removeRows(ui->tableView->currentIndex().row(),1);
model->removeRow(ui->tableView->currentIndex().row());
}
如果我们想将有效的数据插入,可以自定义一个接口,比如:
bool MyTableModel::insertModelData(int row, const MyModelItem &datas)
{
//row为0就是开始,为rowcount就在尾巴
if(row<0||row>rowCount())
return false;
//需要将操作放到beginInsertRows和endInsertRows两个函数调用之间
beginInsertRows(QModelIndex(), row, row);
//在接口对应行插入空数据
modelData.insert(row,datas);
endInsertRows();
return true;
}
结束语
数据展示和修改接口加上增删行已经能满足基本的需求了。下一篇我准备写 model 中表头相关接口的使用。
参考
官方文档:https://doc.qt.io/qt-5/qabstracttablemodel.html
官方文档:https://doc.qt.io/qt-5/model-view-programming.html
主要代码
(因为有点多,所以放最后)
#ifndef MYTABLEMODEL_H
#define MYTABLEMODEL_H
#include <QAbstractTableModel>
/**
* @brief 表格展示的一行的数据结构,随便写的
*/
struct MyModelItem
{
QString name; //姓名
int age; //年龄
QString info; //相关信息
};
/**
* @brief 自定义TableModel,用于展示接口的使用
* @details
* 继承QAbstractTableModel需要实现至少三个接口:
* rowCount、columnCount、data
*/
class MyTableModel : public QAbstractTableModel
{
Q_OBJECT
public:
explicit MyTableModel(QObject *parent = nullptr);
//自定义导入导出数据的接口
void setModelData(const QList<MyModelItem> &datas);
QList<MyModelItem> getModelData() const;
//自定义插入行数据
bool insertModelData(int row,const MyModelItem &datas);
//获取表头数据
//QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
//设置表头数据
//bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole) override;
//获取行数
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
//获取列数
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
//获取单元格数据
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
//设置单元格数据
bool setData(const QModelIndex &index, const QVariant &value,int role = Qt::EditRole) override;
//单元格的可操作性标志位,如可编辑,可选中等
Qt::ItemFlags flags(const QModelIndex& index) const override;
//添加行列
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
//bool insertColumns(int column, int count, const QModelIndex &parent = QModelIndex()) override;
//移除行列
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
//bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex()) override;
private:
//数据
QList<MyModelItem> modelData;
};
#endif // MYTABLEMODEL_H
#include "MyTableModel.h"
MyTableModel::MyTableModel(QObject *parent)
: QAbstractTableModel(parent)
{
}
void MyTableModel::setModelData(const QList<MyModelItem> &datas)
{
//重置model数据之前调用beginResetModel,此时会触发modelAboutToBeReset信号
beginResetModel();
//重置model中的数据
modelData=datas;
//数据设置结束后调用endResetModel,此时会触发modelReset信号
endResetModel();
//注意:reset model后,选中的item会失效,我们可以自己写保存和恢复选中项的逻辑
//如果表的行列数是固定的,只是数据变更了,我们可以用 dataChanged 信号来请求刷新。
//emit dataChanged(index(0,0),index(RowMax-1,ColMax-1),QVector<int>());
}
QList<MyModelItem> MyTableModel::getModelData() const
{
//将内存数据返回
return modelData;
}
bool MyTableModel::insertModelData(int row, const MyModelItem &datas)
{
//row为0就是开始,为rowcount就在尾巴
if(row<0||row>rowCount())
return false;
//需要将操作放到beginInsertRows和endInsertRows两个函数调用之间
beginInsertRows(QModelIndex(), row, row);
//在接口对应行插入空数据
modelData.insert(row,datas);
endInsertRows();
return true;
}
/*QVariant MyTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
// FIXME: Implement me!
}
bool MyTableModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role)
{
if (value != headerData(section, orientation, role)) {
// FIXME: Implement me!
emit headerDataChanged(orientation, section, section);
return true;
}
return false;
}*/
int MyTableModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
//返回表格行数
return modelData.count();
}
int MyTableModel::columnCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
//返回表格列数
return 3;
}
QVariant MyTableModel::data(const QModelIndex &index, int role) const
{
if(!index.isValid())
return QVariant();
if(role == Qt::DisplayRole || role == Qt::EditRole)
{
//DisplayRole返回显示的文本值
const int row = index.row();
switch(index.column())
{
case 0: return modelData.at(row).name;
case 1: return modelData.at(row).age;
case 2: return modelData.at(row).info;
}
}
return QVariant();
}
bool MyTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
//将界面修改的值进行保存
if (index.isValid() && role == Qt::EditRole) {
const int row = index.row();
switch(index.column())
{
case 0: modelData[row].name = value.toString(); break;
case 1: modelData[row].age = value.toInt(); break;
case 2: modelData[row].info = value.toString(); break;
}
//发送信号触发刷新
emit dataChanged(index, index, QVector<int>() << role);
return true;
}
return false;
}
Qt::ItemFlags MyTableModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
//单元格允许的操作,至少得是ItemIsEnabled的才能进行其他操作
return Qt::ItemIsEnabled|Qt::ItemIsSelectable|Qt::ItemIsEditable;
}
bool MyTableModel::insertRows(int row, int count, const QModelIndex &parent)
{
//row为0就是开始,为rowcount就在尾巴
if(row<0||count<1||row>rowCount())
return false;
//需要将操作放到beginInsertRows和endInsertRows两个函数调用之间
beginInsertRows(parent, row, row + count - 1);
for(int i=row;i<row+count;i++)
{
//在接口对应行插入空数据
modelData.insert(i,MyModelItem());
}
endInsertRows();
return true;
}
bool MyTableModel::removeRows(int row, int count, const QModelIndex &parent)
{
if(row<0||count<1||row+count>rowCount())
return false;
//需要将操作放到beginRemoveRows和endRemoveRows两个函数调用之间
beginRemoveRows(parent, row, row + count - 1);
for(int i=row+count-1;i>=row;i--)
{
//移除该行数据
modelData.removeAt(i);
}
endRemoveRows();
return true;
}
void DemoTableModel::setData()
{
//一般我们从数据库、文件、网络获取到了数据后,再通过model的接口去刷新
//这里我们用随机数来模拟实际数据
QRandomGenerator *random=QRandomGenerator::global();
//随机的数据行数
const int row_count=random->bounded(10,20);
QList<MyModelItem> new_data;
for(int row=0;row<row_count;row++)
{
new_data.push_back({
QString("name %1").arg(row),
random->bounded(20,40),
QString("info %1 %2").arg(row).arg(random->bounded(0,100))
});
}
//调用我们的自定义接口设置model的数据
model->setModelData(new_data);
}
void DemoTableModel::getData()
{
//调用自定义接口获取model数据
//QList<MyModelItem> old_data=model->getModelData();
}
void DemoTableModel::addRow()
{
//在选中行和下一行之间插入
//model->insertRows(ui->tableView->currentIndex().row()+1,1);
//model->insertRow(ui->tableView->currentIndex().row()+1);
QRandomGenerator *random=QRandomGenerator::global();
model->insertModelData(ui->tableView->currentIndex().row()+1,{
QString("name new"),
random->bounded(20,40),
QString("info new %1").arg(random->bounded(0,100))
});
}
void DemoTableModel::delRow()
{
//删除选中行
//model->removeRows(ui->tableView->currentIndex().row(),1);
model->removeRow(ui->tableView->currentIndex().row());
}