目录
-
- 一、为什么要做这个管理工具
- 二、整体架构
- 三、思维链路:我是如何串联起所有 Demo 的
-
- 3.1 第一步:统一数据源
- 3.2 第二步:分类标签 + 卡片网格
- 3.3 第三步:C++ 进程管理
- 3.4 第四步:智能路径查找
- 四、踩过的坑
-
- 4.1 个别 Demo 无法启动
- 4.2 Qt Creator 中点击运行,总是启动 qml_drawer
- 4.3 状态指示灯动画停止后不复位
- 五、如何运行
-
- 5.1 构建
- 5.2 启动
- 5.3 分类筛选
- 5.4 运行示例
- 六、运行条件
- 七、适合谁来研究
- 八、总结
一、为什么要做这个管理工具
做 QML 开发的人,时间一长,手里难免攒下一大堆零散的小示例:动画的、控件的、布局的、图表的……每个都是独立的文件夹,独立的 CMakeLists.txt,独立的构建产物。问题是:
- 找起来费劲 —— 想看一个滑块效果,得在几十个 qml_* 文件夹里挨个翻。
- 跑起来麻烦 —— 每次都要在 Qt Creator 里切换构建目标,或者手动找到 build 目录去双击 exe。
- 没有全貌 —— 到底有哪些示例?哪些看过、哪些没看过?完全靠记忆。
所以我做了一个 QML Demo Launcher —— 一个带界面的管理器,把所有子 Demo 按类别组织起来,点击卡片直接启动。
二、整体架构
整个工程的目录结构如下:
qml_demo/
├── CMakeLists.txt ← 顶层构建,自动发现所有子目录
├── app_launcher/ ← 管理器本体
│ ├── CMakeLists.txt
│ ├── main.cpp ← C++ 入口
│ ├── applauncher.h ← 进程管理后端
│ ├── Main.qml ← 主界面
│ ├── components/
│ │ ├── CategoryBar.qml ← 分类标签栏
│ │ ├── DemoCard.qml ← 单个示例卡片
│ │ └── StatusBar.qml ← 底部状态栏
│ └── models/
│ └── DemoModel.qml ← 全部示例的数据注册表
├── qml_animation/ ← 子 Demo(各自独立)
├── qml_button_collection/
├── qml_charts/
├── …(目前共计55个,还会扩展)
架构上采用了 C++ 后端 + QML 前端 的分离模式:
- C++ 层(applauncher.h):负责进程管理——查找可执行文件路径、启动子进程、监听状态变化。
- QML 层(Main.qml + 组件):负责界面渲染——分类过滤、卡片展示、状态指示。
两者通过 setContextProperty 桥接,QML 侧直接调用 appLauncher.launch("qml_animation") 就能启动一个子 Demo。
三、思维链路:我是如何串联起所有 Demo 的
3.1 第一步:统一数据源
所有 Demo 的元信息集中在一个文件里 —— models/DemoModel.qml:
ListModel {
readonly property var categories: ["全部", "动画", "控件", "布局", "列表", "输入", "图像", "基础", "图表"]
readonly property var categoryColors: ({
"全部": "#34495e",
"动画": "#e74c3c",
"控件": "#3498db",
"布局": "#2ecc71",
"列表": "#9b59b6",
"输入": "#f39c12",
"图像": "#1abc9c",
"基础": "#95a5a6",
"图表": "#e67e22"
})
Component.onCompleted: {
// 动画相关
append({ name: "qml_animation", title: "动画系统", category: "动画", desc: "属性动画、顺序/并行动画、动画控制" })
append({ name: "qml_animatedimage", title: "动画图片", category: "动画", desc: "GIF播放、播放控制、帧进度" })
// … 共 48 条记录
}
}
每个条目四个字段:
- name —— 文件夹名,也是可执行文件名的核心部分
- title —— 中文标题,显示在卡片上
- category —— 所属分类
- desc —— 一句话说明这个 Demo 演示什么
3.2 第二步:分类标签 + 卡片网格
界面从上到下三段式布局:
ColumnLayout {
// 1. 标题栏
Text { text: "QML Demo Launcher"; font.pixelSize: 28; font.bold: true }
// 2. 分类标签栏
CategoryBar {
categories: demoModel.categories
selectedCategory: root.selectedCategory
onCategoryClicked: function(category) {
root.selectedCategory = (root.selectedCategory === category) ? "全部" : category
}
}
// 3. 卡片网格(Flow 自动换行)
Flickable {
Flow {
Repeater {
model: {
var items = []
for (var i = 0; i < demoModel.count; i++) {
var item = demoModel.get(i)
if (root.selectedCategory === "全部" || item.category === root.selectedCategory) {
items.push(item)
}
}
return items
}
DemoCard {
onClicked: appLauncher.launch(modelData.name)
}
}
}
}
// 4. 底部状态栏
StatusBar { totalCount: demoModel.count; runningCount: root.getRunningCount() }
}
点击分类标签时,selectedCategory 变化,Repeater 的 model 表达式重新求值,卡片自动过滤。再次点击同一标签则回到"全部"。
3.3 第三步:C++ 进程管理
核心在 applauncher.h 的 launch() 方法:
Q_INVOKABLE void launch(const QString &appName) {
// 防重复启动
if (m_processes.contains(appName) &&
m_processes[appName]->state() == QProcess::Running) {
return;
}
QString appPath = getExecutablePath(appName);
QProcess *process = new QProcess(this);
m_processes[appName] = process;
// 设置工作目录
QFileInfo fileInfo(appPath);
process->setWorkingDirectory(fileInfo.absolutePath());
// 监听启动、结束、错误三个信号
connect(process, &QProcess::started, this, [this, appName]() {
emit demoStarted(appName);
});
connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
this, [this, appName](int exitCode, QProcess::ExitStatus) {
emit demoFinished(appName, exitCode);
m_processes.remove(appName);
sender()->deleteLater();
});
connect(process, &QProcess::errorOccurred, this,
[this, appName](QProcess::ProcessError) {
emit demoError(appName);
m_processes.remove(appName);
sender()->deleteLater();
});
process->start(appPath);
}
QML 侧通过 Connections 监听这三个信号,实时更新卡片的状态指示灯颜色。
3.4 第四步:智能路径查找
这是最折腾的部分。CMake 构建后,子 Demo 的可执行文件可能在好几个位置,命名也有两种风格(appqml_xxx 和 app_qml_xxx)。所以 getExecutablePath() 会尝试 8 种路径:
Q_INVOKABLE QString getExecutablePath(const QString &appName) {
QString suffix = "";
#ifdef Q_OS_WIN
suffix = ".exe";
#endif
QString baseDir = QCoreApplication::applicationDirPath();
QStringList possiblePaths = {
baseDir + "/../app" + appName + suffix, // 同级目录 appqml_xxx
baseDir + "/../app_" + appName + suffix, // 同级目录 app_qml_xxx
baseDir + "/app" + appName + suffix, // 同目录
baseDir + "/app_" + appName + suffix,
baseDir + "/../" + appName + "/app" + appName + suffix, // 子目录
baseDir + "/../" + appName + "/app_" + appName + suffix,
baseDir + "/" + appName + "/app" + appName + suffix,
baseDir + "/" + appName + "/app_" + appName + suffix,
};
for (const QString &path : possiblePaths) {
if (QFile::exists(path)) {
return QDir::cleanPath(path);
}
}
return QDir::cleanPath(baseDir + "/../app" + appName + suffix);
}
这样无论构建目录结构怎么变,总能找到对的那一个。
四、踩过的坑
4.1 个别 Demo 无法启动
现象:点击某些卡片,状态灯变红,提示"启动失败"。
排查过程:
解决方案:在 getExecutablePath() 里同时搜索两种命名模式,哪个存在用哪个。
4.2 Qt Creator 中点击运行,总是启动 qml_drawer
现象:在 Qt Creator 里点绿色三角运行,出来的永远是 qml_drawer 的界面,而不是 app_launcher。
排查过程:
解决方案:在 Qt Creator 的"项目 → 构建设置"中,手动将 app_launcher 设为活动目标。 
我故意将此项目命名为app_launcher,由于首字母排在第一位,默认会被设置为启动项。
4.3 状态指示灯动画停止后不复位
现象:子 Demo 运行结束后,指示灯的闪烁动画停了,但 opacity 卡在 0.3,看起来是暗的。
原因:SequentialAnimation 在 running 变为 false 时不会自动将属性恢复到初始值。
解决:在 DemoCard.qml 里加了一个 Connections 监听 isRunning 变化:
Connections {
target: root
function onIsRunningChanged() {
if (!root.isRunning) {
blinkAnimation.stop()
statusIndicator.opacity = 1.0 // 手动复位
}
}
}
五、如何运行
5.1 构建
确保已安装 Qt 6.5+ 和 CMake 3.16+,然后用QtCreator找到CMakeList,然后打开即可。

构建完成后,所有子项目(包括 app_launcher)的可执行文件都在 build/ 目录下。
5.2 启动
确保 IDE 中将 app_launcher 设为启动目标后,然后点击运行。
启动后的主界面:

5.3 分类筛选
点击顶部的分类标签(动画、控件、布局等),快速过滤出对应类别的示例。再次点击同一标签回到全部视图:

5.4 运行示例
点击任意卡片,该示例的可执行文件会被启动。卡片右上角的状态指示灯会实时变化:
| 灰色 | 未查阅 |
| 蓝色 | 运行中(带闪烁动画) |
| 绿色 | 已查阅(正常退出) |
| 红色 | 启动失败 |

底部状态栏会显示总数和当前运行中的数量。
六、运行条件
- 操作系统:目前仅在 Windows 11 上编译和适配
- Qt 版本:Qt 6.8 / Qt 6.11 均已验证
- 编译器:MinGW1310_64(MSVC 2022 未测试)
- 构建工具:CMake 3.16+
如果你在其他平台(Linux / macOS)上遇到路径问题,可能需要调整 applauncher.h 中的路径查找逻辑。
七、适合谁来研究
1. 对 QML 感兴趣的开发者
这个合集覆盖了 QML 的绝大部分常用组件和模式 —— 从最基础的 Hello World 到粒子系统、图表、表格,每个示例都是独立可运行的最小案例。比起翻 Qt 官方文档,直接看代码跑效果要直观得多。
2. 用 AI 生成 QML 代码但老出错的人
AI 生成的 QML 代码经常有两类问题:一是用了不存在的属性或信号,二是组件嵌套关系不对。这个合集里的每个示例都经过实际编译验证,可以作为"正确答案"来对照。遇到 AI 给的代码跑不通,来这里找个类似的看看差距在哪。
3. QML 入门学习者
每个示例的代码量都很小(通常 50~200 行),专注于演示一个知识点。没有复杂的项目结构干扰,适合从 qml_hello 开始,按分类逐步深入。
八、总结
QML Demo Launcher 本质上解决的是一个"碎片化管理"问题——当你手头有几十个独立的小示例,统一入口和分类浏览就变成了刚需。整个项目的核心思路并不复杂:一份数据源驱动整个界面,C++ 负责干活(进程管理),QML 负责展示(卡片和动画)。真正花时间的是那些"最后一公里"的细节——候选路径的查找、状态灯动画的复位、Qt Creator 启动目标的配置。
源码地址:QML 示例合集管理器





