Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Modular QML Architecture in Qt 6: Component Organization, Resource Management, and Singleton Patterns

Tech May 15 1

Module Definition and Scoping

In modern Qt 6 development, a CMake project inherently functions as a QML module. The recommended approach replaces legacy qmldir management with qt_add_qml_module, which centralizes configuration and automatically generates module metadata during the build process.

Module paths are organized hierarchically but remain logically parallel at runtime. Componants declared within the same module share the same import namespace.

Intra-Module Component Resolution

When referencing components within a single module, naming conventions require that all QML file names begin with an uppercase letter. Resolution follows three primary patterns:

Implicit Directory Access

If target components reside in the exact same directory as the consumer, no import declaration is necessary. They are resolved directly by filename.

project/
 ├─ CMakeLists.txt
 ├─ Dashboard.qml
 └─ StatCard.qml
// Inside Dashboard.qml
Column {
    spacing: 10
    StatCard {
        anchors.horizontalCenter: parent.horizontalCenter
        title: "Active Users"
    }
}

Relative Path Imports

For components located in subdirectories, a relative import statement aggregates all valid QML files within that folder.

project/
 ├─ CMakeLists.txt
 ├─ AppWindow.qml
 └─ navigation/
     ├─ TopRail.qml
     └─ SideDrawer.qml
import QtQuick
import "./navigation"

ApplicationWindow {
    id: mainShell
    width: 640
    height: 480
    visible: true
    SideDrawer { anchors.left: parent.left }
}

Module Namespace Imports

When a qmldir descriptor is present, the directory transforms into an isolated module namespace. Consumers must explicitly import the defined module URI.

// navigation/qmldir
module App.Navigation
TopRail 1.0 TopRail.qml
SideDrawer 1.0 SideDrawer.qml
import QtQuick
import App.Navigation 1.0

ApplicationWindow {
    width: 640
    height: 480
    visible: true
    TopRail { anchors.top: parent.top }
}

The same outcome is achieved via CMake configuration, which eliminates manual qmldir maintenance:

qt_add_qml_module(Application
    URI App.Navigation
    VERSION 1.2
    QML_FILES
        navigation/TopRail.qml
        navigation/SideDrawer.qml
)

Inter-Module Dependencies

Larger applications separate concerns into distinct modules. Cross-module communication relies entirely on unique URIs and versioned imports.

project/
 ├─ CMakeLists.txt
 ├─ AppWindow.qml
 ├─ widgets/
 │   ├─ ProgressBar.qml
 │   └─ ToggleSwitch.qml
 └─ dialogs/
     └─ ConfirmationSheet.qml

Each subdirectory declares its own module descriptor or is registered via separate CMake calls:

qt_add_qml_module(Application
    UI Widgets 1.0
    QML_FILES widgets/ProgressBar.qml widgets/ToggleSwitch.qml
)
qt_add_qml_module(Application
    UI Dialogs 1.0
    QML_FILES dialogs/ConfirmationSheet.qml
)
import QtQuick
import UI.Widgets 1.0
import UI.Dialogs 1.0

ApplicationWindow {
    width: 640
    height: 480
    visible: true
    ProgressBar { anchors.bottom: parent.bottom }
    ConfirmationSheet { anchors.centerIn: parent }
}

Note: Even within the same parent module, singletons or namespaced components require an explicit import statement matching their registered URI to resolve correctly.

Resource Deployment Strategies

Qt provides two primary mechanisms for bundling QML and associated assets, each serving different development and deployment phases.

Filesystem-Driven Loading

During development, QML files remain on the host filesystem. The engine watches for changes, enabling immediate hot-reloading.

qt_add_qml_module(CoreApp
    URI App.Core
    VERSION 1.0
    QML_FILES main_view.qml config_panel.qml
)

C++ initialization uses module URI rather than direct file paths:

QQmlApplicationEngine engine;
engine.loadFromModule("App.Core", "MainView");

Assets referenced in QML use relative or absolute disk paths:

Image { source: "assets/logo_dark.png" }

Binary Resource Compilation

For production releases, assets are compiled into the executable using the Qt Resource Compiler (qrc). Qt treats the resource tree as a virtual filesystem, preserving relative path resolution.

<RCC>
    <qresource prefix="/app_bundle">
        <file>screens/SetupScreen.qml</file>
        <file>images/background.jpg</file>
    </qresource>
</RCC>
qt_add_qml_module(CoreApp
    URI App.Core
    VERSION 1.0
    QML_FILES screens/SetupScreen.qml
    RESOURCES bundle.qrc
)

Consumers access compiled assets using the qrc: scheme:

import QtQuick
import App.Core 1.0

ApplicationWindow {
    id: launcher
    Rectangle {
        anchors.fill: parent
        color: "transparent"
        Image {
            source: "qrc:/app_bundle/images/background.jpg"
            anchors.fill: parent
        }
    }
}

Implementing QML Singletons

Global state or utility objects in QML are typically implemented as singletons. Registration can occur from C++ or be handled natively within the QML build system.

C++ Registration

The engine exposes a QML file as a singleton type via the core registration API:

qmlRegisterSingletonType(QUrl("qrc:/config/GlobalPrefs.qml"),
                         "System.Settings", 1, 0, "GlobalPrefs");

Native QML Singleton

Using pragma Singleton combined with a qmldir entry allows pure QML definitions without C++ boilerplate.

project/
 ├─ CMakeLists.txt
 ├─ AppWindow.qml
 └─ prefs/
     └─ GlobalPrefs.qml
pragma Singleton
import QtQuick

QtObject {
    property bool darkThemeEnabled: true
    property int primaryFontSize: 14
    function resetToDefaults() {
        darkThemeEnabled = true
        primaryFontSize = 14
    }
}

Descriptor entry:

module System.Settings
singleton GlobalPrefs 1.0 GlobalPrefs.qml
import QtQuick
import System.Settings 1.0

ApplicationWindow {
    visible: true
    Text {
        anchors.centerIn: parent
        font.pointSize: GlobalPrefs.primaryFontSize
        text: GlobalPrefs.darkThemeEnabled ? "Night Mode" : "Day Mode"
    }
}

The CMake equivalent requires setting a file property before module declaration:

set_source_files_properties(prefs/GlobalPrefs.qml
    PROPERTIES QT_QML_SINGLETON_TYPE TRUE)
    
qt_add_qml_module(CoreApp
    URI System.Settings
    VERSION 1.0
    QML_FILES prefs/GlobalPrefs.qml
)

Runtimme Component Instantiation

Dynamic UI generation avoids loading all components at startup. Qt provides two idiomatic approaches.

The Loader Element

Best for swapping entire views or heavy components on demand. The element manages memory automatically when the source changes or clears.

ApplicationWindow {
    id: workspace
    Loader {
        id: contentHost
        anchors.fill: parent
    }
    Button {
        text: "Open Analytics"
        onClicked: contentHost.source = "AnalyticsView.qml"
    }
}

Component Factory Method

For creating multiple instances or placing objects at precise coordinates, instantiate a Component definition and call createObject. Manual memory management or parent assignment is required.

ApplicationWindow {
    id: canvas
    Component {
        id: dataNodeFactory
        NodeIndicator {
            radius: 20
            border.width: 2
        }
    }
    Button {
        text: "Add Node"
        onClicked: {
            const newNode = dataNodeFactory.createObject(canvas, {
                "x": 50 + Math.random() * 200,
                "y": 50 + Math.random() * 200,
                "color": "steelblue"
            })
        }
    }
}
Tags: qt6QMLcmake

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.