您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center 汽车系统工程   模型库  
会员   
   
嵌入式软件测试方法&实践
3月20日 线上
需求分析与管理
4月21-22日 北京+线上
SysML和EA进行系统设计建模
4月23-24日 北京+线上
     
   
 订阅
《QTreeView+QAbstractItemModel自定义模型》:系列教程之三
 
作者:百里杨
  263   次浏览      8 次
 2026-1-21
 
编辑推荐:
本文关于Qt中Model/View编程的详细教程,主要介绍了QStandardItemModel和QAbstractItemModel的使用方法, 希望能为大家提供一些参考或帮助。
文章来自于CSDN,由火龙果Alice编辑推荐。

1、了解常用的model类

通过对上一节的阅读,我们知道只要具备model+view就可以显示数据。

那么有哪些model类呢,从下图中我们可以看到

Qt中模型类的层次结构

QStandardItemModel:可以作为QListView、QTableView、QTreeView的标准model。

QAbstractListModel:需要使用QListView显示数据,并配合自定义model时,我们从此类继承。

QAbstractTableModel:需要使用QTableView显示数据时,并配合自定义model时,我们从此类继承。

QAbstractItemModel:需要使用QTreeView显示数据时,并配合自定义model时,我们从此类继承。

此处我们只关注可以用作QTreeView之model的类QAbstractItemModel与QStandardItemModel。

2、QStandardItemModel的使用

首先我们来看看如果用QStandardItemModel作为model时,我们的代码:

  1. QTreeView* view = new QTreeView();
  2. QStandardItemModel* model = new QStandardItemModel();
  3. for (int row = 0; row < 4; ++row) {
  4. QStandardItem *item = new QStandardItem(QString("%1").arg(row) );
  5. model->appendRow( item );
  6. }
  7. view->setModel(model);
运行结果:
代码缺少必要的头文件包含和主函数,无法独立运行。此代码片段依赖于Qt框架,需包含等头文件,并在main函数或适当的事件循环中执行。

用法比较简单,QStandardItemModel可以使用QStandardItem,通过不断添加子节点,从而构建出list、table、tree结构的数据。

使用QStandardItemModel表示数据集具有以下优点:

  • 实现代码简单
  • 该类使用QStandardItem存放数据项,用户不必定义任何数据结构来存放数据项;
  • QStandardItem使用自关联关系,能够表达列表、表格、树甚至更复杂的数据结构,能够涵盖各种各样的数据集;
  • QStandardItem本身存放着多个『角色,数据子项』,视图类、委托类或者其他用户定义的类能够方便地依据角色访问各个数据子项。

缺点:

  • 当数据集中的数据项很多时,施加在数据集上的某些操作的执行效率会很低。
  • 数据太大时,占用内存巨大,性能低下

性能比较,可参考此文末尾的demo代码:https://blog.csdn.net/dpsying/article/details/80456263

3、QAbstractItemModel自定义model

(1)原理知识铺垫

我们以实现如下树形显示为例,进行自定义model。

我们要将数据显示到QTreeView中,按照Model/View框架介绍,需要定义2个类TreeModel和TreeItem,TreeModel继承于QAbstractItemModel,用于向View提供数据;

TreeItem用于定义我们的数据节点,然后被model获取数据。

QTreeView与TreeItem交互过程大致如下:

注意:在树中,我们一般默认认为,只有column为0的单元格才能添加下级单元格,也就是说树中的每一行单元格只能与Column为0的单元格建立父子关系。

所以我们可以简单的认为树,就是一行一行单元格组成的表格,只不过在每一行通过其首个单元格,建立了父子关系。

此处我们的一个TreeItem代表一行若干单元格,我们需要将多个TreeItem建立父子关系,就能够正确表示出树显示所需的数据结构。

而TreeItem的数据是从其他地方获取来的,所以我们先定义树中显示的原始数据结构,如下:

  1. // person信息
  2. typedef struct Person_t{
  3. QString name; // 姓名
  4. QString sex; // 性别
  5. int age; // 年龄
  6. QString phone; // 电话号码
  7. Person_t()
  8. {
  9. age = 0;
  10. }
  11. } Person;
  12. // 省份信息
  13. typedef struct Province_t{
  14. QString name;
  15. QVector<Person*> people;
  16. } Province;
运行结果:
代码无法成功运行,因为使用了Qt相关的类型(如QString、QVector),但未包含相应的头文件(如),导致编译错误。

(2)定义TreeItem类

提供建立树形结构的功能

通过addChild可以添加TreeItem子节点,并保存该子节点在父节点的序号。

  1. void TreeItem::addChild(TreeItem *item)
  2. {
  3. item->setRow(_children.size());
  4. _children.append(item);
  5. }
运行结果:
代码不完整,缺少类定义和相关上下文,无法独立运行

另外提供释放子节点内存,用于删除根节点时,自动逐级释放所有TreeItem内存。

  1. void TreeItem::removeChildren()
  2. {
  3. qDeleteAll(_children);
  4. _children.clear();
  5. }
运行结果:
代码不完整,缺少类定义和相关上下文,无法独立编译运行

返回父节点

TreeItem *parent() { return _parent; }
运行结果:
代码不完整,缺少必要的类和变量定义,无法独立运行

返回子节点数量

int childCount() const { return _children.count(); }
运行结果:
代码不完整,缺少类定义和相关上下文,无法独立运行

提供获取列数据,以及获取TreeItem子节点功能

既然TreeItem代表的是一行数据,那么必定需要提供获取某列数据函数。

QVariant data(int column) const;
运行结果:
代码不完整,缺少类定义和实现,无法单独运行

也必定需要提供获取TreeItem下某子节点函数(某一行)。

TreeItem *child(int row) { return _children.value(row); }
运行结果:
代码不完整,缺少必要的类和变量定义,无法独立运行。

关键:提供设置数据源地址功能

保存数据源地址,以便TreeItem可以访问原始数据;通常情况下,原始数据与TreeItem一一对应。

  1. void setPtr(void* p) { _ptr = p; }
  2. void* ptr() const { return _ptr; }
运行结果:
提供的代码片段是C++中类的纯虚函数声明,缺少具体的实现和完整的类定义,无法独立编译和运行。

由于建立TreeItem对象树时,Province和Person地址会被setPtr()保存到TreeItem上,所以为了便于按类型取数据,在setPtr()时需要setType()保存数据属于哪种类型。

  1. enum Type
  2. {
  3. UNKNOWN = -1,
  4. PROVINCE,
  5. PERSON
  6. };
  7. Type getType() const { return _type; }
  8. void setType(const Type &value) { _type = value; }
运行结果:
代码不完整,缺少类或结构体的定义上下文,且存在语法错误(例如:'const' 修饰符不能用于非成员函数的参数列表中的 'getType()')

到此,我们可以建立TreeItem树,并获取任意行、列的数据。已经满足了TreeModel获取任意数据的要求。

下一步,我们来定义TreeModel类。

(3)定义TreeModel类

我们需要继承自QAbstractItemModel,让我们来看看它有哪些接口。

QAbstractItemModel类中定义如下:

  1. Q_INVOKABLE virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const = 0;
  2. Q_INVOKABLE virtual QModelIndex parent(const QModelIndex &child) const = 0;
  3. Q_INVOKABLE virtual QModelIndex sibling(int row, int column, const QModelIndex &idx) const;
  4. Q_INVOKABLE virtual int rowCount(const QModelIndex &parent = QModelIndex()) const = 0;
  5. Q_INVOKABLE virtual int columnCount(const QModelIndex &parent = QModelIndex()) const = 0;
  6. Q_INVOKABLE virtual bool hasChildren(const QModelIndex &parent = QModelIndex()) const;
  7. Q_INVOKABLE virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const = 0;
  8. Q_INVOKABLE virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
  9. Q_INVOKABLE virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
运行结果:
提供的代码片段是C++中类的纯虚函数声明,缺少具体的实现和完整的类定义,无法独立编译和运行。

其中共5个纯虚函数,index()、parent()、rowCount()、columnCount()和data(),这是我们必须要实现的;另外一般我们还是需要显示表头的,所以还需要实现headerData()。QTreeView显示树时,会自动调用TreeModel,来获取显示一个树所需要的一些信息;我们重写这些函数的目的就是为了向QTreeView提供这些信息的。

接下来我们解释下重写各个函数的作用。

QVariant headerData(int section, Qt::Orientation orientation, int role) const override;

表示获取表头数据,第section列;orientation方向,一般为水平方向;DisplayRole角色的表头数据,DisplayRole表示是用于界面显示的数据。

  1. QStringList headers;
  2. headers << QString("名称/姓名")
  3. << QString("性别")
  4. << QString("年龄")
  5. << QString("电话");
运行结果:
代码缺少包含QString和QStringList的头文件,且未在有效的C++程序上下文中定义main函数或其他执行入口,因此无法成功运行。

  1. QVariant TreeModel::headerData(int section, Qt::Orientation orientation,int role) const
  2. {
  3. if (orientation == Qt::Horizontal)
  4. {
  5. if(role == Qt::DisplayRole)
  6. {
  7. return _headers.at(section);
  8. }
  9. }
  10. return QVariant();
  11. }
运行结果:
当前代码可运行,无具体输出文案

int rowCount(const QModelIndex &parent) const override;

获取索引parent下有多少行。View会遍历每个单元格索引,若不是第一列单元格索引,则不会有子节点,所以直接返回行数为0;

若是第一列单元格索引,那么该单元格是否为空(空表示根节点),则需要返回根节点下行数,反之则返回parent下行数。

  1. int TreeModel::rowCount(const QModelIndex &parent) const
  2. {
  3. if (parent.column() > 0)
  4. return 0;
  5. TreeItem* item = itemFromIndex(parent);
  6. return item->childCount();
  7. }
运行结果:
代码不完整,缺少必要的类定义和上下文(如TreeModel、QModelIndex、TreeItem等),无法独立编译运行。

int columnCount(const QModelIndex &parent) const override;

返回索引parent下有多少列

  1. int TreeModel::columnCount(const QModelIndex &parent) const
  2. {
  3. return _headers.size();
  4. }
运行结果:
代码片段不完整,缺少必要的上下文,如类定义、成员变量声明(_headers)、头文件包含等,无法单独编译运行

QModelIndex index(int row, int column, const QModelIndex &parent) const override;

在parent节点下,第row行,第column列位置上创建索引;将TreeItem指针保存至该索引。

  1. QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const
  2. {
  3. if (!hasIndex(row, column, parent))
  4. return QModelIndex();
  5. TreeItem *parentItem = itemFromIndex(parent);
  6. TreeItem *item = parentItem->child(row);
  7. if (item)
  8. return createIndex(row, column, item);
  9. else
  10. return QModelIndex();
  11. }
运行结果:
代码不完整,缺少必要的类和函数定义,无法独立运行。

QVariant data(const QModelIndex &index, int role) const override;

获取index.row行,index.column列数据;通过itemFromIndex()获取保存在索引index中的TreeItem指针。

  1. QVariant TreeModel::data(const QModelIndex &index, int role) const
  2. {
  3. if (!index.isValid())
  4. return QVariant();
  5. TreeItem *item = itemFromIndex(index);
  6. if (role == Qt::DisplayRole)
  7. {
  8. return item->data(index.column());
  9. }
  10. return QVariant();
  11. }
运行结果:
代码片段是一个C++成员函数定义,缺少类定义、头文件包含、命名空间声明等必要上下文,无法独立编译运行;且未提供QVariant、TreeModel、QModelIndex、Qt等类型的定义或引入,编译器将报错。

QModelIndex parent(const QModelIndex &index) const override;

创建index的父索引,若父节点为根节点,则返回QModelIndex(),默认根节点索引为空。

  1. QModelIndex TreeModel::parent(const QModelIndex &index) const
  2. {
  3. if (!index.isValid())
  4. return QModelIndex();
  5. TreeItem *item = itemFromIndex(index);
  6. TreeItem *parentItem = item->parent();
  7. if (parentItem == _rootItem)
  8. return QModelIndex();
  9. return createIndex(parentItem->row(), 0, parentItem);
  10. }
运行结果:
TreeModel类一般不需要怎么修改,都大同小异,实际使用时,根据需要微调就可以。

TreeModel类一般不需要怎么修改,都大同小异,实际使用时,根据需要微调就可以。

4、测试TreeModel

初始化原始数据:

  1. QVector<Province*> MainWindow::initData()
  2. {
  3. // 初始化数据,5个省,每个省5人
  4. QVector<Province*> proList;
  5. int provinceCount = 5;
  6. int personCount = 5;
  7. for(int i = 0; i < provinceCount; i++)
  8. {
  9. Province* pro = new Province();
  10. pro->name = QString("Province%1").arg(i);
  11. for(int j = 0; j < personCount; j++)
  12. {
  13. Person* per = new Person();
  14. per->name = QString("name%1").arg(j);
  15. per->sex = "man";
  16. per->age = 25;
  17. per->phone = "123456789";
  18. pro->people.append(per);
  19. }
  20. proList.append(pro);
  21. }
  22. return proList;
  23. }
运行结果:
代码缺少必要的头文件包含,如QVector、Province和Person的定义未提供,无法确定其类型和成员结构,导致编译失败。

构建TreeItem对象树,设置model:

  1. void MainWindow::setModel(const QVector<Province *> &proList)
  2. {
  3. QStringList headers;
  4. headers << QString("名称/姓名")
  5. << QString("性别")
  6. << QString("年龄")
  7. << QString("电话");
  8. TreeModel* model = new TreeModel(headers, treeView);
  9. TreeItem* root = model->root();
  10. foreach (auto pro, proList)
  11. {
  12. TreeItem* province = new TreeItem(root);
  13. province->setPtr(pro); // 保存数据指针
  14. province->setType(TreeItem::PROVINCE); // 设置节点类型为PROVINCE
  15. root->addChild(province);
  16. foreach (auto per, pro->people)
  17. {
  18. TreeItem* person = new TreeItem(province);
  19. person->setPtr(per); // 保存数据指针
  20. person->setType(TreeItem::PERSON); // 设置节点类型为PERSON
  21. province->addChild(person);
  22. }
  23. }
  24. treeView->setModel(model);
  25. }
运行结果:
代码存在逻辑错误:在添加子节点后又调用root->addChild(province),会导致重复添加或树结构混乱。正确的做法是在创建province后直接由root addChild,但此时province尚未构建完成,且后续又通过province addChild(person),顺序和逻辑矛盾。此外,TreeModel构造时传入treeView可能造成所有权或内存管理问题。整体设计存在缺陷,无法正确运行。

运行结果:

代码不完整,缺少必要的类定义和上下文(如MainWindow、TreeModel、TreeItem、Province等),无法独立编译运行。

再贴一遍运行结果:

5、QStandardItemModel与自定义model如何选择

在一个项目中开了很多线程,此时QTreeView+QStandardItemModel更新任务信息,在更新QTreeView中一行共7列数据,也就是7个单元格数据,居然花了40ms。。。

似乎QStandardItemModel效率欠佳,当然也可能是系统压力较大的原因。

自己大概整理了下这2种model在不同情况下的使用建议:

model选择

QStandardItemModel

自定义model

开发难度

简单

稍高

显示大量数据

不建议

建议

显示固定少量数据

建议

不建议

需要更新数据

不建议

建议

对于数据量小且不需要更新的场景,我们使用QStandardItemModel来实现比较简单,没有自定义model那么多代码逻辑。

在数据量小,但是需要更新情况下,我们采用自定义model来实现,即使数据量小,更新数据其实也是比较慢的,它会占用较多UI线程时间,如果其他线程业务繁重,就会影响UI线程性能,导致界面卡顿。

在数据量大情况下,无论更新与否,我们都采用自定义model来实现。

原文链接:https://blog.csdn.net/zyhse/article/details/105894431

本文涉及工程代码, 下载链接

   
263   次浏览       8 次
相关文章

深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
相关文档

重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
相关课程

基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程

最新活动计划
嵌入式软件测试方法&实践 3-20[在线]
MBSE理论方法到工作实践 3-28[北京]
需求分析与管理 4-21[在线]
基于LLM的Agent应用开发 4-18[北京]
SysML和EA系统设计建模 4-23[北京]
基于本体的体系架构设计 4-24[北京]
认证课:OCSMP-MU 周末班[在线]
 
 
最新文章
.NET Core 3.0 正式公布:新特性详细解读
.NET Core部署中你不了解的框架依赖与独立部署
C# event线程安全
简析 .NET Core 构成体系
C#技术漫谈之垃圾回收机制(GC)
最新课程
.Net应用开发
C#高级开发技术
.NET 架构设计与调试优化
ASP.NET Core Web 开发
ASP.Net MVC框架原理与应用开发
成功案例
航天科工集团子公司 DotNet企业级应用设计与开发
日照港集 .NET Framewor
神华信 .NET单元测试
台达电子 .NET程序设计与开发
神华信息 .NET单元测试