1. 程式人生 > >Qt 大資料列表展示

Qt 大資料列表展示

http://blog.csdn.net/tujiaw/article/details/52124102

Qt中使用QListWidget, QTableWidget,QTreeWidget(只考慮最大3層)自定義子widget來展示資料的時候,通常子widget的個數達到了上千載入展示就會很慢,而且很耗記憶體。原因是new出來的widget太多了。下面的解決方案希望能幫助你。

原理:

其實一個列表展示給使用者看的高度是很有限的不會超過一個螢幕的高度,而這個高度只需要很少的子widget就可以填充滿,所以,當你有1萬個資料要展示時,並不需要每個資料都new一個widget(自定義的)來展示,你只需要new顯示出來的那幾個widget,當滾動條滾動的時候將超出螢幕的widget隱藏起來,將要新展示的資料重用隱藏的widget來展示而不需要new新的widget,只有當widget個數不足以覆蓋列表顯示的區域時才new新的(最多也就覆蓋一個螢幕需要的個數)

元件組成

  • 一個父widget,容納子widget和滾動條
  • 子widget使用move方法填充滿展示區域
  • 滾動條載入更多內容

基本方法

  • 在showEvent裡面重新整理要展示的資料到widget
  • resizeEvent的時候會改變展示區域的大小需要重新重新整理資料到widget使其新的區域能完全被widget展示出來
  • wheelEvent需要改變滾動條當前值
  • 連線QScrollBar::valueChanged事件,當事件發生時要將新的資料展示到widget上

怎樣重用子widget

QList<ItemWidget*> m_widgets;
  • 1

m_widgets來快取所有創建出來的子widget,當需要新widget的時候看快取裡是否有隱藏的,如果有就拿出來進行新資料的展示,如果沒有就根據資料的型別來建立新的widget

ItemWidget *TreeWidget::getWidget(const ItemInfo &info)
{
    // 存在這種型別的widget,並且沒有被使用(不可見)直接返回
    ItemWidget *widget = nullptr;
    for (int i=0; i<m_widgets.size(); i++) {
        if (m_widgets[i]->data().type == info.type && !m_widgets[i]->isVisible()) {
            widget = m_widgets
[i]; break; }
} if (widget == nullptr) { if (info.type == Top) { widget = new TopWidget(this); } else if (info.type == Parent) { widget = new ParentWidget(this); } else if (info.type == Child) { widget = new ChildWidget(this); } else { Q_ASSERT(0); } connect(widget, &ItemWidget::sigMousePress, [this, widget]() { if (widget->data().type != Child) { ItemInfo newInfo = widget->data(); newInfo.isExpand = !newInfo.isExpand; updateItemInfo(newInfo); refreshWidgets(); } else { gotoSelected(widget->data().id); } emit sigItemMousePress(widget->data()); }); connect(widget, &ItemWidget::sigMouseDoubleClick, [this, widget]() { emit sigItemMouseDoubleClick(widget->data()); }); m_widgets.append(widget); } widget->setData(info); // 如果是當前選中的widget if (widget->data().type == Child) { widget->setSelected(widget->data().id == m_curChildId); } widget->resize(this->width(), widget->height()); widget->show(); return widget; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

重點是重新整理資料到widget上

首先,將所有可見widget隱藏起來,遍歷所有資料,y軸從0開始每次增加遍歷資料展示時需要的高度,當y值大於滾動條值並且小於元件高度時就需要將子widget展示出來 
元件高度就是滾動條page step值 
滾動條mininum為0,maxinum為y - pageStep 
公式:document length = maxinum() - minimum() + pageStep()

void TreeWidget::refreshWidgets()
{
    for (int i=0; i<m_widgets.size(); i++) {
        m_widgets[i]->resize(this->width(), m_widgets[i]->height());
        m_widgets[i]->hide();
    }

    auto moveItem = [this](const ItemInfo &item, int &y, int &startPos, bool &isContinue) {
        if (y >= m_scrollbar->value() && isContinue) {
            getWidget(item)->move(0, startPos);
            startPos += item.height;
            isContinue = startPos < this->height();
        }
        y += item.height;
    };

    int y = 0, startPos = 0;
    bool isContinue = true;
    for (int i=0; i<m_list.count(); i++) {
        const ItemInfo &topItem = m_list[i];
        moveItem(topItem, y, startPos, isContinue);
        if (topItem.childList.count() > 0 && topItem.isExpand) {
            for (int j=0; j<topItem.childList.count(); j++) {
                const ItemInfo &parentItem = topItem.childList[j];
                moveItem(parentItem, y, startPos, isContinue);
                if (parentItem.childList.count() > 0 && parentItem.isExpand) {
                    for (int k=0; k<parentItem.childList.count(); k++) {
                        const ItemInfo &childItem = parentItem.childList[k];
                        moveItem(childItem, y, startPos, isContinue);
                    }
                }
            }
        }
    }

    qDebug() << "y:" << y << ", startPos:" << startPos << ", height:" << this->height();
    m_scrollbar->move(this->width() - m_scrollbar->width(), 0);
    m_scrollbar->resize(m_scrollbar->width(), this->height());
    m_scrollbar->setPageStep(this->height());
    if (y > startPos) {
        m_scrollbar->setMaximum(y - m_scrollbar->pageStep());
        m_scrollbar->show();
        m_scrollbar->raise();
    } else {
        m_scrollbar->setMaximum(0);
        m_scrollbar->setValue(0);
        m_scrollbar->hide();
    }
    qDebug() << "min:" << m_scrollbar->minimum() << ", max:" << m_scrollbar->maximum() << ", value:" << m_scrollbar->value();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

結尾

  • 效能優化
  • 響應事件擴充

原始碼

  • 程式碼中展示的元素有17000多個效率還是很高的
  • 程式碼中有三層也有兩層,當然一層的話相信更簡單
  • 紅色item是最頂層
  • 綠色item是中間層
  • 白色item是最底層 
    這裡寫圖片描述