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:
parent
9038c6f577
commit
1fca1477eb
|
@ -19,3 +19,4 @@ endif()
|
|||
if(TARGET Qt6::Widgets)
|
||||
qt_internal_add_example(flatstyle)
|
||||
endif()
|
||||
qt_internal_add_example(tableofcontents)
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -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 |
|
@ -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 |
|
@ -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
|
||||
*/
|
|
@ -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();
|
||||
}
|
|
@ -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]
|
|
@ -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
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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"}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue