Skip to main content

Tree View

Nested content as themed item rows, with a disclosure affordance drawn from the model's own expansion state.

Loading preview…
#include <QApplication>
#include <QFile>
#include <QHBoxLayout>
#include <QLabel>
#include <QPainter>
#include <QStandardItemModel>
#include <QUrl>
#include <QVBoxLayout>
#include <array>
#ifdef SHADCN_GALLERY_MEDIA
#include <shadcn/media.hpp>
#endif
#include <shadcn/shadcn.hpp>

QWidget* example(QWidget* parent = nullptr) {
using namespace shadcn;
auto* canvas = new QWidget(parent);
canvas->setAutoFillBackground(true);
auto* outer = new QHBoxLayout(canvas);
outer->setContentsMargins(32, 32, 32, 32);
auto* host = new QWidget(canvas);
auto* layout = new QVBoxLayout(host);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(16);
outer->addStretch();
outer->addWidget(host, 0, Qt::AlignCenter);
outer->addStretch();
// A course outline: a branch with children, a branch that is collapsed, a
// heading, and a leaf, so expansion, indentation and the heading treatment
// are all visible at once.
auto* outline = new TreeView(host);
outline->setFixedSize(400, 300);
auto* model = new QStandardItemModel(outline);
model->setHorizontalHeaderLabels({});
const auto lesson = [](const QString& title, const QString& trailing) {
auto* item = new QStandardItem(title);
item->setData(trailing, static_cast<int>(RowRole::TrailingText));
return item;
};
auto* heading = new QStandardItem(QStringLiteral("Systems 101"));
heading->setData(true, static_cast<int>(RowRole::Heading));
heading->setFlags(Qt::ItemIsEnabled);
model->appendRow(heading);
auto* first = new QStandardItem(QStringLiteral("01 Introduction"));
first->appendRow(lesson(QStringLiteral("Overview"), QStringLiteral("12:04")));
first->appendRow(lesson(QStringLiteral("What a system is"), QStringLiteral("18:22")));
model->appendRow(first);
auto* second = new QStandardItem(QStringLiteral("02 Signals"));
second->appendRow(lesson(QStringLiteral("Amplitude"), QStringLiteral("09:41")));
second->appendRow(lesson(QStringLiteral("Frequency"), QStringLiteral("21:07")));
model->appendRow(second);
outline->setModel(model);
// Row 0 is the heading, so the first branch is row 1. Expanding the wrong
// row is silent, and the demo then shows two collapsed branches.
outline->expand(model->index(1, 0));
layout->addWidget(outline);
return canvas;
}

Installation​

add_subdirectory(shadcn-cpp)
target_link_libraries(app PRIVATE shadcn::widgets)

Usage​

TreeView is a QTreeView and reads the same RowRoles as List View. Expansion is the model's, so a collapse survives a repaint and the view never holds a second copy of the state.

#include <QStandardItemModel>
#include <shadcn/rows.hpp>

shadcn::TreeView outline(parent);
auto* model = new QStandardItemModel(&outline);
model->setHorizontalHeaderLabels({});
auto* section = new QStandardItem(QStringLiteral("01 Introduction"));
section->appendRow(new QStandardItem(QStringLiteral("Overview")));
model->appendRow(section);
outline.setModel(model);
outline.expand(model->index(0, 0));

Clicking the affordance toggles the branch, and the press is consumed there so the row is not also selected behind the reader's back.

disclosureRect gives the affordance's rectangle, which is empty for a row with no children. It is the hit target, and it is also what a test or a hit check should aim at.

const auto box = outline.disclosureRect(index);
if (box.isValid() && box.contains(position)) { /* the press was on the chevron */ }

API​

MemberPurpose
disclosureRect(index)The affordance, empty for a leaf.
disclosureSize()The affordance's side, in points.
setCompact(bool)Sets the row density directly.
setCompactBelow(int)The height at which rows become compact.
rowHeight()The height one row occupies now.
visibleRowRects()The rectangles of the expanded rows on screen.

Notes​

Qt's own branch indicator is turned off. Left on, a row carries two chevrons: Qt's at the far left and the themed one beside the text, pointing at each other.

The affordance is placed clear of the row's own fill and border. A one pixel border running beside a 16 pixel affordance is as tall as the affordance, and it hides which way the chevron points, which is the one thing the affordance exists to say.