原文連結: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
如要轉載,請原文轉載,如在轉載時修改本文,請事先告知,謝絕在轉載時通過修改本文達到有利於轉載者的目的。