qt自定义模型必须继承qabstractitemmodel并重写rowcount()、columncount()、data()三个核心函数;需用createindex()生成qmodelindex,通过begininsertrows()/endinsertrows()通知变更,并用qabstractitemmodeltester验证契约合规性。

Qt自定义模型必须继承 QAbstractItemModel
Qt 的视图(如 QTableView、QTreeView)只认标准模型接口,不能直接喂原始容器。你得写一个类,从 QAbstractItemModel 派生,并重写至少三个核心函数:rowCount()、columnCount()、data()。漏掉任意一个,视图要么空白,要么崩溃。
常见错误是只实现 data(),结果 rowCount() 返回 0,表格永远空;或者返回了错误的行数(比如用 std::vector::size() 却忘了检查 parent.isValid()),导致树形结构下子项不显示。
实操建议:
- 先搭骨架:构造函数里调用
QObject(parent),确保模型生命周期受 Qt 管理 - 若数据是扁平列表(如表格),
parent在rowCount()和columnCount()中基本可忽略;但若支持树形(比如带父子关系),必须用parent.internalPointer()找到对应节点再算子项数 -
data()中只处理role == Qt::DisplayRole或Qt::EditRole,其他 role(如Qt::ToolTipRole)按需返回,别直接 return QVariant()
如何让模型响应数据变化:别忘了 beginInsertRows() 和 endInsertRows()
Qt 视图不会轮询模型,它靠信号驱动刷新。你手动改了底层数据(比如往 std::vector 里 push_back),视图根本不知道——除非你显式通知它“有新行来了”。直接调用 dataChanged() 或 layoutChanged() 是错的,它们不触发新增/删除动画,也不更新索引映射。
正确做法是:在插入前调 beginInsertRows(parent, first, last),插入后调 endInsertRows()。这两个函数成对出现,中间只能做数据容器修改,不能调用任何可能触发视图重绘的操作(比如 emit 其他信号)。
容易踩的坑:
-
first和last是**行号索引**,不是容器 size;插入 3 行,要传beginInsertRows(parent, 5, 7),不是(5, 5+3) - 删除时用
beginRemoveRows(),参数范围同样要闭区间;删完立刻清内存,否则后续data()可能访问野指针 - 如果用
QSortFilterProxyModel套了一层,通知必须发给原始模型,proxy 会自动监听
QModelIndex 怎么生成:用 createIndex(),别 new 指针
模型通过 QModelIndex 把视图请求和你的内部数据结构关联起来。这个 index 不是单纯存行列号——它的 internalPointer() 字段可以塞任意指针(比如指向你数据结构中某个 Node*),这是实现树形模型的关键。
必须用 createIndex(row, column, ptr) 构造,而不是手动生成 QModelIndex() 或 new 出来。Qt 内部靠这个函数把 ptr 存进 index,后续 index.internalPointer() 才能取回。
典型误用:
- 在
index()函数里 return QModelIndex(),导致所有子项 index 无效,hasChildren()返回 false - 传入栈变量地址给
createIndex()(比如&localVar),后续访问就崩 - 树形模型中,父节点 index 的
internalPointer()是Node*,子节点 index 的 pointer 应该是其子Node*,层级错乱会导致递归崩溃
调试模型最有效的手段:启用 QAbstractItemModelTester
模型写完跑起来没报错,不代表它符合 Qt 模型契约。很多 bug(比如 parent() 返回错误 index、flags() 缺少 Qt::ItemIsEnabled)只在特定交互下暴露,人工测容易漏。
加一行测试代码,立刻暴露问题:
new QAbstractItemModelTester(model, this);
它会在构造时遍历所有合法 index,检查 index()、parent()、rowCount() 是否自洽,并在控制台输出断言失败信息,比如:
"QAbstractItemModelTester: Invalid index at row 0, column 0, parent is invalid" —— 这说明 index(0,0,QModelIndex()) 返回了非法 index,大概率是 createIndex() 参数错了或数据为空时没 guard。
注意:这个 tester 只在 debug 构建下有效,release 会被编译器优化掉,所以务必在开发阶段就加上。
真正难的不是写完模型,而是让每个 index 的生命周期和内部指针管理严丝合缝;稍有松动,internalPointer() 就变成悬垂指针,crash 发生在最意想不到的点击时刻。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











