在Qt中使用QListView插入大量数据时,可能会遇到卡顿的问题。以下是一些解决方法和优化建议:

使用QAbstractListModel和QAbstractItemDelegate:

QListView体系中,QAbstractListModel用于解决数据存储问题,而QAbstractItemDelegate用于解决数据展示问题。通过这两个类,可以高效地存储和展示大量数据。
例如,可以创建一个继承自QAbstractListModel的模型类,动态生成数据项,而不是使用addItem构造大量的QListWidgetItem。这样可以避免冗余的数据存储和内存开销。
避免在data方法中执行耗时计算:

在QAbstractListModel的data方法中,避免执行耗时的计算。如果需要,可以将数据缓存或使用后台线程来准备数据。
使用beginInsertRows和endInsertRows:

当需要插入大量数据时,使用beginInsertRows和endInsertRows(或对应的删除和更新版本)来批量插入或删除数据,避免频繁更新视图,从而提高性能。
设置统一的项高度:

如果列表项的大小是固定的,使用setUniformItemSizes(true)可以提高滚动和渲染的性能。
实现延迟加载或数据分页:

如果数据的读取是昂贵的操作,可以考虑实现延迟加载或数据分页,这样只有当数据真正需要显示时才读取。
使用分段式加载:

通过分段式加载数据,可以减少一次性加载大量数据导致的卡顿。例如,每12个数据进行一次加载,并在加载完成之前延迟释放内存。
重载QAbstractItemModel中的关键函数:

重载QAbstractItemModel中的rowCount、data、headerData等函数,确保只处理可见区域的数据,从而减少内存占用和提高显示速度。
自定义滚动条:

隐藏QListView自带的滚动条,并实现自定义滚动条,这样可以在不往表格插入大量数据的情况下撑起滚动条,避免卡顿。
使用QStandardItemModel:

在某些情况下,使用QStandardItemModel代替QListWidget可以提高性能。QStandardItemModel更适合处理大量数据,因为它允许更灵活的数据管理

#include "virtual_list.h"
#include <qscrollbar.h>
#include <qevent.h>
#include <qfontmetrics.h>
#ifdef QT_DEBUG
#include <qdebug.h>
#endif // QT_DEBUG


const static int nStdItemHeight = 40; // item固定40高度

VirtualList::VirtualList(QWidget* parent)
    : QWidget(parent),
    m_nShowCount(0),
    m_nCurrentPos(0),
    m_nCurrentIndex(-1),
    m_nSelectedIndex(-1)
{
    QHBoxLayout* pLayout = new QHBoxLayout(this);
    m_pListView = new QListView(this);
    m_pScrollBar = new QScrollBar(this);
    m_pStdModel = new QStandardItemModel(m_pListView);
    m_pListView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    m_pListView->setResizeMode(QListView::ResizeMode::Adjust);
    m_pListView->setEditTriggers(QListView::NoEditTriggers);
    m_pListView->setModel(m_pStdModel);
    m_dataList.reserve(12000);
    pLayout->addWidget(m_pListView);
    pLayout->addWidget(m_pScrollBar);
    m_pListView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    m_pScrollBar->setSingleStep(nStdItemHeight); // 一次移动一个Item
    m_pScrollBar->setPageStep(nStdItemHeight);
    m_pScrollBar->hide();

    connect(m_pScrollBar, &QScrollBar::valueChanged, this, &VirtualList::OnScrollBarValueChanged);
    connect(m_pListView, &QListView::clicked, this, [&] {
        m_nSelectedIndex = m_nCurrentIndex + m_pListView->currentIndex().row();
        });

    installEventFilter(this);
    m_pListView->installEventFilter(this);
    m_pScrollBar->installEventFilter(this);
}

VirtualList::~VirtualList()
{}

void VirtualList::Append(const QString& data)
{
    m_dataList.push_back(data);
    // 数据追加后,更新索引
    if (m_nCurrentIndex < 0)
    {
        m_nCurrentIndex = 0;
    }
    // 处理下滚动条
    HandleScrollBar();
    // 如果当前数据的数量小于显示数量,那么要刷新一次到界面
    if (m_dataList.size() - 1 < m_nShowCount)
    {
        RefreshData();
    }
}

void VirtualList::Append(const QList<QString>& dataList)
{
    m_dataList.append(dataList);
    // 数据追加后,更新索引
    if (m_nCurrentIndex < 0)
    {
        m_nCurrentIndex = 0;
    }
    // 处理下滚动条
    HandleScrollBar();
    // 如果当前数据的数量小于显示数量,那么要刷新一次到界面
    if (m_dataList.size() - dataList.size() < m_nShowCount)
    {
        RefreshData();
    }
}

void VirtualList::Clear()
{
    m_dataList.clear();
    m_nCurrentPos = 0;
    m_nCurrentIndex = -1;
    m_nSelectedIndex = -1;
    for (int i = m_pStdModel->rowCount() - 1; i >= 0; i--)
    {
        QStandardItem* item = m_pStdModel->item(i, 0);  // 获取QStandardItem对象
        delete item;  // 释放QStandardItem对象
        item = nullptr;
        m_pStdModel->removeRow(i);
    }
}

QListView* VirtualList::GetListView() const
{
    return m_pListView;
}

int VirtualList::ColumnCount() const
{
    return 1;
}

int VirtualList::GetSelectedIndex() const
{
    return m_nSelectedIndex;
}

QString VirtualList::GetData(int index) const
{
    if (index < 0 || index >= m_dataList.size())
    {
        return {};
    }
    return m_dataList[index];
}

QList<QString> VirtualList::GetAllData() const
{
    return m_dataList;
}

void VirtualList::resizeEvent(QResizeEvent* event)
{
    QWidget::resizeEvent(event);

    // 获取重置的窗口大小
    QSize listSize = m_pListView->size();
    m_nShowCount = listSize.height() / nStdItemHeight;
    if (m_nShowCount > 0 && m_dataList.size() > 0)
    {
        HandleScrollBar();

        // 填充数据
        RefreshData();
    }
}

bool VirtualList::eventFilter(QObject* obj, QEvent* event)
{
    if (event->type() == QEvent::Wheel)
    {
        if (obj == m_pScrollBar || obj == m_pListView)
        {
            QWheelEvent* we = static_cast<QWheelEvent*>(event);
            if (we->angleDelta().y() > 0) // 向上滚
            {
                m_pScrollBar->setValue(m_pScrollBar->value() - nStdItemHeight);
            }
            else // 向下滚
            {
                m_pScrollBar->setValue(m_pScrollBar->value() + nStdItemHeight);
            }
            return true;
        }
    }
    return QWidget::eventFilter(obj, event);
}

void VirtualList::OnScrollBarValueChanged(int value)
{
#ifdef QT_DEBUG
    qDebug() << value;
#endif
    // 查看最新的数据索引(最新索引是指即将要展示的首个数据索引)
    int newIndex = std::ceil(value / (nStdItemHeight * 1.0f));

    // 最新的索引超出数组大小
    if (newIndex >= m_dataList.size())
    {
        // 最新的和当前的索引差值小于展示的数量时,直接退出
        if (std::abs(newIndex - m_nCurrentIndex) <= m_nShowCount)
        {
            return;
        }
        else
        {
            --newIndex;
        }
    }
    // 数据索引刷新
    m_nCurrentIndex = newIndex;

    // 取消选中状态
    m_pListView->clearSelection();
    // 看看刚才的选中索引是否在范围内
    if (m_nSelectedIndex >= 0 && m_nSelectedIndex >= newIndex && m_nSelectedIndex <= newIndex + m_nShowCount)
    {
        int indexDiff = m_nSelectedIndex - m_nCurrentIndex;
        m_pListView->setCurrentIndex(m_pStdModel->index(indexDiff, 0));
    }

    // 做下越界防护
    if (m_nCurrentIndex >= m_dataList.size())
    {
        m_nCurrentIndex = m_dataList.size() - 1;
    }
    if (m_nCurrentIndex < 0)
    {
        m_nCurrentIndex = 0;
    }

    // 重置页面数据
    RefreshData();
}

void VirtualList::HandleScrollBar()
{
    if (m_dataList.size() > m_nShowCount)
    {
        // 设置大小,[0, item高度 * 数据长度]
        m_pScrollBar->setRange(0, nStdItemHeight * m_dataList.size());
        if (m_nShowCount < m_dataList.size()) // 显示的数量小于总数量
        {
            m_pScrollBar->setVisible(true); // 滚动条显示出来
        }
    }
}

int VirtualList::CalcTextWidth(const QFont& font, const QString& text)
{
    QFontMetrics fm(font);
    return fm.horizontalAdvance(text);
}

void VirtualList::RefreshData()
{
    int showCount = m_nShowCount; // 界面最终展示多少个

    // 最后数据小于应该显示的数量时,重新计算下数量
    if (showCount + m_nCurrentIndex >= m_dataList.size())
    {
        showCount = m_dataList.size() - m_nCurrentIndex;
    }
    if (showCount > 0)
    {
        int diff = std::abs(showCount - m_pStdModel->rowCount());
        if (showCount > m_pStdModel->rowCount()) // 展示的数量大于当前的,要添加
        {
            for (int i = 0; i < diff; i++)
            {
                QStandardItem* pItem = new QStandardItem;
                pItem->setSizeHint(QSize(0, nStdItemHeight));
                m_pStdModel->appendRow(pItem);
            }
        }
        else if (showCount < m_pStdModel->rowCount()) // 展示的数量小于当前的,要删除
        {
            for (int i = m_pStdModel->rowCount() - 1; i >= showCount; i--)
            {
                QStandardItem* item = m_pStdModel->item(i, 0);  // 获取QStandardItem对象
                delete item;  // 释放QStandardItem对象
                item = nullptr;
                m_pStdModel->removeRow(i);
            }
        }
        else // 等于则不动
        {
            ;
        }
        for (int i = 0; i < showCount; i++)
        {
            QStandardItem* pItem = m_pStdModel->item(i);
            pItem->setSizeHint(QSize(CalcTextWidth(pItem->font(), m_dataList[m_nCurrentIndex + i]),
                nStdItemHeight));
            pItem->setText(m_dataList[m_nCurrentIndex + i]);
        }
    }
}

int VirtualList::RowCount() const
{
    return m_dataList.size();
}
  • 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.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.
  • 235.
  • 236.
  • 237.
  • 238.
  • 239.
  • 240.
  • 241.
  • 242.
  • 243.
  • 244.
  • 245.
  • 246.
  • 247.
  • 248.
  • 249.
  • 250.
  • 251.
  • 252.
  • 253.
  • 254.
  • 255.
  • 256.
  • 257.
  • 258.
  • 259.
  • 260.
  • 261.
  • 262.
  • 263.
  • 264.
  • 265.
  • 266.
  • 267.
  • 268.
  • 269.
  • 270.
  • 271.
  • 272.
  • 273.
  • 274.
  • 275.
  • 276.
  • 277.
#ifndef VIRTUAL_LIST_H
#define VIRTUAL_LIST_H

#include <QtWidgets/QMainWindow>
#include <qlistview.h>
#include <qlist.h>
#include <qstandarditemmodel.h>
#include <qboxlayout.h>
#include <qscrollbar.h>

class VirtualList : public QWidget
{
    Q_OBJECT

public:
    VirtualList(QWidget* parent = nullptr);
    ~VirtualList();

public:
    /**
     * @brief 追加单个数据
     * @param data
    */
    void Append(const QString& data);

    /**
     * @brief 批量追加数据
     * @param dataList
    */
    void Append(const QList<QString>& dataList);

    /**
     * @brief 清空表格
    */
    void Clear();

    /**
     * @brief 获取表格
     * @return
    */
    QListView* GetListView() const;

    /**
     * @brief 重置页面数据
    */
    void RefreshData();

    /**
     * @brief 行数
     * @return
    */
    int RowCount() const;

    /**
     * @brief 列数
     * @return
    */
    int ColumnCount() const;

    /**
     * @brief 获取当前索引
     * @return
    */
    int GetSelectedIndex() const;

    /**
     * @brief 获取指定行的数据
     * @param index 行号,索引从0开始
     * @return
    */
    QString GetData(int index) const;

    /**
     * @brief 获取全部数据
     * @return
    */
    QList<QString> GetAllData() const;

protected:
    void resizeEvent(QResizeEvent* event) override;
    bool eventFilter(QObject* obj, QEvent* event) override;

protected:
    /**
     * @brief 竖向滚动条的指改变
     * @param value
    */
    void OnScrollBarValueChanged(int value);

    /**
     * @brief 处理滚动条(是否显示,以及长度区间)
    */
    void HandleScrollBar();

private:
    /**
     * @brief 计算文本长度
     * @param font
     * @param text
     * @return
    */
    int CalcTextWidth(const QFont& font, const QString& text);

private:
    QListView* m_pListView;
    QScrollBar* m_pScrollBar;
    QList<QString> m_dataList;
    QStandardItemModel* m_pStdModel;
    int m_nShowCount; // 显示的数量
    int m_nCurrentPos; // 滚动条当前位置
    int m_nCurrentIndex; // 当前数据索引
    int m_nSelectedIndex; // 选中的索引
};

#endif
  • 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.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
#include "virtual_list.h"
#include <QtWidgets/QApplication>
#include <qlistwidget.h>


int main(int argc, char* argv[])
{
	QApplication a(argc, argv);
	VirtualList w;
	QList<QString> list;
	// 插入50W条数据
	for (int i = 0; i < 500000; i++)
	{
		list.push_back(QString::number(i) + "");
	}
	w.Append(list);
	w.RefreshData();
	w.show();
	return a.exec();
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.