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 元素树与属性绑定关系 Rectangle (root) id: root active: bool Text (title) text: "嵌入式 HMI" Rectangle (indicator) color: 绑定表达式 绑定依赖链 root.active ──▶ indicator.color ──▶ 屏幕状态灯颜色 title.text ──▶ 界面标题文本 依赖变化时自动重新求值;命令式赋值会破坏绑定。

三、信号与槽:事件驱动的交互入口

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定时触发周期刷新、超时检测
组合原则:用 Rectangle 做布局容器,用 anchors 或 Layout 管理位置,用绑定驱动显示,用信号槽承接交互,用 JS 做轻量转换。四者配合,才能写出结构清晰、易维护的 QML 界面。

五、常见误区与复盘

  • 绑定被意外破坏:在 JS 中直接给属性赋值,会覆盖原有绑定。若需临时改值,应使用 Qt.binding() 重新建立绑定。
  • 元素树过深:嵌套层级过多会增加布局计算和事件传递成本,建议用 Loader 或组件拆分控制深度。
  • 在绑定中做重计算:绑定表达式应保持轻量,复杂计算放到 C++ 或 Timer 中异步处理。
  • 信号命名冲突:自定义信号避免与内置信号重名,否则可能导致处理器不触发或行为异常。
  • 忽略 id 作用域:id 只在当前 QML 文件内有效,跨文件引用应通过属性暴露或 C++ 接口。

复盘时可以用一个简单检查表:元素树是否清晰、绑定是否被破坏、信号是否连接到正确槽、JS 是否阻塞主线程、组件是否可复用。把这五点落实到每个界面文件,QML 代码的可维护性会显著提升。