深入浅出 Qt 中 QListView 的设计思想,并掌握大规模、高性能列表的实现方法

在大规模列表控件的显示需求中,必须解决2个问题才能获得较好的性能:

  1. 第一就是数据存在哪里, 避免出现数据的副本。
  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 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值