ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

100w条数据丝滑滚动!Qt Model/View 架构实战

100w条数据丝滑滚动!Qt Model/View 架构实战 目录1.架构整体设计2.核心机制2.1.QModelIndex —— 统一数据寻址2.2.Item Roles —— 多维度数据3.Model 层底层原理3.1.模型类层级3.2.核心接口详解3.2.1.index() —— 创建子节点索引纯虚函数必须重写3.2.2.parent() —— 获取节点的父索引纯虚函数必须重写3.2.3.createIndex() —— 构造索引对象保护函数3.2.4.hasIndex() —— 校验索引合法性3.2.5.rowCount() —— 获取指定父节点的行数纯虚函数必须重写3.2.6.columnCount() —— 获取指定父节点的列数纯虚函数必须重写3.2.7.data() —— 读取单元格数据纯虚函数必须重写3.2.8.setData() —— 写入单元格数据虚函数可选重写3.2.9.itemData() / setItemData() —— 批量读写所有角色3.2.10.单元格属性接口flags()3.2.11.headerData() —— 读取表头数据3.2.12.setHeaderData() —— 修改表头数据3.2.13.插入行beginInsertRows() / endInsertRows()3.2.14.删除行beginRemoveRows() / endRemoveRows()3.2.15.移动行beginMoveRows() / endMoveRows()3.2.16.全量重置beginResetModel() / endResetModel()3.2.17.dataChanged 信号4.View 层工作原理5.Delegate 层工作原理6.代理模型QSortFilterProxyModel7.完整数据流全链路8.完整示例1.架构整体设计Qt 的 Model/View 是基于MVC模型 - 视图 - 控制器演化而来的架构核心思想是数据与表现完全解耦数据层Model只负责数据的存储、访问与维护视图层View只负责界面渲染与用户交互委托层Delegate负责单元格的绘制与编辑。三者通过标准化接口通信实现了高度的灵活性与可复用性。1.核心三角色分工组件基类核心职责模型 ModelQAbstractItemModel数据的唯一持有者向视图提供标准化的数据访问接口维护数据的增删改查数据变更时通知所有关联视图视图 ViewQAbstractItemView从模型拉取数据并渲染到屏幕接收用户输入点击、编辑、拖拽将操作转发给模型 / 委托委托 DelegateQStyledItemDelegate负责单元格的自定义绘制和自定义编辑器是视图与数据编辑之间的中间层2.交互关系用户操作 → View → Delegate编辑 → Model数据写入 Model数据变更→ 信号通知 → View → Delegate绘制 → 屏幕刷新3.架构优势多视图共享数据同一份数据可以同时用表格、列表、树形等多种视图展示一处修改所有视图自动同步。职责单一数据逻辑与 UI 逻辑完全分离便于单元测试、独立维护和替换。高性能视图采用按需渲染机制只绘制屏幕可见区域支持十万级以上数据量仍保持流畅。高度可定制可独立替换模型、视图、委托任意一层不影响其他部分。2.核心机制2.1.QModelIndex —— 统一数据寻址QModelIndex是 Model 与 View 之间通信的唯一凭证视图通过索引向模型请求数据模型通过索引定位数据项。它是一个轻量级对象包含 4 个核心要素1. 索引的组成行号row数据项在父节点下的行位置列号column数据项在父节点下的列位置父索引parent树形结构中指向父节点的索引列表 / 表格模型中永远是无效索引内部指针internalPointer自定义模型中用于绑定自定义数据节点如树形结构的 TreeNode 指针2. 根节点与无效索引无效索引QModelIndex()代表模型的根节点所有顶层数据的父索引都是它。列表模型所有数据都是根节点的子项parent()永远返回空索引。表格模型同列表只有行和列无层级关系。树形模型每个子节点都有自己的父索引通过index()创建子索引通过parent()回溯父节点。3. 索引的创建与获取模型通过createIndex(row, column, ptr)生成索引第三个参数可以存入自定义数据指针视图通过index.internalPointer()取回指针从而关联到真实数据节点。注意QModelIndex 是临时对象不能长期保存。模型执行插入、删除、重置操作后旧索引会失效需重新获取。2.2.Item Roles —— 多维度数据一个单元格不止有 “显示文字” 这一个属性还包含图标、颜色、字体、复选框状态等多个维度。Qt 通过角色Role来区分同一索引下的不同数据维度。data(const QModelIndex index, int role)函数根据传入的role返回对应的数据视图根据不同角色渲染不同的视觉元素。常用角色枚举角色作用Qt::DisplayRole单元格显示的文本最常用Qt::EditRole进入编辑状态时填充到编辑器的文本Qt::DecorationRole单元格图标QIcon/QPixmapQt::CheckStateRole复选框状态Qt::Checked/Unchecked/PartiallyCheckedQt::ForegroundRole文字颜色QBrushQt::BackgroundRole单元格背景色QBrushQt::FontRole单元格字体QFontQt::ToolTipRole鼠标悬浮提示文本Qt::SizeHintRole单元格建议尺寸Qt::UserRole自定义数据起始值可扩展存储自定义业务数据3.Model 层底层原理QAbstractItemModel是 Qt 模型层的抽象基类它定义了视图 / 委托与模型交互的全套标准契约。所有视图QListView/QTableView/QTreeView只依赖这套接口访问数据完全不感知数据的底层存储方式数组、链表、数据库、文件等。3.1.模型类层级QAbstractItemModel抽象基类全功能 ├─ QAbstractListModel列表专用简化列与父子逻辑 │ └─ QStringListModel字符串列表现成模型 ├─ QAbstractTableModel表格专用简化父子逻辑 └─ QStandardItemModel通用表格/树形模型开箱即用简单列表 / 表格优先用QStandardItemModel无需子类化。自定义数据结构、高性能场景子类化QAbstractItemModel。数据库场景使用QSqlTableModel/QSqlQueryModel。3.2.核心接口详解子类化模型时纯虚函数必须实现构成数据访问的最小闭环函数作用rowCount(parent)返回指定父索引下的行数columnCount(parent)返回指定父索引下的列数data(index, role)返回指定索引、指定角色的数据index(row, col, parent)根据行、列、父索引创建子节点索引parent(child)返回子节点的父索引headerData(...)返回表头数据***必须实现的接口3.2.1.index()—— 创建子节点索引纯虚函数必须重写virtual QModelIndex index(int row, int column, const QModelIndex parent QModelIndex()) const 0;根据「父索引 行号 列号」创建并返回对应子数据项的QModelIndex。视图通过这个函数遍历所有数据是视图访问数据的入口。row/column子项在父节点下的行、列位置parent父节点索引传入无效索引QModelIndex()代表「模型根节点」即获取顶层数据项调用时机视图渲染、滚动、展开节点、计算布局时会高频调用此函数生成索引。实现要点列表 / 表格模型所有数据都是顶层节点parent有效时直接返回空索引有效行号列号直接用createIndex(row, column)生成索引。树形模型先通过parent.internalPointer()取出父节点指针再从父节点的子节点列表中找到第row个子节点用createIndex(row, column, 子节点指针)绑定数据。典型实现列表模型QModelIndex CustomListModel::index(int row, int column, const QModelIndex parent) const { // 列表没有子层级父索引有效则无数据 if (parent.isValid()) return QModelIndex(); // 行号列号合法则创建索引 if (hasIndex(row, column, parent)) return createIndex(row, column); return QModelIndex(); }3.2.2.parent()—— 获取节点的父索引纯虚函数必须重写virtual QModelIndex parent(const QModelIndex child) const 0;给定一个子节点索引返回它的父节点索引。这是树形结构向上回溯的唯一途径列表 / 表格模型永远返回空索引。调用时机视图处理树形展开、缩进绘制、选中区域计算、向上递归查找时调用。实现要点列表 / 表格模型直接return QModelIndex();即可所有节点都是顶层节点。树形模型通过child.internalPointer()取出当前节点指针获取节点的父节点指针如果父节点是根节点返回空索引否则找到父节点在祖父节点中的行号用createIndex生成父索引。⚠️ 注意绝对不能在parent()里返回节点自身会造成无限递归导致程序崩溃。3.2.3.createIndex()—— 构造索引对象保护函数QModelIndex createIndex(int row, int column, void *ptr nullptr) const; QModelIndex createIndex(int row, int column, quintptr id) const;这是模型内部的索引工厂函数自定义模型通过它生成合法的QModelIndex并可以把自定义数据指针 / ID 绑定到索引上。void *ptr最常用存储自定义数据节点的指针如TreeNode*后续通过index.internalPointer()取回。quintptr id适合用整数 ID 而非指针的场景通过index.internalId()取回。注意事项这是保护成员函数只能在模型子类内部调用外部无法直接构造索引。索引是轻量临时对象不要长期缓存模型结构变更增删、重置后旧索引会失效。3.2.4.hasIndex()—— 校验索引合法性bool hasIndex(int row, int column, const QModelIndex parent QModelIndex()) const;快速判断指定行、列、父节点下是否存在有效数据项内部通过rowCount和columnCount计算。自定义模型中常用它做边界校验避免越界。3.2.5.rowCount()—— 获取指定父节点的行数纯虚函数必须重写virtual int rowCount(const QModelIndex parent QModelIndex()) const 0;返回某个父节点下有多少行数据。视图靠它知道需要渲染多少行、滚动条高度、布局计算。parent父节点索引空索引代表查询顶层行数。实现要点列表 / 表格模型parent.isValid()时返回 0没有子层级否则返回数据容器的大小。树形模型取出父节点指针返回其children列表的大小。调用时机视图初始化、每次数据结构变更、滚动计算时都会调用属于高频接口。3.2.6.columnCount()—— 获取指定父节点的列数纯虚函数必须重写virtual int columnCount(const QModelIndex parent QModelIndex()) const 0;返回某个父节点下有多少列数据决定表格的水平列数、树的列数。实现要点绝大多数场景下列数是固定的直接返回常量即可。列表模型通常返回 1。和rowCount一样parent有效时一般返回 0列表 / 表格场景。3.2.7.data()—— 读取单元格数据纯虚函数必须重写virtual QVariant data(const QModelIndex index, int role Qt::DisplayRole) const 0;这是模型最核心的数据出口视图 / 委托通过它获取单元格的各类数据文本、图标、颜色、勾选状态等。index数据项的索引role数据角色用来区分同一单元格的不同维度数据核心角色一览角色用途返回类型Qt::DisplayRole单元格显示的文本QStringQt::EditRole进入编辑状态时填充编辑器的文本QStringQt::DecorationRole单元格左侧图标QIcon/QPixmapQt::CheckStateRole复选框状态Qt::CheckStateQt::ForegroundRole文字颜色QBrushQt::BackgroundRole单元格背景色QBrushQt::FontRole单元格字体QFontQt::ToolTipRole鼠标悬浮提示QStringQt::UserRole及以上自定义业务数据任意 QVariant 类型极高频率调用视图每绘制一个单元格就会调用多次data()分别获取文本、图标、颜色等。滚动界面时会持续触发。实现要点与最佳实践入口必须做合法性校验索引无效、行号越界直接返回QVariant()。按 role 分支返回数据不支持的 role 返回空QVariant。必须保证函数高效禁止在data()中做文件 IO、数据库查询、复杂计算否则滚动会严重卡顿。显示和编辑可以返回不同值DisplayRole可以加单位 / 格式化EditRole返回原始值方便编辑。典型实现片段QVariant CustomTableModel::data(const QModelIndex index, int role) const { if (!index.isValid() || index.row() m_data.size()) return QVariant(); const auto item m_data[index.row()]; int col index.column(); switch (role) { case Qt::DisplayRole: if (col 0) return item.name; if (col 1) return item.value; break; case Qt::CheckStateRole: if (col 2) return item.checked ? Qt::Checked : Qt::Unchecked; break; case Qt::ForegroundRole: if (item.value 0) return QColor(Qt::red); break; } return QVariant(); }***可选接口3.2.8.setData()—— 写入单元格数据虚函数可选重写virtual bool setData(const QModelIndex index, const QVariant value, int role Qt::EditRole);用于修改单元格数据是模型的数据写入入口。用户编辑单元格、代码修改勾选状态等都走这个函数。true写入成功false写入失败索引无效、数据不合法、不支持该 role实现强制要求写入成功后必须发射dataChanged信号通知所有关联视图刷新对应单元格。典型实现片段bool CustomTableModel::setData(const QModelIndex index, const QVariant value, int role) { if (!index.isValid() || index.row() m_data.size()) return false; auto item m_data[index.row()]; bool changed false; if (role Qt::EditRole index.column() 0) { item.name value.toString(); changed true; } else if (role Qt::CheckStateRole index.column() 2) { item.checked (value.valueQt::CheckState() Qt::Checked); changed true; } if (changed) { emit dataChanged(index, index, {role}); return true; } return false; }3.2.9.itemData()/setItemData()—— 批量读写所有角色一次性读写一个单元格的所有角色数据底层分别调用data()和setData()常用于拖拽、复制粘贴场景一般不需要重写。3.2.10.单元格属性接口flags()virtual Qt::ItemFlags flags(const QModelIndex index) const;返回指定单元格的交互属性决定单元格能否被选中、编辑、勾选、拖拽等。很多功能比如复选框、编辑只在data()里返回数据是不够的必须同时在flags()里开启对应标志位。常用标志位标志作用Qt::ItemIsEnabled单元格可用可交互默认一般都开启Qt::ItemIsSelectable单元格可被选中Qt::ItemIsEditable单元格可双击进入编辑状态Qt::ItemIsUserCheckable单元格显示复选框配合Qt::CheckStateRole使用Qt::ItemIsDragEnabled单元格可拖拽Qt::ItemIsDropEnabled单元格可接收拖放Qt::ItemIsTristate复选框支持三态半选常见坑只在data()返回CheckStateRole但flags()没加ItemIsUserCheckable→ 复选框不显示。想让单元格可编辑只重写setData()不够必须在flags()里加上ItemIsEditable。3.2.11.headerData()—— 读取表头数据virtual QVariant headerData(int section, Qt::Orientation orientation, int role Qt::DisplayRole) const;返回水平 / 垂直表头的数据最常用的是表头文字也支持设置表头的颜色、字体、对齐方式等。section第几列水平表头或第几行垂直表头orientationQt::Horizontal水平表头 /Qt::Vertical垂直表头role和data()的 role 含义一致常用DisplayRole3.2.12.setHeaderData()—— 修改表头数据修改表头内容成功后发射headerDataChanged信号通知视图刷新表头。***结构变更接口模型数据结构发生变化增 / 删 / 移动行 / 列、全量重置时绝对不能直接修改内部容器就结束必须用标准的begin.../end...函数包裹操作。这是 90% 以上 Model 相关 Bug 的根源。begin...函数会提前通知视图「结构即将变化」视图会更新内部的选中区域、展开状态、滚动位置等end...函数完成最终通知并触发界面刷新。跳过 begin/end 会导致视图行号与模型错位、选中区域错乱、甚至崩溃。3.2.13.插入行beginInsertRows()/endInsertRows()void beginInsertRows(const QModelIndex parent, int first, int last); void endInsertRows();向模型中插入新行必须成对使用中间执行真实的数据插入。parent插入位置的父节点索引first/last插入的起始行号和结束行号包含两端void CustomTableModel::addRow(const TableRowData item) { int row m_data.size(); beginInsertRows(QModelIndex(), row, row); // 通知视图即将插入第row行 m_data.append(item); // 真实插入数据 endInsertRows(); // 完成插入触发视图刷新 }对应还有beginInsertColumns/endInsertColumns用于插入列。3.2.14.删除行beginRemoveRows()/endRemoveRows()void beginRemoveRows(const QModelIndex parent, int first, int last); void endRemoveRows();从模型中删除行用法和插入完全对称先通知再删数据。void CustomTableModel::removeRow(int row) { if (row 0 || row m_data.size()) return; beginRemoveRows(QModelIndex(), row, row); m_data.removeAt(row); endRemoveRows(); }3.2.15.移动行beginMoveRows()/endMoveRows()用于行的移动操作比「先删后插」性能更好且能保留选中状态。3.2.16.全量重置beginResetModel()/endResetModel()void beginResetModel(); void endResetModel();通知视图模型所有数据即将全部重建视图会丢弃所有旧索引、旧状态完全重新加载。使用场景批量加载大量数据数据整体替换排序后整体重排会丢失所有视图状态选中位置、展开状态、滚动位置。能局部刷新就不要用 reset频繁 reset 会导致界面闪烁和体验下降。void CustomTableModel::clearAll() { beginResetModel(); m_data.clear(); endResetModel(); }***数据变更通知信号3.2.17.dataChanged信号void dataChanged(const QModelIndex topLeft, const QModelIndex bottomRight, const QVectorint roles QVectorint());当已有单元格的数据内容发生变化时发射通知视图刷新指定矩形区域内的单元格。这是局部刷新、性能最优的更新方式。topLeft/bottomRight变更区域的左上角和右下角索引单个单元格则两个参数相同roles指定哪些角色发生了变化视图只刷新对应维度不传则刷新所有角色触发时机setData()写入成功后必须手动发射代码批量修改一批数据后手动发射一次区域范围的dataChanged注意插入 / 删除行不要发dataChanged必须用beginInsertRows/endInsertRows这套机制。核心原则永远不要直接修改模型内部的数据容器而不发通知。视图的渲染、选中、拖拽全部依赖模型的通知信号漏发信号会导致显示异常、索引错位甚至程序崩溃。4.View 层工作原理QAbstractItemView是所有视图的基类常见子类QListView、QTableView、QTreeView。1. 渲染流程视图采用视口渲染机制只绘制屏幕可见的单元格因此支持超大数据量视图计算当前可见的行 / 列范围。对每个可见单元格通过model()-index(row, col, parent)生成索引。调用model()-data(index, role)获取对应角色的数据。将数据交给委托delegate()-paint()绘制到视口上。2. 选择模型 QItemSelectionModel视图的选中状态不存储在模型中而是由独立的QItemSelectionModel管理。同一模型可以绑定多个视图每个视图可以有独立的选择模型也可以共享选择模型。支持多种选择模式单选、多选、行选择、列选择、连续选择等。3. 多视图共享模型同一个 Model 实例可以setModel给多个不同的 View。当模型数据变化时所有关联视图会自动同步刷新这是 Model/View 架构最典型的应用优势。5.Delegate 层工作原理委托是单元格的 “画家 编辑器”默认实现是QStyledItemDelegate负责两件核心工作绘制单元格、提供编辑控件。1.绘制流程视图不直接绘制单元格而是调用委托的paint()函数完成绘制void paint(QPainter *painter, const QStyleOptionViewItem option, const QModelIndex index) const默认实现会根据DisplayRole、DecorationRole、CheckStateRole等绘制文本、图标、复选框。重写此函数可实现自定义绘制比如进度条、开关、星级评分等。2.编辑交互流程用户双击单元格进入编辑状态时完整流程如下视图调用delegate-createEditor()创建编辑器控件默认是 QLineEdit。调用delegate-setEditorData()从模型取出EditRole数据填充到编辑器。用户在编辑器中修改内容。编辑提交后调用delegate-setModelData()将编辑器内容写回模型。编辑器销毁视图刷新单元格显示。重写createEditor、setEditorData、setModelData即可实现自定义编辑器如下拉框、数字输入框、颜色选择器等。6.代理模型QSortFilterProxyModel代理模型是装饰器模式的典型应用它本身不持有数据而是嵌套在源模型与视图之间对数据做二次加工。1.工作原理源模型真实数据 → 代理模型排序/过滤/转换 → 视图代理模型同样继承自QAbstractItemModel对视图来说它就是一个普通模型对内它转发请求给源模型同时做一层索引映射。2.核心能力排序点击表头排序无需修改源模型。过滤按关键字 / 条件隐藏部分行 / 列源数据保持不变。数据转换将源模型的数据格式转换后展示给视图。索引映射mapToSource()代理索引转源索引mapFromSource()源索引转代理索引。3. 优势不侵入原数据模型符合单一职责。同一个源模型可以挂载多个代理实现不同的排序 / 过滤视图。7.完整数据流全链路1.数据显示链路视图需要渲染单元格 → 生成 QModelIndex → 调用 model-data(index, 对应角色) → 模型返回 QVariant 数据 → 交给 delegate-paint() → 绘制到屏幕2.用户编辑链路用户双击单元格 → 视图委托 delegate 创建编辑器 → delegate 从 model 取 EditRole 数据填充编辑器 → 用户编辑完成提交 → delegate 将数据写回 model-setData() → model 发射 dataChanged 信号 → 视图刷新对应单元格3.代码修改数据链路代码调用模型接口修改数据 → 模型发射 dataChanged / begin/end 信号 → 所有关联视图收到信号 → 视图重新请求对应区域数据 → 刷新界面显示8.完整示例完整树形 ModelQTreeView可扩展复选框运行图如下代码如下CustomTreeModel.h#ifndef CUSTOMTREEMODEL_H #define CUSTOMTREEMODEL_H #include QAbstractItemModel #include QList #include QString #include QIcon struct TreeItem { QString text; QIcon icon; bool checked; TreeItem* parent; QListTreeItem* children; explicit TreeItem(const QString txt, TreeItem* p nullptr) : text(txt), parent(p), checked(false) {} ~TreeItem(){ qDeleteAll(children); } }; class CustomTreeModel : public QAbstractItemModel { Q_OBJECT public: explicit CustomTreeModel(QObject *parent nullptr); ~CustomTreeModel() override; QVariant data(const QModelIndex index, int role Qt::DisplayRole) const override; bool setData(const QModelIndex index, const QVariant value, int role Qt::EditRole) override; Qt::ItemFlags flags(const QModelIndex index) const override; int rowCount(const QModelIndex parent QModelIndex()) const override; int columnCount(const QModelIndex parent QModelIndex()) const override; QModelIndex index(int row, int column, const QModelIndex parent QModelIndex()) const override; QModelIndex parent(const QModelIndex child) const override; QVariant headerData(int section, Qt::Orientation orientation, int role Qt::DisplayRole) const override; // 对外接口 TreeItem* addChild(TreeItem* parentItem, const QString text); void clearTree(); private: QModelIndex indexOfItem(TreeItem* item) const; private: TreeItem* m_root; }; #endif // CUSTOMTREEMODEL_HCustomTreeModel.cpp#include CustomTreeModel.h CustomTreeModel::CustomTreeModel(QObject *parent) : QAbstractItemModel(parent), m_root(new TreeItem(Root)) { } CustomTreeModel::~CustomTreeModel() { delete m_root; } QVariant CustomTreeModel::data(const QModelIndex index, int role) const { if (!index.isValid()) return QVariant(); TreeItem* item static_castTreeItem*(index.internalPointer()); if (role Qt::DisplayRole) { return item-text; } else if(role Qt::DecorationRole) { return item-icon; } else if(role Qt::CheckStateRole) { return item-checked ? Qt::Checked : Qt::Unchecked; } return QVariant(); } bool CustomTreeModel::setData(const QModelIndex index, const QVariant value, int role) { if (!index.isValid()) return false; TreeItem* item static_castTreeItem*(index.internalPointer()); if(role Qt::CheckStateRole) { item-checked (value.valueQt::CheckState() Qt::Checked); emit dataChanged(index, index); return true; } return false; } Qt::ItemFlags CustomTreeModel::flags(const QModelIndex index) const { if (!index.isValid()) return Qt::NoItemFlags; return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; } int CustomTreeModel::rowCount(const QModelIndex parent) const { TreeItem* parentItem; if(parent.isValid()) parentItem static_castTreeItem*(parent.internalPointer()); else parentItem m_root; return parentItem-children.size(); } int CustomTreeModel::columnCount(const QModelIndex parent) const { Q_UNUSED(parent); return 1; } QModelIndex CustomTreeModel::index(int row, int column, const QModelIndex parent) const { if (!hasIndex(row, column, parent)) return QModelIndex(); TreeItem* parentItem; if(parent.isValid()) parentItem static_castTreeItem*(parent.internalPointer()); else parentItem m_root; TreeItem* child parentItem-children[row]; return createIndex(row, column, child); } QModelIndex CustomTreeModel::parent(const QModelIndex child) const { if (!child.isValid()) return QModelIndex(); TreeItem* childItem static_castTreeItem*(child.internalPointer()); TreeItem* pItem childItem-parent; if(pItem m_root) return QModelIndex(); // 获取父节点在祖父节点中的行号 TreeItem* grandParent pItem-parent; int row grandParent-children.indexOf(pItem); return createIndex(row, 0, pItem); } QVariant CustomTreeModel::headerData(int section, Qt::Orientation orientation, int role) const { if(orientation Qt::Horizontal role Qt::DisplayRole section 0) { return 树节点; } return QAbstractItemModel::headerData(section, orientation, role); } TreeItem* CustomTreeModel::addChild(TreeItem *parentItem, const QString text) { if(!parentItem) parentItem m_root; int newRow parentItem-children.size(); beginInsertRows(indexOfItem(parentItem), newRow, newRow); TreeItem* newItem new TreeItem(text, parentItem); parentItem-children.append(newItem); endInsertRows(); return newItem; } void CustomTreeModel::clearTree() { beginResetModel(); qDeleteAll(m_root-children); m_root-children.clear(); endResetModel(); } // 辅助函数根据TreeItem获取QModelIndex QModelIndex CustomTreeModel::indexOfItem(TreeItem* item) const { if(item m_root) return QModelIndex(); TreeItem* p item-parent; int r p-children.indexOf(item); return createIndex(r,0,item); }TreeView 使用示例CustomTreeModel* treeModel new CustomTreeModel(this); ui-treeView-setModel(treeModel); treeModel-addChild(node1, QString::fromLocal8Bit(子节点1-1)); treeModel-addChild(node1, QString::fromLocal8Bit(子节点1-2)); TreeItem* node2 treeModel-addChild(nullptr, QString::fromLocal8Bit(一级节点2)); treeModel-addChild(node2, QString::fromLocal8Bit(子节点2-1)); TreeItem* node3 treeModel-addChild(nullptr, QString::fromLocal8Bit(一级节点3)); treeModel-addChild(node3, QString::fromLocal8Bit(子节点3-1)); treeModel-addChild(node3, QString::fromLocal8Bit(子节点3-2)); treeModel-addChild(node3, QString::fromLocal8Bit(子节点3-3)); treeModel-addChild(node3, QString::fromLocal8Bit(子节点3-4)); node4 treeModel-addChild(node3, QString::fromLocal8Bit(子节点3-5)); treeModel-addChild(node4, QString::fromLocal8Bit(子节点3-5-1)); treeModel-addChild(node4, QString::fromLocal8Bit(子节点3-5-2)); treeModel-addChild(node4, QString::fromLocal8Bit(子节点3-5-3)); treeModel-addChild(node4, QString::fromLocal8Bit(子节点3-5-4)); treeModel-addChild(nullptr, QString::fromLocal8Bit(一级节点4));重要注意事项列表 / 表格模型parent()永远返回空索引实现最简单树形模型createIndex(,,ptr)绑定节点禁止裸指针野指针模型析构统一释放所有 TreeItem增删行必须包裹 beginInsertRows /endInsertRows禁止直接操作容器大量数据刷新优先使用beginResetModel()/endResetModel()复选框必须同时配置flags()Qt::CheckStateRole否则不显示勾选框。
返回列表