Skip to content

Repository files navigation

QObjectTreeModel

A reactive QAbstractItemModel that automatically mirrors any QObject parent-child hierarchy as a tree model — extracted from Meld Studio and released under the MIT license.

Useful as a debug model for visually inspecting QObject parent-child hierarchies. Passively monitors any QObject root you pass to it and exposes its parent-child hierarchy as a tree model.

Recording

A short recording of the kind of view the QObjectTreeModel + QObjcetTreeView is able to expose on a code base, in this case Meld Studio. Shows expanding / collapsing the view and deleting a QML Item.


Features

  • Fully reactive — Reparent, rename, or delete a QObject from anywhere and the model updates automatically.
  • Both deletion paths — handles deleteLater()and raw delete correctly, including full subtree cleanup.
  • Move supportmoveRows() for in-place reordering and cross-parent moves, ready for drag-and-drop.
  • QML-ready — registered as a QML_ELEMENT with invokable helpers for use directly in QML.
  • Thoroughly tested — ~95 tests across 4,400+ lines covering insertions, deletions, moves, reparenting, root management, QWidget edge cases, and more, all validated by QAbstractItemModelTester.

Requirements

DependencyVersion
C++20
Qt6.10+
CMake3.16+

Note: This library uses Qt private APIs (QObjectPrivate). It has been developed and tested against Qt 6.8–6.10 but should work with other versions with minimal or no changes. The model could be updated to not depend on QObjectPrivate without much work as it only really needs private access to allow for QObject reperents with index for changing the order of the children of a QObject (something that we used internally at Meld Studio but for a readonly model would not be required).

Quick Start

Clone the project, open it in QtCreator and your good to go.

Using Your Own Project

All you need are to include the qobjecttreemodel.h and qobjecttreemodel.cpp in your own project for the model. Optionally you can also use the QObjectTreeView.qml source file for a QML view to inspect and manipulate the model.

C++

#include"qobjecttreemodel.h"// Create a model with an external root
QObject root;
root.setObjectName("Root");
auto model = QObjectTreeModel::CreateWithRoot(&root);
// Children added via standard Qt parenting are tracked automaticallyauto *child = new QObject(&root);
child->setObjectName("Child 1");
// model now has 1 row under root — no extra calls needed// Use SetParent for precise insertion orderauto *child2 = new QObject();
QObjectTreeModel::SetParent(child2, &root, 0); // insert at index 0// Move a child to a different parentQObjectTreeModel::SetParent(child, child2);
// Remove without destroying
QObjectList taken = model->takeRows(0, 1, model->indexOf(&root));
// Remove and destroy
model->removeRows(0, 1, model->indexOf(&root));

QML

import QObjectTreeModel
Window {
QtObject {
id: sceneRoot
objectName:"Scene"
}
TreeView {
qobjectTreeModel: QObjectTreeModel { root: sceneRoot }
// The model updates reactively as children are added/removed
}
}

API Overview

Construction

MethodDescription
QObjectTreeModel(parent)Creates a model with an internal root object
CreateWithRoot(root, parent)Factory: wraps an externally-owned root (model does not take ownership)

Data Roles

RoleValueDescription
Qt::DisplayRoleobjectNameThe QObject's objectName property
Roles::ObjectNameobjectNameSame as DisplayRole
Roles::MetaTypeNameclassNameThe QMetaObject::className() (e.g. "QPushButton")

Key Methods

MethodDescription
indexOf(object)Returns the QModelIndex for a tracked QObject
objectFromIndex(index)Returns the QObject at a given model index
removeRows(row, count, parent)Removes rows and destroys the objects via deleteLater()
takeRows(row, count, parent)Removes rows without destroying — ownership transfers to caller
takeAllRows(parent)Takes all children from under a parent
moveRows(src, row, count, dst, dstRow)Moves a row between or within parents
setRoot(root)Replaces the root object (nullptr creates a new internal root)

Static Helpers (also Q_INVOKABLE for QML)

Note you need to use QObjectTreeModel::SetParent instead of QObject::SetParent if you want the model to detect move operations if moving a QObject between two QObjects already in the model.

MethodDescription
SetParent(object, parent)Reparent with automatic model update (appends)
SetParent(object, parent, index)Reparent at a specific child index
IsAncestorOf(ancestor, descendant)Walk-up ancestry check
GetParent(object)Returns QObject::parent() (exposed for QML)

How It Works

QObjectTreeModel installs an event filter on every tracked QObject and maintains two hash maps for O(1) lookups:

m_childParentMap: QObject* → QObject* (child → parent)
m_parentChildMap: QObject* → QObjectList (parent → ordered children)

Event-driven reactivity

EventWhat happens
ChildAddedNew child detected — inserted into the model
ChildRemovedChild removed — model checks if this is a move (new parent already tracked) or a true removal
DeferredDeleteObject scheduled for deletion via deleteLater() — removed from model before destruction
QObject::destroyedRaw delete — subtree removed synchronously via DirectConnection signal
ParentChangeQWidget-specific fallback when QWidget::setParent() suppresses normal child events
ThreadChangeCross-thread move detected — root is unset to prevent undefined behavior

Move detection

When a QObject is reparented, Qt sends a ChildRemoved to the old parent and a ChildAdded to the new parent. The model uses a temporary event deferrer to batch these into a single moveRows operation rather than a remove + insert.

Testing

The test suite (tst_qobjecttreemodel.cpp) covers:

  • Model contract — every test runs with QAbstractItemModelTester to validate index consistency, data roles, and parent/child relationships
  • Insertion — single child, nested trees, large trees (1000+ objects)
  • DeletiondeleteLater() path, raw delete path, subtree cascades
  • Moves — in-place reordering, cross-parent moves, circular hierarchy rejection
  • Root management — internal/external roots, root replacement, root destruction
  • QWidget specificsQWidget::setParent() suppression handling, ParentChange events
  • Edge cases — thread affinity changes, out-of-range indices, no-op moves

Project Structure

├── qobjecttreemodel.h # Public API — single header
├── qobjecttreemodel.cpp # Implementation (~1,600 lines)
├── tst_qobjecttreemodel.cpp # Test suite (~4,400 lines, ~95 tests)
├── Main.qml # Demo app entry point
├── QObjectTreeView.qml # Interactive QML tree view with drag-and-drop
├── main.cpp # Demo app bootstrap
└── CMakeLists.txt # Build configuration

License

MIT — see LICENSE.

Attribution

Extracted from Meld Studio, a live streaming and content creation application. Originally developed as an way to expose internal QObject trees to views and as an internal tool for debugging deep QObject hierarchies at runtime.

About

A reactive QAbstractItemModel that automatically mirrors any QObject parent-child hierarchy as a tree model, extracted from Meld Studio.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages