Add an example to create and use custom tree model in quick tree view

Add an example 'Table of Contents' to show how to create and use custom
tree model (derived from QAbstractItemModel) in the Quick TreeView
control.

Fixes: QTBUG-127016
Pick-to: 6.8
Change-Id: I9a5d9e837e1b18de322a0ec895d9832918fd5816
Reviewed-by: Mitch Curtis <mitch.curtis@qt.io>
This commit is contained in:
Santhosh Kumar 2024-07-11 16:51:16 +02:00
parent 9038c6f577
commit 1fca1477eb
14 changed files with 523 additions and 0 deletions

View File

@ -19,3 +19,4 @@ endif()
if(TARGET Qt6::Widgets)
qt_internal_add_example(flatstyle)
endif()
qt_internal_add_example(tableofcontents)

View File

@ -0,0 +1,37 @@
# Copyright (C) 2024 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
cmake_minimum_required(VERSION 3.16)
project(tableofcontents LANGUAGES CXX)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 6.8 REQUIRED COMPONENTS Quick)
qt_standard_project_setup(REQUIRES 6.8)
qt_add_executable(apptableofcontents
main.cpp
)
qt_add_resources(apptableofcontents "contentdata"
FILES
content.txt
arrow_icon.png
)
qt_add_qml_module(apptableofcontents
URI tableofcontents
VERSION 1.0
QML_FILES
Main.qml
SOURCES
treeitem.h
treeitem.cpp
treemodel.h
treemodel.cpp
)
target_link_libraries(apptableofcontents
PRIVATE Qt6::Quick
)

View File

@ -0,0 +1,94 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.impl
ApplicationWindow {
width: 800
height: 600
visible: true
title: "Table of Contents"
TreeView {
id: treeView
anchors.fill: parent
anchors.margins: 10
clip: true
selectionModel: ItemSelectionModel {}
model: TreeModel { }
delegate: TreeViewDelegate {
id: viewDelegate
readonly property real _padding: 5
readonly property real szHeight: contentItem.implicitHeight * 2.5
implicitWidth: _padding + contentItem.x + contentItem.implicitWidth + _padding
implicitHeight: szHeight
background: Rectangle { // Background rectangle enabled to show the alternative row colors
id: background
anchors.fill: parent
color: {
if (viewDelegate.model.row === viewDelegate.treeView.currentRow) {
return Qt.lighter(palette.highlight, 1.2)
} else {
if (viewDelegate.treeView.alternatingRows && viewDelegate.model.row % 2 !== 0) {
return (Application.styleHints.colorScheme === Qt.Light) ?
Qt.darker(palette.alternateBase, 1.25) :
Qt.lighter(palette.alternateBase, 2.)
} else {
return palette.base
}
}
}
Rectangle { // The selection indicator shown on the left side of the highlighted row
width: viewDelegate._padding
height: parent.height
visible: !viewDelegate.model.column
color: {
if (viewDelegate.model.row === viewDelegate.treeView.currentRow) {
return (Application.styleHints.colorScheme === Qt.Light) ?
Qt.darker(palette.highlight, 1.25) :
Qt.lighter(palette.highlight, 2.)
} else {
return "transparent"
}
}
}
}
indicator: Item {
x: viewDelegate._padding + viewDelegate.depth * viewDelegate.indentation
implicitWidth: viewDelegate.szHeight
implicitHeight: viewDelegate.szHeight
visible: viewDelegate.isTreeNode && viewDelegate.hasChildren
rotation: viewDelegate.expanded ? 90 : 0
TapHandler {
onSingleTapped: {
let index = viewDelegate.treeView.index(viewDelegate.model.row, viewDelegate.model.column)
viewDelegate.treeView.selectionModel.setCurrentIndex(index, ItemSelectionModel.NoUpdate)
viewDelegate.treeView.toggleExpanded(viewDelegate.model.row)
}
}
ColorImage {
width: parent.width / 3
height: parent.height / 3
anchors.centerIn: parent
source: "qrc:/arrow_icon.png"
color: palette.buttonText
}
}
contentItem: Label {
x: viewDelegate._padding + (viewDelegate.depth + 1 * viewDelegate.indentation)
width: parent.width - viewDelegate._padding - x
text: viewDelegate.model.display
elide: Text.ElideRight
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 B

View File

@ -0,0 +1,40 @@
Getting Started How to familiarize yourself with Qt Widgets Designer
Launching Designer Running the Qt Widgets Designer application
The User Interface How to interact with Qt Widgets Designer
Designing a Component Creating a GUI for your application
Creating a Dialog How to create a dialog
Composing the Dialog Putting widgets into the dialog example
Creating a Layout Arranging widgets on a form
Signal and Slot Connections Making widget communicate with each other
Using a Component in Your Application Generating code from forms
The Direct Approach Using a form without any adjustments
The Single Inheritance Approach Subclassing a form's base class
The Multiple Inheritance Approach Subclassing the form itself
Automatic Connections Connecting widgets using a naming scheme
A Dialog Without Auto-Connect How to connect widgets without a naming scheme
A Dialog With Auto-Connect Using automatic connections
Form Editing Mode How to edit a form in Qt Widgets Designer
Managing Forms Loading and saving forms
Editing a Form Basic editing techniques
The Property Editor Changing widget properties
The Object Inspector Examining the hierarchy of objects on a form
Layouts Objects that arrange widgets on a form
Applying and Breaking Layouts Managing widgets in layouts
Horizontal and Vertical Layouts Standard row and column layouts
The Grid Layout Arranging widgets in a matrix
Previewing Forms Checking that the design works
Using Containers How to group widgets together
General Features Common container features
Frames QFrame
Group Boxes QGroupBox
Stacked Widgets QStackedWidget
Tab Widgets QTabWidget
Toolbox Widgets QToolBox
Connection Editing Mode Connecting widgets together with signals and slots
Connecting Objects Making connections in Qt Widgets Designer
Editing Connections Changing existing connections

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -0,0 +1,18 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\example tableofcontents
\keyword Qt Quick Controls - Table of Contents
\title Qt Quick Controls - Table of Contents
\keyword Qt Quick Controls 2 - Table of Contents
\ingroup qtquickcontrols-tableofcontents
\brief Demonstrate the custom tree model in the TreeView control.
This example demonstrates how to create and use custom tree model in the
TreeView control.
\image qtquickcontrols-tableofcontents.png
\include examples-run.qdocinc
*/

View File

@ -0,0 +1,21 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
QObject::connect(
&engine,
&QQmlApplicationEngine::objectCreationFailed,
&app,
[]() { QCoreApplication::exit(-1); },
Qt::QueuedConnection);
engine.loadFromModule("tableofcontents", "Main");
return app.exec();
}

View File

@ -0,0 +1,75 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
/*
treeitem.cpp
A container for items of data supplied by the simple tree model.
*/
#include "treeitem.h"
//! [0]
TreeItem::TreeItem(QVariantList data, TreeItem *parent)
: m_itemData(std::move(data)), m_parentItem(parent)
{}
//! [0]
//! [1]
void TreeItem::appendChild(std::unique_ptr<TreeItem> &&child)
{
m_childItems.push_back(std::move(child));
}
//! [1]
//! [2]
TreeItem *TreeItem::child(int row)
{
return row >= 0 && row < childCount() ? m_childItems.at(row).get() : nullptr;
}
//! [2]
//! [3]
int TreeItem::childCount() const
{
return int(m_childItems.size());
}
//! [3]
//! [4]
int TreeItem::columnCount() const
{
return int(m_itemData.count());
}
//! [4]
//! [5]
QVariant TreeItem::data(int column) const
{
return m_itemData.value(column);
}
//! [5]
//! [6]
TreeItem *TreeItem::parentItem()
{
return m_parentItem;
}
//! [6]
//! [7]
int TreeItem::row() const
{
if (m_parentItem == nullptr)
return 0;
const auto it = std::find_if(m_parentItem->m_childItems.cbegin(), m_parentItem->m_childItems.cend(),
[this](const std::unique_ptr<TreeItem> &treeItem) {
return treeItem.get() == this;
});
if (it != m_parentItem->m_childItems.cend())
return std::distance(m_parentItem->m_childItems.cbegin(), it);
Q_ASSERT(false); // should not happen
return -1;
}
//! [7]

View File

@ -0,0 +1,33 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef TREEITEM_H
#define TREEITEM_H
#include <QVariant>
#include <QList>
#include <memory>
//! [0]
class TreeItem
{
public:
explicit TreeItem(QVariantList data, TreeItem *parentItem = nullptr);
void appendChild(std::unique_ptr<TreeItem> &&child);
TreeItem *child(int row);
int childCount() const;
int columnCount() const;
QVariant data(int column) const;
int row() const;
TreeItem *parentItem();
private:
std::vector<std::unique_ptr<TreeItem>> m_childItems;
QVariantList m_itemData;
TreeItem *m_parentItem;
};
//! [0]
#endif // TREEITEM_H

View File

@ -0,0 +1,157 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
/*
treemodel.cpp
Provides a simple tree model to show how to create and use hierarchical
models.
*/
#include "treemodel.h"
#include "treeitem.h"
#include <QFile>
#include <QStringList>
#include <QStack>
using namespace Qt::StringLiterals;
//! [0]
TreeModel::TreeModel(QObject *parent)
: QAbstractItemModel(parent)
, rootItem(std::make_unique<TreeItem>(QVariantList{tr("Title"), tr("Summary")}))
{
QFile file(":/content.txt"_L1);
file.open(QIODevice::ReadOnly | QIODevice::Text);
setupModelData(QStringView{QString::fromUtf8(file.readAll())}.split(u'\n'), rootItem.get());
file.close();
}
//! [0]
//! [1]
TreeModel::~TreeModel() = default;
//! [1]
//! [2]
int TreeModel::columnCount(const QModelIndex &parent) const
{
if (parent.isValid())
return static_cast<TreeItem*>(parent.internalPointer())->columnCount();
return rootItem->columnCount();
}
//! [2]
//! [3]
QVariant TreeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || role != Qt::DisplayRole)
return {};
const auto *item = static_cast<const TreeItem*>(index.internalPointer());
return item->data(index.column());
}
//! [3]
//! [4]
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{
return index.isValid()
? QAbstractItemModel::flags(index) : Qt::ItemFlags(Qt::NoItemFlags);
}
//! [4]
//! [5]
QVariant TreeModel::headerData(int section, Qt::Orientation orientation,
int role) const
{
return orientation == Qt::Horizontal && role == Qt::DisplayRole
? rootItem->data(section) : QVariant{};
}
//! [5]
//! [6]
QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const
{
if (!hasIndex(row, column, parent))
return {};
TreeItem *parentItem = parent.isValid()
? static_cast<TreeItem*>(parent.internalPointer())
: rootItem.get();
if (auto *childItem = parentItem->child(row))
return createIndex(row, column, childItem);
return {};
}
//! [6]
//! [7]
QModelIndex TreeModel::parent(const QModelIndex &index) const
{
if (!index.isValid())
return {};
auto *childItem = static_cast<TreeItem*>(index.internalPointer());
TreeItem *parentItem = childItem->parentItem();
return parentItem != rootItem.get()
? createIndex(parentItem->row(), 0, parentItem) : QModelIndex{};
}
//! [7]
//! [8]
int TreeModel::rowCount(const QModelIndex &parent) const
{
if (parent.column() > 0)
return 0;
const TreeItem *parentItem = parent.isValid()
? static_cast<const TreeItem*>(parent.internalPointer())
: rootItem.get();
return parentItem->childCount();
}
//! [8]
void TreeModel::setupModelData(const QList<QStringView> &lines, TreeItem *parent)
{
struct ParentIndentation
{
TreeItem *parent;
qsizetype indentation;
};
QStack<ParentIndentation> state;
state.push({parent, 0});
for (const auto &line : lines) {
qsizetype position = 0;
for ( ; position < line.length() && line.at(position).isSpace(); ++position) {
}
const QStringView lineData = line.sliced(position).trimmed();
if (!lineData.isEmpty()) {
// Read the column data from the rest of the line.
const auto columnStrings = lineData.split(u'\t', Qt::SkipEmptyParts);
QVariantList columnData;
columnData.reserve(columnStrings.count());
for (const auto &columnString : columnStrings)
columnData << columnString.toString();
if (position > state.top().indentation) {
// The last child of the current parent is now the new parent
// unless the current parent has no children.
auto *lastParent = state.top().parent;
if (lastParent->childCount() > 0)
state.push({lastParent->child(lastParent->childCount() - 1), position});
} else {
while (position < state.top().indentation && !state.isEmpty())
state.pop();
}
// Append a new item to the current parent's list of children.
auto *lastParent = state.top().parent;
lastParent->appendChild(std::make_unique<TreeItem>(columnData, lastParent));
}
}
}

View File

@ -0,0 +1,43 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef TREEMODEL_H
#define TREEMODEL_H
#include <QAbstractItemModel>
#include <QModelIndex>
#include <QVariant>
#include <QQmlEngine>
class TreeItem;
//! [0]
class TreeModel : public QAbstractItemModel
{
Q_OBJECT
QML_NAMED_ELEMENT(TreeModel)
public:
Q_DISABLE_COPY_MOVE(TreeModel)
explicit TreeModel(QObject *parent = nullptr);
~TreeModel() override;
QVariant data(const QModelIndex &index, int role) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
QModelIndex index(int row, int column,
const QModelIndex &parent = {}) const override;
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &parent = {}) const override;
int columnCount(const QModelIndex &parent = {}) const override;
private:
static void setupModelData(const QList<QStringView> &lines, TreeItem *parent);
std::unique_ptr<TreeItem> rootItem;
};
//! [0]
#endif // TREEMODEL_H

View File

@ -100,6 +100,7 @@ Creator.
\li \l{Qt Quick Controls - Gallery}{Controls Gallery}
\li \l{tableview/gameoflife}{TableView}
\li \l{Qt Quick Examples - Text}{Text and Fonts}
\li \l{Qt Quick Controls - Table of Contents}
\endlist
\enddiv
\div {class="doc-column"}

View File

@ -46,6 +46,9 @@
\snippet qml/treeview/qml-customdelegate.qml 0
More information on how to create and use a custom tree model can be found
in the example \l {Qt Quick Controls - Table of Contents}
The properties that are marked as \c required will be filled in by
TreeView, and are similar to attached properties. By marking them as
required, the delegate indirectly informs TreeView that it should take