QRowTable表格控件(三)-效率优化之-合理使用QStandardItem
原文链接:qrowtable表格控件(三)-效率优化之-合理使用qstandarditem
一、开心一刻
砖家在河边看到两只乌龟缩着一动不动,问一农民:“它们在干吗?”。
农民说:“在比赛!”。
砖家不解:“动都没动过,比什么赛?”。
农民:“在比装死!”。
砖家:“可是壳上有甲骨文的那只,早就死了啊?”。
这时,一只乌龟猛然探出头来骂道:“md,死了也不吭一声!”。
突然另一只也伸出头来:“*!砖家的话你也信,你输了 !”。
二、概述
最近换了一家新单位,工作的内容也发了一些变化。接触了一些大牛,也让我对qt有了一个新的认识。
不看源码真他妈不行呀
这不今天就给大家说一说我最近工作中遇到的一个坑,而这个坑只有看了源码后才明白。
上一篇qrowtable表格控件(二)-红涨绿跌文章讲到了我们怎么往表格中添加数据,并且是了一个简单的股票组件。可以存放各种行情数据、持仓和订单等等。
下面问题就来了,我这个demo中的数据只有不到10行,当你真的把这个控件投入的生产环境时就会发现,demo就是demo,它就是个demo而已。
博主自己大概测试了下把数据调到10000行,等了几分钟界面还没有出来,就放弃了。
测试代码如下:
int rate = 10000; model->setrowcount(rate * itemvec.size()); for (int j = 0; j < rate; ++j) { for (int i = 0; i < itemvec.size(); ++i) { const optionalmarketitem & info = itemvec.at(i); qstandarditem * item_price = new qstandarditem; item_price->settext(qstring::number(info.price)); item_price->setdata(int(qt::alignright | qt::alignvcenter), qt::textalignmentrole); model->setitem(i, 0, item_price); ... } }
既然代码性能不行,我们当然需要去找更好的实现方式了,总不能就这么上线吧。
于是乎,有了如下代码,30000行数据1-2s即可初始化完毕,震惊脸。
int rate = 10000; model->setrowcount(rate * itemvec.size()); for (int j = 0; j < rate; ++j) { for (int i = 0; i < itemvec.size(); ++i) { const optionalmarketitem & info = itemvec.at(i); int row = i + j * itemvec.size(); qmodelindex index = model->index(row, 0); model->setdata(index, qstring::number(info.price), qt::displayrole); model->setdata(index, int(qt::alignright | qt::alignvcenter),qt::textalignmentrole); } }
我槽,上述两种书写方式有球区别,怎么会差别如此之大,下面让我为大家细细道来。
三、效果展示
以下是红涨绿跌效果图,demo中展示了30000数据,应该算是比较多,可以满足大多数的应用场景。
腹黑版
四、qstandarditem
1、qstandarditem是什么鬼
qt的帮助文档是一个好东西,打开assisant.exe,搜索qstandarditem类,可以搜索如下提示信息。
什么意思呢!
为了阅读起来更流畅,我这里就行中文内容的意译。
虽然英文解释很多,但是意译成中文后就很简单了,毕竟帮助文档要说的很清晰、场景囊括的会比较全一些
意译:qstandarditem是一个数据结构,他可以存储一个cell的各种信息,比如文本、图标、是否可选、字体、别景色、前景色等等。并且qstandarditem可以有孩子和兄弟,他是为model提供数据存储的节点。
这里我在补充一些内容,让大家对qstandarditem右更进一步的了解。
qtableview:作为表格cell时,有一个作为根节点的qstandarditem,其他节点都是qstandarditem节点的孩子节点,并且都是兄弟节点(这里暂时不考虑多列的情况)。
qtreeview:作为树节点cell时,有一个作为根节点的qstandarditem,其他节点都是他的孩子节点,但是其他节点也可以作为父节点存在(这里暂时不考虑多列的情况)。
2、性能分析
a、qstandarditem构造慢?
简单了解qstandarditem对象后,下面开始从代码上分析性能问题到底出现在了哪里。
//优化前代码 qstandarditem * item_price = new qstandarditem; item_price->settext(qstring::number(info.price)); item_price->setdata(int(qt::alignright | qt::alignvcenter), qt::textalignmentrole); model->setitem(i, 0, item_price); //优化后代码 int row = i + j * itemvec.size(); qmodelindex index = model->index(row, 0); model->setdata(index, qstring::number(info.price), qt::displayrole); model->setdata(index, int(qt::alignright | qt::alignvcenter),qt::textalignmentrole);
仔细比较以上代码,优化前的diamante是我们自己构造了qstandarditem,然后设置数据并存储到qstandarditemmodel中,而优化后的代码我们直接把数据通过qstandarditemmodel进行了设置。
这两种方式到底有何区别???
解决这种问题,qt给我们提供了很好的问题解决方式,直接跟踪源码即可。想要把qt了解透彻,源码是唯一的途径。其他什么各种搜索引擎都弱爆了。
如上图所示,跟踪qt的源码发现,当我们通过model设置数据项时,qt内部也是为我们构造了一个qstandarditem对象,然后把数据放到这个对象上的。
说明qstandarditem对象的构造并不是性能所在,性能问题还需要进一步分析。
很重要:qt的model中把数据又单独封装了一层,数据存储在qstandarditem对象中。本篇文章主要分析的性能瓶颈在qstandarditem对象的使用上,如果想要极致的性能体验,还有比本篇文章更容易的方式,只是需要自己写的代码就会变得更多,如果有需要的话可自行搜索自定义model,然后自己对数据进行管理,这样就少了qstandarditem对象的构造和很多数据类型的转换
由于博主使用的场景,表格数据不会超过100行,因此轻量级的处理已经可以满足需求,没有进一步去重写model数据源管理。
百度不到的话,欢迎评论区留言,后续博主有时间进一步优化。
b、setdata有问题?
先抛出答案,问题确实处在setdata上
如下两种图是qstandarditem在设置数据孩子数据时很重要的一个调用,把参数中的item设置为当前节点的孩子节点。
仔细看图中红色框圈起来的内容,有一个emitchanged变量控制了3个信号的触发。
问题就出现在这个emitchanged变量上,他的意思就是说当前item是否发现了变化。
仔细回想我们优化前的代码,qstandarditem对象是不是我们自己构造的,然后设置给了model,这是不是搬起石头砸自己的脚。
仔细一分析:好像是这么回事,优化前的代码在行数较少时不会有明细问题,可是当数据量很大时,其实这是有问题的。
c、性能根源
既然知道是多发送了3个信号导致了性能问题,那么接下来就是分析这3个信号都干了什么。
下面按触发顺序来分别解释每一个信号
1、layoutabouttobechanged
如下图是帮助文档描述
意译:该信号的触发在model的布局即将发生变化时触发。model还有布局,这是什么鬼,其实就是说model中的item发现了变化。
这个信号其实还提供了参数,可以方便我们对某一些节点进行刷新,当我们指定了父节点和刷新策略时生效。
qstandarditem的setdata这里没有指定参数,表示全量刷新,使用时需要非常注意。
2、layoutchanged
如下图是帮助文档描述
意译:该信号的触发在model的布局发生变化之后,也就说需要全量刷新model时,可以通过触发该信号达到目的。
比如重新排序、数据源发送变化等。
这个信号不建议大家主动调用,数据量大时会导致性能问题
如果非要调用,也应该到信号的参数带上这样就是局部刷新
博主之前做过一个控件,是优化qtreeview控件相关的,意思是说想让qtreeview的行高可以自定义。
做过这块内容的同学可能都知道,model在通过data函数获取数据时有一个字段role,这个字段表示了他想获取什么样的数据,解决办法也就在这里了,当role等于qt::sizehintrole时,表示我们想要获取的行高,我们通过这里设置一个合适的行高即可。
qt为了优化性能,不会每一次都计算树控件的行高,这里做了一个优化,只有第一次也就说model发现变化时才从qstandarditem中获取行高,然后所有的额行高信息都存储在了视图的viewitem缓存中,这直接导致了我们在界面上拖拽垂直表头行高,内容行高不会发生变化。
这里就需要用到layoutchanged信号,当我们给qstandarditem重新设置了行高之后,需要激活model布局发生变化事件。
3、itemchanged
如下图所示,看名字就知道itemchanged这个喜好是干嘛使得。
==分析了以上3个函数,大家心里是不是对qstandarditem有了一个全新的认识。==
==既然自己构造item这么坑,博主建议大家干脆就不要使用new qstandard这句代码了。==
凡事总有例外,既然qt把这个类导出给我们使用了,总是有他的道理,对于一些特殊场景可能需要自定义item,这时候qt建议我们是这么做的。
如上图,我们需要重写几个函数,这里大家知道就行。大家记住,一般情况下都不需要这么干。
3、qstandarditem使用上的坑
1、原则上qstandarditem不需要我们去构造,使用model的index函数访问cell时qt内部会帮我们构造,特别是对于数据量大时,qt内部构造会有很大的效率提升。
2、model的setitem使用上需要注意,除非一些特殊场景(比如我们自定义item),否则尽量不要使用。
自定义item,需要重写很多东西;设置item时,原有item将会被删除
3、对于需要设置cell自定义窗口用法
通过指定行列设置
setcellwidget(int row, int column, qwidget *widget)
五、相关文章
如果您觉得文章不错,不妨给个打赏,写作不易,感谢各位的支持。您的支持是我最大的动力,谢谢!!!
很重要--转载声明
本站文章无特别说明,皆为原创,版权所有,转载时请用链接的方式,给出原文出处。同时写上原作者: or twowords
如要转载,请原文转载,如在转载时修改本文,请事先告知,谢绝在转载时通过修改本文达到有利于转载者的目的。