欢迎光临
我们一直在努力

QML 示例合集管理器:用一个界面统管全部Demo

目录

    • 一、为什么要做这个管理工具
    • 二、整体架构
    • 三、思维链路:我是如何串联起所有 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 无法启动

现象:点击某些卡片,状态灯变红,提示"启动失败"。

排查过程:

  • 先看 qDebug 输出,发现路径查找返回了默认路径,说明 8 种路径都没命中。
  • 去 build 目录一看,有的子项目生成的 exe 叫 appqml_xxx.exe,有的叫 app_qml_xxx.exe —— 命名不统一。
  • 原来是不同子项目的 CMakeLists.txt 里 qt_add_executable 的目标名不一致。
  • 解决方案:在 getExecutablePath() 里同时搜索两种命名模式,哪个存在用哪个。

    4.2 Qt Creator 中点击运行,总是启动 qml_drawer

    现象:在 Qt Creator 里点绿色三角运行,出来的永远是 qml_drawer 的界面,而不是 app_launcher。

    排查过程:

  • 打开 Qt Creator 的"项目 → 运行设置",发现默认启动目标被自动设成了 qml_drawer。
  • 原因是 Qt Creator 会按字母顺序排列所有构建目标,而 qml_drawer 恰好排在第一个。
  • 虽然顶层 CMakeLists.txt 里已经写了 set_property(DIRECTORY APPEND PROPERTY VS_STARTUP_PROJECT app_launcher),但这条语句只对 Visual Studio 生效,Qt Creator 不认这个属性。
  • 解决方案:在 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 示例合集管理器

    在这里插入图片描述

    赞(0)
    未经允许不得转载:171主机测评 » QML 示例合集管理器:用一个界面统管全部Demo
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址