Qt Model/View 学习(4) - 实现自己的QAbstractTableModel类(支持显示与修改)


0. 前言

可算到了这一篇了!

上一篇文章中把Qt::ItemDataRole介绍完了,有了前面2篇文章的铺垫,现在终于能够开始动手实现自己的Model类了。

我们在这篇文章中提到Model相关类主要有QAbstractItemModelQAbstractListModelQAbstractTableModel这三个,由于我们已经学习过QModelIndex了,实现的难度区别不大,本文就选择从QAbstractTableModel派生自己的Model类。

话不多说,直奔主题。

系列文章回顾
Qt Model/View 学习(1) - 是什么和为什么?
Qt Model/View 学习(2) - QModelIndex索引模型数据
Qt Model/View 学习(3) - 索引来一堆东西,究竟取谁(ItemDataRole)?


1. Data设计

为了(搞节目效果 )强调Data确实可以很散漫,设计如下数据结构:

//    数据Data,共20个数据

    // 10个标题
    QStringList titles = {"飞机重量", "经度", "纬度", "高度", "飞行次数",
                            "城市", "宠物名称", "姓名", "身高", "体重"};
    // 10个数据
    struct GPS
    {
    // 经纬高
        double lng=108.11, lat=37.11, alt=500.11;
    };
    struct Pilot
    {
        QString name="Tom";	// 姓名
        QString city="北京";	// 城市
        double stature=180.0;	// 身高
        double weight=66.5;	// 体重
        unsigned flyTimes=52;	// 飞行次数
    };

    GPS pos;	// GPS对象
    Pilot person;	// 飞行员对象
    QString petName="Jerry";	// 宠物名称
    double planeWeight=10000;  // 飞机重量

总共20个数据,有QString、double、unsigned等各种类型。在我们的Model类中将被解析为4*5的表格。

此处为了便利就直接将数据放到Model类的私有区了,在实际程序设计中可以考虑将变量整体封装成类/结构,然后将封装的对象指针传入Model类中,这样可实现将ModelData分开。


2. Model类设计

2.1 数据显示与对齐、字体修改

先找到QAbstractTableModel中关于Subclassing的部分:
在这里插入图片描述
官方文档告诉我们:必须重新实现rowCount()、columnCount()、data()函数。

那我们从QAbstractTableModel派生出自己的MyTableModel类,简简单单实现一下这几个函数。如果对于QModelIndexQt::DisplayRole还不太熟悉,那就快去看看之前的文章吧~

virtual int rowCount(const QModelIndex &/*parent*/ = QModelIndex()) const override
    {
//        解析为4*5矩阵,行数为4
        return 4;
    }

    virtual int columnCount(const QModelIndex &/*parent*/ = QModelIndex()) const override
    {
//        解析为4*5矩阵,列数为5
        return 5;
    }

//    核心函数,View类从Model中取数据,传入的参数在之前的文章中有介绍
    virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const
    {
        switch(role)
        {
//        显示数据用
        case Qt::DisplayRole:
            return getTableDisplayData(index);
            break;


//            其余不处理
        default:
            return QVariant();
            break;
        }
    }

其中的getTableDisplayData()函数声明如下,由于只是将数据按矩阵格式对应了一下,用了一堆if-else-switch就不贴全源代码了……

QVariant getTableDisplayData(const QModelIndex& index) const
    {
/* 暂定显示形式为:第1行和第3行显示标题,第2行和第4行显示数值
 * 姓名 身高     体重  城市  宠物名称
 *
 * 飞机重量 飞行次数  经度  纬度  高度
 *
 */
//        稍作判断
        if(!index.isValid() || index.column() >= columnCount() || index.row() >= rowCount())
            return QVariant();

//        数据模型映射

//        第一行显示:姓名 身高 体重 城市 宠物名称
        if(index.row()==0)
        {
//titles = {"飞机重量", "经度", "纬度", "高度", "飞行次数", "城市", "宠物名称", "姓名", "身高", "体重"};
            switch(index.column())
            {
            case 0: return titles[7];
                break;

            case 1: return titles[8];
                break;

            case 2: return titles[9];
                break;

            case 3: return titles[5];
                break;

            case 4: return titles[6];
                break;

            default: return QVariant();
                break;
            }
        }
        //后续类似......
	}

main()函数中添加以下代码以使用咱们的MyTableModel类:

//    使用TableView控件
    QTableView tbl;
//    声明自己的Model对象
    MyTableModel model;

//    关键接口:设置View对应的Model
    tbl.setModel(&model);

    tbl.resize(600,200);
    tbl.show();

Ctrl+R运行起来看看效果:
在这里插入图片描述

显示数据问题不大,但作为一名轻度强迫症,还想实现几点显示效果:

  1. 数据都居中显示;
  2. 第1行和第3行作为标题行,字体加粗一下;

如果有好好看上一篇关于ItemDataRole的文章,这两条应该是很容易实现的,只需要在data()函数中添加对齐字体Role处理代码如下:

//    核心函数,View类从Model中取数据,传入的参数在之前的文章中有介绍
    virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
    {
        switch(role)
        {
//        显示数据用
        case Qt::DisplayRole:
            return getTableDisplayData(index);
            break;

//            对齐处理
        case Qt::TextAlignmentRole:
            return Qt::AlignCenter;
            break;

//            字体处理
        case Qt::FontRole:
        {
            QFont font;
//            row()==0,2时为标题行,需要加粗
            if(index.row()%2==0) font.setBold(true);
            return font;
        }
            break;


//            其余不处理
        default:
            return QVariant();
            break;
        }
    }

这样看起来就稍微舒服一些了:
在这里插入图片描述

但实话说,还是很难看,个人还会想要达到以下效果:

  1. 把行列的表头给去了;
  2. 每列的宽度设置为刚刚好;
  3. 窗口大小刚好放下表格,而不像现在显示不全;

不过遗憾地,这几条都不是在MyTableModel中能够处理的,而是对QTableView进行操作。避免跑题,此处就不贴代码了,只提思路,具体代码可以参考附件。

  1. QTableView::horizontalHeader()获取水平表头,调用QHeaderView::hide()函数将其隐藏;垂直表头处理类似;
  2. QTableView::resizeColumnsToContents()函数,自适应列宽;
  3. 计算自适应之后的表格总大小,使用QTableView::resize()函数设置控件大小,再使用QTableView::setHorizontalScrollBarPolicy()隐藏水平滚动条,类似地,垂直滚动条也隐藏;

View类将在下一篇文章进行系统讲解。

最后显示的结果如下,比较符合强迫症审美了~

在这里插入图片描述

2.2 数据修改

继续看QAbstractTableModel中关于Subclassing的部分:
在这里插入图片描述
提到:可编辑的模型需要实现setData()flags()函数,后者返回值需包含Qt::ItemIsEditable

看起来都不难,那就继续简单实现一下:


//    可编辑模型需要实现flags(),返回值包含Qt::ItemIsEditable
    virtual Qt::ItemFlags flags(const QModelIndex &index) const override
    {
        return Qt::ItemIsEditable | QAbstractTableModel::flags(index);
    }
//    编辑模型的重点函数
    virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override
    {
        switch(role)
        {
        case Qt::EditRole:
//            修改成功时需要触发dataChanged()信号,返回true;修改失败返回false
//            第0、2行为标题行,不允许修改
            if(!index.isValid() || index.row()%2==0) return false;
            return editTableData(index, value);
            break;

        default:
            return false;
            break;
        }
    }

其中editTableData()函数和getTableDisplayData()类似,为了完成这样稀碎的数据的映射用了一堆switch-case比较无聊,此处只贴上部分源代码:

bool editTableData(const QModelIndex &index, const QVariant &value)
    {
//        根据传入index,修改表格上对应位置的数据

//        稍作判断
        if(!index.isValid() || index.column() >= columnCount() || index.row() >= rowCount()) return false;

//        修改结果
        bool ret = true;
        switch(index.row())
        {

//        只处理row()==1,3

        // 姓名 身高     体重  城市  宠物名称
        case 1:
            switch(index.column())
            {
            case 0:
                person.name = value.toString();
                break;

            case 1:
                person.stature = value.toDouble();
                break;

            case 2:
                person.weight = value.toDouble();
                break;

            case 3:
                person.city = value.toString();
                break;

            case 4:
                petName = value.toString();
                break;

//                其余列未修改
            default:
                ret = false;
                break;
            }

            break;
        //此处省略case 3的修改
        
        default:
            ret = false;
            break;
        }

//        修改成功则触发信号
        if(ret) emit dataChanged(index, index);

        return ret;
    }

Ctrl+R运行试一下,已经可以修改内容了:
在这里插入图片描述


3. 小结

  1. 设计了一堆稀碎的原始数据,说实话添加了很多编码麻烦,但是在Model中正确处理后,都没啥问题;所以在设计数据结构的时候,最好能有一定的解析规律
  2. QAbstractTableModel派生出自己的MyTableModel类,通过实现data()、rowCount()、columnCount()接口,完成数据的显示;
  3. 实现flags()、setData()接口后,完成表格编辑功能;
  4. 使用QAbstractItemView::setModel()接口,将自定义的Model对象指针传入,完成ModelView的联动;
  5. 项目的源代码在此处下载。

如有错误欢迎指正,共同进步~


今天你学废了吗?

Qt中的Model/View是一种基于MVCModel-View-Controller)设计模式的实现方式。Model/View架构将数据的存储和显示分离开来,使得程序的结构更加清晰,并且可以提高程序的可维护性和可扩展性。 在Qt中,Model/View是面向对象的。它由三个基础组成:QAbstractItemModel、QAbstractTableModel和QAbstractListModel。QAbstractItemModel为QAbstractTableModel和QAbstractListModel提供了接口规范,使用它可以将数据模型与View分离开来。QAbstractTableModel主要为表格型数据模型定义了一套标准。QAbstractListModel与之似,为列表型数据模型定义了一套标准。 在Model/View中,Model提供了从数据源中获取数据并将其封装成数据项及其属性的方式。而View根据Model提供的数据项及其属性,对其进行可视化展示。对于数据的修改和删除等操作,则通过View传递给Model来进行实现Model的数据来源可以是任何型的数据,例如数据库、XML文件、内存中的数据等等。ModelView之间的通信是通过信号和槽机制来实现的。当Model的数据发生变化时,它会发出数据变化的信号,View会从这些信号中得知数据发生了哪些改变,然后对其进行更新。 Model/View提供了一种灵活、高效、可扩展的方案来处理数据。在Qt中,开发者可以使用其提供的各种ModelView,或者继承这些实现自己的数据模型和视图,以便更好地满足自己的需求。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值