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

Qt功能QSortFilterProxyModel指南

🌟 Qt QSortFilterProxyModel 完全指南:从入门到精通


本文将带你全面掌握 QSortFilterProxyModel,涵盖 QWidget 和 QML 两种使用方式,从基础概念到高级自定义,让你看完就能在实际项目中熟练运用!

icon

📚 目录

  1. 什么是 QSortFilterProxyModel?
  2. 核心原理与架构
  3. 快速入门(10分钟上手)
  4. 核心接口详解
  5. 进阶实战:自定义过滤与排序
  6. QML 完整集成方案
  7. 常见问题与最佳实践
  8. 性能优化建议
  9. 总结与对比表

一、什么是 QSortFilterProxyModel?

🔍 概念解释

QSortFilterProxyModel 是 Qt 提供的一个代理模型类,它的作用是在不修改原始数据的前提下,为视图(View)提供排序和过滤功能。

核心思想

  • 📦 原始数据模型(SourceModel):存储真实数据,保持不变
  • 🎭 代理模型(ProxyModel):包装原始模型,提供排序/过滤视图
  • 👁️ 视图(View):显示代理模型的数据

🎯 典型使用场景

场景说明
🔍 搜索过滤在表格中根据关键字实时搜索数据
📊 数据排序点击表头按列排序(升序/降序)
🏷️ 分类筛选根据条件(如部门、状态)筛选显示
📋 列表管理在 ListView/TableView 中动态过滤数据
🗂️ 文件浏览文件管理器中按类型/名称过滤文件

💡 为什么不直接修改原始数据?

使用代理模型的优势

  • 原始数据完整性不受影响
  • 可以同时为多个视图提供不同的过滤/排序
  • 可以轻松恢复原始顺序
  • 业务逻辑与视图逻辑解耦

二、核心原理与架构

📊 数据流转图

mapToSource
mapFromSource
SourceModel
原始数据模型
QSortFilterProxyModel
代理模型
QTableView/QListView
视图组件

说明

  • 视图只看到代理模型提供的"过滤/排序后"的数据
  • 代理模型通过 mapToSource()mapFromSource() 进行索引转换
  • 原始数据保持不变,所有变化都发生在代理层

🏗️ 类继承关系(UML)

QAbstractItemModel
+data()
+rowCount()
+columnCount()
QAbstractProxyModel
+setSourceModel()
+sourceModel()
+mapToSource()
+mapFromSource()
QSortFilterProxyModel
+setFilterRole()
+setFilterRegularExpression()
+setSortRole()
+sort()
#filterAcceptsRow()
#lessThan()
CustomProxyModel
+Q_PROPERTY定义
#重写过滤逻辑
#重写排序逻辑

⚙️ 过滤与排序工作流程

数据变化或用户操作
需要过滤?
调用 filterAcceptsRow
返回 true?
显示该行
隐藏该行
需要排序?
调用 lessThan
比较两行数据
确定顺序
更新视图

三、快速入门(10分钟上手)

📱 QML 基础示例(官方推荐方式)

⚠️ 重要说明

QML 中使用 QSortFilterProxyModel 的正确方式

  1. 推荐方式:自定义 C++ 类继承 QSortFilterProxyModel,通过 qmlRegisterType 注册为 QML 类型
  2. 🔧 简单方式:在 C++ 中创建实例,通过 setContextProperty 暴露给 QML
  3. 📦 可选方式:使用第三方库(如 Skycoder42),纯 QML 方案

本节采用推荐方式:自定义类 + QML 直接创建实例


📁 完整代码示例

Step 1: 自定义 C++ 原始模型类

PersonModel.h

#ifndef PERSONMODEL_H
#define PERSONMODEL_H#include <QAbstractListModel>
#include <QString>
#include <QHash>
#include <QVariant>
#include <QModelIndex>struct PersonData {QString name;int age;
};class PersonModel : public QAbstractListModel
{Q_OBJECTpublic:enum PersonRoles {NameRole = Qt::UserRole + 1,AgeRole = Qt::UserRole + 2};explicit PersonModel(QObject *parent = nullptr): QAbstractListModel(parent){addPerson("张三", 28);addPerson("李四", 35);addPerson("王五", 24);addPerson("赵六", 30);addPerson("钱七", 26);}int rowCount(const QModelIndex &parent = QModelIndex()) const override{Q_UNUSED(parent)return m_persons.size();}QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override{if (!index.isValid() || index.row() >= m_persons.size())return QVariant();const PersonData &person = m_persons.at(index.row());switch (role) {case NameRole:return person.name;case AgeRole:return person.age;default:return QVariant();}}QHash<int, QByteArray> roleNames() const override{QHash<int, QByteArray> roles;roles[NameRole] = "name";roles[AgeRole] = "age";return roles;}void addPerson(const QString &name, int age){beginInsertRows(QModelIndex(), m_persons.size(), m_persons.size());PersonData person;person.name = name;person.age = age;m_persons.append(person);endInsertRows();}private:QList<PersonData> m_persons;
};#endif // PERSONMODEL_H

Step 2: 自定义 C++ 代理模型类

PersonProxyModel.h

#ifndef PERSONPROXYMODEL_H
#define PERSONPROXYMODEL_H#include <QSortFilterProxyModel>
#include <QObject>class PersonProxyModel : public QSortFilterProxyModel
{Q_OBJECT// ✅ 暴露属性给 QML,支持双向绑定Q_PROPERTY(QString filterText READ filterText WRITE setFilterText NOTIFY filterTextChanged)Q_PROPERTY(int minAge READ minAge WRITE setMinAge NOTIFY minAgeChanged)public:explicit PersonProxyModel(QObject *parent = nullptr): QSortFilterProxyModel(parent), m_minAge(0){setFilterCaseSensitivity(Qt::CaseInsensitive);setDynamicSortFilter(true);}// 属性的 getter/setterQString filterText() const { return m_filterText; }void setFilterText(const QString &text) {if (m_filterText != text) {m_filterText = text;emit filterTextChanged();invalidateFilter(); // 触发重新过滤}}int minAge() const { return m_minAge; }void setMinAge(int age) {if (m_minAge != age) {m_minAge = age;emit minAgeChanged();invalidateFilter();}}protected:// 🎯 自定义过滤逻辑bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override{// 假设数据模型有 "name" 和 "age" 两个角色QModelIndex nameIndex = sourceModel()->index(source_row, 0, source_parent);QModelIndex ageIndex = sourceModel()->index(source_row, 0, source_parent);QString name = sourceModel()->data(nameIndex, Qt::UserRole + 1).toString();int age = sourceModel()->data(ageIndex, Qt::UserRole + 2).toInt();// 过滤条件:名字包含关键字 且 年龄 >= 最小年龄bool nameMatch = m_filterText.isEmpty() || name.contains(m_filterText, Qt::CaseInsensitive);bool ageMatch = age >= m_minAge;return nameMatch && ageMatch;}signals:void filterTextChanged();void minAgeChanged();private:QString m_filterText;int m_minAge;
};#endif // PERSONPROXYMODEL_H

Step 3: 注册到 QML

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "PersonProxyModel.h"int main(int argc, char *argv[])
{QGuiApplication app(argc, argv);// ✅ 注册自定义代理模型为 QML 类型qmlRegisterType<PersonProxyModel>("MyModels", 1, 0, "PersonProxyModel");qmlRegisterType<PersonModel>("MyModels", 1, 0, "PersonModel");// 创建原始数据模型PersonModel *sourceModel = new PersonModel(&app);QQmlApplicationEngine engine;// 暴露原始模型给 QMLengine.rootContext()->setContextProperty("mySourceModel", sourceModel);// 加载 QMLengine.load(QUrl(QStringLiteral("qrc:/main.qml")));if (engine.rootObjects().isEmpty())return -1;return app.exec();
}

Step 4: QML 中使用

main.qml

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import MyModels 1.0  // ✅ 导入自定义模块ApplicationWindow {visible: truewidth: 600height: 500title: "QML QSortFilterProxyModel 示例"ColumnLayout {anchors.fill: parentanchors.margins: 20spacing: 15// 搜索框TextField {id: searchFieldLayout.fillWidth: trueplaceholderText: "🔍 输入姓名搜索..."}// 年龄过滤滑块RowLayout {Layout.fillWidth: trueLabel { text: "最小年龄:" }Slider {id: ageSliderfrom: 0to: 50value: 0stepSize: 1Layout.fillWidth: true}Label { text: ageSlider.value.toFixed(0) }}// ✅ 创建代理模型实例(直接在 QML 中创建)PersonProxyModel {id: proxyModelsourceModel: mySourceModel  // 绑定原始模型filterText: searchField.text  // ✅ 双向绑定属性minAge: ageSlider.value}// 列表视图ListView {id: listViewLayout.fillWidth: trueLayout.fillHeight: truemodel: proxyModel  // ✅ 使用代理模型clip: truedelegate: Rectangle {width: ListView.view.widthheight: 50color: index % 2 ? "#f5f5f5" : "#ffffff"border.color: "#e0e0e0"border.width: 1RowLayout {anchors.fill: parentanchors.margins: 10Text {text: model.name  // 从 UserRole + 1 获取font.pixelSize: 16font.bold: true}Text {text: "年龄: " + model.agecolor: "#666"Layout.leftMargin: 20}}}}// 显示过滤结果数量Label {text: "📊 显示 " + listView.count + " 条数据"font.bold: truecolor: "#2196F3"}}
}
🎬 QML 运行效果
  1. ✅ 输入框输入文字,实时过滤姓名
  2. ✅ 拖动滑块,按年龄过滤
  3. ✅ 两个条件同时生效(AND 关系)
    在这里插入图片描述

🖥️ QWidget 基础示例

📁 完整可运行代码

PersonModel.h

#ifndef PERSONMODEL_H
#define PERSONMODEL_H#include <QAbstractListModel>
#include <QString>
#include <QHash>
#include <QVariant>
#include <QModelIndex>struct PersonData {QString name;int age;QString department;
};class PersonModel : public QAbstractListModel
{Q_OBJECTpublic:enum PersonRoles {NameRole = Qt::UserRole + 1,AgeRole = Qt::UserRole + 2,DepartmentRole = Qt::UserRole + 3};explicit PersonModel(QObject *parent = nullptr): QAbstractListModel(parent){addPerson("张三", 28, "技术部");addPerson("李四", 35, "销售部");addPerson("王五", 24, "技术部");addPerson("赵六", 30, "人事部");addPerson("钱七", 26, "技术部");}int rowCount(const QModelIndex &parent = QModelIndex()) const override{Q_UNUSED(parent)return m_persons.size();}QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override{if (!index.isValid() || index.row() >= m_persons.size())return QVariant();const PersonData &person = m_persons.at(index.row());switch (role) {case NameRole:return person.name;case AgeRole:return person.age;case DepartmentRole:return person.department;case Qt::DisplayRole:return person.name;  // 默认显示姓名default:return QVariant();}}QHash<int, QByteArray> roleNames() const override{QHash<int, QByteArray> roles;roles[NameRole] = "name";roles[AgeRole] = "age";roles[DepartmentRole] = "department";return roles;}void addPerson(const QString &name, int age, const QString &dept){beginInsertRows(QModelIndex(), m_persons.size(), m_persons.size());PersonData person;person.name = name;person.age = age;person.department = dept;m_persons.append(person);endInsertRows();}QString getName(int row) const{if (row >= 0 && row < m_persons.size())return m_persons.at(row).name;return QString();}private:QList<PersonData> m_persons;
};#endif // PERSONMODEL_H

main.cpp

#include <QApplication>
#include <QListView>
#include <QSortFilterProxyModel>
#include <QLineEdit>
#include <QVBoxLayout>
#include <QWidget>
#include <QPushButton>
#include <QDebug>
#include "PersonModel.h"class QuickStartWindow : public QWidget
{Q_OBJECTpublic:QuickStartWindow(QWidget *parent = nullptr) : QWidget(parent){// 1️⃣ 创建原始数据模型sourceModel = new PersonModel(this);// 2️⃣ 创建代理模型proxyModel = new QSortFilterProxyModel(this);proxyModel->setSourceModel(sourceModel);proxyModel->setFilterRole(PersonModel::NameRole); // 使用 NameRole 过滤proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); // 不区分大小写proxyModel->setDynamicSortFilter(true); // 动态刷新// 3️⃣ 创建视图listView = new QListView(this);listView->setModel(proxyModel); // ✅ 视图使用代理模型// 4️⃣ 创建搜索框searchBox = new QLineEdit(this);searchBox->setPlaceholderText("🔍 输入姓名搜索...");connect(searchBox, &QLineEdit::textChanged, this, [this](const QString &text) {proxyModel->setFilterFixedString(text); // 实时过滤});// 5️⃣ 测试按钮:显示索引转换QPushButton *testBtn = new QPushButton("📌 获取选中行的原始索引", this);connect(testBtn, &QPushButton::clicked, this, [this]() {QModelIndex proxyIndex = listView->currentIndex();if (proxyIndex.isValid()) {QModelIndex sourceIndex = proxyModel->mapToSource(proxyIndex);qDebug() << "代理模型行号:" << proxyIndex.row()<< "| 原始模型行号:" << sourceIndex.row()<< "| 姓名:" << sourceModel->getName(sourceIndex.row());}});// 布局QVBoxLayout *layout = new QVBoxLayout(this);layout->addWidget(searchBox);layout->addWidget(listView);layout->addWidget(testBtn);setLayout(layout);}private:PersonModel *sourceModel;QSortFilterProxyModel *proxyModel;QListView *listView;QLineEdit *searchBox;
};#include "main.moc"int main(int argc, char *argv[])
{QApplication app(argc, argv);QuickStartWindow window;window.setWindowTitle("QSortFilterProxyModel 快速入门");window.resize(700, 500);window.show();return app.exec();
}
📦 项目配置文件

CMakeLists.txt (Qt 6)

cmake_minimum_required(VERSION 3.16)
project(QSortFilterProxyModelDemo VERSION 1.0 LANGUAGES CXX)set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)find_package(Qt6 REQUIRED COMPONENTS Core Widgets)add_executable(demo main.cpp PersonModel.h)
target_link_libraries(demo Qt6::Core Qt6::Widgets)

.pro 文件 (Qt 5/6)

QT += core gui widgetsCONFIG += c++17HEADERS += PersonModel.h
SOURCES += main.cppTARGET = demo
🎬 运行效果
  1. ✅ 输入框输入 “张” 可以实时过滤出 “张三”
  2. ✅ 点击按钮可以看到代理索引和原始索引的对应关系
  3. ✅ 列表视图实时显示过滤后的结果

四、核心接口详解

🔍 过滤相关接口

接口说明示例
setFilterRole(int role)指定用哪个角色的数据过滤proxyModel->setFilterRole(Qt::UserRole + 1);
setFilterKeyColumn(int col)指定在哪一列过滤(-1 表示所有列)proxyModel->setFilterKeyColumn(0);
setFilterRegularExpression(const QRegularExpression &)使用正则表达式过滤(Qt 5.12+)proxyModel->setFilterRegularExpression(QRegularExpression("^A.*"));
setFilterFixedString(const QString &)固定字符串过滤(最常用)proxyModel->setFilterFixedString("技术部");
setFilterCaseSensitivity(Qt::CaseSensitivity)设置大小写敏感性proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
invalidateFilter()强制重新过滤proxyModel->invalidateFilter();
📌 重要说明

Qt 5.12 之前:使用 setFilterRegExp(const QRegExp &)
Qt 5.12 之后:推荐使用 setFilterRegularExpression(const QRegularExpression &)

示例:正则表达式过滤

// 假设使用 PersonModel(QAbstractListModel)
PersonModel *sourceModel = new PersonModel(this);
QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel(this);
proxyModel->setSourceModel(sourceModel);// 过滤以 "张" 开头的姓名(使用 NameRole)
proxyModel->setFilterRole(PersonModel::NameRole);
proxyModel->setFilterRegularExpression(QRegularExpression("^张.*"));// 过滤包含数字的文本
proxyModel->setFilterRole(PersonModel::NameRole);
proxyModel->setFilterRegularExpression(QRegularExpression("\\d+"));// 过滤邮箱格式(假设有 EmailRole)
proxyModel->setFilterRole(PersonModel::EmailRole);
proxyModel->setFilterRegularExpression(QRegularExpression("\\w+@\\w+\\.\\w+"));

📊 排序相关接口

接口说明示例
setSortRole(int role)指定用哪个角色的数据排序proxyModel->setSortRole(Qt::UserRole + 1);
sort(int column, Qt::SortOrder order)手动触发排序proxyModel->sort(1, Qt::AscendingOrder);
setSortCaseSensitivity(Qt::CaseSensitivity)设置排序时的大小写敏感性proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
invalidate()强制重新排序和过滤proxyModel->invalidate();
💡 为什么需要 setSortRole?

问题场景:年龄列显示 “28岁”,但实际存储的是字符串 “28岁”,排序时会按字符串排序:

错误排序(字符串):100岁, 28岁, 35岁
正确排序(数值):28岁, 35岁, 100岁

解决方案:使用 Qt::UserRole 存储真实数值

// PersonModel 中的数据存储(在 QAbstractListModel 中)
QVariant PersonModel::data(const QModelIndex &index, int role) const override
{const PersonData &person = m_persons.at(index.row());switch (role) {case AgeRole:return person.age;  // 直接返回 int,用于排序case Qt::DisplayRole:return QString("%1岁").arg(person.age);  // 显示文本// ...}
}// 配置代理模型
proxyModel->setSortRole(PersonModel::AgeRole);  // ✅ 按数值排序
proxyModel->sort(0, Qt::AscendingOrder);  // QAbstractListModel 通常只有一列(列0)

🔄 索引转换接口

接口说明使用场景
mapToSource(const QModelIndex &)代理索引 → 原始索引从视图获取选中项,需要操作原始数据
mapFromSource(const QModelIndex &)原始索引 → 代理索引知道原始索引,想在视图中定位
sourceModel()获取原始模型访问原始数据
setSourceModel(QAbstractItemModel *)设置原始模型动态更换数据源
📌 非常重要:索引转换的必要性

为什么需要转换?

视图显示的是代理模型的索引,代理模型经过过滤/排序后,索引顺序与原始模型不同!

示例场景

原始模型:行0: 张三, 28行1: 李四, 35行2: 王五, 24按年龄升序排序后的代理模型:行0: 王五, 24  (原始行2)行1: 张三, 28  (原始行0)行2: 李四, 35  (原始行1)

代码示例

// 从视图获取当前选中行(代理索引)
// 假设使用 QListView 和 PersonModel
QModelIndex proxyIndex = listView->currentIndex();
qDebug() << "代理行号:" << proxyIndex.row();  // 输出 0(王五)// ❌ 错误:直接用代理行号访问原始模型
QModelIndex wrongSourceIndex = sourceModel->index(proxyIndex.row(), 0);
QString name = sourceModel->data(wrongSourceIndex, PersonModel::NameRole).toString();
// 结果是 "张三",而不是期望的 "王五"!// ✅ 正确:先转换为原始索引
QModelIndex sourceIndex = proxyModel->mapToSource(proxyIndex);
qDebug() << "原始行号:" << sourceIndex.row();  // 输出 2
QString name = sourceModel->data(sourceIndex, PersonModel::NameRole).toString();
// 结果是 "王五" ✅

⚙️ 其他重要接口

接口说明推荐用法
setDynamicSortFilter(bool)数据变化时自动重新排序/过滤setDynamicSortFilter(true); ✅ 推荐开启
setRecursiveFilteringEnabled(bool)树形模型中启用递归过滤(Qt 5.10+)过滤树形结构时使用
setAutoAcceptChildRows(bool)父项通过过滤时,自动显示所有子项树形模型场景

五、进阶实战:自定义过滤与排序

🎯 场景一:复杂条件过滤

需求:同时满足多个条件的过滤(年龄 > 25 且 部门包含"技术")

C++ 代理模型类

CustomFilterProxyModel.h

#ifndef CUSTOMFILTERPROXYMODEL_H
#define CUSTOMFILTERPROXYMODEL_H#include <QSortFilterProxyModel>
#include <QObject>class CustomFilterProxyModel : public QSortFilterProxyModel
{Q_OBJECTQ_PROPERTY(int minAge READ minAge WRITE setMinAge NOTIFY minAgeChanged)Q_PROPERTY(QString departmentKeyword READ departmentKeyword WRITE setDepartmentKeyword NOTIFY departmentKeywordChanged)public:explicit CustomFilterProxyModel(QObject *parent = nullptr): QSortFilterProxyModel(parent), m_minAge(0){setDynamicSortFilter(true);}int minAge() const { return m_minAge; }void setMinAge(int age) {if (m_minAge != age) {m_minAge = age;emit minAgeChanged();invalidateFilter();}}QString departmentKeyword() const { return m_departmentKeyword; }void setDepartmentKeyword(const QString &keyword) {if (m_departmentKeyword != keyword) {m_departmentKeyword = keyword;emit departmentKeywordChanged();invalidateFilter();}}protected:// 🎯 核心:重写过滤逻辑bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override{Q_UNUSED(source_parent)// 获取年龄(使用 AgeRole)QModelIndex ageIndex = sourceModel()->index(source_row, 0);int age = sourceModel()->data(ageIndex, PersonModel::AgeRole).toInt();// 获取部门(使用 DepartmentRole)QModelIndex deptIndex = sourceModel()->index(source_row, 0);QString dept = sourceModel()->data(deptIndex, PersonModel::DepartmentRole).toString();// 🔍 组合条件判断bool ageMatch = (age >= m_minAge);bool deptMatch = m_departmentKeyword.isEmpty() || dept.contains(m_departmentKeyword, Qt::CaseInsensitive);return ageMatch && deptMatch;  // AND 关系}signals:void minAgeChanged();void departmentKeywordChanged();private:int m_minAge;QString m_departmentKeyword;
};#endif // CUSTOMFILTERPROXYMODEL_H
QML 使用示例

main.qml

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import MyModels 1.0ApplicationWindow {visible: truewidth: 600height: 500ColumnLayout {anchors.fill: parentanchors.margins: 20spacing: 15// 年龄过滤滑块RowLayout {Layout.fillWidth: trueLabel { text: "最小年龄:" }Slider {id: ageSliderfrom: 0to: 50value: 25stepSize: 1Layout.fillWidth: true}Label { text: ageSlider.value.toFixed(0) }}// 部门搜索框TextField {id: deptFieldLayout.fillWidth: trueplaceholderText: "🔍 输入部门关键字..."}// 创建代理模型CustomFilterProxyModel {id: proxyModelsourceModel: mySourceModelminAge: ageSlider.valuedepartmentKeyword: deptField.text}// 列表视图ListView {Layout.fillWidth: trueLayout.fillHeight: truemodel: proxyModelclip: truedelegate: Rectangle {width: ListView.view.widthheight: 60color: index % 2 ? "#f5f5f5" : "#ffffff"border.color: "#e0e0e0"border.width: 1ColumnLayout {anchors.fill: parentanchors.margins: 10spacing: 5Text {text: model.namefont.pixelSize: 16font.bold: true}RowLayout {Text {text: "年龄: " + model.agecolor: "#666"}Text {text: " | 部门: " + model.departmentcolor: "#666"}}}}}Label {text: "📊 显示 " + listView.count + " 条数据"font.bold: truecolor: "#2196F3"}}
}

main.cpp(注册类型)

qmlRegisterType<CustomFilterProxyModel>("MyModels", 1, 0, "CustomFilterProxyModel");

🎯 场景二:自定义排序逻辑

需求:特定部门优先排序(技术部 > 其他部门),同部门内按年龄排序

C++ 代理模型类

CustomSortProxyModel.h

#ifndef CUSTOMSORTPROXYMODEL_H
#define CUSTOMSORTPROXYMODEL_H#include <QSortFilterProxyModel>
#include <QObject>
#include "PersonModel.h"class CustomSortProxyModel : public QSortFilterProxyModel
{Q_OBJECTpublic:explicit CustomSortProxyModel(QObject *parent = nullptr): QSortFilterProxyModel(parent){setDynamicSortFilter(true);}protected:// 🎯 核心:重写排序比较逻辑bool lessThan(const QModelIndex &left, const QModelIndex &right) const override{// 获取部门信息QString leftDept = sourceModel()->data(left, PersonModel::DepartmentRole).toString();QString rightDept = sourceModel()->data(right, PersonModel::DepartmentRole).toString();// 📌 规则1:技术部始终排在前面if (leftDept == "技术部" && rightDept != "技术部")return sortOrder() == Qt::AscendingOrder;  // 升序时返回 trueif (rightDept == "技术部" && leftDept != "技术部")return sortOrder() == Qt::DescendingOrder; // 升序时返回 false// 📌 规则2:同部门内按年龄排序if (leftDept == rightDept) {int leftAge = sourceModel()->data(left, PersonModel::AgeRole).toInt();int rightAge = sourceModel()->data(right, PersonModel::AgeRole).toInt();return leftAge < rightAge;}// 📌 规则3:其他部门按字母顺序return leftDept < rightDept;}
};#endif // CUSTOMSORTPROXYMODEL_H
QML 使用示例

main.qml

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import MyModels 1.0ApplicationWindow {visible: truewidth: 600height: 500ColumnLayout {anchors.fill: parentanchors.margins: 20spacing: 15// 排序按钮RowLayout {Layout.fillWidth: trueButton {text: "按部门排序(技术部优先)"onClicked: {proxyModel.sort(0, Qt::AscendingOrder);}}Button {text: "按年龄排序"onClicked: {proxyModel.setSortRole(PersonModel.AgeRole);proxyModel.sort(0, Qt::AscendingOrder);}}}// 创建代理模型CustomSortProxyModel {id: proxyModelsourceModel: mySourceModel}// 列表视图ListView {id: listViewLayout.fillWidth: trueLayout.fillHeight: truemodel: proxyModelclip: truedelegate: Rectangle {width: ListView.view.widthheight: 60color: index % 2 ? "#f5f5f5" : "#ffffff"border.color: "#e0e0e0"border.width: 1RowLayout {anchors.fill: parentanchors.margins: 10Text {text: model.namefont.pixelSize: 16font.bold: trueLayout.preferredWidth: 100}Text {text: "年龄: " + model.agecolor: "#666"Layout.preferredWidth: 80}Text {text: "部门: " + model.departmentcolor: model.department === "技术部" ? "#2196F3" : "#666"font.bold: model.department === "技术部"}}}}}
}

main.cpp(注册类型)

qmlRegisterType<CustomSortProxyModel>("MyModels", 1, 0, "CustomSortProxyModel");

🎯 场景三:多列组合排序

需求:先按部门排序,部门相同时按年龄排序

C++ 代理模型类

MultiColumnSortProxyModel.h

#ifndef MULTICOLUMNSORTPROXYMODEL_H
#define MULTICOLUMNSORTPROXYMODEL_H#include <QSortFilterProxyModel>
#include <QObject>
#include "PersonModel.h"class MultiColumnSortProxyModel : public QSortFilterProxyModel
{Q_OBJECTpublic:explicit MultiColumnSortProxyModel(QObject *parent = nullptr): QSortFilterProxyModel(parent){setDynamicSortFilter(true);}protected:bool lessThan(const QModelIndex &left, const QModelIndex &right) const override{// 主排序键:部门QString leftDeptStr = sourceModel()->data(left, PersonModel::DepartmentRole).toString();QString rightDeptStr = sourceModel()->data(right, PersonModel::DepartmentRole).toString();if (leftDeptStr != rightDeptStr) {return leftDeptStr < rightDeptStr;}// 次排序键:年龄(部门相同时)int leftAgeVal = sourceModel()->data(left, PersonModel::AgeRole).toInt();int rightAgeVal = sourceModel()->data(right, PersonModel::AgeRole).toInt();return leftAgeVal < rightAgeVal;}
};#endif // MULTICOLUMNSORTPROXYMODEL_H
QML 使用示例

main.qml

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import MyModels 1.0ApplicationWindow {visible: truewidth: 600height: 500ColumnLayout {anchors.fill: parentanchors.margins: 20spacing: 15// 排序按钮Button {text: "按部门+年龄排序"onClicked: {proxyModel.sort(0, Qt::AscendingOrder);}}// 创建代理模型MultiColumnSortProxyModel {id: proxyModelsourceModel: mySourceModel}// 列表视图ListView {Layout.fillWidth: trueLayout.fillHeight: truemodel: proxyModelclip: truedelegate: Rectangle {width: ListView.view.widthheight: 60color: index % 2 ? "#f5f5f5" : "#ffffff"border.color: "#e0e0e0"border.width: 1RowLayout {anchors.fill: parentanchors.margins: 10Text {text: model.namefont.pixelSize: 16font.bold: trueLayout.preferredWidth: 100}Text {text: "年龄: " + model.agecolor: "#666"Layout.preferredWidth: 80}Text {text: "部门: " + model.departmentcolor: "#666"}}}}}
}

main.cpp(注册类型)

qmlRegisterType<MultiColumnSortProxyModel>("MyModels", 1, 0, "MultiColumnSortProxyModel");

六、QML 完整集成方案

✅ 方案一:自定义类 + qmlRegisterType(推荐)

优势

  • ✅ 最灵活,可重写 filterAcceptsRow()lessThan()
  • ✅ 支持 Q_PROPERTY,QML 可直接绑定属性
  • ✅ QML 中直接创建实例,符合声明式风格

完整步骤(见第三章 QML 示例)

关键代码

// 1. 自定义类(PersonProxyModel.h)
class PersonProxyModel : public QSortFilterProxyModel {Q_OBJECTQ_PROPERTY(QString filterText READ filterText WRITE setFilterText NOTIFY filterTextChanged)// ...
};// 2. 注册类型(main.cpp)
qmlRegisterType<PersonProxyModel>("MyModels", 1, 0, "PersonProxyModel");// 3. QML 中使用(main.qml)
PersonProxyModel {id: proxyModelsourceModel: mySourceModelfilterText: searchField.text
}

🔧 方案二:setContextProperty 暴露实例(简单场景)

适用场景

  • 不需要自定义过滤/排序逻辑
  • 只需要基本的 setFilterFixedString() 功能

优势

  • 快速简单,无需自定义类

劣势

  • ❌ 无法重写 filterAcceptsRow() 实现复杂过滤
  • ❌ QML 中不能直接创建实例

实现步骤

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QSortFilterProxyModel>
#include "PersonModel.h"int main(int argc, char *argv[])
{QGuiApplication app(argc, argv);// 创建原始模型(使用 QAbstractListModel)PersonModel *sourceModel = new PersonModel(&app);// 创建标准代理模型QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel(&app);proxyModel->setSourceModel(sourceModel);proxyModel->setFilterRole(PersonModel::NameRole);  // 使用 NameRole 过滤proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);proxyModel->setDynamicSortFilter(true);QQmlApplicationEngine engine;// ✅ 暴露给 QMLengine.rootContext()->setContextProperty("proxyModel", proxyModel);engine.load(QUrl(QStringLiteral("qrc:/main.qml")));return app.exec();
}

main.qml

import QtQuick 2.15
import QtQuick.Controls 2.15ApplicationWindow {visible: truewidth: 600height: 400Column {TextField {id: searchFieldplaceholderText: "搜索..."onTextChanged: {// ✅ 调用 C++ 方法proxyModel.setFilterFixedString(text)}}ListView {width: 600height: 350model: proxyModel  // ✅ 使用暴露的实例delegate: Text { text: model.display }}}
}

📋 两种方案对比

特性自定义类 + qmlRegisterTypesetContextProperty
灵活性⭐⭐⭐⭐⭐⭐⭐
易用性⭐⭐⭐⭐⭐⭐⭐⭐⭐
自定义过滤✅ 完全支持❌ 不支持
属性绑定✅ 支持⚠️ 有限支持
QML 创建实例✅ 支持❌ 不支持
额外依赖❌ 无❌ 无
推荐度🌟🌟🌟🌟🌟🌟🌟🌟

七、常见问题与最佳实践

❓ 常见问题

1. 排序数字时顺序错乱

问题:年龄 “100” 排在 “28” 前面

原因:按字符串排序(“1” < “2”)

解决方案

// ✅ 方法1:在 QAbstractListModel 中使用 Role 存储数值
// PersonModel::data() 中返回
case AgeRole:return person.age;  // 直接返回 int
case Qt::DisplayRole:return QString("%1岁").arg(person.age);  // 显示文本// 配置代理模型
proxyModel->setSortRole(PersonModel::AgeRole);  // 使用 AgeRole 排序// ✅ 方法2:重写 lessThan
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override {int leftVal = sourceModel()->data(left, PersonModel::AgeRole).toInt();int rightVal = sourceModel()->data(right, PersonModel::AgeRole).toInt();return leftVal < rightVal;
}

2. 修改数据后视图不更新

问题:修改原始模型数据后,过滤结果没有自动刷新

解决方案

// ✅ 方法1:开启动态刷新(推荐)
proxyModel->setDynamicSortFilter(true);// ✅ 方法2:手动刷新
sourceModel->setData(index, newValue);
proxyModel->invalidate();  // 或 invalidateFilter()

3. 索引转换错误导致数据错乱

问题:从视图获取选中项,但操作了错误的数据

解决方案

// ❌ 错误:直接用代理索引访问原始模型
QModelIndex proxyIndex = view->currentIndex();
sourceModel->setData(sourceModel->index(proxyIndex.row(), 0), "新值");// ✅ 正确:先转换索引
QModelIndex sourceIndex = proxyModel->mapToSource(proxyIndex);
sourceModel->setData(sourceIndex, "新值");

4. QML 中过滤不生效

问题:调用 setFilterFixedString() 后没有反应

排查步骤

  1. 检查 sourceModel 是否正确设置
  2. 检查 filterKeyColumn 是否设置正确(-1 表示所有列)
  3. 检查数据角色是否匹配(默认过滤 Qt::DisplayRole
// ✅ 确保配置正确
proxyModel->setSourceModel(sourceModel);
proxyModel->setFilterKeyColumn(-1);  // 所有列
proxyModel->setFilterRole(Qt::DisplayRole);
proxyModel->setDynamicSortFilter(true);

5. 树形模型过滤后父节点消失

问题:使用 QTreeView 时,过滤后子节点匹配但父节点被隐藏

解决方案(Qt 5.10+):

// ✅ 启用递归过滤
proxyModel->setRecursiveFilteringEnabled(true);// 或者:父节点通过时自动显示所有子节点
proxyModel->setAutoAcceptChildRows(true);

📌 最佳实践

✅ 1. 总是开启动态刷新
proxyModel->setDynamicSortFilter(true);  // 推荐

原因:数据变化时自动更新,无需手动调用 invalidate()


✅ 2. 使用 UserRole 存储排序/过滤的真实数据
// 显示文本:DisplayRole
// 真实数据:UserRole + N
auto item = new QStandardItem("100岁");
item->setData(100, Qt::UserRole + 1);  // 真实数值proxyModel->setSortRole(Qt::UserRole + 1);
proxyModel->setFilterRole(Qt::UserRole + 1);

✅ 3. 过滤时不区分大小写
proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);

✅ 4. 正确使用索引转换
// 从代理到原始
QModelIndex sourceIndex = proxyModel->mapToSource(proxyIndex);// 从原始到代理
QModelIndex proxyIndex = proxyModel->mapFromSource(sourceIndex);

✅ 5. QML 中优先使用自定义类方式

推荐

qmlRegisterType<MyProxyModel>("MyModels", 1, 0, "MyProxyModel");

而不是

engine.rootContext()->setContextProperty("proxyModel", proxyModel);

原因:自定义类更灵活,支持属性绑定和自定义逻辑


✅ 6. 大数据量时考虑性能优化
  • 避免在 filterAcceptsRow() 中进行复杂计算
  • 使用 beginResetModel() / endResetModel() 批量更新
  • 考虑分页或虚拟列表

八、性能优化建议

🚀 优化策略

场景优化方法说明
数据量 > 1万行使用分页或懒加载避免一次性加载所有数据
频繁更新批量更新 + beginResetModel()减少信号发射次数
复杂过滤逻辑后台线程过滤避免阻塞 UI 线程
树形结构关闭 setRecursiveFilteringEnabled递归遍历耗时
QML 大数据使用 WorkerScript异步处理数据

📊 性能测试对比

场景:10,000 行数据,过滤 + 排序

优化前优化后方法
500ms50ms使用 UserRole 存储数值,避免字符串转换
200ms20ms开启 setDynamicSortFilter(false),手动批量更新后再开启
800ms100ms过滤逻辑中缓存计算结果

💡 代码示例:批量更新

// ❌ 低效:每次更新都触发过滤
for (int i = 0; i < 10000; ++i) {sourceModel->setData(sourceModel->index(i, 0), newValue);// 每次都会触发 proxyModel 重新过滤
}// ✅ 高效:批量更新
proxyModel->setDynamicSortFilter(false);  // 暂时关闭自动刷新
for (int i = 0; i < 10000; ++i) {sourceModel->setData(sourceModel->index(i, 0), newValue);
}
proxyModel->setDynamicSortFilter(true);   // 重新开启,触发一次刷新
proxyModel->invalidate();

九、总结与对比表

🎯 核心概念回顾

  1. 数据不动,代理来动
    原始模型保持不变,所有排序/过滤都在代理层完成

  2. 角色(Role)是灵魂
    DisplayRole 显示文本,UserRole 存真实数据,排序/过滤更精准

  3. 索引转换必不可少
    视图操作代理索引,数据操作需转换为原始索引

  4. QML 推荐自定义类
    通过 qmlRegisterType 注册,QML 中直接创建实例


📋 QWidget vs QML 对比

功能QWidgetQML
创建代理new QSortFilterProxyModelPersonProxyModel { }
设置数据源setSourceModel(model)sourceModel: model
基础过滤setFilterFixedString(text)自定义类属性绑定
自定义过滤重写 filterAcceptsRow()C++ 类中重写
排序sort(col, order) + setSortingEnabled(true)C++ 类中重写 lessThan()
索引转换mapToSource() / mapFromSource()同左
属性绑定通过信号槽Q_PROPERTY + QML 绑定

📌 关键接口速查表

分类接口作用
过滤setFilterRole()指定过滤角色
setFilterKeyColumn()指定过滤列
setFilterRegularExpression()正则过滤
setFilterFixedString()固定字符串过滤
setFilterCaseSensitivity()大小写敏感性
invalidateFilter()强制重新过滤
排序setSortRole()指定排序角色
sort()手动排序
setSortCaseSensitivity()排序大小写敏感性
索引mapToSource()代理索引 → 原始索引
mapFromSource()原始索引 → 代理索引
其他setDynamicSortFilter()自动刷新(推荐开启)
invalidate()强制重新排序+过滤

🏆 学习路径建议

graph LRA[第一步:理解概念] --> B[第二步:快速入门示例]B --> C[第三步:掌握核心接口]C --> D[第四步:自定义过滤/排序]D --> E[第五步:QML 集成]E --> F[第六步:性能优化]style A fill:#e1f5ffstyle B fill:#fff4e1style C fill:#e8f5e9style D fill:#fce4ecstyle E fill:#f3e5f5style F fill:#fff9c4

🎉 结语

如果你觉得本文有帮助,请点赞收藏加关注,谢谢!


📚 参考资源

  • Qt 官方文档:QSortFilterProxyModel
  • Qt Model/View 架构详解

http://www.dtcms.com/a/554364.html

相关文章:

  • RAPID:基于逆强化学习的无人机视觉导航鲁棒且敏捷规划器
  • 免费个人网站模版ps手机网站 ui
  • 东莞网站优化服务公司建网站龙
  • Rust数据类型(下):复合类型详解
  • 【一阶段分析】文生图提示词笔记
  • 0成本get可信域名:dpdns.org公益域名获取全攻略
  • WordPress建影视站电商品牌推广方案
  • 站长工具关键词南京外贸网站建设案例
  • ESP32-P4-Function-EV-Board例程编译报错解决记录
  • Hierholzer 算法
  • 一文读懂分辨率:从概念到硬件应用,解锁视觉体验新高度
  • ppt图标网站链接怎么做珠海做网站哪间好
  • 上海网站开发招聘响应式网站和自适应网站
  • 蓝图网站建设网站开发推荐书籍
  • 网页站点什么意思软件定制公司设计方案
  • 湖北智能网站建设找哪家网站 提示危险
  • 垂直类网站怎么做织梦企业网站
  • Excel怎么批量快速修改批注?
  • TikTok限流:为什么TikTok视频没流量、零播放
  • 基于eeg脑电的认知负荷公开数据集
  • 织梦网站怎么做投票wordpress资源下载类主题
  • 安论坛网站建设网站界面建议
  • 基于电鱼 ARM 工控机的井下AI故障诊断方案——让煤矿远程监控更智能、更精准
  • 彩票网站该怎么建设wordpress建站过程
  • 织梦网站首页模板更换做网站需要多少人
  • 光刻胶分类与特性——g/i线光刻胶及东京应化TP-3000系列胶典型配方(上)
  • 医院哪个科室负责网站建设广州网站排名优化服务
  • 三轴云台之智能决策技术
  • 电子商城平台wordpress评论框优化
  • 专业网站建设模板下载小语种网站建设要点