QTableWidget懒加载剩内存,不卡!

前言

用到表格加载数据,少量数据看不出什么异常,当数据过于庞大,特别是表格中可能还加入了一些界面实时刷新如进度条等,那最终体现出来的效果很差,加载卡,占内存。
所以,下面总结分享的是一种懒加载的策略。

QTableWidget常用属性设置

    QStringList headList;
    headList<<"序号"<<"影片名"<<"票房(万元)"<<"票房占比";
    m_table->setColumnCount(headList.length());
    m_table->setHorizontalHeaderLabels(headList);
    m_table->verticalHeader()->setVisible(false);//隐藏行号
    m_table->verticalHeader()->setDefaultSectionSize(m_itemHeight);//默认行高
    //  需加 #include 
    //m_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);//自动分均每列列宽
    //m_table->horizontalHeader()->setMinimumSectionSize(100);//默认列宽
    m_table->setColumnWidth(0,200);//设置第0列列宽
    m_table->horizontalHeader()->setDefaultAlignment(Qt::AlignCenter);//表头居中对齐
    m_table->horizontalHeaderItem(0)->setTextAlignment(Qt::AlignRight);//设置第0列列宽
    m_table->setSelectionBehavior(QAbstractItemView::SelectRows);//以行为单位选择
    //m_table->setEditTriggers(QAbstractItemView::NoEditTriggers);//禁止编辑
    m_table->horizontalHeader()->setStyleSheet
            ("QHeaderView::section{background-color:rgb(216,226,236);"
              "border:0.5px solid rgb(254,254,254);}");

加载策略说明

主要有两个点:

1、对于需要实时更新加载的情况,每次加载数据时,不用调clearContents()、removeRow(int row)等方法去清空再new,而是去重复利用上次已经new出的Item,改变其内容为最新即可

2、只加载界面可见范围内Item,数据的具体数量只需通过滚动条的值体现出来,每次改变滚动条时,再去按照上述1去刷新Item的内容

QTableWidget懒加载剩内存,不卡!_第1张图片
QTableWidget懒加载剩内存,不卡!_第2张图片

直接上代码

代码结构
QTableWidget懒加载剩内存,不卡!_第3张图片
mytablewidget.h

#ifndef MYTABLEWIDGET_H
#define MYTABLEWIDGET_H

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
class TableWidget: public QTableWidget
{
public:
    TableWidget(QWidget *parent = nullptr):QTableWidget(parent){}
    ~TableWidget(){}
protected:
    void wheelEvent(QWheelEvent *event) override {}//屏蔽鼠标滑轮事件
};

class MyTableWidget: public QWidget
{
public:
    MyTableWidget(QWidget *parent = nullptr);
    ~MyTableWidget();
    virtual void InitTable();
    virtual void LoadTableData();

    void MakeData();
    QString GetStr();

    void SetMenu(QMenu* menu);
    void SetItemText(int row, int colunm, QString str);
    void SetItemProgress(int row, int colunm, double value, double total);
protected:
    void wheelEvent(QWheelEvent *event) override;
    void resizeEvent(QResizeEvent *event) override;
    void contextMenuEvent(QContextMenuEvent *event) override;

    void showEvent(QShowEvent *event) override;
    void hideEvent(QHideEvent *event) override;
public:
private:
    TableWidget* m_table;
    QScrollBar *m_scrollBar;
    int m_itemHeight;
    int m_currentRow;
    int m_dataCnt;
    bool m_bShow;
public:
    //data
    std::map<QString, double> m_mapData;//name boxOffice
    QMenu* m_menu = nullptr;
};
#endif // MYTABLEWIDGET_H

mytablewidget.cpp

#include "mytablewidget.h"
#include 
#include 
MyTableWidget::MyTableWidget(QWidget *parent):QWidget(parent)
{
    m_bShow = false;
    m_scrollBar = nullptr;
    m_table = nullptr;
    m_itemHeight = 20;
    m_dataCnt = 0;
}

MyTableWidget::~MyTableWidget()
{

}

void MyTableWidget::InitTable()
{
    m_table = new TableWidget(this);
    //点击表头排序
//    connect(m_table->horizontalHeader(),&QHeaderView::sectionClicked,this,[=](int colunm){
//        m_table->sortItems(colunm,Qt::AscendingOrder);
//    });
    m_table->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);//关闭滚动条
    QHBoxLayout* hbox =new QHBoxLayout(this);
    hbox->setMargin(0);
    hbox->setSpacing(0);
    hbox->addWidget(m_table);
    m_scrollBar = new QScrollBar(Qt::Vertical);
    connect(m_scrollBar,&QScrollBar::valueChanged,this,[=](int value){
        LoadTableData();
    });
    hbox->addWidget(m_scrollBar);

    QStringList headList;
    headList<<"序号"<<"影片名"<<"票房(万元)"<<"票房占比";
    m_table->setColumnCount(headList.length());
    m_table->setHorizontalHeaderLabels(headList);
    m_table->verticalHeader()->setVisible(false);//隐藏行号
    m_table->verticalHeader()->setDefaultSectionSize(m_itemHeight);//默认行高
    //  需加 #include 
    //m_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);//自动分均每列列宽
    //m_table->horizontalHeader()->setMinimumSectionSize(100);//默认列宽
    m_table->setColumnWidth(0,200);//设置第0列列宽
    m_table->horizontalHeader()->setDefaultAlignment(Qt::AlignCenter);//表头居中对齐
    m_table->horizontalHeaderItem(0)->setTextAlignment(Qt::AlignRight);//设置第0列列宽
    m_table->setSelectionBehavior(QAbstractItemView::SelectRows);//以行为单位选择
    m_table->setEditTriggers(QAbstractItemView::NoEditTriggers);//禁止编辑
    m_table->horizontalHeader()->setStyleSheet
            ("QHeaderView::section{background-color:rgb(216,226,236);"
              "border:0.5px solid rgb(254,254,254);}");

    //线程加载数据
    QThread* thread = new QThread();
    connect(thread, &QThread::started, thread, [=]()mutable{
        qDebug()<<"started!";
        MakeData();
        thread->quit();
    }, Qt::DirectConnection);
    connect(thread, &QThread::finished, this, [=]()mutable{
        thread->wait();
        thread->deleteLater();
        qDebug()<<"finished!";
        LoadTableData();
    });
    thread->start();
}

void MyTableWidget::LoadTableData()
{
    if(!m_table)
        return;
    int oldRow = m_table->rowCount() - 1;
    if(m_currentRow < oldRow)//如果item有了就不删了重新创建,在之前item修改内容即可,界面尺寸改变,显示的行就改变,多了就删除,
    for (int i=oldRow - 1; i>oldRow - m_currentRow - 1; i--)
    {
        m_table->removeRow(i);
    }
     m_table->setRowCount(m_currentRow+1);
     int total = 1000;
     std::map<QString, double>::iterator itor = m_mapData.begin();
     int beginRow = m_scrollBar->value();//滚动条值,表格行
     for (int i=0; i<beginRow; i++){ itor++;}//从滚动条的值对应显示
     for (int i =0 ; i<m_currentRow; i++,itor++)
     {
         if(itor == m_mapData.end())
         {
             m_table->removeRow(i);
             break;
         }
         SetItemText(i, 0, QString::number(beginRow++));
         SetItemText(i, 1, itor->first);
         SetItemText(i, 2, QString::number(itor->second,'f',2));
         SetItemProgress(i, 3, itor->second, total);
     }
}

void MyTableWidget::SetMenu(QMenu *menu)
{
    m_menu = menu;
}

void MyTableWidget::resizeEvent(QResizeEvent *event)
{
    m_currentRow = height()/m_itemHeight;
    m_dataCnt = m_mapData.size();
    if(m_dataCnt - m_currentRow > 0)
    {
        if(m_scrollBar)
        {
            m_scrollBar->setRange(0,m_dataCnt - m_currentRow + 2);
            m_scrollBar->show();
        }
    }
    else
    {
        if(m_scrollBar)
        {
            m_scrollBar->setRange(0,0);
            m_scrollBar->hide();
        }
    }
    LoadTableData();
}

void MyTableWidget::contextMenuEvent(QContextMenuEvent *event)
{
    QTableWidgetItem* item = m_table->itemAt(event->pos());
    if(item && m_menu)
        m_menu->exec(event->globalPos());
}

void MyTableWidget::wheelEvent(QWheelEvent *event)
{
    int value = m_scrollBar->value();
    if(event->delta() > 0)
    {
        value -= m_currentRow;
        if(value < m_scrollBar->minimum())
            value = m_scrollBar->minimum();
        m_scrollBar->setValue(value);
    }
    else
    {
        value += m_currentRow;
        if(value > m_scrollBar->maximum())
            value = m_scrollBar->maximum();
        m_scrollBar->setValue(value);
    }
}

void MyTableWidget::showEvent(QShowEvent *event)
{
    if(! m_bShow)
    {
        InitTable();
        m_bShow = true;
    }
}

void MyTableWidget::hideEvent(QHideEvent *event)
{

}

void MyTableWidget::MakeData()
{
    int dataCnt = 1000000;
    for (int i=0; i<dataCnt;i++) {
        double value = rand()%1000000+1000;
        m_mapData[GetStr()] = value/100;
    }
}

QString MyTableWidget::GetStr()
{
   int length = rand()%15+5;
   const char ch[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
   int size = sizeof(ch);
   char* str = new char[length + 1];
   int num = 0;
   int i;
   for (i = 0; i < length; ++i)
   {
       num = rand() % (size - 1);
       str[i] = ch[num];
   }
    str[i] = '\0';
   QString res(str);
   //return res;
   return "《"+res+"》";
}


void MyTableWidget::SetItemText(int row, int colunm, QString str)
{
    QTableWidgetItem* item = m_table->item(row, colunm);
    if(nullptr == item)
    {
        item = new QTableWidgetItem;
        m_table->setItem(row, colunm, item);
    }
    item->setText(str);
}

void MyTableWidget::SetItemProgress(int row, int colunm, double value, double total)
{
    QWidget *widget = m_table->cellWidget(row, colunm);
    QProgressBar* bar = static_cast<QProgressBar*>(widget);
    if(nullptr == bar)
    {
        bar = new QProgressBar;
        bar->setObjectName("progress");
        m_table->setCellWidget(row, colunm, bar);//设置后会自动show
        bar->hide();//先hide 加载完会自动show
    }
    double percent =(double) value/total;
    bar->setAlignment(Qt::AlignCenter);
    bar->setRange(0,100);
    bar->setValue(percent*100);
    bar->setFormat(QString("%1%").arg(QString::number(percent*100,'f',1)));
}

总结

有兴趣自己可以对比一下,我分享的和正常一次加载百万级别的数据到表格中,这俩种方式,看下程序内存,和加载速度,逻辑处理还是很快的 ,绘制UI确实比较慢,占内存。理论上,我分享的这种策略加载数据的大小上限,主要就跟数据存储方式和容量有关了,毕竟加载界面可视范围的Item还是比较快的.,但是比如你用容器map存储还是有上限的。还有就是线程那,要加 Qt::DirectConnection 在线程执行,具体了解更多Qt 信号和槽及第五个参数

你可能感兴趣的:(qt,c++,算法,开发语言,qt)