在大规模列表控件的显示需求中,必须解决2个问题才能获得较好的性能:
- 第一就是数据存在哪里, 避免出现数据的副本。
- 第二就是如何展示Item,如何复用或避免创建大量的Item控件。
在QListView体系里,QAbstractListModel解决的是“数据存哪”,解决的是第一个问题,而QAbstractItemDelegate解决的是数据“如何展示”,解决的是第二个问题。
因此,在大规模列表的编写代码中,高效的数据存储和高效的数据UI展示,需要用到这两个类。接下来,我们通过三个例子,循序渐进地介绍QListView,使读者掌握QListView的使用技巧和设计思想。
示例 1: 使用 QListWidget 的基本用法
QListWidget
是一个方便的控件,它内部管理了一个项目列表,并提供了一些简单的接口来添加、删除和修改这些项目。但没有对数据存储和数据展示进行过多的优化,这种方式适合于简单的应用场景,其中列表的大小不会很大,因为每个项目都会被存储为一个 QListWidgetItem
对象。
#include <QApplication>
#include <QListWidget>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QListWidget* listWidget = new QListWidget;
for (int i = 0; i < 100; ++i) {
listWidget->addItem(QString("项目 %1").arg(i));
}
listWidget->show();
return app.exec();
}
尽管这种方式使用起来非常简单,但它并不适合处理大量数据。因为 QListWidget
会为每个项目创建一个 QListWidgetItem
对象,这将导致大量的内存消耗。
示例 2: 使用 QListView 和 QAbstractItemDelegate(解决数据存哪的问题)
在这个示例中,我们将直接从 QAbstractListModel
派生一个Model类,而不是使用 addItem
构造大量的ItemData。这样,我们就无需存储这些数据。这个方法在处理具有可预知数据模式的大量数据时特别有用,因为它避免了冗余的数据存储和内存开销。
首先,我们定义一个 SyntheticListModel
类,它继承自 QAbstractListModel
。这个模型将根据索引动态生成数据项:
#include <QAbstractListModel>
#include <QVariant>
#include <QModelIndex>
class SyntheticListModel : public QAbstractListModel {
Q_OBJECT
public:
SyntheticListModel(int numItems, QObject *parent = nullptr)
: QAbstractListModel(parent), m_numItems(numItems) {
}
int rowCount(const QModelIndex &parent = QModelIndex()) const override {
// 在顶层,返回项的数量;否则返回0,因为这是一个列表,没有子项
return parent.isValid() ? 0 : m_numItems;
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {
if (index.isValid() && index.row() < m_numItems) {
if (role == Qt::DisplayRole) {
// 根据行号动态生成数据
return QString("项目 %1").arg(index.row());
}
// 可以根据需要添加其他角色的处理
}
return QVariant(); // 无效索引或角色时返回无效的QVariant
}
private:
int m_numItems; // 列表中项目的数量
};
然后,我们创建一个简单的 QStyledItemDelegate
,这个委托可以根据需要自定义项的显示方式:
#include <QStyledItemDelegate>
#include <QPainter>
class SimpleDelegate : public QStyledItemDelegate {
Q_OBJECT
public:
using QStyledItemDelegate::QStyledItemDelegate; // 继承构造函数
// 重写 paint 方法以自定义项目显示方式
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override {
// 调用基类的 paint 方法进行默认的绘制
QStyledItemDelegate::paint(painter, option, index);
// 可以添加额外的绘制代码,如绘制边框、背景等
}
// 如果需要,可以重写 createEditor、setEditorData、setModelData 等方法以自定义编辑行为
};
最后,我们在 main
函数中创建 QListView
,并将其与我们的自定义模型和委托相连接:
#include <QApplication>
#include <QListView>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
// 创建列表视图
QListView listView;
// 创建模型,这里我们创建了 100000 个假数据项
SyntheticListModel model(100000);
// 创建委托
SimpleDelegate delegate;
// 将模型和委托设置到列表视图
listView.setModel(&model);
listView.setItemDelegate(&delegate);
listView.show();
return app.exec();
}
在这个示例中,我们展示了如何使用 QListView
和自定义的 QAbstractListModel
来动态生成数据,而不需要在内存中维护一份数据存储的副本。在实际业务中,数据可以直接从业务模块中获取,这样避免出现数据的两个副本。SimpleDelegate
负责定制列表项的视觉呈现,但在这个简化的例子中,我们仅使用了默认的绘制逻辑。如果需要更复杂的项显示或编辑功能,可以在委托中进一步扩展 paint
和其他相关方法。这些内容我们在示例3中展现。
示例 3: 使用 QListView 和自定义 QAbstractListModel(解决数据如何展示问题)
实例2中没有展开SimpleDelegate 的实现,在实际开发场景中,界面展示的需求往往更加复杂,特别是QListView的View模型采用的是paint函数来呈现,和其他图形界面框架(如AndroidFramework)构造一个QWidget*
控件的形式不同,paint的形式用起来更复杂,但性能天花板更高。
下面是一个使用自定义模型 LargeListModel
和委托 SimpleDelegate
的例子。在这个示例中,我们将创建一个自定义的 QAbstractListModel
类,名为 LargeListModel
,它将处理大量数据项。此外,我们还将扩展 SimpleDelegate
类来自定义 QListView
中项的视觉呈现。这个委托将负责绘制项的背景、文本和一些装饰元素,从而提供更丰富的用户界面。
首先,我们定义 LargeListModel
类,该类派生自 QAbstractListModel
:
#include <QAbstractListModel>
class