当前位置: 首页 > news >正文

QML与C++:基于ListView调用外部模型进行增删改查(性能优化版)

目录

    • 引言
    • 相关阅读
    • 工程结构
      • 数据模型设计
        • DataModel 类
        • ContactProxyModel 类
      • 为什么使用QSortFilterProxyModel?
      • 应用初始化与模型连接
      • UI实现
    • 性能分析与优化
    • 运行效果
    • 扩展思考
    • 总结
    • 下载链接

引言

在上一篇中介绍了基于ListView调用外部模型进行增删改查,本文在此基础之上进行性能优化,由于篇幅有限,会省略部分代码,完整代码请看本文最后的下载链接

本文将以一个能够流畅处理10万条联系人数据的QML应用为例,详细介绍如何利用Qt的模型-视图架构和QSortFilterProxyModel实现高性能的数据筛选和展示功能。通过这个实例,读者可以了解Qt Model & View在大数据量处理方面的优势以及相关的开发技巧。

相关阅读

  • 接上篇 —— QML与C++:基于ListView调用外部模型进行增删改查(附自定义组件)

工程结构

该示例项目采用了典型的Qt/QML混合开发架构,主要由C++实现数据模型,QML负责用户界面设计。下面通过mermaid图展示工程的整体结构:

main.cpp
DataModel.h/cpp
Main.qml
QAbstractListModel
ContactProxyModel
QSortFilterProxyModel
ContactDialog.qml
components
CustomTextField.qml
IconButton.qml
CustomButton.qml

核心文件说明:

  • main.cpp: 应用入口,创建模型并连接到QML
  • datamodel.h/cpp: 数据模型定义和实现
  • Main.qml: 主界面设计
  • ContactDialog.qml: 联系人添加/编辑对话框
  • components/: 自定义控件目录

数据模型设计

本项目的核心在于高效的数据模型设计,主要采用了两层模型结构:

  1. 基础数据模型 (DataModel):继承自QAbstractListModel,负责基础数据的存储和管理
  2. 代理模型 (ContactProxyModel):继承自QSortFilterProxyModel,负责数据筛选和排序

下面详细分析这两个模型的实现:

DataModel 类
class ContactItem {
public:ContactItem(const QString &name, const QString &phone): m_name(name), m_phone(phone) {}QString name() const { return m_name; }QString phone() const { return m_phone; }QString firstLetter() const { return m_name.isEmpty() ? "?" : m_name.left(1).toUpper(); }private:QString m_name;QString m_phone;
};class DataModel : public QAbstractListModel
{Q_OBJECTpublic:enum Roles {NameRole = Qt::UserRole + 1,PhoneRole,FirstLetterRole};explicit DataModel(QObject *parent = nullptr);int rowCount(const QModelIndex &parent = QModelIndex()) const override;QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;QHash<int, QByteArray> roleNames() const override;Q_INVOKABLE bool addContact(const QString &name, const QString &phone);Q_INVOKABLE bool removeContact(int index);Q_INVOKABLE bool editContact(int index, const QString &name, const QString &phone);private:QList<ContactItem> m_items;
};

DataModel类继承自QAbstractListModel,主要职责是存储和管理联系人数据。它具有以下几个关键特性:

  1. 使用了轻量级的ContactItem类来封装每个联系人的信息
  2. 定义了自定义角色枚举,用于在QML中访问数据
  3. 实现了必要的虚函数:rowCount、data和roleNames
  4. 提供了添加、删除和编辑联系人的Q_INVOKABLE方法,使其可从QML直接调用

在构造函数中生成了10万条测试数据:

DataModel::DataModel(QObject *parent): QAbstractListModel(parent)
{// 记录开始时间qint64 startTime = QDateTime::currentMSecsSinceEpoch();// 预分配空间以提高性能m_items.reserve(100000);// 开始批量插入beginInsertRows(QModelIndex(), 0, 99999);// 添加10万条测试数据for(int i = 0; i < 100000; ++i) {m_items.append(ContactItem(generateRandomName(), generateRandomPhone()));}endInsertRows();// 计算并输出耗时qint64 endTime = QDateTime::currentMSecsSinceEpoch();qDebug() << "添加10万条数据耗时:" << (endTime - startTime) << "毫秒";
}

这里有几个值得注意的性能优化点:

  1. 使用reserve预分配空间,避免频繁的内存重分配
  2. 使用beginInsertRowsendInsertRows包裹大量数据的插入,这是一种批处理技术,减少了模型更新通知的次数
  3. 添加计时代码,用于测量大量数据处理的性能
ContactProxyModel 类
class ContactProxyModel : public QSortFilterProxyModel
{Q_OBJECTQ_PROPERTY(QString filterString READ filterString WRITE setFilterString NOTIFY filterStringChanged)public:explicit ContactProxyModel(QObject *parent = nullptr);QString filterString() const { return m_filterString; }void setFilterString(const QString &filterString);Q_INVOKABLE bool removeContact(int index);Q_INVOKABLE bool editContact(int index, const QString &name, const QString &phone);signals:void filterStringChanged();protected:bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;private:QString m_filterString;
};

ContactProxyModel是整个应用的关键部分,它继承自QSortFilterProxyModel,主要负责数据的过滤和展示。它具有以下特点:

  1. 定义了一个Q_PROPERTY属性filterString,用于接收来自QML的搜索文本
  2. 重写了filterAcceptsRow方法,实现自定义的过滤逻辑
  3. 为QML提供了代理方法,将操作转发至底层的DataModel

实现部分:

ContactProxyModel::ContactProxyModel(QObject *parent): QSortFilterProxyModel(parent)
{setFilterCaseSensitivity(Qt::CaseInsensitive);setSortCaseSensitivity(Qt::CaseInsensitive);
}void ContactProxyModel::setFilterString(const QString &filterString)
{if (m_filterString != filterString) {m_filterString = filterString;emit filterStringChanged();invalidateFilter();}
}bool ContactProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{if (m_filterString.isEmpty())return true;QModelIndex nameIndex = sourceModel()->index(sourceRow, 0, sourceParent);QModelIndex phoneIndex = sourceModel()->index(sourceRow, 0, sourceParent);QString name = sourceModel()->data(nameIndex, DataModel::NameRole).toString();QString phone = sourceModel()->data(phoneIndex, DataModel::PhoneRole).toString();return name.contains(m_filterString, Qt::CaseInsensitive) ||phone.contains(m_filterString, Qt::CaseInsensitive);
}bool ContactProxyModel::removeContact(int index)
{if (DataModel *model = qobject_cast<DataModel*>(sourceModel())) {QModelIndex sourceIndex = mapToSource(this->index(index, 0));return model->removeContact(sourceIndex.row());}return false;
}bool ContactProxyModel::editContact(int index, const QString &name, const QString &phone)
{if (DataModel *model = qobject_cast<DataModel*>(sourceModel())) {QModelIndex sourceIndex = mapToSource(this->index(index, 0));return model->editContact(sourceIndex.row(), name, phone);}return false;
}

为什么使用QSortFilterProxyModel?

QSortFilterProxyModel是Qt中非常强大的一个类,在本项目中使用它主要有以下几个优势:

  1. 数据与视图分离:原始数据模型(DataModel)只负责数据的存储和管理,而过滤和排序逻辑由代理模型(ContactProxyModel)处理,实现了关注点分离

  2. 高效的数据过滤:QSortFilterProxyModel内部实现了高效的过滤机制,即使在10万条数据的情况下,也能实现实时过滤而不影响UI的响应性

  3. 无需修改原始数据:过滤和排序不会修改原始数据,仅影响视图的展示,保持了数据的完整性

  4. 懒加载机制:QSortFilterProxyModel采用了懒加载机制,只有当数据需要显示时才会进行过滤操作,大大提高了性能

  5. 索引映射:代理模型自动处理了索引的映射,在删除或编辑项目时,不必手动计算筛选后的索引与原始数据的对应关系

下面的代码片段展示了这一映射机制:

bool ContactProxyModel::removeContact(int index)
{if (DataModel *model = qobject_cast<DataModel*>(sourceModel())) {QModelIndex sourceIndex = mapToSource(this->index(index, 0));return model->removeContact(sourceIndex.row());}return false;
}

这段代码通过mapToSource将代理模型中的索引映射回源模型中的索引,然后调用源模型的removeContact方法。这种机制在处理筛选后的数据时尤为重要,因为筛选后的索引与原始数据的索引并不一致。

应用初始化与模型连接

在main.cpp中,创建数据模型和代理模型,并将它们连接起来:

int main(int argc, char *argv[])
{QGuiApplication app(argc, argv);QQmlApplicationEngine engine;QObject::connect(&engine,&QQmlApplicationEngine::objectCreationFailed,&app,[]() { QCoreApplication::exit(-1); },Qt::QueuedConnection);// 创建数据模型实例DataModel *model = new DataModel(&engine);// 创建代理模型实例ContactProxyModel *proxyModel = new ContactProxyModel(&engine);proxyModel->setSourceModel(model);// 将模型暴露给QMLengine.rootContext()->setContextProperty("contactModel", proxyModel);engine.loadFromModule("qml_listview_model", "Main");return app.exec();
}

这里的关键点是:

  1. 创建DataModel实例作为基础数据模型
  2. 创建ContactProxyModel实例并通过setSourceModel设置其数据源
  3. 通过setContextProperty将代理模型暴露给QML,使得QML可以直接访问和操作模型

UI实现

应用的UI部分主要通过QML实现,包括主界面(Main.qml)和联系人编辑对话框(ContactDialog.qml)。UI设计采用了现代的Material Design风格,具有搜索框、联系人列表和操作按钮等组件。

Main.qml的核心部分是ListView组件,它绑定的代理模型:

ListView {id: contactListViewanchors.fill: parentanchors.rightMargin: scrollBar.widthmodel: contactModelspacing: 10clip: true// 启用滚动条绑定ScrollBar.vertical: scrollBardelegate: Rectangle {// 联系人项的UI实现// ...}
}

搜索功能的实现非常简洁:

CustomTextField {id: searchFieldLayout.fillWidth: trueplaceholderText: "搜索联系人..."leftIcon: "qrc:/icons/find.png"onTextChanged: contactModel.filterString = textonRightIconClicked: {text = ""contactModel.filterString = ""}
}

当用户在搜索框中输入文本时,onTextChanged事件会将文本赋值给代理模型的filterString属性。然后代理模型会自动应用过滤逻辑并更新视图。


性能分析与优化

在处理10万条数据的过程中,采用了以下性能优化策略:

  1. 数据批量处理:使用beginInsertRows/endInsertRows批量添加数据,减少模型更新通知
  2. 内存预分配:使用reserve预分配内存空间,避免频繁的内存重分配
  3. 懒加载机制:利用QSortFilterProxyModel的懒加载特性,只处理需要显示的数据
  4. 高效数据结构:使用轻量级的ContactItem类,减少内存占用
  5. 视图优化:ListView中使用clip属性限制渲染区域,减少不必要的绘制

在DataModel的构造函数中,增加了性能测量代码:

qint64 startTime = QDateTime::currentMSecsSinceEpoch();
// 添加数据的代码...
qint64 endTime = QDateTime::currentMSecsSinceEpoch();
qDebug() << "添加10万条数据耗时:" << (endTime - startTime) << "毫秒";

在本机上,这段代码只需130毫秒就能完成10万条数据的加载。而在搜索过滤时,即使是10万条数据,响应也是即时的,不会出现明显的延迟。

运行效果

联系人列表

如图所示,应用在加载10万条联系人数据后仍能保持流畅的操作体验,包括:

  • 滚动列表时没有明显卡顿
  • 搜索过滤响应及时
  • 编辑操作即时反映在界面上

扩展思考

本示例已经能够处理10万级的数据量并通过UI展示出来,然后继续尝试加载百万级数据(在本机,ListView通过模型加载1百万条数据需要1.3秒),但在此基础上进行搜索过滤操作,会比较卡。以下是一些可能的扩展和优化方向:

  1. 分页加载:对于超大数据量,可以考虑分页加载。
  2. 多线程数据处理:将数据加载和处理放在单独的线程中,避免阻塞UI线程。
  3. 数据持久化:添加数据库支持,实现数据的持久化存储。
  4. 更复杂的过滤和排序:支持多条件过滤和自定义排序规则。

总结

本文详细介绍了如何利用Qt的模型-视图架构和QSortFilterProxyModel实现高性能的数据处理和展示。关键要点包括:

  1. 使用QAbstractListModel实现基础数据模型,负责数据的存储和管理
  2. 使用QSortFilterProxyModel实现代理模型,负责数据的过滤和排序
  3. 采用批量处理、内存预分配等技术提高性能
  4. 利用Qt的模型-视图架构实现数据和视图的分离,提高代码的可维护性

下载链接

完整项目源码可从以下链接获取:GitCode -> QML ListView 示例

QML-ListView示例

相关文章:

  • 【Leetcode-Hot100】最大子数组和
  • 【教程】如何使用Labelimg查看已经标注好的YOLO数据集标注情况
  • C++| 深入剖析std::list底层实现:链表结构与内存管理机制
  • VTK知识学习(51)- 交互与Widget(三)
  • 小程序获取用户总结(全)
  • ArrayList vs LinkedList,HashMap vs TreeMap:如何选择最适合的集合类?
  • CEPH配置优化建议
  • 小程序css实现容器内 数据滚动 无缝衔接 点击暂停
  • AtomNet:在极端MCU约束下基于算子设计微型模型
  • LivePortrait 使用指南:让静态照片“动”起来的魔法工具
  • 【自动化测试】如何获取cookie,跳过登录的简单操作
  • 一个异步架构设计:批量消费RabbitMQ,批量写入Elasticsearch(golang实现)
  • hadoop执行sqoop任务找不到jar
  • Dijkstra算法求解最短路径—— 从零开始的图论讲解(2)
  • 第十章 go mod操作
  • 【Java SE】Collections类详解
  • 2.1 腾讯校招通关指南-算法与数据结构
  • trl的安装与单GPU多GPU测试
  • 一文读懂WPF系列之依赖属性与附加属性
  • C++进阶——C++11_智能指针
  • 中俄弘扬正确二战史观:缅怀历史,重拾初心,阻止悲剧重演
  • 上海消防全面推行“检查码”,会同相关部门推行“综合查一次”
  • 第32届梅花奖终评启幕,上海京剧院《智取威虎山》满堂彩
  • 欧盟委员会计划对950亿欧元美国进口产品采取反制措施
  • 国家主席习近平同普京总统签署关于进一步深化中俄新时代全面战略协作伙伴关系的联合声明
  • 巴基斯坦军方:印度导弹袭击已造成至少3死14伤