2. QML基础语法
QML 是 Qt 面向声明式 UI 的语言,它把“界面长什么样”和“界面如何响应”拆成两层:结构由元素树描述,行为由属性绑定、信号与 JavaScript 表达式驱动。对工业 HMI 来说,这种分层尤其重要——设备状态、报警、按钮互锁、数值显示都依赖清晰的元素层级和稳定的数据流。本节从文件结构入手,逐步建立 QML 的语法骨架。
一、QML 文件结构:一个根元素 + 若干子元素
每个 .qml 文件本质上是一个组件定义,文件首行通常导入模块,随后是唯一的根元素。根元素决定该组件的类型,子元素通过嵌套形成对象树。工业 HMI 中常见的根元素是 Rectangle、Item 或自定义组件,内部再放置文本、按钮、仪表和状态灯。
import QtQuick 2.15
import QtQuick.Controls 2.15
Rectangle {
id: root
width: 800
height: 480
color: "#F5F7FA"
Text {
id: title
text: "注塑机监控"
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
font.pixelSize: 24
}
}
这段代码体现了 QML 文件的基本结构:导入区、根元素、属性赋值、子元素嵌套。根元素 Rectangle 提供矩形绘制能力,Text 作为子元素被自动加入其子项列表,形成父子关系。父元素负责布局范围,子元素通过 parent 或 anchors 定位。
二、元素与属性:对象树是 HMI 的骨架
QML 中的元素即对象,属性即对象的状态。属性赋值有两种常见形式:静态赋值和绑定表达式。静态赋值如 width: 800,绑定表达式如 width: parent.width * 0.5。绑定表达式会在依赖项变化时自动重新求值,这是 QML 区别于传统命令式 UI 的核心机制。
工业 HMI 中,属性往往与设备状态关联。例如温度显示文本的颜色可以绑定到报警状态:
Text {
id: tempText
text: "温度:" + plc.temperature + " ℃"
color: plc.temperature > 180 ? "#D93025" : "#1E8E3E"
}
这里 plc.temperature 是 C++ 暴露给 QML 的属性,一旦数值变化,text 和 color 会自动更新。理解“属性绑定是声明式数据流”这一点,是写出可维护 HMI 的关键。
三、id 与信号:对象引用与事件响应
id 是 QML 中对象的唯一标识,用于在同一文件内跨层级引用。id 不参与属性系统,不能被绑定,也不能重复。信号则是对象发出的事件,如 clicked、pressed、valueChanged。通过 on<SignalName> 语法可以编写信号处理函数。
Button {
id: startBtn
text: "启动"
onClicked: {
plc.start()
statusText.text = "运行中"
}
}
Text {
id: statusText
text: "待机"
}
按钮点击后调用 plc.start(),同时修改 statusText.text。这里 id 让按钮能够引用下方文本对象,信号处理函数则把用户操作转化为业务动作。工业场景中,信号常用于启动、停止、复位、确认报警等操作。
startBtn、tempText,避免与属性名冲突;信号处理函数中尽量只做转发,复杂逻辑交给 C++ 或 JavaScript 函数。四、JavaScript 表达式:让界面具备计算能力
QML 内嵌 JavaScript 引擎,属性绑定、信号处理和函数体都可以使用 JavaScript 表达式。常见用法包括字符串拼接、数值计算、条件判断和数组遍历。工业 HMI 中,经常需要把原始数值转换为工程值、单位或状态文本。
function formatPressure(v) {
if (v < 0) return "异常"
return v.toFixed(2) + " MPa"
}
Text {
text: formatPressure(plc.pressure)
}
需要注意的是,属性绑定中的 JavaScript 表达式应尽量保持“纯函数”风格,避免副作用。若在绑定中修改其他属性,容易造成循环绑定和性能问题。复杂逻辑建议封装为函数,或下沉到 C++ 层。
五、基本布局与定位:锚定、行列与定位器
QML 提供多种布局方式,工业 HMI 中最常用的是锚定(anchors)、行列布局(Row/Column/Grid)和定位器(Positioner)。锚定适合相对定位,行列布局适合规则排列,定位器适合动态生成的一组元素。
| 布局方式 | 典型元素 | 适用场景 | 注意事项 |
|---|---|---|---|
| 锚定 | anchors.fill / centerIn / top 等 | 标题居中、面板填充、按钮贴边 | 避免循环锚定,父子关系要清晰 |
| 行列布局 | Row、Column、Grid | 按钮组、参数列表、状态灯阵列 | 间距用 spacing,尺寸由子项决定 |
| 定位器 | Repeater + Row/Column | 动态生成报警条目、设备列表 | 配合 model 使用,注意索引与 id 管理 |
| 布局管理器 | RowLayout、ColumnLayout、GridLayout | 需要自适应伸缩的复杂面板 | 使用 Layout.fillWidth 等附加属性 |
下面是一个典型的工业 HMI 局部布局:左侧状态灯,右侧参数文本,底部按钮组。通过锚定和 Column 组合,可以在不同分辨率下保持稳定结构。
Rectangle {
width: 400; height: 240
color: "#FFFFFF"
Rectangle {
id: lamp
width: 24; height: 24
radius: 12
color: plc.running ? "#1E8E3E" : "#9AA0A6"
anchors.left: parent.left
anchors.top: parent.top
anchors.margins: 16
}
Column {
anchors.left: lamp.right
anchors.top: parent.top
anchors.margins: 16
spacing: 8
Text { text: "运行状态:" + (plc.running ? "运行" : "停止") }
Text { text: "温度:" + plc.temperature + " ℃" }
}
Row {
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
spacing: 12
Button { text: "启动"; onClicked: plc.start() }
Button { text: "停止"; onClicked: plc.stop() }
}
}
六、常见误区与复盘
- id 重复或滥用:同一文件内 id 必须唯一;跨文件引用应通过属性或信号,而不是依赖全局 id。
- 绑定中写副作用:在属性绑定里修改其他属性,容易造成循环绑定和界面卡顿。
- 锚定冲突:同时使用
anchors.fill和width/height可能产生冲突,应明确谁决定尺寸。 - 忽略父元素范围:子元素默认可以超出父元素边界,工业 HMI 中应使用
clip: true或合理布局避免遮挡。 - 信号处理过重:把复杂业务逻辑写在
onClicked中会降低可测试性,建议转发到 C++ 或独立 JS 函数。
在实际项目中,建议先画出对象树草图,再为每个对象分配 id 和属性,最后补充信号处理与 JavaScript 函数。这样能避免“边写边改”导致的层级混乱。对于工业 HMI,尤其要注意属性绑定的依赖关系,确保设备状态变化时界面能及时、准确地刷新。