本章回答三个问题:QML 从哪来、是什么、为什么值得学。读完你将对 QML 在整个 Qt 生态中的位置、它与 Widgets/JavaScript/HTML 的关系、以及它背后的渲染机制有清晰认识。
1. QML 的历史与发展
| 时间 | 里程碑 |
|---|---|
| 2009 | Nokia 收购 Trolltech(Qt 原厂)后提出 Qt Quick 构想 |
| 2010 | Qt 4.7 引入 QML(Qt Quick 1),主打"快速开发富交互 UI" |
| 2012 | Qt 5.0 发布 Qt Quick 2,基于 GPU 场景图(Scene Graph)渲染,性能大幅提升 |
| 2017 | Qt 5.9 LTS:Qt Quick Controls 2 成熟,成为新控件标准 |
| 2019 | Qt 5.12 LTS:QML 支持 required、Qt.callLater 等现代语法 |
| 2021 | Qt 6.0 发布:QML 引擎升级为 QJSEngine,移除 Controls 1 与 Qt Quick 1 |
| 2023~ | Qt 6.5/6.8 LTS:QML 编译(qmltc)、QML 模块化、工具链成熟 |
QML 的诞生背景:传统 Qt Widgets 用 C++ 描述 UI,开发速度慢、动效能力弱;移动互联网时代需要一种"像 Web 一样快速迭代、又拥有原生性能"的方案。QML 就是 Qt 对这一需求的回答。
2. 什么是 QML
QML(Qt Meta-Object Language)是 Qt 提供的一种声明式UI 描述语言。它的语法基于 JSON 风格扩展而来,用来描述:
- 界面由哪些对象组成(对象树);
- 每个对象的属性是什么(颜色、尺寸、文本……);
- 对象之间如何联动(属性绑定);
- 用户交互如何响应(信号处理器)。
QML 的核心哲学:你描述"界面应该长什么样",而不是"一步一步怎么画出界面"。
// 声明式:描述"是什么" Rectangle { width: 200; height: 100 color: "lightblue" Text { anchors.centerIn: parent text: "Hello QML" } }对应命令式(伪代码):
auto rect = new QWidget(); rect->setGeometry(0, 0, 200, 100); rect->setStyleSheet("background: lightblue;"); auto label = new QLabel("Hello QML", rect); label->move(50, 40);声明式的好处:
- 代码即设计稿:阅读成本低,UI 结构一目了然。
- 自动联动:属性绑定让数据与界面自动同步,少写大量 updateUI() 样板代码。
- 易组合复用:任意 .qml 文件都能当组件用。
- 动效友好:动画、状态、过渡都是声明式语言的一部分。
3. QML 引擎如何工作(架构视角)
一个 QML 文件的生命周期:
关键组件:
| 组件 | 作用 |
|---|---|
| QQmlEngine | QML 运行时核心:类型注册表、import 管理、JS 引擎持有 |
| QQmlApplicationEngine | 便捷封装:加载 qrc 中的 QML 并管理根对象 |
| QQmlComponent | 代表一个可实例化的 QML 类型(文件或内联 Component) |
| QQmlContext | 对象的作用域上下文:提供 id 解析与 context property |
| Scene Graph | 将 QML 对象树转换为 GPU 绘制指令(Qt Quick 2) |
注意:QML 不是"解释执行"的简单脚本。Qt6 默认在构建时用 qmlcachegen 预编译,运行时大部分 QML 已编译为字节码/C++,性能接近原生。
4. QML 与 Qt Widgets 的对比
4.1 详细对比表
| 维度 | Qt Widgets | QML / Qt Quick |
|---|---|---|
| UI 描述语言 | C++(代码式) | QML(声明式)+ JavaScript |
| 编程范式 | 命令式 + 对象树 | 声明式对象树 + 属性绑定 |
| 渲染 | CPU(QPainter),标量绘制 | GPU 场景图(OpenGL/Vulkan/D3D/Metal) |
| 动画 | 需 QPropertyAnimation 等类,样板多 | 内建动画/Behavior/Transition,几行搞定 |
| 样式 | QSS 样式表(类 CSS) | 委托(delegate)体系 + Style 附加属性 |
| 控件生态 | 非常成熟(QTableView、QTreeView、QMainWindow…) | Controls 2 较全,复杂表格需自定义 |
| 移动端支持 | 弱(桌面向) | 官方支持 Android/iOS |
| 嵌入式 | 支持但资源占用高 | 更轻量,Qt for MCU 使用 QML 子集 |
| 性能(大量数据表格) | QTableView 万行级无压力 | 需要虚拟化/委托优化,行数过大吃力 |
| 学习曲线 | C++ 门槛 | 语法简单,但绑定/作用域概念需时间 |
| 开发效率 | 中 | 高(尤其 UI 迭代与原型) |
| 与 C++ 集成 | 天然 | 通过 Q_PROPERTY/Q_INVOKABLE/注册类型 |
| 热重载 | 需第三方/无 | 官方工具原生支持 |
4.2 怎么选?(决策建议)
| 项目特征 | 推荐 |
|---|---|
| 桌面工具、代码编辑器、复杂报表 | Widgets(或 QML + 内嵌 C++ 表格) |
| 移动 App、平板 | QML |
| 车载 HMI、嵌入式仪表 | QML |
| 现代 UI、强动效、品牌定制界面 | QML |
| 团队全是 C++ 老手、无 JS 经验 | 两者皆可,新项目建议学 QML |
| 存量 Widgets 项目 | 不强制迁移;可局部用 QQuickWidget 嵌入 QML |
重要:两者可以共存。在 QWidget 应用里嵌入 QQuickWidget,或在 QML 中通过 QWidget::createWindowContainer(不推荐)都可行;大型商业软件常见"Widgets 外壳 + QML 内页"混合架构。
5. QML 的适用场景
5.1 移动端应用
- Qt Quick Controls 2 提供 iOS/Android 原生观感控件(Material 风格)。
- 触摸事件、多点触控、屏幕旋转都原生支持。
- 打包工具:Qt 官方提供 Android APK / iOS 的构建链。
5.2 嵌入式与汽车 HMI
- 仪表盘、中控屏、后视镜、智能家居面板。
- Qt for MCU 提供 QML 子集(QML for MCU)用于微控制器。
- 场景图渲染对嵌入式 GPU 友好;也支持纯软件渲染兜底。
5.3 桌面现代 UI
- 快速原型、可视化工具、设计工具、音视频播放器皮肤。
- 与 C++ 后端(网络、数据库、算法)组合,UI 层用 QML。
5.4 数据可视化与仪表板
- 与 Qt Charts(QML 版)或自定义 Canvas/Shapes 结合。
- 实时刷新的监控面板:QML 属性绑定天然适合"数据变→界面变"。
5.5 不适合的场景
- 需要原生级文本编辑(代码编辑器)——Qt 建议 QScintilla/QPlainTextEdit(Widgets)。
- 百万行级表格——用 QTableView 或 QML 虚拟化方案。
- 深度系统集成(托盘、全局快捷键、原生对话框)——需要 C++/平台代码。
6. QML / JavaScript / HTML 的关系(彻底讲清)
6.1 三者是什么
| QML | JavaScript (JS) | HTML | |
|---|---|---|---|
| 本质 | 声明式 UI 语言 | 脚本编程语言 | 网页标记语言 |
| 语法来源 | JSON 扩展 | ECMAScript 标准 | SGML/XML 家族 |
| 谁在运行 | QML 引擎(C++ 对象系统) | 内嵌 JS 引擎(Qt5 V4 / Qt6 QJSEngine) | 浏览器 |
| 作用 | 描述对象树与属性 | 编写逻辑、表达式、函数 | 描述网页结构 |
6.2 QML 中的 JS 是什么地位
QML 中到处是 JS:
Item { property int count: 0 property string label: "共 " + count + " 个" // JS 表达式 function format(v) { return v.toFixed(2) } // JS 函数 MouseArea { onClicked: { // JS 代码块 parent.count += 1 console.log(parent.label) } } }JS 在 QML 中的角色:
- 表达式:属性绑定右侧几乎都是 JS 表达式。
- 函数:function 声明(文档级)与 .js 文件(import "utils.js" as Utils)。
- 信号处理器:onXxx: { ... } 里的语句就是 JS。
- 数据操作:数组、对象、字符串处理。
6.3 QML 中的 JS 限制(重要)
QML 的 JS不是浏览器 JS:
- ❌ 没有 document / window / DOM API。
- ❌ 没有 CSS。
- ❌ 没有浏览器事件模型(addEventListener 不适用,用信号)。
- ❌ Qt6 没有 setTimeout(Qt5 有但非标准);用 Timer。
- ❌ Qt6 中 QML 内不再提供 XMLHttpRequest 便捷访问;用 C++ 网络层。
- ✅ 支持大部分 ECMAScript(Qt6 支持 ES6+:箭头函数、模板字符串、let/const、Promise、class 等)。
6.4 与 HTML 的对比示例
<!-- HTML --> <div class="card" onclick="doSomething()"> <p>Hello</p> </div>// QML Rectangle { id: card MouseArea { anchors.fill: parent; onClicked: doSomething() } Text { text: "Hello" } }相似点:层级结构、属性赋值、事件回调。不同点:QML 对象是强类型 C++ 对象(属性有类型、有信号),HTML 元素是文档节点;QML 没有"CSS 选择器"式全局样式,样式就是属性本身。
7. Qt QML / Qt Quick / QML 三者的精确关系
| 名称 | 是什么 | 例子 |
|---|---|---|
| QML | 语言本身(语法、对象、绑定) | property int x: 0 |
| Qt QML | 提供语言运行时的 C++ 模块 | QQmlEngine、QQmlComponent |
| Qt Quick | 基于 QML 的 UI 元素库 | Item、Rectangle、ListView |
口语中"QML 程序"通常指"用 Qt Quick 写的 QML 程序"。
8. Qt Quick 模块结构(Qt6 视角)
| 模块 | import 语句 | 用途 |
|---|---|---|
| QtQuick | import QtQuick | 基础元素、定位器、动画、状态、模型视图 |
| QtQuick.Window | import QtQuick.Window | Window、Screen、Dialog(窗口级) |
| QtQuick.Controls | import QtQuick.Controls | 现成控件(Button/TextField/ComboBox/TabBar…) |
| QtQuick.Layouts | import QtQuick.Layouts | RowLayout/ColumnLayout/GridLayout |
| QtQuick.Dialogs | import QtQuick.Dialogs | 文件/颜色/字体对话框 |
| QtQuick.Shapes | import QtQuick.Shapes | 矢量路径绘制(SVG 式) |
| QtQuick.Particles | import QtQuick.Particles | 粒子系统 |
| QtQuick.Effects(6.5+) | import QtQuick.Effects | 模糊、阴影等图像特效 |
| QtQuick.Timeline(6.4+) | import QtQuick.Timeline | 时间轴动画(Design Studio 用) |
| QtQuick.XmlListModel | import QtQuick.XmlListModel | XML 数据模型(旧,慎用) |
Qt 5 中:Window 属于 QtQuick.Window 2.x;Controls 1.x(旧控件)与 Controls 2.x(新控件)并存,Qt6 只保留 2.x 体系。
9. 渲染原理:Scene Graph 与 RHI
9.1 为什么 QML 动画流畅
QML 渲染分为两层:
- QML 对象树(逻辑层):属性、绑定、信号由 CPU 处理。
- Scene Graph 场景图(渲染层):Qt Quick 把可视元素翻译成绘制节点(rectangle、texture、text 等),在渲染线程批量提交给 GPU。
动画时只更新"属性值 + 场景图节点参数",GPU 负责插值绘制,所以 60fps 很轻松。这是 Widgets(CPU 逐像素绘制)做不到的。
9.2 渲染后端
- Qt5:默认 OpenGL(QSGRendererInterface 尚未抽象完整)。
- Qt6:引入RHI(Rendering Hardware Interface),自动选择 OpenGL / Vulkan / Direct3D 11 / Metal。
- 软件渲染兜底:QT_QUICK_BACKEND=software(调试渲染问题时用)。
9.3 对开发者意味着什么
- 不要手动逐像素绘图(除非用 Canvas/Shapes),交给 Scene Graph。
- 大列表用虚拟化(ListView 只创建可见项)。
- 图片用 sourceSize 限制解码尺寸,避免 GPU 显存浪费。
10. QML 开发工具链
| 工具 | 作用 |
|---|---|
| Qt Creator | 官方 IDE:编辑、调试、QML Profiler、Design Mode |
| qml(Qt6)/qmlscene(Qt5) | 命令行运行单个 QML 文件 |
| qmlformat | 自动格式化 QML 代码(团队风格统一利器) |
| qmllint | 静态检查 QML 语法/绑定/类型问题 |
| qmlcachegen / qmltc | QML 预编译/编译到 C++(Qt6) |
| qmlprofiler | 性能分析:绑定求值、JS 调用、渲染帧率 |
| Qt Design Studio | 面向设计师的 UI 设计工具(生成 QML) |
| Qt Quick Designer | Creator 内置的可视化编辑器 |
建议:CI 中加 qmllint 检查;提交前跑 qmlformat,团队代码风格统一。
11. 学习路线图(再回顾)
12. 官方资料(加长清单)
- Qt 6 文档首页:Qt 6.11
- QML 语言参考:The QML Reference | Qt Qml | Qt 6.11.2
- Qt Quick 模块:Qt Quick | Qt 6.11.2
- Qt Quick Controls:Qt Quick Controls | Qt 6.11.2
- 场景图说明:Qt Quick Scene Graph | Qt Quick | Qt 6.11.2
- Qt 6 变化清单:https://doc.qt.io/qt-6/qt6-changes.html
- Qt 5 对应页面:把 qt-6 换成 qt-5(如 https://doc.qt.io/qt-5/qmlreference.html)
- Qt 在线示例浏览器:https://doc.qt.io/qt-6/qtsamples.html