Qt Model/View 学习(2) - QModelIndex索引模型数据

本文深入探讨Qt中的Model/View框架,重点介绍了Model相关类及其核心——QAbstractItemModel。此外,还详细解析了QModelIndex的使用方法,帮助读者更好地理解和使用Qt的Model/View框架。


0. 前言

上一篇文章讲了Qt中Model/View框架的起源(和给人的优越感 ),以及它大概是怎么个思路,最后花了不少篇幅讲了为什么要使用Model/View,现在想想多是在给自己打气,好坚持学下去👻。

本文开始介绍有关函数接口了,既然数据Data是用户自定义的,那就从Model开始介绍。

系列文章回顾
Qt Model/View 学习(1) - 是什么和为什么?


1. Model有关类

先上一张Qt有关Model类的UML类图,空心箭头表示泛化关系,也就是派生类指向基类。如果想了解更多一点关于UML类图的知识,可以移步这篇文章
在这里插入图片描述
图中C位自然是QAbstractItemModel了,它继承自QObject获得了信号和槽机制,派生出了诸多Model类,但其中核心就2个:

  • QAbstractListModel:提供一维矩阵模型的抽象基类;既然是抽象类,那我们只能派生自己的类才能使用;
  • QAbstractTableModel:提供表格模型的抽象基类

其它的有:

  • QFileSystemModel:提供本地文件系统的数据模型,使用场景十分受限,也可以先不用过多理会
  • QAbstractProxyModel:提供排序、过滤、或其它数据处理任务的代理模型,暂时可以不用过多理会
  • QStandardItemModel:提供了一种使用控件的Model类,换言之,它Model了,但没有完全Model,在学习Model/View的路上可以不用过多理会
  • QDirModel:官方建议使用性能更好的QFileSystemModel,那可以不用过多理会

所以,虽然继承树似乎内容庞大,但我们只要搞懂QAbstractListModelQAbstractTableModel这俩,就能掌握Model的精髓了,好耶!

而这俩,从名字看就很简单:一个一维数组一个二维数组,而且打开这两个类的文档,篇幅极少,滚动条那么大一个,就这?

在这里插入图片描述


2. Model索引:QModelIndex

先别着急膨胀,QAbstractListModelQAbstractTableModel的文档内容少是因为大部分内容都在QAbstractItemModel中实现了,而打开QAbstractItemModel类的文档就又有些劝退了……

不过换言之,只要掌握了QAbstractItemModel,就能掌握Model的精髓了。

后续将用 “Model类” 来指代从 QAbstractItemModel派生的类;

上一篇文章中讲过,Model是对数据的解析模型,比如20个散装变量,可以被解析成一个4*5的矩阵。而如何去索引矩阵中的数据就是重点了,这需要介绍本小节的主角QModelIndex了,先上一张图:
在这里插入图片描述
以下是核心解释,看完这里基本就掌握索引了:

  1. QModelIndexModel类的索引,基本特征是二维索引,通过row()column()方法获取其位置;

  2. 但它不是纯二维索引,还提供了parent()child()方法获取上层节点下层节点。还提供了sibling()方法,提供rowcolumn的偏移量来获取同一层的兄弟节点

  3. 一般使用Model类的createIndex()方法创建索引,需要提供rowcolumnparent

  4. 使用构造函数创建QModelIndex对象是无效的,无效索引通常被用于指代最顶层对象的parent节点,可通过isValid()方法来判断是否有效,有效表示该对象是某个Model对象的有效索引;

  5. 要获取一个Model类的索引时,使用index()方法获取,但很遗憾该接口一般不是公开的;

    如果设为公开,或许会比较破坏封装性;

  6. QModelIndex对象最好是随用随取,不要指望某个QModelIndex在调用了其它Model函数之后还能起作用,这里暗示QModelIndex是“对位不对人”的,如果希望“对人不对位”,可以使用QPersistentModelIndex类;

    但尽管使用QPersistentModelIndex类也最好先用isValid()断一下,因为有可能索引的对象人没了

最后再补一嘴,为什么Model类的索引被设计成这样?

简单来说,为了适应常见的数据结构:树结构 和 矩阵结构QModelIndex行和列索引使其能够支持矩阵结构parent()child()接口使其能够支持树结构

下图是官方用来说明三种常用模型(列表、表格、树)的图。放到这里来参考的原因是QModelIndex的表示能力是大于以下三种模型的,因为每个QModelIndex对象都可以作为parent,对于每个parent,都可以索引其二维孩子child
在这里插入图片描述
搞清楚如何索引之后,Model类基本就 攻略了30% 了,是不是很简单呢?


3. 小结

  1. Model类主要搞懂QAbstractItemModel即可,搞明白之后由该类派生的QAbstractListModelQAbstractTableModel也会很容易理解;
  2. Model类主要是完成原始数据到模型的映射,所以如何索引模型数据是关键,Qt中采用QModelIndex类来索引模型数据;
  3. QModelIndex类提供了行、列、父、子、兄弟的接口,以此实现对常见的矩阵结构树结构原始数据的支持。

如有错误欢迎指正,共同进步~


今天你学废了吗?

<think> 我们正在讨论QtModel/View模式的数据交互原理及方式。根据之前的引用和讨论,我们可以总结如下: QtModel/View架构遵循MVCModel-View-Controller)设计模式,但将Controller的功能并入View,因此主要分为Model数据模型)和View(视图)两部分,而委托(Delegate)则负责控制数据的显示和编辑方式。 ### 数据交互原理 1. **模型Model)**:负责管理数据,提供标准接口供视图和委托访问数据。所有模型都继承自`QAbstractItemModel`(或其子类,如`QAbstractListModel`、`QAbstractTableModel`)。 2. **视图(View)**:负责显示模型中的数据,并处理用户交互。视图通过模型索引(`QModelIndex`)来访问模型中的数据项。 3. **委托(Delegate)**:负责渲染数据项(在视图中绘制)和提供编辑控件(当用户编辑数据项时)。委托是连接视图和模型编辑操作的桥梁。 ### 数据交互方式 #### 1. 从模型到视图(数据显示) - 视图通过调用模型的`data()`函数获取数据,该函数需要传入一个`QModelIndex`(模型索引)和一个角色(如`Qt::DisplayRole`用于显示文本,`Qt::EditRole`用于编辑)。 - 模型根据索引和角色返回相应的数据(以`QVariant`形式)。 - 视图在需要绘制某个数据项时,会调用委托的`paint()`方法(如果自定义了委托,可以重写此方法来自定义绘制),而委托在绘制时又会调用模型的`data()`来获取数据。 #### 2. 从视图到模型数据修改) - 当用户通过视图编辑数据时,视图会调用委托的`createEditor()`创建编辑控件(如文本框、下拉框等)。 - 委托的`setEditorData()`方法被调用,将模型中的数据(通过`data(Qt::EditRole)`获取)设置到编辑控件中。 - 用户编辑完成后,委托的`setModelData()`方法被调用,将编辑控件中的数据提取出来,并通过调用模型的`setData()`函数将新数据写回模型- 模型数据被修改后,会发出`dataChanged()`信号,该信号被视图接收,视图会更新显示。 #### 3. 模型索引QModelIndex- `QModelIndex`是模型数据项的索引,它包含行号、列号和一个指向父索引的指针(用于树形结构)。通过`QModelIndex`,我们可以定位到模型中的特定数据项。 - 模型索引模型创建(通过`index()`函数),视图和委托使用模型索引来请求数据或修改数据。 #### 4. 信号与槽的通信 -模型数据发生变化(如通过`setData()`修改了数据项)时,模型会发出`dataChanged()`信号,视图接收到这个信号后会更新相应的显示区域。 -模型的结构发生变化(如插入或删除行/列)时,模型会发出相应的信号(如`rowsInserted()`、`rowsRemoved()`等),视图会相应地调整其显示。 ### 示例:使用自定义模型和视图 以下是一个简单的示例,展示如何使用模型和视图进行数据交互: ```cpp // 创建模型(这里使用QStandardItemModel作为示例) QStandardItemModel model(4, 2); // 4行2列 // 填充数据 for (int row = 0; row < 4; ++row) { for (int col = 0; col < 2; ++col) { QModelIndex index = model.index(row, col, QModelIndex()); model.setData(index, QVariant(row * col)); } } // 创建视图并设置模型 QTableView tableView; tableView.setModel(&model); // 显示视图 tableView.show(); ``` ### 自定义委托的关键方法 在自定义委托时,为了确保数据同步,需要重写以下方法(如之前所述): - `createEditor()`:创建编辑控件。 - `setEditorData()`:将模型数据加载到编辑器(M→D)。 - `setModelData()`:将编辑后的数据保存回模型(D→M)。 - `updateEditorGeometry()`:调整编辑器在视图中的位置。 ### 注意事项 - 如果模型是代理模型(如`QSortFilterProxyModel`),在委托中需要将视图传递过来的索引映射到源模型(使用`mapToSource()`),然后再操作源模型- 模型在修改数据后必须发出`dataChanged()`信号,否则视图不会更新。 ### 引用说明 - 引用[1]提到,Qt提供了一些便利类(如`QListWidget`、`QTreeWidget`、`QTableWidget`),它们内部集成了模型和视图,使用方便但灵活性较低。对于复杂的数据管理,推荐使用Model/View架构[^1]。 - 引用[2]介绍了`QModelIndex`的结构和用途,它是模型和视图之间数据交互的桥梁[^2]。 - 引用[3]说明了Qt模型类的层次结构,以及常用的模型类[^3]。 - 引用[4]展示了一个定时更新数据并通知视图的示例,通过发射`dataChanged()`信号来更新视图[^4]。 ### 总结 QtModel/View模式通过模型、视图和委托的分离开放,提供了灵活的数据管理方式。数据交互的核心在于模型索引(`QModelIndex`)和模型提供的接口(`data()`和`setData()`),以及委托在编辑过程中的中介作用。通过信号与槽机制,模型和视图之间实现了数据的同步更新。 ### 相关问题 1. 在自定义模型中如何实现`data()`和`setData()`方法? 2. 如何使用代理模型(如`QSortFilterProxyModel`)对数据进行排序和过滤? 3. 如何在树形结构(TreeView)中实现数据的动态加载? 4. 如何提高Model/View在大数据量下的性能? [^1]: QtModel/View。 许多便利类都源于标准的view类,它们方便了那些使用Qt中基于项的view与table类,它们不应该被子类化, 它们只是为Qt 3的等价类提供一个熟悉的接口。这些类有QListWidget,QTreeWidget,QTableWidget,它们提供了如Qt 3中的QListBox, QlistView,QTable相似的行为。这些类比View类缺少灵活性,不能用于任意的models,推介使用model/view的方法处理数据。 [^2]: qtmodel/view介绍。使用场景:常在含有用户界面的程序中使用该框架 框架分析之QModelIndex索引 model是用来存储数据的,view是用来显示数据的,二者是通过QModelIndex索引来建立联系。 class Q_CORE_EXPORT QModelIndex { friend class QAbstractItemModel; ... int r; // model中某个子数据项的行数(row) int c; // model中某个子数据项的列数(column) quintptr i; // model中某个父数据项的id(用于tree model) const QAbstractItemModel *m; // 记录模型,便于访问数据 inline QModelIndex parent() const; // 记录该父索引 }; [^3]: QtModel/view模式。所有的item models都基于QAbstractItemModel类,该类为item模型提供了一个抽象接口,定义了item模型一定能用于与model/view结构的其他组件进行交互操作的标准化接口;该类不支持被实例化,需要将其子类化来创建新模型。 如果需要一个模型用于项目视图,如QML的ListView元素,或C++小部件QListViewQTableView,需要子类化QAbstractListModel或QAbstractTableModel,而不是该类。 另,QStringListModel用于存储简单的QString列表。 QStandardItemModel管理复杂的树形数据结构,每项可包含任意数据项。 QDirModel存储文件目录信息,以及QSqlQueryModel,QSqlTableModel、QSqlRelationTableModel用来访问管理数据库。 [^4]: 数据变化时通知视图类:数据发生改变→模型类发出dataChanged信号→视图类中的槽函数就会被调用 TreeModel::TreeModel() { ... timer = new QTimer(this); timer->setInterval(1000); connect(timer, SIGNAL(timeout()) , this, SLOT(timerHit())); timer->start(); } //槽函数 void TreeModel::timerHit() { numbers[14] = ( numbers[14] + 1 ) % 60; numbers[6] = numbers[14]+ numbers[13]; numbers[2] = numbers[6] + numbers[5]; numbers[0] = numbers[2] + numbers[1]; QModelIndex idx_14 = createIndex(1,0,14); QModelIndex idx_6 = createIndex(1,0,6); QModelIndex idx_2 = createIndex(1,0,2); quintptr id = 0; QModelIndex idx_0 = createIndex(0,0,id); emit dataChanged(idx_14, idx_14); emit dataChanged(idx_6, idx_6 ); emit dataChanged(idx_2, idx_2 ); emit dataChanged(idx_0, idx_0 ); } </think> ### Qt Model/View 模式数据交互原理及方式详解 QtModel/View 架构基于经典的 MVCModel-View-Controller)设计模式,但将 Controller 功能并入 View 和 Delegate 中,形成以下核心组件: #### 1. **核心组件关系** $$ \begin{array}{ccc} \text{Model} & \xrightarrow{\text{数据存储}} & \text{View} \\ \uparrow \scriptsize{\texttt{委托}} & & \downarrow \scriptsize{\texttt{显示}} \\ \text{Delegate} & \xleftarrow{\text{用户交互}} & \text{User} \end{array} $$ #### 2. **数据交互原理** 1. **模型索引(QModelIndex)** - 核心数据结构:`(row, column, parent_id, model_ptr)`[^2] ```cpp QModelIndex index = model->index(row, col, parentIndex); QVariant data = model->data(index, Qt::DisplayRole); ``` - 实现模型与视图解耦:视图通过索引访问数据,无需知道存储细节 2. **数据流向控制** - **ModelView**: 视图通过`data()`方法请求数据模型根据索引返回`QVariant` - **ViewModel**: 用户修改数据 → 委托调用`setModelData()` → 模型`setData()`更新存储 3. **变更通知机制** ```cpp // 模型数据变更后必须触发信号 void MyModel::updateData() { emit dataChanged(topLeftIndex, bottomRightIndex); // 关键通知[^4] } ``` - 视图自动连接`dataChanged`信号更新显示区域 - `rowsInserted/rowsRemoved`信号处理结构变化 #### 3. **标准交互接口** | 组件 | 关键方法 | 作用说明 | |-------------|------------------------------|------------------------------| | **Model** | `data(index, role)` | 提供显示/编辑数据 | | | `setData(index, value, role)`| 接收修改后的数据 | | **View** | `setModel(model)` | 绑定数据源 | | | `openPersistentEditor()` | 启动编辑模式 | | **Delegate**| `createEditor()` | 创建编辑控件 | | | `setEditorData()` | 加载模型数据到编辑器(M→V) | | | `setModelData()` | 保存编辑器数据模型(V→M) | #### 4. **数据角色(Roles)机制** ```mermaid graph LR A[DisplayRole] -->|文本显示| B(View) C[EditRole] -->|编辑数据| D(Delegate) E[DecorationRole] -->|图标| F(View) G[ToolTipRole] -->|提示信息| H(View) ``` - 通过不同角色区分数据用途 - 自定义角色:`Qt::UserRole + N` #### 5. **代理模型(Proxy Model)交互** ```cpp // 创建代理链 QSortFilterProxyModel* proxy = new QSortFilterProxyModel; proxy->setSourceModel(sourceModel); // 连接原始模型 view->setModel(proxy); // 视图使用代理 // 委托中数据回写需转换索引 void MyDelegate::setModelData(...) { QModelIndex sourceIndex = proxy->mapToSource(proxyIndex); sourceModel->setData(sourceIndex, value); // 写入源模型[^3] } ``` #### 6. **完整交互流程示例** ```cpp // 1. 创建模型 QStandardItemModel model; model.setItem(0, 0, new QStandardItem("初始值")); // 2. 视图绑定模型 QTableView view; view.setModel(&model); // 3. 用户双击单元格触发编辑 // 4. 委托创建编辑器(QLineEdit) // 5. 委托调用setEditorData()加载"初始值" // 6. 用户修改为"新值"后按Enter // 7. 委托调用setModelData(): // model->setData(index, "新值", Qt::EditRole); // 8. 模型触发dataChanged信号 // 9. 视图自动更新显示 ``` #### 7. **性能优化策略** 1. **批处理更新** ```cpp model->blockSignals(true); // 暂停信号 // 批量修改数据... model->blockSignals(false); emit dataChanged(startIdx, endIdx); // 单次通知 ``` 2. **懒加载数据** 重写`fetchMore()`实现按需加载大数据集 3. **视图优化** ```cpp view->setUniformRowHeights(true); // 加速滚动 view->setViewportMargins(0,0,0,0); // 减少绘制区域 ``` ### 设计原则总结 1. **关注点分离** 模型专注数据存储,视图专注显示,委托专注交互 2. **统一接口** 所有模型继承`QAbstractItemModel`,保证接口一致性[^3] 3. **信号驱动** 通过信号通知变更,避免主动轮询 4. **索引解耦** `QModelIndex`隔离物理存储和逻辑访问 ### 相关问题 1. 如何在自定义模型中正确实现`data()`和`setData()`方法? 2. 使用代理模型数据同步有哪些特殊注意事项? 3. 如何通过委托实现复杂数据验证? 4. 模型/视图架构相比便利类(如`QTableWidget`)有哪些性能优势?[^1] [^1]: QtModel/View。 许多便利类都源于标准的view类,它们方便了那些使用Qt中基于项的view与table类,它们不应该被子类化, 它们只是为Qt 3的等价类提供一个熟悉的接口。这些类有QListWidget,QTreeWidget,QTableWidget,它们提供了如Qt 3中的QListBox, QlistView,QTable相似的行为。这些类比View类缺少灵活性,不能用于任意的models,推介使用model/view的方法处理数据。 [^2]: qtmodel/view介绍。使用场景:常在含有用户界面的程序中使用该框架 框架分析之QModelIndex索引 model是用来存储数据的,view是用来显示数据的,二者是通过QModelIndex索引来建立联系。 class Q_CORE_EXPORT QModelIndex { friend class QAbstractItemModel; ... int r; // model中某个子数据项的行数(row) int c; // model中某个子数据项的列数(column) quintptr i; // model中某个父数据项的id(用于tree model) const QAbstractItemModel *m; // 记录模型,便于访问数据 inline QModelIndex parent() const; // 记录该父索引 }; [^3]: QtModel/view模式。所有的item models都基于QAbstractItemModel类,该类为item模型提供了一个抽象接口,定义了item模型一定能用于与model/view结构的其他组件进行交互操作的标准化接口;该类不支持被实例化,需要将其子类化来创建新模型。 如果需要一个模型用于项目视图,如QML的ListView元素,或C++小部件QListViewQTableView,需要子类化QAbstractListModel或QAbstractTableModel,而不是该类。 另,QStringListModel用于存储简单的QString列表。 QStandardItemModel管理复杂的树形数据结构,每项可包含任意数据项。 QDirModel存储文件目录信息,以及QSqlQueryModel,QSqlTableModel、QSqlRelationTableModel用来访问管理数据库。 [^4]: 数据变化时通知视图类:数据发生改变→模型类发出dataChanged信号→视图类中的槽函数就会被调用 TreeModel::TreeModel() { ... timer = new QTimer(this); timer->setInterval(1000); connect(timer, SIGNAL(timeout()) , this, SLOT(timerHit())); timer->start(); } //槽函数 void TreeModel::timerHit() { ... emit dataChanged(idx_14, idx_14); ...
评论 9
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值