DemoTableModel.h
#pragma once
#include <QWidget>
#include "MyTableModel.h"
namespace Ui {
class DemoTableModel;
}
/**
* @brief 展示QTableView+QAbstractTableModel的基本使用
*/
class DemoTableModel : public QWidget
{
Q_OBJECT
public:
explicit DemoTableModel(QWidget *parent = nullptr);
~DemoTableModel();
private:
void initTable();
void initOperate();
public slots:
//设置model的数据
void setData();
//从model导出数据
void getData();
//添加行
void addRow();
private slots:
void on_btnGetData_clicked();
//删除行
void delRow();
//排序开关
void sortEnableChange();
private:
Ui::DemoTableModel *ui;
//自定义model的实例,可以封装到自定义的view中
MyTableModel *model;
//保存和恢复选中项
QModelIndex selectedIndex;
};
DemoTableModel.cpp
#include "DemoTableModel.h"
#include "ui_DemoTableModel.h"
#include <QRandomGenerator>
#include <QSortFilterProxyModel>
DemoTableModel::DemoTableModel(QWidget *parent) :
QWidget(parent),
ui(new Ui::DemoTableModel)
{
ui->setupUi(this);
initTable();
initOperate();
}
DemoTableModel::~DemoTableModel()
{
delete ui;
}
void DemoTableModel::initTable()
{
QTableView *table=ui->tableView;
model=new MyTableModel(ui->tableView);
//想要利用view支持的点击表头排序功能
//我们可以用QSortFilterProxyModel完成排序,或者实现model的sort接口来完成排序
//其次,view要设置setSortingEnabled为true,且表头是可点击的
#if 0
//借助QSortFilterProxyModel
QSortFilterProxyModel *proxy=new QSortFilterProxyModel(ui->tableView);
proxy->setSourceModel(model);
table->setModel(proxy);
#else
//借助model的sort接口,基类实现不执行任何操作,要自己实现
table->setModel(model);
#endif
//保存和恢复model选中项,因为在resetModel后会失效
connect(model,&MyTableModel::modelAboutToBeReset,this,[=]{
selectedIndex=table->currentIndex();
},Qt::DirectConnection);
connect(model,&MyTableModel::modelReset,this,[=]{
//不用担心无效的index,接口内部会处理
table->setCurrentIndex(selectedIndex);
},Qt::DirectConnection);
//开启隔行变色,设置之后qss的对应设置才有效
table->setAlternatingRowColors(true);
//单行选中=按行选中+单次选择
table->setSelectionBehavior(QAbstractItemView::SelectRows);
table->setSelectionMode(QAbstractItemView::SingleSelection);
//默认行高
table->verticalHeader()->setDefaultSectionSize(25);
//行表头文字居中,默认列居中行左对齐,也可以在headerData接口里设置
//table->verticalHeader()->setDefaultAlignment(Qt::AlignCenter);
//修改表头,自定义接口
model->setHorHeaderData(QList<QString>{"Name","Age","Info"});
//表头实例
QHeaderView *header=table->horizontalHeader();
//拖拽交换行
header->setSectionsMovable(true);
//如何决策宽高
//header->setSectionResizeMode(QHeaderView::Fixed);
//是否可以点击
header->setSectionsClickable(true);
//选中时高亮
//header->setHighlightSections(false);
//默认宽高,放到table设置宽高的接口前,不然被覆盖
header->setDefaultSectionSize(100);
//最后一列填充
header->setStretchLastSection(true);
//排序
//table->setSortingEnabled(true);
//设置第三列列宽
//table->setColumnWidth(2,200);
}
void DemoTableModel::initOperate()
{
//设置model的数据
connect(ui->btnSetData,&QPushButton::clicked,this,&DemoTableModel::setData);
//导出model中的数据
ui->btnGetData->setEnabled(false);
connect(ui->btnGetData,&QPushButton::clicked,this,&DemoTableModel::getData);
//添加行
connect(ui->btnAddRow,&QPushButton::clicked,this,&DemoTableModel::addRow);
//删除行
connect(ui->btnDelRow,&QPushButton::clicked,this,&DemoTableModel::delRow);
//排序开关
connect(ui->sortBox,&QCheckBox::stateChanged,this,&DemoTableModel::sortEnableChange);
//初始化数据
setData();
}
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());
}
void DemoTableModel::sortEnableChange()
{
if(ui->sortBox->isChecked()){
//开启排序
ui->tableView->setSortingEnabled(true);
//第2列升序
ui->tableView->sortByColumn(1,Qt::AscendingOrder);
}else{
//关闭排序功能
ui->tableView->setSortingEnabled(false);
//这里还有个问题是,如果需要恢复默认顺序如何做
//对于sortproxy这个比较简单,因为他改变的是显示的位置,
//但是model的sort直接改的容器中数据的位置
//我们可以给每行的数据标记一个原始的index,恢复时根据index值排序
}
}
void DemoTableModel::on_btnGetData_clicked()
{
}
MyTableModel.h
#pragma once
#include <QAbstractTableModel>
/**
* @brief 表格展示的一行的数据结构,随便写的
*/
struct MyModelItem
{
QString name; //姓名
int age; //年龄
QString info; //相关信息
//加一个接口,便于根据列号查询
QVariant at(int index) const
{
switch (index) {
case 0: return name;
case 1: return age;
case 2: return info;
}
return name;
}
//列数
static constexpr int columnCount()
{
return 3;
}
};
/**
* @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);
//自定义设置表头数据
void setHorHeaderData(const QList<QString> &headers);
//获取表头数据
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;
//排序,基类实现不执行任何操作
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
private:
//排序辅助接口,参照QAbstractItemModelPrivate::isVariantLessThan的实现
//判断参数1是否小于参数2
bool lessThan(const QVariant &left, const QVariant &right) const;
private:
//数据
QList<MyModelItem> modelData;
//横项列表头
QList<QString> horHeaderData;
};
MyTableModel.cpp
#include "MyTableModel.h"
#include <QDateTime>
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;
}
void MyTableModel::setHorHeaderData(const QList<QString> &headers)
{
//自定义的表头设置接口
horHeaderData=headers;
emit headerDataChanged(Qt::Horizontal, 0, headers.count()-1);
}
QVariant MyTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
//注意,如果用了sortproxymodel,这个section是实际数据的index,不是界面看到的index
//区分横表头和竖表头
if(orientation == Qt::Horizontal){
//这里我们只设置居中对齐和文本
if (role == Qt::DisplayRole){
//这里把横项列表头的文本设计为可以设置的
if(section>=0 && section<horHeaderData.count())
return horHeaderData.at(section);
return QString("Col %1").arg(section + 1);
}else if(role == Qt::TextAlignmentRole){
return Qt::AlignCenter;
}
}else{
if (role == Qt::DisplayRole)
return QString("Row %1").arg(section + 1);
else if(role == Qt::TextAlignmentRole)
return Qt::AlignCenter;
}
return QVariant();
}
bool MyTableModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role)
{
//role懒得判断了
role;
//设计为横项列表头可以设置
if (orientation == Qt::Horizontal && section>=0 && section<horHeaderData.count()) {
horHeaderData[section] = value.toString();
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 MyModelItem::columnCount();
}
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 MyTableModel::sort(int column, Qt::SortOrder order)
{
if(modelData.isEmpty()||column<0||column>=columnCount())
return;
//判断升序降序
const bool is_asc = (order == Qt::AscendingOrder);
//排序
std::sort(modelData.begin(), modelData.end(),
[column, is_asc, this](const MyModelItem &left,const MyModelItem &right){
//我用QVariant只是在以前的基础上改的,自定义类型可以不用这个
//这里假设单元格数据都是任意类型的
const QVariant left_val = left.at(column);
const QVariant right_val = right.at(column);
//辅助接口,a<b返回true
return is_asc
?lessThan(left_val,right_val)
:lessThan(right_val,left_val);
});
//更新view
dataChanged(index(0,0),index(modelData.count()-1,columnCount()-1));
}
bool MyTableModel::lessThan(const QVariant &left, const QVariant &right) const
{
//参照QAbstractItemModelPrivate::isVariantLessThan的实现
//这些都是通用型的排序规则,一般我们会有自定义的需求,比如根据字符串中的数字排序
//有些类型需要包含头文件才能使用,如datetime
if (left.userType() == QMetaType::UnknownType)
return false;
if (right.userType() == QMetaType::UnknownType)
return true;
switch (left.userType()) {
case QMetaType::Int:
return left.toInt() < right.toInt();
case QMetaType::UInt:
return left.toUInt() < right.toUInt();
case QMetaType::LongLong:
return left.toLongLong() < right.toLongLong();
case QMetaType::ULongLong:
return left.toULongLong() < right.toULongLong();
case QMetaType::Float:
return left.toFloat() < right.toFloat();
case QMetaType::Double:
return left.toDouble() < right.toDouble();
case QMetaType::QChar:
return left.toChar() < right.toChar();
case QMetaType::QDate:
return left.toDate() < right.toDate();
case QMetaType::QTime:
return left.toTime() < right.toTime();
case QMetaType::QDateTime:
return left.toDateTime() < right.toDateTime();
case QMetaType::QString: break;
default: break;
}
//Locale表示支持本地字符串
//if (isLocaleAware)
return left.toString().localeAwareCompare(right.toString()) < 0;
//else
// return left.toString().compare(right.toString(), cs) < 0;
}