QML樹控制元件TreeView的使用(上)
阿新 • • 發佈:2019-02-17
在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標頭檔案如下:
TreeItem.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; };
可以看出TreeModel是繼承自QAbstractItemModel,其中QVariant data(const QModelIndex &index, int role) const;成員函式和QHash<int, QByteArray> roleNames() const;成員函式比較關鍵,是QML讀取C++資料的關鍵函式。#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.cpp原始檔如下:
以上即為TreeModel的原始檔。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(); }
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的插入與刪除操作,敬請期待!迫不及待想接收更多技術知識的朋友,可以掃描左側二維碼關注本人公眾號,您的肯定與支援是我前進的動力^_^.
【http://download.csdn.net/detail/shado_walker/9761108】