QTreeView使用总结: 自定义model示例,大大优化性能和内存
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则优秀。
定义数据类型:班级、学生
//学生信息
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;
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为存放模拟数据的变量:
QVector<CLASS*> mClasses; //模拟数据
5,QStandardItemModel 常规model
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); //默认列宽100t->header()->setStretchLastSection(true); //最后一列自适应宽度t->header()->setSortIndicator(0,Qt::AscendingOrder); //按第1列升序排序//3,构造ModelQStringList 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,应用modelt->setModel(model);
}
6,自定义model
Qt提供一个基础的model类QAbstractItemModel,前面几种常用model也基本从此类而来。
我们写一个自定义的TreeModel,继承自该类,实现里面的一些重载函数:
#include "TreeItem.h"
#include <QAbstractItemModel>
#include <QModelIndex>
#include <QVariant>
#include "define.h"class TreeModel : public QAbstractItemModel
{Q_OBJECTpublic: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 为我们自定义的指代一个节点的类:
#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()获取最终数据
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();
}
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获取数据时实时计算得出的!
可能你会担心,这样计算量会不会变大,导致反应速度变慢?
其实视图只会对当前需要显示的数据来请求,意思就是,无论总数据多少,只对当前可见的内容进行计算,你想想电脑屏幕就那么大,这个计算量简直毫无压力。
因此,由于实际需要存储列数变少,内存占用又得到可观的缩减。
不过这种好处只适用于多列的数据有关联可推算的情况。
我的项目内存在大量此类数据,获得收益较大。
进一步了解可以阅读源码。
7,源码
TreeDemo13 自定义model示例
源码我使用Qt5.8.0 mingw版本在win7编译通过,如有编译问题尝试使用相同环境试试。
QTreeView使用总结: 自定义model示例,大大优化性能和内存相关推荐
- asp.net mvc 自定义 pager 封装与优化
asp.net mvc 自定义 pager 封装与优化 Intro 之前做了一个通用的分页组件,但是有些不足,从翻页事件和分页样式都融合在后台代码中,到翻页事件可以自定义,再到翻页和样式都和代码分离, ...
- 什么是php model类,thinkphp的自定义model类有什么作用?
请问,thinkphp的自定义model类有什么作用?如: <?php //自定义Modle类 namespace Home\Model; use Think\Model; class StuM ...
- ML之LiR:利用LiR线性回归算法(自定义目标函数MSE和优化器GD)对Boston房价数据集(两特征+归一化)进行回归预测
ML之LiR:利用LiR线性回归算法(自定义目标函数MSE和优化器GD)对Boston房价数据集(两特征+归一化)进行回归预测 目录 利用LiR线性回归算法(自定义目标函数MSE和优化器GD)对Bos ...
- boost::geometry模块使用变换自定义坐标系示例
boost::geometry模块使用变换自定义坐标系示例 实现功能 C++实现代码 实现功能 boost::geometry模块使用变换自定义坐标系示例 C++实现代码 #include <i ...
- boost::geometry模块自定义坐标系示例
boost::geometry模块自定义坐标系示例 实现功能 C++实现代码 实现功能 boost::geometry模块自定义坐标系示例 C++实现代码 #include <iostream& ...
- boost::geometry模块自定义多边形示例
boost::geometry模块自定义多边形示例 实现功能 C++实现代码 实现功能 boost::geometry模块自定义多边形示例 C++实现代码 #include <boost/geo ...
- boost::geometry模块实现自定义Linestring示例
boost::geometry模块实现自定义Linestring示例 实现功能 C++实现代码 实现功能 boost::geometry模块实现自定义Linestring示例 C++实现代码 #inc ...
- boost::geometry模块实现自定义点示例
boost::geometry模块实现自定义点示例 实现功能 C++实现代码 实现功能 boost::geometry模块实现自定义点示例 C++实现代码 #include <iostream& ...
- 01_MUI之Boilerplate中:HTML5示例,动态组件,自定义字体示例,自定义字体示例,图标字体示例
1安装HBuilder5.0.0,安装后的界面截图如下: 2 按照https://www.muicss.com/docs/v1/css-js/boilerplate-html中的说明,创建上图的 ...
- sql 自定义函数 示例_SQL滞后函数概述和示例
sql 自定义函数 示例 In the article SQL Server Lead function overview and examples, we explored Lead functio ...
最新文章
- dll文件32位64位检测工具以及Windows文件夹SysWow64的坑
- python event多线程回调
- 在家点点接入云信,打造全新社区商业和社交生态
- SpringSecurity 权限控制准备之IOC容器结构说明
- (模板)网页游戏用的“内容区”的“图赏影音”模板
- pytorch dropout_PyTorch初探MNIST数据集
- 转载:ie6,ie7兼容性总结
- VB.net小技巧——VB中调用matlab
- ABB 机器人DH参数
- 暴风影音- C++研发工程师(windows方向-在线视频)
- libtorrent-bittorrent
- hp计算机u盘启动,惠普台式机u盘启动(免费分享惠普电脑u盘启动步骤)
- 运行“travel[org.apache.tomcat.maven:tomcat7 maven plugin:2.1:run]时出错:未指定项目JDK
- springboot基于微信小程序的运动软件前端的设计与实现毕业设计源码100932
- 被讨厌的勇气:共同体感觉
- 清除node本地缓存
- Parallel ScavengeGC收集器
- NFT和游戏化的革命-通过 Play Earn 尝试世界上第一个国际象棋游戏
- 全自动爬虫,你爱了么
- android 语音和输入法按钮切换,android 切换系统语言,输入法也随之切换