Loader 组件的加载是异步过程,如果路径写错、文件不存在、或者组件里语法错误,都可能加载失败。
这篇的两个示例解决两个实际问题:怎么监听加载状态并反馈给用户,怎么用同一个 Loader 在多个页面文件之间切换出标签页效果。
- 加载状态 --- 监听
status从Null到Error的完整变化,正确与错误的加载给不同提示 - 动态标签页 --- 点按钮切换
source指向不同页面文件,底部显示当前加载的是谁
加载状态:监听 status 四态

两个按钮分别加载"存在的组件"和"不存在的组件",下方一行文字实时显示当前状态,并按成功/失败切换颜色。
演示代码
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: loader.source = "component/ExistingComponent.qml"
}
Button {
text: "加载无效组件"
onClicked: loader.source = "component/NonExistentComponent.qml"
}
}
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
onStatusChanged: {
if (status === Loader.Error) {
statusText.color = "#e74c3c"
} else if (status === Loader.Ready) {
statusText.color = "#2ecc71"
} else {
statusText.color = "#333"
}
}
}
}
Text {
id: statusText
Layout.fillWidth: true
text: {
switch (loader.status) {
case Loader.Null: return "组件未加载"
case Loader.Loading: return "正在加载..."
case Loader.Ready: return "加载完成"
case Loader.Error: return "加载错误,无效组件"
default: return ""
}
}
font.pointSize: 11
color: "#333"
}
}
}
关键逻辑解析
status 是加载过程的完整状态机 。四种取值按生命周期排:Loader.Null(无 source)、Loader.Loading(正在解析)、Loader.Ready(成功)、Loader.Error(失败)。这个示例把状态直接映射成文字展示,是最直观的用法。
加载本地资源时 Loading 往往一闪而过 。QML 文件在资源系统里属于本地加载,多数时候同步完成,status 直接跳到 Ready 或 Error,看不到中间的 Loading。Loading 主要在 asynchronous: true 或网络加载时才明显。
错误来源不止"文件不存在" 。source 指向不存在的文件会进 Error,组件文件里写了非法属性、引用了不存在的类型同样进 Error。实际排查时可以看控制台输出,QML 引擎会把具体失败原因打出来,界面上只负责提示"出错"。
颜色反馈写在 onStatusChanged 。状态一变化就按 Error 红、Ready 绿、其它灰改提示文字颜色,比在 Text 里做三重绑定更集中,也方便以后在状态变化时统一挂日志、统计。
这个 demo 的"加载无效组件"是故意触错 。点它之后 Loader.Error,控制台会打印一条加载失败日志,属于演示预期行为,方便你直观看到错误态长什么样。
动态标签页:一个 Loader 换多个页面

三个按钮对应三个页面文件,点击后右侧面板切换到对应页面,底部显示当前文件名。这就是"用 Loader 手搓标签页"。
演示代码
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: "页面1"
onClicked: loader.source = "component/Page1.qml"
}
Button {
text: "页面2"
onClicked: loader.source = "component/Page2.qml"
}
Button {
text: "页面3"
onClicked: loader.source = "component/Page3.qml"
}
}
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 {
text: "当前: " + (loader.source ? loader.source.toString().split("/").pop() : "无")
font.pointSize: 11
color: "#666"
}
}
}
被切换的三个页面文件结构相同,只有颜色和文字不同,例如 component/Page1.qml:
qml
import QtQuick
Rectangle {
color: "#d5e8f0"
radius: 6
Text {
anchors.centerIn: parent
text: "页面 1"
font.pixelSize: 24
font.bold: true
color: "#2c3e50"
}
}
关键逻辑解析
切换 source 就是"先销毁、再创建" 。给 loader.source 赋一个新文件,旧页面实例立刻销毁,新页面创建。所以这种标签页不保留页面状态 ------你在页面 2 里输入的内容,切走再切回来就没了。这是和 StackLayout(页面常驻、状态保留)最本质的区别,选哪种取决于页面状态要不要留存。
相对路径自动对齐 。三个按钮写的都是 "component/PageN.qml",和上一类的 source 一样基于当前文件目录解析,页面文件放子目录 component/ 下统一管理。
底部文件名怎么取 。loader.source 存的是 URL,toString() 后是 qrc:/component/Page1.qml 这样的完整资源地址,用 split("/").pop() 取最后一段得到文件名。loader.source 为空时(还没加载过)直接显示"无"。
页面根元素依然不写尺寸 。Loader 用 anchors.fill 占满灰色面板,加载进来的 Rectangle 自动被拉伸到同样大小,页面文件只关心内部内容布局。
Loader 标签页和 StackLayout 该选谁
| Loader 换 source | StackLayout | |
|---|---|---|
| 未显示的页面 | 不创建,省内存 | 常驻,全部创建 |
| 页面状态 | 切换即丢失 | 一直保留 |
| 首次切换 | 要现场创建,可能略慢 | 无额外开销 |
| 适合 | 页面重、数量多、状态无所谓 | 表单等需要保留输入的场景 |
页面本身很重或数量多,用 Loader 按需创建更省资源;表单、向导这类切走不能丢输入的场景,用 StackLayout。
运行验证
- Qt Creator 打开
qml_loader/CMakeLists.txt,按Ctrl+R运行; - 左侧点「加载状态」,先点「加载有效组件」,再点「加载无效组件」看提示文字颜色变化;
- 点「动态标签页」,三个按钮来回切,观察底部文件名跟着变。
扩展复用方向
- 把
status和加载失败日志接起来,出错时用console.error带上loader.source,方便定位是哪个文件坏了; - 在换页前用
onStatusChanged挂一个转圈指示,Loading期间显示BusyIndicator,避免快速点切时闪白; - 标签页按钮可以换成
TabBar,把当前索引和source列表用下标关联,就得到一个状态不保留的轻量 Tab 容器。
已验证环境:
- Qt 版本:Qt 6.8.2 / Qt 6.11.1
- 操作系统:Windows 11
- GitHub:QML-Minimal-Demos/qml_loader