大家好,又见面了,我是你们的朋友全栈君。
本文属于《QTreeView使用系列教程》之一,欢迎查看其它文章。
1、了解常用的model类
通过对上一节的阅读,我们知道只要具备model+view就可以显示数据。
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表示数据集具有以下优点:
- 实现代码简单
- 该类使用QStandardItem存放数据项,用户不必定义任何数据结构来存放数据项;
- QStandardItem使用自关联关系,能够表达列表、表格、树甚至更复杂的数据结构,能够涵盖各种各样的数据集;
- QStandardItem本身存放着多个『角色,数据子项』,视图类、委托类或者其他用户定义的类能够方便地依据角色访问各个数据子项。
性能比较,可参考此文末尾的demo代码:https://blog.csdn.net/dpsying/article/details/80456263
3、QAbstractItemModel自定义model
(1)原理知识铺垫
我们要将数据显示到QTreeView中,按照Model/View框架介绍,需要定义2个类TreeModel和TreeItem,TreeModel继承于QAbstractItemModel,用于向View提供数据;
TreeItem用于定义我们的数据节点,然后被model获取数据。
所以我们可以简单的认为树,就是一行一行单元格组成的表格,只不过在每一行通过其首个单元格,建立了父子关系。
此处我们的一个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代表的是一行数据,那么必定需要提供获取某列数据函数。
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获取任意数据的要求。
(3)定义TreeModel类
我们需要继承自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提供这些信息的。
表示获取表头数据,第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();
}
获取索引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 TreeModel::columnCount(const QModelIndex &parent) const
{
return _headers.size();
}
在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);
else
return QModelIndex();
}
获取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();
}
创建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;
}
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); // 设置节点类型为PROVINCE
root->addChild(province);
foreach (auto per, pro->people)
{
TreeItem* person = new TreeItem(province);
person->setPtr(per); // 保存数据指针
person->setType(TreeItem::PERSON); // 设置节点类型为PERSON
province->addChild(person);
}
}
treeView->setModel(model);
}
5、QStandardItemModel与自定义model如何选择
在一个项目中开了很多线程,此时QTreeView+QStandardItemModel更新任务信息,在更新QTreeView中一行共7列数据,也就是7个单元格数据,居然花了40ms。。。
似乎QStandardItemModel效率欠佳,当然也可能是系统压力较大的原因。
model选择 |
QStandardItemModel |
自定义model |
开发难度 |
简单 |
稍高 |
显示大量数据 |
不建议 |
建议 |
显示固定少量数据 |
建议 |
不建议 |
需要更新数据 |
不建议 |
建议 |
对于数据量小且不需要更新的场景,我们使用QStandardItemModel来实现比较简单,没有自定义model那么多代码逻辑。
在数据量小,但是需要更新情况下,我们采用自定义model来实现,即使数据量小,更新数据其实也是比较慢的,它会占用较多UI线程时间,如果其他线程业务繁重,就会影响UI线程性能,导致界面卡顿。
在数据量大情况下,无论更新与否,我们都采用自定义model来实现。
若对你有帮助,欢迎点赞、收藏、评论,你的支持就是我的最大动力!!!
同时,阿超为大家准备了丰富的学习资料,欢迎关注公众号“超哥学编程”,即可领取。
本文涉及工程代码,公众号回复:34CustomModel,即可下载。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/130486.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...