QML树控件TreeView的使用(上)
在Qt5.5之前是没有树控件的,我们在使用时用的是ListView来构造出一个树,Qt5.5之后的QML开发阶段,有了树控件TreeView,本篇着重记录QML的TreeView的使用。根据MVC分解文件(类)如下:
TreeController.h TreeController.cpp
TreeModel.h TreeModel.cpp
TreeItem.c TreeItem.cpp
各个类职能划分如下:
TreeController主要负责用户的操作,例如加载qml文件,用户主动加载数据,主动删除数据,主动插入数据等。TreeModel主要存放数据(TreeItem为TreeModel的成员,也是存放数据的)。QML的TreeView主要显示数据。
重点在于QML的TreeView与TreeModel如何交互将数据正确显示出来。
TreeModel.h头文件如下:
TreeModel.cpp源文件如下:
TreeItem.cpp源文件如下:
通过以上源文件可以看出,初始化的测试数据在TreeModel类的构造中传入。通过roleNames()函数增加QAbstractItemModel的角色,使得在QML中可以使用字段name和simplify,当QML加载读取数据时,通过枚举的角色NAME和SIMPLIFY返回对应的数据,即为要显示的数据,这样,就可以将C++的数据显示到QML的TreeView中了,以上其他的一些成员函数,都是一些获取数据个数的基本函数。
加载TreeView的QML文件如下TreeViewTest.qml:
clicked(QModelIndex index)中的应用,运行起来可以查看打的isExpanded:的值。值得注意的是,当点击根节点的三角进行展开收缩时,用isExpanded(QModelIndex index)函数获取的返回值跟实际的展开状态是反的,不点击三角进行收缩展开是正常的(由于在点击三角符号时,调用了一次原生的展开或收缩,然后又在clicked中调用了TreeView的expand(index);函数或发送emit: view.expand(index);信号,这会产生二次展开或收缩,负负得正,所以导致调用isExpanded(QModelIndex index)获取的值不理想,,,这可以算是QML的一个Bug吧),因此本例中用了成员变量property bool isCollapse: true进行变换以完成想要的功能。
加载QML界面的TreeController.cpp主要加载与数据暴露如下:
TreeController.h TreeController.cpp
TreeModel.h TreeModel.cpp
TreeItem.c TreeItem.cpp
各个类职能划分如下:
TreeController主要负责用户的操作,例如加载qml文件,用户主动加载数据,主动删除数据,主动插入数据等。TreeModel主要存放数据(TreeItem为TreeModel的成员,也是存放数据的)。QML的TreeView主要显示数据。
重点在于QML的TreeView与TreeModel如何交互将数据正确显示出来。
TreeModel.h头文件如下:
class TreeModel : public QAbstractItemModel { Q_OBJECT enum ItemRoles { NAME = Qt::UserRole + 1, SIMPLIFY }; public: TreeModel(QObject *parent = NULL); ~TreeModel(); void appendChild(const QModelIndex& index); bool removeRows(int row, int count, QModelIndex parent); QModelIndex parent(const QModelIndex &index) const; Qt::ItemFlags flags(const QModelIndex &index) const; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; int rowCount(const QModelIndex &parent = QModelIndex()) const; int columnCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role) const; QHash<int, QByteArray> roleNames() const; private: TreeItem *m_rootItem; };TreeItem.h头文件如下:
#ifndef TREEITEM_H #define TREEITEM_H #include <QList> #include <QVariant> #include <QStringList> #include <QModelIndex> class TreeItem { public: TreeItem::TreeItem(); TreeItem(const QList<QVariant> &data, TreeItem* parent); ~TreeItem(); void appendChild(TreeItem *child); void deleteAllChild(); TreeItem *child(int row); int childCount() const; int columnCount() const; QVariant data(int column) const; int row() const; TreeItem *parent(); void setParent(TreeItem *parent); private: TreeItem *m_parentItem; QList<TreeItem*> m_childItems; QList<QVariant> m_itemData; }; #endif可以看出TreeModel是继承自QAbstractItemModel,其中QVariant data(const QModelIndex &index, int role) const;成员函数和QHash<int, QByteArray> roleNames() const;成员函数比较关键,是QML读取C++数据的关键函数。
TreeModel.cpp源文件如下:
TreeModel::TreeModel(QObject *parent) : QAbstractItemModel(parent), m_rootItem(NULL) { m_rootItem = new TreeItem; QList<QVariant> list; list.append("ZhongGuo"); list.append("ZhG"); auto item = new TreeItem(list, m_rootItem); m_rootItem->appendChild(item); QList<QVariant> BJ_List; BJ_List.append("BeiJing"); BJ_List.append("BJ"); auto BJ_Item = new TreeItem(BJ_List, item); item->appendChild(BJ_Item); QList<QVariant> ShX_List; ShX_List.append("ShannXi"); ShX_List.append("ShX"); QList<QVariant> XiAn_List; XiAn_List.append("XiAn"); XiAn_List.append("XA"); QList<QVariant> XiAn_GaoXin_List; XiAn_GaoXin_List.append("GaoXin"); XiAn_GaoXin_List.append("XA_GaoXin"); auto ShX_Item = new TreeItem(ShX_List, item); auto XA_Item = new TreeItem(XiAn_List, ShX_Item); auto XA_GX_Item = new TreeItem(XiAn_GaoXin_List, XA_Item); item->appendChild(ShX_Item); ShX_Item->appendChild(XA_Item); XA_Item->appendChild(XA_GX_Item); QList<QVariant> GuangDong_List; GuangDong_List.append("GuangDong"); GuangDong_List.append("GD"); QList<QVariant> DongGuan; DongGuan.append("DongGuan"); DongGuan.append("DG"); auto GuangDong_Item = new TreeItem(GuangDong_List, item); auto DongGuan_Item = new TreeItem(DongGuan, GuangDong_Item); item->appendChild(GuangDong_Item); GuangDong_Item->appendChild(DongGuan_Item); QList<QVariant> ShangHai; ShangHai.append("ShangHai"); ShangHai.append("ShH"); auto ShangHai_Item = new TreeItem(ShangHai, item); item->appendChild(ShangHai_Item); } TreeModel::~TreeModel() { delete m_rootItem; } int TreeModel::columnCount(const QModelIndex &parent) const { return 2; //返回实际的列数 (实际是他返回了0,因为根节点用的是无参的构造),TreeView控件会认为表是空表,不获取数据 if (parent.isValid()) { return static_cast<TreeItem*>(parent.internalPointer())->columnCount(); } else { return m_rootItem->columnCount(); } } QHash<int, QByteArray> TreeModel::roleNames() const { QHash<int, QByteArray> names(QAbstractItemModel::roleNames()); names[NAME] = "name"; names[SIMPLIFY] = "simplify"; return names; } QVariant TreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } switch (role) { case NAME: { return static_cast<TreeItem*>(index.internalPointer())->data(0); } case SIMPLIFY: { return static_cast<TreeItem*>(index.internalPointer())->data(1); } } } Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const { if (!index.isValid()) { return 0; } return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) { return QModelIndex(); } TreeItem *parentItem; if (!parent.isValid()) { parentItem = m_rootItem; } else { parentItem = static_cast<TreeItem*>(parent.internalPointer()); } TreeItem *childItem = parentItem->child(row); if (childItem) { return createIndex(row, column, childItem); } else { return QModelIndex(); } } QModelIndex TreeModel::parent(const QModelIndex &index) const { if (!index.isValid()) { return QModelIndex(); } TreeItem *childItem = static_cast<TreeItem*>(index.internalPointer()); TreeItem *parentItem = childItem->parent(); if (parentItem == m_rootItem) { return QModelIndex(); } return createIndex(parentItem->row(), 0, parentItem); } int TreeModel::rowCount(const QModelIndex &parent) const { TreeItem *parentItem; if (parent.column() > 0) { return 0; } if (!parent.isValid()) { parentItem = m_rootItem; } else { parentItem = static_cast<TreeItem*>(parent.internalPointer()); } return parentItem->childCount(); }以上即为TreeModel的源文件。
TreeItem.cpp源文件如下:
#include "TreeItem.h" TreeItem::TreeItem() :m_parentItem(NULL) { } TreeItem::TreeItem(const QList<QVariant> &data, TreeItem* parent) : m_parentItem(NULL) { m_parentItem = parent; m_itemData = data; } TreeItem::~TreeItem() { qDeleteAll(m_childItems); } void TreeItem::appendChild(TreeItem *item) { item->setParent(this); m_childItems.append(item); } void TreeItem::deleteAllChild() { for (int index = 0; index < m_childItems.size(); index++) { m_childItems[index]->deleteAllChild(); } qDeleteAll(m_childItems); m_childItems.clear(); } TreeItem *TreeItem::child(int row) { return m_childItems.value(row); } int TreeItem::childCount() const { return m_childItems.count(); } int TreeItem::columnCount() const { return m_itemData.count(); //return 1; } QVariant TreeItem::data(int column) const { return m_itemData .value(column); } TreeItem *TreeItem::parent() { return m_parentItem; } void TreeItem::setParent(TreeItem *parent) { m_parentItem = parent; } int TreeItem::row() const { if (!m_parentItem) { return 0; } return m_parentItem->m_childItems.indexOf(const_cast<TreeItem*>(this)); }
通过以上源文件可以看出,初始化的测试数据在TreeModel类的构造中传入。通过roleNames()函数增加QAbstractItemModel的角色,使得在QML中可以使用字段name和simplify,当QML加载读取数据时,通过枚举的角色NAME和SIMPLIFY返回对应的数据,即为要显示的数据,这样,就可以将C++的数据显示到QML的TreeView中了,以上其他的一些成员函数,都是一些获取数据个数的基本函数。
以上成员函数中,有一个比较特殊的函数:int TreeModel::columnCount(const QModelIndex &parent) const;因为在创建根目录时用的是无参构造m_rootItem = new TreeItem;所以QAbstractItemModel在调用hasChildren(const QModelIndex& parent)计算根的列数时,返回了0,导致TreeView控件会认为表是空表,所以不获取数据,最终显示出来是空的,调用堆栈如下图:
因此,在这里用无参构造根节点时,可以手动设置返回的列数(例如本例中返回2)。
加载TreeView的QML文件如下TreeViewTest.qml:
import QtQuick 2.4 import QtQuick.Layouts 1.2 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 Rectangle { id: root property var ctrl property var model x:100 y:50 width: 800 height: 600 TreeView { id:view width: 500 TableViewColumn { title: "Name" role: "name" width: 150 } TableViewColumn { title: "Simplify" role: "simplify" width: 200 } model: root.model itemDelegate: Item { Text { anchors.verticalCenter: parent.verticalCenter color: "red" elide: styleData.elideMode text: styleData.value } } property bool isCollapse: true onClicked: { console.log("onClicked:", index) console.log("isExpanded:",isExpanded(index)) if (isCollapse) { console.log("expand") emit: view.expand(index); isCollapse = false; } else { console.log("collapse") emit: view.collapse(index); isCollapse = true; } /*if (isExpanded(index)) { collapse(index); } else { expand(index); }*/ } } }这里需要注意的是TreeView的bool isExpanded(QModelIndex index)在
clicked(QModelIndex index)中的应用,运行起来可以查看打的isExpanded:的值。值得注意的是,当点击根节点的三角进行展开收缩时,用isExpanded(QModelIndex index)函数获取的返回值跟实际的展开状态是反的,不点击三角进行收缩展开是正常的(由于在点击三角符号时,调用了一次原生的展开或收缩,然后又在clicked中调用了TreeView的expand(index);函数或发送emit: view.expand(index);信号,这会产生二次展开或收缩,负负得正,所以导致调用isExpanded(QModelIndex index)获取的值不理想,,,这可以算是QML的一个Bug吧),因此本例中用了成员变量property bool isCollapse: true进行变换以完成想要的功能。
加载QML界面的TreeController.cpp主要加载与数据暴露如下:
void TreeController::onBtnClicked() { //m_view.setFlags(Qt::FramelessWindowHint | Qt::Window | Qt::WindowSystemMenuHint | Qt::WindowMinimizeButtonHint | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint); if (m_isInit) { m_view.setTitle("LoadTest"); m_view.rootContext()->setContextProperty("win", this); m_view.rootContext()->setContextProperty("quickView", &m_view); #ifndef _DEBUG m_view.setSource(QUrl("qrc:/ui/TreeViewTest.qml")); #else m_view.setSource(QUrl("ui/TreeViewTest.qml")); #endif m_item = m_view.rootObject(); QVariant v; v.setValue(this); m_item->setProperty("ctrl", v); QVariant v1; v1.setValue(&m_model); m_item->setProperty("model", v1); m_isInit = false; } m_view.show(); }以上主要完成QML界面的加载和ctrl与model的暴露,其他窗口的操作(最小化最大化关闭)等操作函数在此忽略,有想了解更多技术的,可以扫描左侧栏二维码,关注本人公众号【三个程序员】,接收更实时有料的推送。
以上所有,完成了QML的TreeView加载数据并显示,展示了树控件TreeView的应用。
其效果如下图:
关于QML的TreeView使用得上篇到此结束,下篇主要写一些关于QML树控件TreeView的插入与删除操作,敬请期待!迫不及待想接收更多技术知识的朋友,可以扫描左侧二维码关注本人公众号,您的肯定与支持是我前进的动力^_^.
完整的demo程序工程地址如下:QML树控件TreeView的使用
【http://download.csdn.net/detail/shado_walker/9761108】
声明:该文观点仅代表作者本人,牛骨文系教育信息发布平台,牛骨文仅提供信息存储空间服务。
- 上一篇: C#获取当前应用程序所在路径及环境变量
- 下一篇: c# 用代码来设置程序的PrivatePath