DemoTableModel

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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值