在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.