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頭文件如下:
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】

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章