首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >QTreeView使用总结13,自定义model示例,大大优化性能和内存[通俗易懂]

QTreeView使用总结13,自定义model示例,大大优化性能和内存[通俗易懂]

作者头像
全栈程序员站长
发布于 2022-09-03 06:09:01
发布于 2022-09-03 06:09:01
2.8K00
代码可运行
举报
运行总次数:0
代码可运行

大家好,又见面了,我是你们的朋友全栈君。

1,简介

前面简单介绍过Qt的模型/视图框架,提到了Qt预定义的几个model类型: QStringListModel:存储简单的字符串列表 QStandardItemModel:可以用于树结构的存储,提供了层次数据 QFileSystemModel:本地系统的文件和目录信息 QSqlQueryModel、QSqlTableModel、QSqlRelationalTableModel:存取数据库数据

一般情况下满足需求了,不过有时候需要一些定制功能,或者是大量数据下对性能和开销比较注重,觉得自带的model无用功能太多效率比较低,这时候自定义model就比较适合了。 我使用自定义model 同时出于这两方面需要,既为了性能也为了特殊功能。

2,参考资料

豆子《Qt学习之路2》中的几篇关于自定义model的文章: 自定义model之一: 自定义只读模型 自定义model之二: 自定义可编辑模型 自定义model之三: 布尔表达式树模型

3,效果

本篇文章写的费了点功夫,为了演示本章内容,花了几个小时的时间整理代码和示例。 因为技术都应用在我的项目里,实际所用的model实现了很多特殊功能,非常复杂,我要提炼出一个简单可读的demo。

如图,分别演示了以常规的 QStandardItemModel 和使用自定义的model的效果。 示例中只使用了10W行的数据量级 运行程序你就会发现,常规model在初始化tree的过程就比自定义model慢很多,更可怕的是,它所占用的内存开销是自定义model的数倍甚至数十倍!数据量越大内存差距越明显。

这里以10个一级节点班级,每个班级1W个学生,共10W条记录的数据量测试: QStandardItemModel 方法程序占用总内存大概160多M,而自定义model 占用的30多M。 而Qt一个简单窗口程序本身有20多M内存。 可见自定义model显示这10W条记录基本没使用多少内存,如果考虑百万、千万级别的数据,不使用自定义model或比较有效的优化方法,内存将很快耗尽。

4,构造演示数据

我演示的例子为一级节点班级、二级节点学生信息。

其中学生信息原始数据只有姓名、三门课成绩,需显示的列多一些,包含: 班级/姓名、语文、数学、外语、总分、平均分、是否合格、是否评优

其中后面几列是根据学生成绩计算得出的: 所有课成绩都>60则合格,所有课成绩都>90则优秀。

定义数据类型:班级、学生

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//学生信息
typedef struct _STUDENT{
    QString name;   //姓名
    int score1;     //语文成绩
    int score2;     //数学成绩
    int score3;     //外语成绩
    _STUDENT()
    {
        name = "";
        score1 = score2 = score3 = 0;
    }
}STUDENT,*PSTUDENT;

//班级信息
typedef struct _CLASS{
    QString name;   //班级
    QVector<STUDENT*> students;
    _CLASS()
    {
        name = "";
    }
}CLASS;
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    //初始化模拟数据:学生成绩
    //10个班级、每个班级10000个学生,共10W行记录
    int nClass = 10;
    int nStudent = 10000;
    for(int i=0;i<nClass;i++)
    {
        CLASS* c = new CLASS;
        c->name = QString("class%1").arg(i);
        for(int j=0;j<nStudent;j++)
        {
            STUDENT* s = new STUDENT;
            s->name = QString("name%1").arg(j);
            s->score1 = s->score2 = s->score3 = (j%10)*10;
            c->students.append(s);
        }
        mClasses.append(c);
    }
}

其中mClasses为存放模拟数据的变量:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
QVector<CLASS*> mClasses;   //模拟数据

5,QStandardItemModel 常规model

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void MainWindow::on_btn1_clicked()
{
    //1,QTreeView常用设置项
    QTreeView* t = ui->treeView;
//    t->setEditTriggers(QTreeView::NoEditTriggers);			//单元格不能编辑
    t->setSelectionBehavior(QTreeView::SelectRows);			//一次选中整行
    t->setSelectionMode(QTreeView::SingleSelection);        //单选,配合上面的整行就是一次选单行
//    t->setAlternatingRowColors(true);                       //每间隔一行颜色不一样,当有qss时该属性无效
    t->setFocusPolicy(Qt::NoFocus);                         //去掉鼠标移到单元格上时的虚线框

    //2,列头相关设置
    t->header()->setHighlightSections(true);                //列头点击时字体变粗,去掉该效果
    t->header()->setDefaultAlignment(Qt::AlignCenter);      //列头文字默认居中对齐
    t->header()->setDefaultSectionSize(100);                //默认列宽100
    t->header()->setStretchLastSection(true);               //最后一列自适应宽度
    t->header()->setSortIndicator(0,Qt::AscendingOrder);    //按第1列升序排序

    //3,构造Model
    QStringList headers;
    headers << QStringLiteral("班级/姓名")
            << QStringLiteral("语文")
            << QStringLiteral("数学")
            << QStringLiteral("外语")
            << QStringLiteral("总分")
            << QStringLiteral("平均分")
            << QStringLiteral("是否合格")
            << QStringLiteral("是否评优");
    QStandardItemModel* model = new QStandardItemModel(ui->treeView);
    model->setHorizontalHeaderLabels(headers);

    foreach (CLASS* c, mClasses)
    {
        //一级节点:班级
        QStandardItem* itemClass = new QStandardItem(c->name);
        model->appendRow(itemClass);

        foreach (STUDENT* s, c->students)
        {
            //二级节点:学生信息
            int score1 = s->score1;
            int score2 = s->score2;
            int score3 = s->score3;
            int nTotal = score1 + score2 + score3;
            int nAverage = nTotal/3;
            bool bPass = true;
            if(score1 < 60 || score2 < 60 || score3 < 60)
            {
                //任意一门课不合格则不合格
                bPass = false;
            }
            bool bGood = false;
            if(score1 >= 90 && score2 >= 90 && score3 >= 90)
            {
                //每门课都达到90分以上评优
                bGood = true;
            }

            QList<QStandardItem*> items;
            QStandardItem* item0 = new QStandardItem(s->name);
            QStandardItem* item1 = new QStandardItem(QString::number(score1));
            QStandardItem* item2 = new QStandardItem(QString::number(score2));
            QStandardItem* item3 = new QStandardItem(QString::number(score3));
            QStandardItem* item4 = new QStandardItem(QString::number(nTotal));
            QStandardItem* item5 = new QStandardItem(QString::number(nAverage));
            QStandardItem* item6 = new QStandardItem(bPass ? "合格" : "不合格");
            QStandardItem* item7 = new QStandardItem(bGood ? "优秀" : "-");
            items << item0 << item1 << item2 << item3 << item4 << item5 << item6 << item7;
            itemClass->appendRow(items);
        }
    }

    //4,应用model
    t->setModel(model);
}

6,自定义model

Qt提供一个基础的model类QAbstractItemModel,前面几种常用model也基本从此类而来。 我们写一个自定义的TreeModel,继承自该类,实现里面的一些重载函数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include "TreeItem.h"
#include <QAbstractItemModel>
#include <QModelIndex>
#include <QVariant>
#include "define.h"


class TreeModel : public QAbstractItemModel
{
    Q_OBJECT

public:
    explicit TreeModel(QStringList headers,QObject *parent = 0);
    ~TreeModel();

    //以下为自定义model需要实现的一些虚函数,将会被Qt在查询model数据时调用
    //headerData: 获取表头第section列的数据
    //data: 核心函数,获取某个索引index的元素的各种数据
    //      role决定获取哪种数据,常用有下面几种:
    //      DisplayRole(默认):就是界面显示的文本数据
    //      TextAlignmentRole:就是元素的文本对齐属性
    //      TextColorRole、BackgroundRole:分别指文本颜色、单元格背景色
    //flags: 获取index的一些标志,一般不怎么改
    //index: Qt向你的model请求一个索引为parent的节点下面的row行column列子节点的元素,在本函数里你需要返回该元素的正确索引
    //parent:获取指定元素的父元素
    //rowCount: 获取指定元素的子节点个数(下一级行数)
    //columnCount: 获取指定元素的列数
    QVariant headerData(int section, Qt::Orientation orientation,int role = Qt::DisplayRole) const override;
    QVariant data(const QModelIndex &index, int role) const override;
    Qt::ItemFlags flags(const QModelIndex &index) const override;
    QModelIndex index(int row, int column,const QModelIndex &parent = QModelIndex()) const override;
    QModelIndex parent(const QModelIndex &index) const override;
    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    int columnCount(const QModelIndex &parent = QModelIndex()) const override;

public:
    TreeItem *itemFromIndex(const QModelIndex &index) const;
    TreeItem *root();

private:
    QStringList mHeaders;   //表头内容
    TreeItem *mRootItem;    //根节点
};

这些函数基本作用在注释内注明了,主要需要根据自己的情况写好data函数,其它的内容可以参考我的示例代码,略微调整。 其中TreeItem 为我们自定义的指代一个节点的类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <QVariant>

class TreeItem
{
public:
    explicit TreeItem(TreeItem *parentItem = 0);
    ~TreeItem();

    void appendChild(TreeItem *child);      //在本节点下增加子节点
    void removeChilds();                    //清空所有节点

    TreeItem *child(int row);               //获取第row个子节点指针
    TreeItem *parentItem();                 //获取父节点指针
    int childCount() const;                 //子节点计数
    int row() const;                        //获取该节点是父节点的第几个子节点

    //核心函数:获取节点第column列的数据
    QVariant data(int column) const;

    //设置、获取节点是几级节点(就是树的层级)
    int level(){ return mLevel; }
    void setLevel(int level){ mLevel = level; }

    //设置、获取节点存的数据指针
    void setPtr(void* p){ mPtr = p; }
    void* ptr(){ return mPtr; }

    //保存该节点是其父节点的第几个子节点,查询优化所用
    void setRow(int row){
        mRow = row;
    }
	
private:
    QList<TreeItem*> mChildItems;   //子节点
    TreeItem *mParentItem;          //父节点
    int mLevel;     //该节点是第几级节点
    void* mPtr;     //存储数据的指针
    int mRow;       //记录该item是第几个,可优化查询效率

};

其中只需存一个真实数据的指针void* mPtr 即可,这样便大大减少了因为常规Model内重复存储数据所带来的内存开销,这也是该方法能节约内存的主要原因。

另外介绍几个核心函数实现: TreeModel::data():视图获取数据时调用的函数,里面通过具体的TreeItem::data()获取最终数据

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
QVariant TreeModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
    if (role == Qt::DisplayRole)
    {
        return item->data(index.column());
    }
    return QVariant();
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
QVariant TreeItem::data(int column) const
{
    if(mLevel == 1)
    {
        //一级节点,班级
        if(column == 0)
        {
            CLASS* c = (CLASS*)mPtr;
            return c->name;
        }
    }
    else if(mLevel==2)
    {
        //二级节点学生信息
        STUDENT* s = (STUDENT*)mPtr;
        switch (column)
        {
        case 0: return s->name;
        case 1: return QString::number(s->score1);
        case 2: return QString::number(s->score2);
        case 3: return QString::number(s->score3);
        case 4: return QString::number(s->score1 + s->score2 + s->score3);
        case 5: return QString::number( (s->score1 + s->score2 + s->score3)/3 );
        case 6:
        {
            if(s->score1 < 60 || s->score2 < 60 || s->score3 < 60)
            {
                //任意一门课不合格则不合格
                return QStringLiteral("不合格");
            }
            else
            {
                return QStringLiteral("合格");
            }
        }
        case 7:
        {
            if(s->score1 >= 90 && s->score2 >= 90 && s->score3 >= 90)
            {
                //每门课都达到90分以上评优
                return QStringLiteral("优秀");
            }
            else
            {
                return QStringLiteral("-");
            }
        }
        default:
            return QVariant();
        }
    }
    return QVariant();
}

看到这里,可以发现,自定义model实际需要存储的数据,比界面所显示的列数要少的多! 只要能通过现有数据推算出来的列的数据,都可以不存储! 比如我们只存储了基本的3门课程分数,其他内容全为显示时视图向我们的自定义model获取数据时实时计算得出的!

可能你会担心,这样计算量会不会变大,导致反应速度变慢? 其实视图只会对当前需要显示的数据来请求,意思就是,无论总数据多少,只对当前可见的内容进行计算,你想想电脑屏幕就那么大,这个计算量简直毫无压力。

因此,由于实际需要存储列数变少,内存占用又得到可观的缩减。 不过这种好处只适用于多列的数据有关联可推算的情况。 我的项目内存在大量此类数据,获得收益较大。

进一步了解可以阅读源码。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/139868.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年5月2,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
《QTreeView+QAbstractItemModel自定义模型》:系列教程之三[通俗易懂]
Qt中模型类的层次结构
全栈程序员站长
2022/07/01
7.1K0
《QTreeView+QAbstractItemModel自定义模型》:系列教程之三[通俗易懂]
QTreeView使用总结7,右键菜单
void customContextMenuRequested(const QPoint &pos);
全栈程序员站长
2022/08/25
5.4K0
QTreeView使用总结7,右键菜单
​Qt QTreeView 使用方法详解
QTreeView 是 Qt 框架中用于展示层次化数据的核心控件,属于 Model/View 架构的重要实现。它能够以树形结构可视化复杂数据关系,并提供丰富的交互能力,是构建文件资源管理器、组织架构图、配置面板等场景的理想选择。
lealc
2025/05/28
3010
​Qt QTreeView 使用方法详解
Qt树形控件QTreeView使用1——节点的添加删除操作[通俗易懂]
QtreeView是ui中最常用的控件,Qt中QTreeWidget比QTreeView更简单,但没有QTreeView那么灵活(QTreeWidget封装的和MFC的CTreeCtrl很类似,没有mvc的特点)。
全栈程序员站长
2022/09/06
7.5K0
QTreeView使用总结1,一个简单示例
除去了一切操作响应等细节,只是展示使QTreeView显示出带层次结构的数据,至少需要哪些代码。
全栈程序员站长
2022/07/01
2.3K0
QTreeView使用总结1,一个简单示例
Qt Model View TreeView及对应Model
点击上方蓝字可直接关注!方便下次阅读。如果对你有帮助,可以点个在看,让它可以帮助到更多老铁~
用户5908113
2020/02/17
2.9K0
Qt Model View TreeView及对应Model
C/C++ Qt TableDelegate 自定义代理组件
TableDelegate 自定义代理组件的主要作用是对原有表格进行调整,例如默认情况下Table中的缺省代理就是一个编辑框,我们只能够在编辑框内输入数据,而有时我们想选择数据而不是输入,此时就需要重写编辑框实现选择的效果,代理组件常用于个性化定制Table表格中的字段类型。
王 瑞
2022/12/23
9720
C/C++ Qt TableDelegate 自定义代理组件
QTableView + QStandardItemModel模式显示表格冻结列(模板)
QTableView 是 Qt 框架中一个用于显示和编辑表格数据的控件。它提供了一个灵活的界面,允许用户通过行和列来浏览和修改数据。QTableView 支持多种数据模型,如 QAbstractItemModel,并且可以与模型一起使用,以提供数据视图。此外,QTableView 还提供了排序、筛选和分组等功能,以满足不同的需求。
Qt历险记
2024/07/29
1K0
QTableView + QStandardItemModel模式显示表格冻结列(模板)
C++ Qt开发:StandardItemModel数据模型组件
Qt 是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍StandardItemModel数据模型组件的常用方法及灵活运用。
王 瑞
2023/12/23
1.1K0
C++ Qt开发:StandardItemModel数据模型组件
【QT】QT模型/视图
MVC(Model-View-Controller)包括了3个组件:模型(model)是应用对象,用来表示数据;视图(View)是模型的用户界面,用来显示数据;控制(Controller)定义了用户界面对用户输入的反应方式。
半生瓜的blog
2023/05/13
3.4K0
【QT】QT模型/视图
C/C++ Qt TreeWidget 单层树形组件应用
TreeWidget 目录树组件,该组件适用于创建和管理目录树结构,在开发中我们经常会把它当作一个升级版的ListView组件使用,因为ListView每次只能显示一列数据集,而使用TableWidget组件显示多列显得不够美观,此时使用Tree组件显示单层结构是最理想的方式,本章博文将通过TreeWidget实现多字段显示,并增加一个自定义菜单,通过在指定记录上右键可弹出该菜单并对指定记录进行操作。
王 瑞
2022/12/23
1.1K0
C/C++ Qt TreeWidget 单层树形组件应用
C/C++ Qt StandardItemModel 数据模型应用
QStandardItemModel 是标准的以项数据为单位的基于M/V模型的一种标准数据管理方式,Model/View 是Qt中的一种数据编排结构,其中Model代表模型,View代表视图,视图是显示和编辑数据的界面组件,而模型则是视图与原始数据之间的接口,通常该类结构都是用在数据库中较多,例如模型结构负责读取或写入数据库,视图结构则负责展示数据,其条理清晰,编写代码便于维护。
王 瑞
2022/12/22
1.9K0
C/C++ Qt StandardItemModel 数据模型应用
Qt编写自定义控件46-树状导航栏
树状导航栏控件是所有控件中最牛逼最经典最厉害的一个,在很多购买者中,使用频率也是最高,因为该导航控件集合了非常多的展示效果,比如左侧图标+右侧箭头+元素前面的图标设置+各种颜色设置等,全部涵盖了,代码量也比较多,该控件前后完善了三年,还提供了角标展示文字信息,纵观市面上web也好,cs架构的程序也好,这种导航条使用非常多,目前只提供了二级菜单,如果需要三级菜单需要自行更改源码才行。
feiyangqingyun
2019/08/28
3K0
Qt编写自定义控件46-树状导航栏
26.QT-模型视图之自定义委托
由于模型负责组织数据,而视图负责显示数据,所以当用户想修改显示的数据时,就要通过视图中的委托来完成
诺谦
2018/07/31
2.5K0
26.QT-模型视图之自定义委托
QT 播放器之列表[通俗易懂]
重写自定义菜单函数,如果右键的时候没有选中任何索引,需要把播放,删除,打开目录设置为不可点击
全栈程序员站长
2022/07/01
2.1K0
25.QT-模型视图
在Qt中,不管模型以什么结构组织数据,都必须为每个数据提供不同的索引值,使得视图能通过索引值访问模型中的具体数据
诺谦
2018/07/31
1.7K0
25.QT-模型视图
Qt中自定义QTreeWidget实现节点拖拽复制功能[通俗易懂]
QT中在QWidget支持拖拽功能,QTreeWidget继承自QWidget,所以自然也具有节点的拖拽功能。
全栈程序员站长
2022/11/14
5K0
Qt中自定义QTreeWidget实现节点拖拽复制功能[通俗易懂]
Qml自定义ListModel(视频)
  声明MyListModel到Qml中,并创建其实例MyListModel { id: myListModel }。ListView的代理(delegate)通过设置modelData改变其myListModel对象的QStringList内容。
Qt君
2023/03/17
7610
Qml自定义ListModel(视频)
Qt编写自定义控件34-磁盘容量统计
磁盘容量统计控件,说白了,就是用来统计本地盘符占用的容量,包括但不限于已用空间、剩余空间、总大小、已用百分比等,其中对应的百分比采用进度条显示,该进度条的前景色和背景色及文字颜色可以设置,在整体换肤的时候就需要用到。
feiyangqingyun
2019/08/27
1.6K0
Qt编写自定义控件34-磁盘容量统计
Qt TableWidget 控件 及自定义委托
上次和大家分享了TreeWidget的简单使用,本次和大家分享下TableWidget的简单应用以及项目视图中的自定义委托。
用户5908113
2019/12/19
2.4K0
相关推荐
《QTreeView+QAbstractItemModel自定义模型》:系列教程之三[通俗易懂]
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档