QT M/V架构开发实战:QAbstractItemModel介绍
·
目录
- · @[TOC](目录)
- 前言
- 一、QAbstractItemModel初步介绍
- 二、核心接口
- 三、关键信号
目录
- · @[TOC](目录)
- 前言
- 一、QAbstractItemModel初步介绍
- 二、核心接口
- 三、关键信号
前言
本文主要介绍的是使用代码生成的情况下对控件的介绍,包括拥有的功能及能修改的样式,也会说明在qtdesiner拖拽控件生成和使用代码生成控件的区别(如果有的话,遇到了的会说),此版本不属于最终版本,以后遇到什么新奇的点会继续更新!本文基于QT官方的文档进行的编写,QT版本为qt 5.14.0,编写环境为Windows11。不得不说官方文档真是个好东西,有时候有些不会的上去一看就能有灵感解决了,可惜没有中文版本的。
一、QAbstractItemModel初步介绍
QAbstractItemModel是所有模型类的基类,他的一些方法是模型类使用的关键,接下来介绍QAbstractItemModel类的一些核心接口和使用方法。
模型中的数据项通过 QModelIndex对象来定位。索引包含行、列和父索引信息。
data()和 setData()函数使用 int role参数来区分数据的不同用途(显示文本、图标、对齐方式、背景色、编辑值等)。Qt 定义了一系列标准角色 (Qt::DisplayRole, Qt::EditRole等),你也可以定义自定义角色 (Qt::UserRole及以上)。
当模型中的数据发生变化(增删改)时,模型会发出特定的信号(如 dataChanged, rowsInserted),视图监听这些信号并自动更新显示区域。
二、核心接口
1)rowCount()
返回给定父索引 (parent) 下的直接子项的行数。
int rowCount(const QModelIndex &parent = QModelIndex()) const
参数:
parent指定要查询其子项数量的父节点。如果 parent是无效索引 (QModelIndex()),则表示查询根节点的子项数量(通常是整个表格的行数或树的顶级节点数)。
实现要点:
对于列表或表格模型(扁平结构),通常忽略 parent(或检查 parent.isValid()),直接返回总行数。
对于树形模型,需要根据 parent指向的节点返回其直接子节点的数量。
如果 parent指向一个无效的子节点(比如一个叶子节点),应该返回 0。
2)columnCount()
返回给定父索引 (parent) 下的直接子项的列数。
int rowCount(const QModelIndex &parent = QModelIndex()) const
参数:
同上rowCount()
实现要点:
在大多数情况下(尤其是树形结构),同一父节点下的所有子项具有相同的列数。通常可以忽略 parent(或检查 parent.isValid()),直接返回模型的固定列数。
如果不同层级的节点列数不同,需要根据 parent来返回相应的列数。
3)data()
这是最重要的函数!!!! 返回指定索引 (index) 和角色 (role) 对应的数据。
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const
参数:
index: 要获取数据的项的位置(包含行、列、父索引信息)。
role: 指定需要数据的用途。
常见标准角色:
Qt::DisplayRole(0): 用于在视图中显示的文本 (QString)。
Qt::EditRole(2): 用于编辑器的数据(通常是 QString或基本类型)。
Qt::DecorationRole(1): 用于显示的图标 (QIcon, QPixmap, QColor)。
Qt::TextAlignmentRole(7): 文本对齐方式 (Qt::Alignment或 int)。
Qt::BackgroundRole(8): 项的背景画刷 (QBrush)。
Qt::ForegroundRole(9): 项的前景(文本)画刷 (QBrush)。
Qt::CheckStateRole(10): 项的勾选状态 (Qt::Checked, Qt::Unchecked, Qt::PartiallyChecked),通常用于树/列表的第一列。
Qt::UserRole(0x0100) 及以上: 用于自定义数据。
返回值:
QVariant类型,可以包装任何 Qt 支持的数据类型。如果该索引/角色组合没有有效数据,应返回一个无效的 QVariant (QVariant()或 QVariant::Invalid),视图会忽略它或使用默认行为。
实现要点:
首先检查 index是否有效 (index.isValid()) 以及 index的行/列是否在有效范围内。
根据 index定位到底层数据结构中的具体数据项。
根据 role参数返回相应的数据。例如,对于 Qt::DisplayRole,返回要显示的字符串;对于 Qt::DecorationRole,返回图标。
对于不支持的角色,返回无效 QVariant。
4)index()
根据给定的行 (row)、列 (column) 和父索引 (parent),创建并返回对应的模型索引 (QModelIndex)。
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const
参数:
row: 在父节点下的行号(从 0 开始)。
column: 在父节点下的列号(从 0 开始)。
parent: 父节点的索引。如果指向根节点,则为无效索引 (QModelIndex())。
返回值:
一个有效的 QModelIndex(如果参数有效且数据项存在),否则返回无效索引。
实现要点:
在构建树形结构时非常关键。
检查 row和 column是否在父节点 parent的有效范围内(使用 rowCount(parent)和 columnCount(parent))。
根据你的底层数据结构,找到位于 parent节点下第 row行、第 column列的子数据项。
使用 createIndex(row, column, void internalPointer)创建索引。internalPointer是一个指向该数据项内部表示(如指针、ID)的 void。视图不会直接使用它,但你的模型在 data()和 parent()中可以通过 index.internalPointer()获取它,从而快速定位到数据项。对于简单的模型(如基于二维数组的表格),internalPointer可以设为 nullptr。
确保返回的索引包含正确的行、列和父节点信息。
5)parent()
提供水平或垂直表头的数据。
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const
参数:
section: 表头的第几段(对于水平表头是列号,对于垂直表头是行号)。
orientation: Qt::Horizontal或 Qt::Vertical。
role: 数据角色(同 data())。
实现要点:
通常重写此函数以返回列名或行号。例如:
if (role == Qt::DisplayRole) {if (orientation == Qt::Horizontal) {return QString("Column %1").arg(section);} else {return section + 1; // 显示行号 (1-based)}
}
return QVariant(); // 其他角色返回无效
6)headerData()
返回给定子索引 (child) 的父索引。
QModelIndex parent(const QModelIndex &child) const
参数:
child是你要查找其父节点的子项索引。
返回值:
父节点的 QModelIndex。如果 child是根节点下的项(即没有父节点),则返回无效索引 (QModelIndex())。
实现要点:
对于扁平模型(列表、表格),所有项的父节点都是根节点,因此总是返回无效索引 (QModelIndex())。
对于树形模型:检查child是否有效;从 child索引中(通常通过 child.internalPointer())获取它所代表的数据项;找到该数据项的父项。使用 createIndex()创建指向该父项的索引。创建父索引时,需要知道该父项在其自己的父节点(即祖父节点)中的行号和列号(通常是 0)。这通常需要你的数据结构能向上追溯。;如果 child本身就是顶级节点(根节点的直接子节点),则返回无效索引。
7)setData()
尝试将索引 index处角色 role的数据设置为 value。
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole)
返回值:
设置成功返回 true,失败返回 false。
重写要点:
检查 index是否有效且可编辑(通常还需检查 flags(index)是否包含 Qt::ItemIsEditable)。
根据 role(通常是 Qt::EditRole)获取 value中包含的新数据。
更新底层数据结构中对应的数据项。
如果数据成功改变,必须在返回 true之前发出 dataChanged(index, index, QVector() << role)信号,通知视图该区域需要刷新。如果多个连续项改变,可以扩大索引范围 (index.topLeft()到 index.bottomRight())。
返回 true表示成功。
8)flags()
返回索引 index对应项的标志,描述其状态和行为(是否可选、可编辑、可拖放等)。
Qt::ItemFlags flags(const QModelIndex &index) const
返回值:
Qt::ItemFlags的组合(enum值通过 |组合)。
常用标志:
Qt::ItemIsSelectable(0x1): 项可以被选择。
Qt::ItemIsEditable(0x2): 项可以被编辑(需要实现 setData)。
Qt::ItemIsDragEnabled(0x4): 项可以作为拖拽操作的源。
Qt::ItemIsDropEnabled(0x8): 项可以作为拖拽操作的目标。
Qt::ItemIsUserCheckable(0x10): 项可以显示一个复选框(需要处理 Qt::CheckStateRole)。
Qt::ItemIsEnabled(0x20): 项可以被交互(默认包含)。
Qt::ItemIsAutoTristate(0x40): 用于复选框,父节点根据子节点状态自动变为部分选中状态。
Qt::ItemIsTristate(0x40): 已弃用,同 ItemIsAutoTristate。
实现要点:
通常组合基础标志 (QAbstractItemModel::flags(index)) 和你的自定义标志。例如,使第一列可编辑:
Qt::ItemFlags f = QAbstractItemModel::flags(index);
if (index.column() == 0) { // 假设第一列可编辑f |= Qt::ItemIsEditable;
}
return f;
9)insertRows() \ insertColumns()
在父节点 parent下,从行 row开始插入 count行。
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex())
参数:
child是你要查找其父节点的子项索引。
返回值:
成功返回 true。
实现要点:
在修改数据结构之前,调用 beginInsertRows(parent, row, row + count - 1)。这是必须的! 它通知连接的视图即将发生的变化。
在底层数据结构中实际插入 count行空数据(或默认数据)。
在修改数据结构之后,调用 endInsertRows()。这也是必须的! 它通知视图插入已完成,视图可以刷新。
insertColumns同理。
10)removeRows() \ removeColumns()
在父节点 parent下,从行 row开始删除 count行。
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex())
参数:
child是你要查找其父节点的子项索引。
返回值:
成功返回 true。
实现要点:
在修改数据结构之前,调用 beginRemoveRows(parent, row, row + count - 1)。必须!告诉view去刷新等一些操作,不然可能导致崩溃!
从底层数据结构中删除这 count行数据。
在修改数据结构之后,调用 endRemoveRows()。必须!
removeColumns同理。
三、关键信号
1)dataChanged
dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>())
触发时机: 一个或多个数据项的内容(特定角色的值)发生了变化。
参数:
topLeft
, bottomRight
: 定义发生变化的矩形区域(左上角和右下角索引)。如果只有一个项变化,两者相同。
roles
: 发生变化的角色列表。如果未指定或为空,表示所有角色都可能发生了变化(视图会刷新所有内容)。如果指定了角色,视图可以只更新与该角色相关的部分(更高效)。
何时发出: 在 setData()成功修改数据后立即发出。
2)rowsAboutToBeInserted
rowsAboutToBeInserted(const QModelIndex &parent, int first, int last)
触发时机: 在行被插入之前发出(由 beginInsertRows()内部发出)。
参数: parent
是父节点索引,first
和 last
是将要插入的行范围(闭区间)。
作用: 视图据此准备更新(如调整滚动条)。
3)rowsInserted
rowsInserted(const QModelIndex &parent, int first, int last)
触发时机: 在行被插入之后发出(由 endInsertRows()内部发出)。
参数: 同上。
作用: 视图据此更新显示内容。
4)rowsAboutToBeRemoved
rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
5)rowsRemoved
与插入类似,用于行删除。由 beginRemoveRows()/ endRemoveRows()发出。
rowsRemoved(const QModelIndex &parent, int first, int last)
6)modelAboutToBeReset()/ modelReset()
触发时机: 当模型的数据结构发生剧烈变化(无法通过增删行/列来通知),需要视图完全重置时(例如,加载一个全新的数据集)。
用法:
beginResetModel(); // 发出 modelAboutToBeReset()
// ... 彻底清空或重新加载底层数据 ...
endResetModel(); // 发出 modelReset()
作用: 视图收到 modelAboutToBeReset()后会清理内部状态(如选择),收到 modelReset()后会重新从模型获取所有数据。
本次分享就到这里了,如果有什么错误的话请指正,或者有什么疑问的,也可以在评论区一起探讨!