QML Loader:动态创建控件与延迟加载

上一类用 Loader 管理"替换区域",一个容器同一时间只有一个页面。但有些场景反过来:要在同一个容器里不断增加新实例,或者加载一个很重的组件时先给用户一个等待反馈。这篇的两个示例分别解决这两件事。

  • 动态创建控件 --- Qt.createComponent + createObject 往容器里不断塞新实例,点实例还能删掉它
  • 延迟加载 --- 用 Timer 模拟耗时加载,等待期间显示 BusyIndicator,完成后再挂载真正的组件

动态创建控件

点「创建矩形」,面板里随机位置出现一个半透明彩色小方块;点小方块本身可以删除它;「清空」一键移除全部。

演示代码

qml 复制代码
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

FadeInAnimation {
    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 20
        spacing: 15

        // ... 省略标题组件 TitleSeparator ...

        RowLayout {
            Layout.fillWidth: true
            spacing: 10

            Button {
                text: "创建矩形"
                onClicked: {
                    var component = Qt.createComponent("component/ColorRect.qml");
                    if (component.status === Component.Ready) {
                        var object = component.createObject(container, {
                            "x": Math.random() * Math.max(0, container.width - 50),
                            "y": Math.random() * Math.max(0, container.height - 50)
                        });
                        if (object === null) {
                            console.log("Error creating object");
                        }
                    } else if (component.status === Component.Error) {
                        console.log("Error loading component:", component.errorString());
                    }
                }
            }

            Button {
                text: "清空"
                onClicked: {
                    var children = container.children.slice();
                    for (var i = 0; i < children.length; i++) {
                        children[i].destroy();
                    }
                }
            }
        }

        Rectangle {
            id: container
            Layout.fillWidth: true
            Layout.fillHeight: true
            color: Qt.rgba(0.95, 0.95, 0.95, 1)
            radius: 4
            clip: true
        }

        Text {
            text: "子控件数量: " + container.children.length
            font.pointSize: 11
            color: "#666"
        }
    }
}

被创建的 component/ColorRect.qml 自带动画颜色,并且支持点自己删除:

qml 复制代码
import QtQuick

Rectangle {
    width: 50
    height: 50
    radius: 6
    color: Qt.rgba(Math.random(), Math.random(), Math.random(), 0.35)

    MouseArea {
        anchors.fill: parent
        onClicked: parent.destroy()
    }
}

关键逻辑解析

LoadercreateObject 是两种粒度的动态化Loader 管理"一个占位区域",每次只实例化一个组件;createObject 把组件模板实例化到任意父对象下,可以创建任意多个、位置自己定。要往列表、画布、聊天流里动态加条目,用的是后者。

Qt.createComponent 返回的是模板对象 ,不是实例。它和 QML 里的 Component 是同一个东西,拿到后要检查 component.status === Component.ReadycreateObject,异步加载时 createObject 要等 onStatusChanged 里做;本地资源通常同步完成,直接创建即可。

createObject 第二个参数是初始属性表 。用 { "x": ..., "y": ... } 在创建瞬间把位置传进去,等价于实例化后再逐个赋值,但避免中间态。这里 Math.random() * (container.width - 50) 保证小方块不会超出右边界,外面再用 Math.max(0, ...) 兜底容器还没布局完成的空窗。

删除实例调 destroy()destroy() 是异步的:当前 JavaScript 块执行完才真正销毁,所以 ColorRect 自己的 onClickedparent.destroy() 很安全。销毁后对象就消失了,不能再访问它的属性。

清空时先 slice() 再遍历container.children 是实时数组,边遍历边 destroy() 会改变数组本身导致跳项,复制一份快照再删才是安全的写法。

底部数字实时反映数量container.children.length 绑定到 Text,每次创建或删除都会自动刷新,能直观看到"现在挂着几个实例"。

延迟加载:等待期间给反馈

点「延迟加载(2秒)」,先转 2 秒圈,时间到了灰色面板里才出现真正的"重组件"。

演示代码

qml 复制代码
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

FadeInAnimation {
    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 20
        spacing: 15

        // ... 省略标题组件 TitleSeparator ...

        Button {
            text: "延迟加载(2秒)"
            Layout.fillWidth: true
            onClicked: {
                busyIndicator.running = true
                delayTimer.start()
            }
        }

        BusyIndicator {
            id: busyIndicator
            running: false
            visible: running
            Layout.alignment: Qt.AlignHCenter
        }

        Rectangle {
            Layout.fillWidth: true
            Layout.fillHeight: true
            color: Qt.rgba(0.95, 0.95, 0.95, 1)
            radius: 4

            Loader {
                id: loader
                anchors.fill: parent
                anchors.margins: 10
            }

            Text {
                anchors.centerIn: parent
                text: "点击上方按钮加载"
                font.pointSize: 11
                color: "#999"
                visible: loader.status !== Loader.Ready
            }
        }
    }

    Timer {
        id: delayTimer
        interval: 2000
        onTriggered: {
            loader.source = "component/HeavyComponent.qml"
            busyIndicator.running = false
        }
    }
}

等 2 秒后加载的 component/HeavyComponent.qml,里面放了一个转个不停的方块,模拟"构造起来很耗时"的组件:

qml 复制代码
import QtQuick

Rectangle {
    color: "#666"
    radius: 6

    Column {
        anchors.centerIn: parent
        spacing: 15

        Text {
            text: "耗时组件已加载完成"
            font.pixelSize: 16
            color: "#fff"
            anchors.horizontalCenter: parent.horizontalCenter
        }

        Rectangle {
            width: 50
            height: 50
            color: "#fff"
            radius: 6
            anchors.horizontalCenter: parent.horizontalCenter

            RotationAnimation on rotation {
                from: 0
                to: 360
                duration: 3000
                loops: Animation.Infinite
            }
        }
    }
}

关键逻辑解析

Timer 在这里模拟"异步耗时" 。真实场景里组件慢的原因往往是:文件在网络上要下载、组件里初始化逻辑重、依赖的后台数据没就绪。这个 demo 用 2 秒 Timer 代替这些原因,把"等待 → 就绪 → 挂载"的骨架先搭出来。

等待反馈的核心是 BusyIndicatorrunningvisible 绑定 。点按钮把 runningtrue,圈开始转;visible: running 让它在不转时完全不占位。Timer 触发后把 source 挂上、runningfalse,圈停、组件出现,一气呵成。

Loader 的占位提示没变 。面板中间的「点击上方按钮加载」仍绑定 loader.status !== Loader.Ready,组件加载完成自动让位------这层逻辑和"加载文件"那篇完全一样,可以复用。

真实的异步加载不用 Timer,用 Loader.asynchronous 。把 loader.asynchronous = true 后,QML 引擎在后台线程解析组件文件,期间 status 会停在 Loading,界面保持流畅。配合 status === Loader.Loading 显示 BusyIndicator,就是标准做法------Timer 版的价值在于,加载对象不是 QML 文件而是"外部数据"时,你也需要这样一段手动控制的等待逻辑。

运行验证

  1. Qt Creator 打开 qml_loader/CMakeLists.txt,按 Ctrl+R 运行;
  2. 左侧点「动态创建控件」,连点「创建矩形」,点面板里的小方块单独删除,再点「清空」看数量归零;
  3. 点「延迟加载」,观察转圈 2 秒后组件出现。

扩展复用方向

  • 动态创建聊天消息气泡、日志行时,用 ListView + 数据模型而不是 createObject 堆叠,性能更好;createObject 适合数量小、位置自由的场景;
  • createObject 创建的对象统一挂 Component.onDestruction 清理事件监听,防止内存泄漏;
  • Timer 延迟换成真实异步(数据请求完成再设 source),BusyIndicator 的骨架可以直接沿用。

已验证环境

相关推荐
Quz1 小时前
QML Loader:组件切换与属性传递
qt
老赵的博客1 小时前
c++QT之动态库加载常见报错
c++·qt
Quz1 天前
QML Loader:从文件加载组件、加载内联组件
qt
Quz1 天前
QML Loader: 状态监听与动态标签页
qt
似水এ᭄往昔1 天前
【Qt】--常用控件(显示类控件)
开发语言·qt
慧都小项1 天前
从 Figma 到 Qt:Qtitan 能减少哪些界面还原工作?
前端·qt·嵌入式·figma·图形界面·工业界面·qtitan
用户122003879001 天前
Qt学习之绘图入门练习
qt
西西弗Sisyphus1 天前
Qt 实现一个 Windows 启动项查看器
开发语言·windows·qt
郝学胜-神的一滴2 天前
Qt 高级编程 045:坐标体系深度实战
开发语言·c++·windows·python·qt·程序人生