上一类用 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()
}
}
关键逻辑解析
Loader 和 createObject 是两种粒度的动态化 。Loader 管理"一个占位区域",每次只实例化一个组件;createObject 把组件模板实例化到任意父对象下,可以创建任意多个、位置自己定。要往列表、画布、聊天流里动态加条目,用的是后者。
Qt.createComponent 返回的是模板对象 ,不是实例。它和 QML 里的 Component 是同一个东西,拿到后要检查 component.status === Component.Ready 再 createObject,异步加载时 createObject 要等 onStatusChanged 里做;本地资源通常同步完成,直接创建即可。
createObject 第二个参数是初始属性表 。用 { "x": ..., "y": ... } 在创建瞬间把位置传进去,等价于实例化后再逐个赋值,但避免中间态。这里 Math.random() * (container.width - 50) 保证小方块不会超出右边界,外面再用 Math.max(0, ...) 兜底容器还没布局完成的空窗。
删除实例调 destroy() 。destroy() 是异步的:当前 JavaScript 块执行完才真正销毁,所以 ColorRect 自己的 onClicked 里 parent.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 代替这些原因,把"等待 → 就绪 → 挂载"的骨架先搭出来。
等待反馈的核心是 BusyIndicator 的 running 和 visible 绑定 。点按钮把 running 置 true,圈开始转;visible: running 让它在不转时完全不占位。Timer 触发后把 source 挂上、running 置 false,圈停、组件出现,一气呵成。
Loader 的占位提示没变 。面板中间的「点击上方按钮加载」仍绑定 loader.status !== Loader.Ready,组件加载完成自动让位------这层逻辑和"加载文件"那篇完全一样,可以复用。
真实的异步加载不用 Timer,用 Loader.asynchronous 。把 loader.asynchronous = true 后,QML 引擎在后台线程解析组件文件,期间 status 会停在 Loading,界面保持流畅。配合 status === Loader.Loading 显示 BusyIndicator,就是标准做法------Timer 版的价值在于,加载对象不是 QML 文件而是"外部数据"时,你也需要这样一段手动控制的等待逻辑。
运行验证
- Qt Creator 打开
qml_loader/CMakeLists.txt,按Ctrl+R运行; - 左侧点「动态创建控件」,连点「创建矩形」,点面板里的小方块单独删除,再点「清空」看数量归零;
- 点「延迟加载」,观察转圈 2 秒后组件出现。
扩展复用方向
- 动态创建聊天消息气泡、日志行时,用
ListView+ 数据模型而不是createObject堆叠,性能更好;createObject适合数量小、位置自由的场景; - 给
createObject创建的对象统一挂Component.onDestruction清理事件监听,防止内存泄漏; - 把
Timer延迟换成真实异步(数据请求完成再设source),BusyIndicator的骨架可以直接沿用。
已验证环境:
- Qt 版本:Qt 6.8.2 / Qt 6.11.1
- 操作系统:Windows 11
- GitHub:QML-Minimal-Demos/qml_loader