3. QML语言基础
QML 是 Qt Quick 的声明式界面语言,它把“界面长什么样”和“界面如何响应”拆成两层:对象树描述结构与外观,属性绑定与信号槽描述动态行为。对嵌入式 HMI 而言,QML 的价值在于用少量代码快速构建可维护的触控界面,同时借助 JavaScript 表达式完成轻量逻辑,把重计算留给 C++ 后端。本节围绕语法、元素树、绑定、信号槽、JS 表达式与基本组件六个核心点展开。
一、QML 语法与元素树:声明式对象树
QML 文件由若干对象声明组成,每个对象以类型名开头,后跟一对花括号,括号内是属性、子对象和信号处理器。属性采用 name: value 形式,子对象直接嵌套在父对象内部,形成一棵以根对象为起点的元素树。这棵树不仅是视觉层级,也是坐标、可见性、事件传递和生命周期管理的依据。
import QtQuick 2.15
import QtQuick.Controls 2.15
Rectangle {
id: root
width: 480; height: 272
color: "#1e1e2e"
Text {
id: title
anchors.centerIn: parent
text: "嵌入式 HMI"
color: "#cdd6f4"
font.pixelSize: 24
}
Rectangle {
id: indicator
width: 16; height: 16
radius: 8
color: root.active ? "#a6e3a1" : "#f38ba8"
anchors.top: title.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 12
}
}
上例中 root 是根矩形,title 与 indicator 是它的子元素。子元素通过 parent 或 id 引用父级,anchors 把子元素的位置绑定到父元素或其他兄弟元素上。元素树的层级决定了绘制顺序:后声明的子元素绘制在上层,因此需要控制遮挡关系时,应调整声明顺序或使用 z 属性。
width 和 height,避免依赖窗口默认尺寸导致布局漂移。二、属性绑定:让界面自动跟随数据
属性绑定是 QML 最核心的机制。当属性值写成表达式而非字面量时,QML 会记录该表达式依赖的属性,并在依赖变化时自动重新求值。上例中 indicator.color 绑定到 root.active,只要 active 改变,颜色自动切换,无需手动刷新。
绑定与赋值的区别必须分清:color: root.active ? "green" : "red" 是绑定;而 color = "green" 是命令式赋值,会破坏原有绑定。在嵌入式 HMI 中,状态灯、进度条、告警文本都适合用绑定表达,但要注意绑定链过长会带来求值开销,应避免在绑定中做复杂计算或频繁访问 C++ 属性。
三、信号与槽:事件驱动的交互入口
QML 中的信号用于表达“发生了什么”,槽是响应信号的处理器。内置信号如 clicked、pressed、textChanged 可直接用 onXxx 语法处理;自定义信号用 signal 声明,并通过 emit 触发。信号与槽把界面事件与业务逻辑解耦,是 HMI 交互的基础。
Rectangle {
id: root
signal alarmTriggered(string code)
Button {
id: btn
text: "确认"
onClicked: {
root.alarmTriggered("A001")
}
}
onAlarmTriggered: {
console.log("收到告警:", code)
statusText.text = "已确认 " + code
}
}
在嵌入式场景中,按钮点击、触摸滑动、定时器超时、串口数据到达都可以映射为信号。建议把界面信号连接到 C++ 的 Q_INVOKABLE 方法或 Connections 对象,让 QML 只负责展示与轻量交互,复杂状态机放在后端。
四、JavaScript 表达式与基本组件
QML 内嵌 JavaScript 引擎,属性绑定、信号处理器和函数体都可以写 JS 表达式。常用能力包括字符串拼接、条件运算、数组遍历和简单数学计算。但要注意:JS 适合轻量逻辑,不适合高频循环或大数据处理,否则会阻塞 GUI 线程,导致触控卡顿。
基本组件是构建 HMI 的积木。下表列出常用组件及其典型用途:
| 组件 | 作用 | 嵌入式 HMI 典型场景 |
|---|---|---|
| Rectangle | 矩形绘制与容器 | 背景、卡片、状态灯底座 |
| Text | 文本显示 | 标题、数值、告警信息 |
| Image | 图片显示 | 图标、设备示意图 |
| Button | 可点击按钮 | 确认、启动、停止操作 |
| Slider | 滑动调节 | 亮度、温度设定值 |
| ProgressBar | 进度显示 | 加载、运行进度 |
| ListView | 列表视图 | 参数列表、日志滚动 |
| Timer | 定时触发 | 周期刷新、超时检测 |
五、常见误区与复盘
- 绑定被意外破坏:在 JS 中直接给属性赋值,会覆盖原有绑定。若需临时改值,应使用
Qt.binding()重新建立绑定。 - 元素树过深:嵌套层级过多会增加布局计算和事件传递成本,建议用
Loader或组件拆分控制深度。 - 在绑定中做重计算:绑定表达式应保持轻量,复杂计算放到 C++ 或
Timer中异步处理。 - 信号命名冲突:自定义信号避免与内置信号重名,否则可能导致处理器不触发或行为异常。
- 忽略 id 作用域:id 只在当前 QML 文件内有效,跨文件引用应通过属性暴露或 C++ 接口。
复盘时可以用一个简单检查表:元素树是否清晰、绑定是否被破坏、信号是否连接到正确槽、JS 是否阻塞主线程、组件是否可复用。把这五点落实到每个界面文件,QML 代码的可维护性会显著提升。