本文属于《QTreeView使用系列教程》之一,欢迎查看其它文章。

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时,我们的代码:

QTreeView* view = new QTreeView();
QStandardItemModel* model = new QStandardItemModel();
for (int row = 0; row < 4; ++row) {QStandardItem *item = new QStandardItem(QString("%1").arg(row) );model->appendRow( item );
}
view->setModel(model);

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

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

缺点:

性能比较,可参考此文末尾的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的数据是从其他地方获取来的,所以我们先定义树中显示的原始数据结构,如下:

// person信息
typedef struct Person_t{QString name;   // 姓名QString sex;    // 性别int age;     // 年龄QString phone;  // 电话号码Person_t(){age = 0;}
} Person;// 省份信息
typedef struct Province_t{QString name;QVector<Person*> people;
} Province;

(2)定义TreeItem类

  • 提供建立树形结构的功能

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

void TreeItem::addChild(TreeItem *item)
{item->setRow(_children.size());_children.append(item);
}

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

void TreeItem::removeChildren()
{qDeleteAll(_children);_children.clear();
}

返回父节点

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一一对应。

void setPtr(void* p) { _ptr = p; }
void* ptr() const { return _ptr; }

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

enum Type
{UNKNOWN = -1,PROVINCE,PERSON
};
Type getType() const { return _type; }
void setType(const Type &value) { _type = value; }

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

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

(3)定义TreeModel类

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

QAbstractItemModel类中定义如下:

Q_INVOKABLE virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const = 0;
Q_INVOKABLE virtual QModelIndex parent(const QModelIndex &child) const = 0;
Q_INVOKABLE virtual QModelIndex sibling(int row, int column, const QModelIndex &idx) const;
Q_INVOKABLE virtual int rowCount(const QModelIndex &parent = QModelIndex()) const = 0;
Q_INVOKABLE virtual int columnCount(const QModelIndex &parent = QModelIndex()) const = 0;
Q_INVOKABLE virtual bool hasChildren(const QModelIndex &parent = QModelIndex()) const;
Q_INVOKABLE virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const = 0;
Q_INVOKABLE virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
Q_INVOKABLE virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;

其中共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表示是用于界面显示的数据。

QStringList headers;
headers << QString("名称/姓名")<< QString("性别")<< QString("年龄")<< QString("电话");
QVariant TreeModel::headerData(int section, Qt::Orientation orientation,int role) const
{if (orientation == Qt::Horizontal){if(role == Qt::DisplayRole){return _headers.at(section);}}return QVariant();
}
  • int rowCount(const QModelIndex &parent) const override;

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

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

int TreeModel::rowCount(const QModelIndex &parent) const
{if (parent.column() > 0)return 0;TreeItem* item = itemFromIndex(parent);return item->childCount();
}
  • int columnCount(const QModelIndex &parent) const override;

返回索引parent下有多少列

int TreeModel::columnCount(const QModelIndex &parent) const
{return _headers.size();
}
  • QModelIndex index(int row, int column, const QModelIndex &parent) const override;

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

QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const
{if (!hasIndex(row, column, parent))return QModelIndex();TreeItem *parentItem = itemFromIndex(parent);TreeItem *item = parentItem->child(row);if (item)return createIndex(row, column, item);elsereturn QModelIndex();
}
  • QVariant data(const QModelIndex &index, int role) const override;

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

QVariant TreeModel::data(const QModelIndex &index, int role) const
{if (!index.isValid())return QVariant();TreeItem *item = itemFromIndex(index);if (role == Qt::DisplayRole){return item->data(index.column());}return QVariant();
}
  • QModelIndex parent(const QModelIndex &index) const override;

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

QModelIndex TreeModel::parent(const QModelIndex &index) const
{if (!index.isValid())return QModelIndex();TreeItem *item = itemFromIndex(index);TreeItem *parentItem = item->parent();if (parentItem == _rootItem)return QModelIndex();return createIndex(parentItem->row(), 0, parentItem);
}

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

4、测试TreeModel

初始化原始数据:

QVector<Province*> MainWindow::initData()
{// 初始化数据,5个省,每个省5人QVector<Province*> proList;int provinceCount = 5;int personCount = 5;for(int i = 0; i < provinceCount; i++){Province* pro = new Province();pro->name = QString("Province%1").arg(i);for(int j = 0; j < personCount; j++){Person* per = new Person();per->name = QString("name%1").arg(j);per->sex = "man";per->age = 25;per->phone = "123456789";pro->people.append(per);}proList.append(pro);}return proList;
}

构建TreeItem对象树,设置model:

void MainWindow::setModel(const QVector<Province *> &proList)
{QStringList headers;headers << QString("名称/姓名")<< QString("性别")<< QString("年龄")<< QString("电话");TreeModel* model = new TreeModel(headers, treeView);TreeItem* root = model->root();foreach (auto pro, proList){TreeItem* province = new TreeItem(root);province->setPtr(pro); // 保存数据指针province->setType(TreeItem::PROVINCE); // 设置节点类型为PROVINCEroot->addChild(province);foreach (auto per, pro->people){TreeItem* person = new TreeItem(province);person->setPtr(per);    // 保存数据指针person->setType(TreeItem::PERSON);  // 设置节点类型为PERSONprovince->addChild(person);}}treeView->setModel(model);
}

再贴一遍运行结果:

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

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

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

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

model选择

QStandardItemModel

自定义model

开发难度

简单

稍高

显示大量数据

不建议

建议

显示固定少量数据

建议

不建议

需要更新数据

不建议

建议

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

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

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


若对你有帮助,欢迎点赞、收藏、评论,你的支持就是我的最大动力!!!

同时,阿超为大家准备了丰富的学习资料,欢迎关注公众号“超哥学编程”,即可领取。

本文涉及工程代码,公众号回复:34CustomModel,即可下载。

《QTreeView+QAbstractItemModel自定义模型》:系列教程之三相关推荐

  1. Dojo mobile TweetView 系列教程之三——Tweets和Mentions视图

    Dojo mobile TweetView 系列教程之三--Tweets和Mentions视图 分类: Javascript Dojo扩展 (dojox)2011-05-18 19:13 2211人阅 ...

  2. dedecms二次开发:自定义模型使用教程

    dedecms二次开发:自定义模型使用教程 在织梦系统中有内容模型这个概念,不同内容模型可以用来构建不同内容形式的站点,在系统中自带了以下几种模型:普通文章.图集.软件.商品.分类信息.专题.通过系统 ...

  3. RabbitMQ系列教程之三:发布\/订阅(Publish\/Subscribe)

    在前一个教程中,我们创建了一个工作队列.工作队列背后的假设是每个任务会被交付给一个[工人].在这一部分我们将做一些完全不同的事情--我们将向多个[消费者]传递信息.这种模式被称为"发布/订阅 ...

  4. 乐鑫代理启明云端分享|ESP32系列教程之三: VS Code远程连接Linux

    提示:本文档为ESP32教程系列,旨在为客户进行ESP32系列芯片开发提供环境搭建.工程示例演示等方面的参考文档及视频演示,降低ESP32系列芯片.模组开发的入门难度. ESP32教程系列文档主要参考 ...

  5. 【Docker系列教程之三】Docker容器是如何工作的

    在上一篇的文章中,我给大家主要介绍了一下 Docker 环境的搭建,简单的讲解了一下 Docker 架构,以及用 Docker 命令简单演示了一下如何拉去一个 images 镜像.本篇我们将剖析一下  ...

  6. 王姨劝我学HarmonyOS鸿蒙2.0系列教程之三Ability概述调用方法!

    原创PDF |<Android 深入系统完全讲解>免费开源,可能价值百万! 王姨劝我学HarmonyOS鸿蒙2.0系列教程之一环境搭建&&跑起来模拟器! 王姨劝我学Harm ...

  7. SAP系统和微信集成的系列教程之三:微信用户关注公众号之后,自动在SAP C4C系统创建客户主数据

    这是Jerry 2020年的第84篇文章,也是汪子熙公众号总共第266篇原创文章. 本系列的英文版Jerry写作于2017年,这个教程总共包含十篇文章,发表在SAP社区上. 系列目录 (1) 微信开发 ...

  8. 公众号向特定用户主动推送消息_SAP系统和微信集成的系列教程之三:微信用户关注公众号之后,自动在SAP C4C系统创建客户主数据...

    这是Jerry 2020年的第84篇文章,也是汪子熙公众号总共第266篇原创文章. 本系列的英文版Jerry写作于2017年,这个教程总共包含十篇文章,发表在SAP社区上: https://blogs ...

  9. linux虚拟主机管理系统wdcp系列教程之三

    我们安装了网站服务管理系统wdcp之后,在使用过程中可能会出现这样或那样的疑问,下面给大家整理几点出来,方便大家学习.还有不懂的可以到wdlinux论坛寻找相关教程. 1.wdcp后台访问安全设置即限 ...

  10. Exchange server 2010系列教程之三 发送邮件测试

    最近有些忙,好几天没有上来写教程了,接着往下写吧.就当是自己的学习笔记,呵呵,有不到之处,还请大家多多指教. 上一篇我们已经把服务器架设好了,那么我们来测试一下发送邮件. 1.首先在AD DC上面新建 ...

最新文章

  1. 科技发展给保险行业带来了什么改变?
  2. ubuntu下python+tornado+supervisor+nginx部署
  3. AutoMySQLBackup 3.0 Bug:du: WARNING: use --si, not -H
  4. Windoes 10 笔记本上安装telnet方法
  5. JVM 类加载机制:编译器常量与初始化
  6. 编写XML作为配置文件的高级操作库
  7. 把原来的所有Blog全部转移过来了:-P
  8. 即时通讯音视频开发(五):认识主流视频编码技术H.264
  9. STM32 之九 HAL 库串口(USART/UART)驱动 BUG 及解决方法
  10. STM32 F101系列的程序怎么转化为STM F103出现编译错误!
  11. python socket poll
  12. MongoDB配置主从同步(二)
  13. angularjs金额大写过滤器
  14. yolo如何降低loss_YOLO训练中的问题与怀疑
  15. java word转pdf 在linux转pdf乱码解决方法
  16. 武汉坚守第三十二天——鱼菜价格已超高,病死猪肉现武汉
  17. Java Swing制作古老的打砖块游戏
  18. set name utd8_ml utd 8机器学习数据的最新生命
  19. 搭建红外遥控arm-hadoop集群过程
  20. [多目标优化算法]1.NSGA-II——非支配排序遗传算法

热门文章

  1. android expandablelistview横向,ExPandableListView实现时间轴效果【Android】
  2. 计算机维修不是事儿光盘视频,硬盘维修及数据恢复不是事儿配套光盘视频教程...
  3. c++ 读取二进制bin文件
  4. Qt自带mingw使用
  5. ue怎么转换html格式,UE编辑器UltraEdit怎么格式化代码
  6. 移动网络安装测试软件,adsl网速测试(中国移动宽带专用测速软件)
  7. SSI接口 AC97
  8. 计算机网络线路故障及排查方法,计算机网络常见故障排查
  9. 【Visio】 Visio的安装
  10. 测试用例设计方法有哪些?