背景
近来在做Qt界面设计相关的项目,由于主窗体的框架是用QtQuick搭建的,所以必须从传统的C++开发转向JS风格的QML开发,QML我理解他的话就是一个JavaScript风格的描述性说明语言,至于QtQuick则相当于Qt官方为qml开发提供的一个通用强大的组件库吧,QML与QtQuick的关系相当于C++与STL吧。扯远了,说白了就是在项目的开发过程中,需要构建一个提供导航功能的流程树,其所有节点默认是展开的,点击打开其子节点对应的界面。
树控件TreeView是在Qt5.5版本以后才有的,其功能也十分强大,可直接代替之前ListView等控件对于树结构的不灵活构建,可使用MVC数据与视图分离的方式灵活配置树结构和数据。
点击三角符号才会展开,或者实现以下代码,通过单击或双击父节点展开子项,但也需要一项一项的展开,如下代码:
property bool isExpand: true
onClicked: {
if(isExpand) {
emit: myTree.expand(index);
isExpand = false;
}
else {
emit: myTree.collapse(index);
isExpand = true;
}
}
问题
不像QTreeView,它有expandAll()的方法,可在加载界面和数据后直接展开全部节点。qml的TreeView控件的帮助文档中只提供了单个节点的展开和收缩方法如下代码:
// 参数为单个节点的index
void collapse(QModelIndex index);
//Collapses the model item specified by the index.
//See also collapsed and isExpanded.
void expand(QModelIndex index);
//Expands the model item specified by the index.
//See also expanded and isExpanded.
并且在网上找了各种文章,也没发现Qt提供了展开所有包含子项的树节点,甚至在一些贴吧里还发现了提出此问题的同行,至今没发现解决办法。由于项目的需求,这个问题是我必须迈过的坎,通过大概一周的尝试和写demo,最终以自己的方式解决,虽然此方法看起来不是很聪明的样子,但是也完全解决了此问题,希望能给遇到此问题的同行提供一些思路。
思路
我们再来拆解下问题,项目加载TreeView时实现默认(自动)展开所有节点(包含子项的节点),而qml TreeView中只提供了展开单个树节点的方法,那么这问题可转化为,找到树上所有包含子项的节点的QModelIndex然后依次调用void expand(QModelIndex index);方法即可。此时的难点就变成了遍历此树,找出包含子项的节点的index。
由于TreeView采用了MVC的方式构建,树的数据是在C++中的TreeModel类中实现的,因此解决此问题必须的使用qml和c++混合编程,这时我们的思路就是:项目加载完qml的view和model后,qml调用c++的TreeModel类的方法,此方法中遍历树数据结构,找到包含子项的节点,则发射TreeModel定义的信号,信号中包含此节点的QModelIndex,qml的tree控件响应此信号,调用展开方法。遍历结束,则全部节点展开完成。流程图如下图:
实现了,转了一个圈(qml --> c++ --> qml)啊,是不是有点笨。
实现
此篇文章是在读者已经掌握了实现MCV框架的qml TreeView控件的所有知识的基础上阅读的,如果对qml和qml TreeView不熟,建议先恶补一下基础知识,此处推荐一个大佬的博客链接,也是我学习qml从头看完的帖子,
插一句,Qt自带的帮助文档真的特别特别方便和全面,建议从事Qt开发的同行,时时刻刻都应该打开帮助。
扯远了扯远了,我们赶快进入正题吧!
1. TreeView加载完毕调用C++ TreeModel类的槽函数
采用qml的方法如下代码:
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQml.Models 2.2 // 使用TreeView
import my.qt.TreeModel 1.0 // 注册到元对象中的C++类
Window {
// 属性 略
TreeModel {
id: treeModel; // C++类
}
TreeView {
// 略
}
// qml加载完毕 默认执行此段
Component.onCompleted: {
treeModel.handleTreeExpand(); // C++槽函数
}
}
2. C++ TreeModel类槽函数handleTreeExpand的实现
TreeModel的槽函数handleTreeExpand主要实现,遍历树的所以节点,找出包含子项的节点,发射TreeModel中定义的信号void expandTreeNode(QModelIndex index); 这部分的代码如下:
TreeModel.h
#include <QAbstractItemModel>
#include <QModelIndex>
#include <QVariant>
#include "TreeItem.h" // 用来保存树节点的数据结构类,不明白请参考MVC的实现
class TreeItem;
class TreeModel : public QAbstractItemModel
{
Q_OBJECT
public:
// ...
signals:
// 发射展开信号和要展开的节点index
void expandTreeNode(QModelIndex index);
public:
// C++处理展开槽函数 遍历树结构 找到需展开的节点
void handleTreeExpand();
private:
// 处理父节点下的子节点 递归 知道没有子节点返回
void handleChildItemExpand(TreeItem* parent);
TreeItem* m_rootItem; // 保存树结构的根节点
}
TreeModel.cpp
#include "TreeModel.h"
// ...
// 槽函数实现
void TreeModel::handleTreeExpand()
{
// 根节点下一级子节点的个数
int count = m_rootItem->childCount();
for(int i = 0; i < count; ++i)
{
// 依次遍历一级子节点
auto childItem = m_rootItem->child(i);
for(0 < chileItem->childCount())
{
// 包含子项的节点 展开
emit expandTreeNode(createIndex(i, 0, childItem));
// 处理 此一级节点的子节点
handleChildItemExpand(childItem);
}
}
}
void TreeModel::handleChildItemExpand()
{
int count = parent->childCount();
for(int i = 0; i < count; ++i)
{
for(0 < chileItem->childCount())
{
// 包含子项的节点 展开
emit expandTreeNode(createIndex(i, 0, childItem));
// 递归处理 此级节点的子节点
handleChildItemExpand(childItem);
}
}
}
3. qml中连接C++的信号expandTreeNode实现节点展开
直接上代码实现吧
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQml.Models 2.2 // 使用TreeView
import my.qt.TreeModel 1.0 // 注册到元对象中的C++类
Window {
// 属性 略
MyTreeModel {
id: treeModel; // C++类
}
TreeView {
id: myTree;
// 略
}
// qml加载完毕 默认执行此段
Component.onCompleted: {
treeModel.handleTreeExpand(); // C++槽函数
}
// 连接C++中定义的信号
Connections {
target: treeModel;
onExpandTreeNode: { // 注意qml中对于信号的写法
emit: myTree.expand(index);
}
}
}
看到这里,肯定会有童鞋问了:怎样把C++类注册到qml元对象中?qml中可以访问C++类的哪些属性?害,知识就在那,学不学看你。
写在最后
如果我说这个问题的思路,方法和实现已经全部讲完,可能某些童鞋会一脸懵吧,甚至会说:这算什么文章嘛? 代码断断续续,这省那略的,完全看不懂;全篇一个实现效果图都没有,运行结果也没有,确定解决了吗?只看到了啰里啰唆的废话了。
这是我写完后看了这篇文章的感受,代替一些童鞋说了出来,如果有同感的,欢迎点评。
还是得解释一下滴,这个问题是我在公司项目中遇到的,测试代码和demo都是在内网工作机上实现的,并且项目这部分工作提交了后,我才有功夫写这篇文章的,所以这个方法是完全能解决这个问题的。当然还是得在QML与C++混合编程,并且采用了MCV的框架的背景下才行。如果有实现方面的问题欢迎随时评论交流。