这是 QML 中最小可行的 TreeView 模型吗?
Posted
技术标签:
【中文标题】这是 QML 中最小可行的 TreeView 模型吗?【英文标题】:Is this the Minimum Viable TreeView Model in QML? 【发布时间】:2019-05-22 19:50:30 【问题描述】:我正在制作一个包含三个项目的折叠列表:“嘿”、“Whats”和“Up?”。我想把它放到树视图中。我知道这个列表将只包含这三个项目。因此,我想知道如何将这些项目“嵌套”在一起。
我知道有一些敏捷系统的实现支持添加和删除父/子对象、查找索引……强大的模型。但是,我实际上只需要在可展开/可折叠的视图中显示这些项目。以下是我阅读过的与 C++ 和 QAbstractItemModels 相关的内容:
QML Treeview QML QAbstractItemModel This Question by My_Cat_Jessica This question by kavaliero 基于: This 'working' example 由 Qt 自己编写(实际上不适用于 TreeView。但适用于 QTreeView!)这里是用模型实现树视图的最简单可行的代码:
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window
id: mywindow
visible: true
width: 640
height: 480
TreeView
id: treeview
anchors.fill: parent
TableViewColumn
title: "Phrase"
role: "phrase"
model: phraseModel
ListModel
id: phraseModel
ListElement phrase: "Hey";
ListElement phrase: "What's";
ListElement phrase: "Up?";
我希望输出产生这样的嵌套堆栈:
Hey
What's
Up?
但我将所有内容都放在一个列中,彼此对齐:
Hey
What's
Up?
我知道我没有分配父母,我不完全确定如何做到这一点 - 但我什至不确定这是否是需要对此代码执行的操作。所以我的问题是:将这三个元素堆叠成可展开/可折叠视图的最后一步是什么?
【问题讨论】:
在这里您可以找到 QML 中包含 TreeModel 和 TreeView 的完整工作示例:github.com/Daguerreo/QMLTreeView 【参考方案1】:没有可以使用 TreeView 的原生 QML 模型,所以我实现了一个尝试通用的模型:
树元素
// treeelement.h
#ifndef TreeElement_H
#define TreeElement_H
#include <QObject>
#include <QQmlListProperty>
class TreeElement : public QObject
Q_OBJECT
public:
Q_PROPERTY(QQmlListProperty<TreeElement> items READ items)
Q_CLASSINFO("DefaultProperty", "items")
TreeElement(QObject *parent = Q_NULLPTR);
Q_INVOKABLE TreeElement *parentItem() const;
bool insertItem(TreeElement *item, int pos = -1);
QQmlListProperty<TreeElement> items();
TreeElement *child(int index) const;
void clear();
Q_INVOKABLE int pos() const;
Q_INVOKABLE int count() const;
private:
static void appendElement(QQmlListProperty<TreeElement> *property, TreeElement *value);
static int countElement(QQmlListProperty<TreeElement> *property);
static void clearElement(QQmlListProperty<TreeElement> *property);
static TreeElement *atElement(QQmlListProperty<TreeElement> *property, int index);
QList<TreeElement *> m_childs;
TreeElement *m_parent;
;
#endif // TreeElement_H
// treeelement.cpp
#include "treeelement.h"
TreeElement::TreeElement(QObject *parent) :
QObject(parent),
m_parent(nullptr)
TreeElement *TreeElement::parentItem() const
return m_parent;
QQmlListProperty<TreeElement> TreeElement::items()
return QQmlListProperty<TreeElement> (this,
this,
&TreeElement::appendElement,
&TreeElement::countElement,
&TreeElement::atElement,
&TreeElement::clearElement);
TreeElement *TreeElement::child(int index) const
if(index < 0 || index >= m_childs.length())
return nullptr;
return m_childs.at(index);
void TreeElement::clear()
qDeleteAll(m_childs);
m_childs.clear();
bool TreeElement::insertItem(TreeElement *item, int pos)
if(pos > m_childs.count())
return false;
if(pos < 0)
pos = m_childs.count();
item->m_parent = this;
item->setParent(this);
m_childs.insert(pos, item);
return true;
int TreeElement::pos() const
TreeElement *parent = parentItem();
if(parent)
return parent->m_childs.indexOf(const_cast<TreeElement *>(this));
return 0;
int TreeElement::count() const
return m_childs.size();
void TreeElement::appendElement(QQmlListProperty<TreeElement> *property, TreeElement *value)
TreeElement *parent = qobject_cast<TreeElement *>(property->object);
parent->insertItem(value);
int TreeElement::countElement(QQmlListProperty<TreeElement> *property)
TreeElement *parent = qobject_cast<TreeElement *>(property->object);
return parent->count();
void TreeElement::clearElement(QQmlListProperty<TreeElement> *property)
TreeElement *parent = qobject_cast<TreeElement *>(property->object);
parent->clear();
TreeElement *TreeElement::atElement(QQmlListProperty<TreeElement> *property, int index)
TreeElement *parent = qobject_cast<TreeElement *>(property->object);
if(index < 0 || index >= parent->count())
return nullptr;
return parent->child(index);
树模型
// treemodel.h
#ifndef TreeModel_H
#define TreeModel_H
#include <QAbstractItemModel>
#include <QQmlListProperty>
class TreeElement;
class TreeModel : public QAbstractItemModel
Q_OBJECT
public:
Q_PROPERTY(QQmlListProperty<TreeElement> items READ items)
Q_PROPERTY(QVariantList roles READ roles WRITE setRoles NOTIFY rolesChanged)
Q_CLASSINFO("DefaultProperty", "items")
TreeModel(QObject *parent = Q_NULLPTR);
~TreeModel() override;
QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE;
QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE;
Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE;
int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
QQmlListProperty<TreeElement> items();
QVariantList roles() const;
void setRoles(const QVariantList &roles);
Q_INVOKABLE QModelIndex indexFromElement(TreeElement *item);
Q_INVOKABLE bool insertElement(TreeElement *item, const QModelIndex &parent = QModelIndex(), int pos = -1);
TreeElement *elementFromIndex(const QModelIndex &index) const;
private:
TreeElement *m_root;
QHash<int, QByteArray> m_roles;
signals:
void rolesChanged();
;
#endif // TreeModel_H
// treemodel.cpp
#include "treemodel.h"
#include "treeelement.h"
TreeModel::TreeModel(QObject *parent) :
QAbstractItemModel(parent)
m_root = new TreeElement;
TreeModel::~TreeModel()
delete m_root;
QHash<int, QByteArray> TreeModel::roleNames() const
return m_roles;
QVariant TreeModel::data(const QModelIndex &index, int role) const
if (!index.isValid())
return QVariant();
TreeElement *item = static_cast<TreeElement*>(index.internalPointer());
QByteArray roleName = m_roles[role];
QVariant name = item->property(roleName.data());
return name;
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
if (!index.isValid())
return nullptr;
return QAbstractItemModel::flags(index);
QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const
if (!hasIndex(row, column, parent))
return QModelIndex();
TreeElement *parentItem = elementFromIndex(parent);
TreeElement *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();
TreeElement *childItem = static_cast<TreeElement*>(index.internalPointer());
TreeElement *parentItem = static_cast<TreeElement *>(childItem->parentItem());
if (parentItem == m_root)
return QModelIndex();
return createIndex(parentItem->pos(), 0, parentItem);
int TreeModel::rowCount(const QModelIndex &parent) const
if (parent.column() > 0)
return 0;
TreeElement *parentItem = elementFromIndex(parent);
return parentItem->count();
int TreeModel::columnCount(const QModelIndex &parent) const
Q_UNUSED(parent)
return 1;
QQmlListProperty<TreeElement> TreeModel::items()
return m_root->items();
QVariantList TreeModel::roles() const
QVariantList list;
QHashIterator<int, QByteArray> i(m_roles);
while (i.hasNext())
i.next();
list.append(i.value());
return list;
void TreeModel::setRoles(const QVariantList &roles)
static int nextRole = Qt::UserRole + 1;
for(QVariant role : roles)
m_roles.insert(nextRole, role.toByteArray());
nextRole ++;
emit rolesChanged();
QModelIndex TreeModel::indexFromElement(TreeElement *item)
QVector<int> positions;
QModelIndex result;
if(item)
do
int pos = item->pos();
positions.append(pos);
item = item->parentItem();
while(item != nullptr);
for (int i = positions.size() - 2; i >= 0 ; i--)
result = index(positions[i], 0, result);
return result;
bool TreeModel::insertElement(TreeElement *item, const QModelIndex &parent, int pos)
TreeElement *parentElement = elementFromIndex(parent);
if(pos >= parentElement->count())
return false;
if(pos < 0)
pos = parentElement->count();
beginInsertRows(parent, pos, pos);
bool retValue = parentElement->insertItem(item, pos);
endInsertRows();
return retValue;
TreeElement *TreeModel::elementFromIndex(const QModelIndex &index) const
if(index.isValid())
return static_cast<TreeElement *>(index.internalPointer());
return m_root;
main.cpp
#include "treemodel.h"
#include "treeelement.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
static void registertypes()
qmlRegisterType<TreeElement>("foo", 1, 0, "TreeElement");
qmlRegisterType<TreeModel>("foo", 1, 0, "TreeModel");
Q_COREAPP_STARTUP_FUNCTION(registertypes)
int main(int argc, char *argv[])
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl)
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
, Qt::QueuedConnection);
engine.load(url);
return app.exec();
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.4
import foo 1.0
Window
visible: true
width: 640
height: 480
title: qsTr("Hello World")
TreeModel
id: treemodel
roles: ["phrase"]
TreeElement
property string phrase: "Hey"
TreeElement
property string phrase: "What's"
TreeElement
property string phrase: "Up?"
TreeView
anchors.fill: parent
model: treemodel
TableViewColumn
title: "Name"
role: "phrase"
width: 200
输出:
您找到的完整示例here
【讨论】:
> 没有可以使用 TreeView 的原生 QML 模型 很高兴知道,谢谢! TreeView 仅在 QtQuick.Controls 1.x 中可用,如果我想使用最新的 QtQuick.Controls 怎么办?【参考方案2】:我在 QML 中创建了一个可折叠的框架(一个带有标题和内容的组合框)。如果您确定永远不会更改结构,则可以将其用于您的目的:
我通过删除无用部分(动画、装饰等)来简化代码。所以下面的代码可以改进。但是,我保留了这个想法:
// CollapsibleGroupBox.qml
Item
property alias contentItem: content.contentItem;
property string title: ""
Item
id: titleBar
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: 30
Row
anchors.fill: parent
CheckBox
Layout.alignment: Qt.AlignLeft
id: expand
checked: true;
Text
Layout.alignment: Qt.AlignLeft
text: title
Pane
anchors.left: parent.left
anchors.right: parent.right
anchors.top: titleBar.bottom
anchors.bottom: parent.bottom
topPadding: 0
visible: expand.checked
id: content
// Main.qml
Item
height: 500
width: 500
CollapsibleGroupBox
anchors.fill: parent
title: "Hey!"
contentItem: CollapsibleGroupBox
title: "What's"
contentItem: CollapsibleGroupBox
title: "up?"
你会得到:
您也可以将复选框替换为MouseArea
。
【讨论】:
谢谢!这正是我想要的。【参考方案3】:我还创建了一个仅使用 QML 组件的模型:
import QtQuick 2.9
import QtQuick.Window 2.2
import UISettings 1.0
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtQuick.Controls 1.4 as SV
Window
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Flickable
id: flick
anchors.fill: parent
clip: true
contentHeight: col.implicitHeight
property var mymodel:
"animals":
"big":
"land": "elephants",
"water": "whales"
,
"small":
"land": "mice",
"water": "fish"
,
"plants":
"trees": "evergreens"
Column
id: col
Component.onCompleted: componentListView.createObject(this, "objmodel":flick.mymodel);
Component
id: componentListView
Repeater
id: repeater
property var objmodel: ()
model: Object.keys(objmodel)
ColumnLayout
Layout.leftMargin: 50
Button
property var sprite: null
text: modelData
onClicked:
if(sprite === null)
if(typeof objmodel[modelData] === 'object')
sprite = componentListView.createObject(parent, "objmodel":objmodel[modelData]);
else
sprite.destroy()
【讨论】:
以上是关于这是 QML 中最小可行的 TreeView 模型吗?的主要内容,如果未能解决你的问题,请参考以下文章