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)

五、相關文章

  1. Qt實現表格控件-支持多級列表頭、多級行表頭、單元格合並、字體設置等

  2. Qt高仿Excel表格組件-支持凍結列、凍結行、內容自適應和合並單元格

  3. 屬性瀏覽器控件QtTreePropertyBrowser編譯成動態庫(設計師插件)

  4. 超級實用的屬性瀏覽器控件--QtTreePropertyBrowser

  5. Qt之表格控件螞蟻線

  6. QRowTable表格控件-支持hover整行、checked整行、指定列排序等

  7. QRowTable表格控件(二)-紅漲綠跌


如果您覺得文章不錯,不妨給個 打賞,寫作不易,感謝各位的支持。您的支持是我最大的動力,謝謝!!!




很重要--轉載聲明

  1. 本站文章無特別說明,皆為原創,版權所有,轉載時請用鏈接的方式,給出原文出處。同時寫上原作者:朝十晚八 or Twowords

  2. 如要轉載,請原文轉載,如在轉載時修改本文,請事先告知,謝絕在轉載時通過修改本文達到有利於轉載者的目的。



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM