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 让按钮能够引用下方文本对象,信号处理函数则把用户操作转化为业务动作。工业场景中,信号常用于启动、停止、复位、确认报警等操作。

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 函数。
掌握 QML 基础语法的核心,是理解“对象树 + 属性绑定 + 信号驱动”这三件事。对象树决定界面结构,属性绑定决定数据如何流动,信号决定用户操作如何进入系统。三者配合,才能构建出稳定、可维护的工业 HMI 界面。
QML 基础语法结构关系 对象树 · 属性绑定 · 信号驱动 · JavaScript 表达式 · 布局定位 根元素 Rectangle id: root · width · height · color 子元素 Text id: title · text · font 子元素 Button id: startBtn · text 子元素 Rectangle id: lamp · radius · color 信号与处理 onClicked onPressed JavaScript 表达式 属性绑定 函数调用 布局与定位 anchors Row / Column 对象树决定结构 · 属性绑定决定数据流 · 信号决定交互入口 id 用于引用 · JavaScript 用于计算 · 布局用于适配不同分辨率

在实际项目中,建议先画出对象树草图,再为每个对象分配 id 和属性,最后补充信号处理与 JavaScript 函数。这样能避免“边写边改”导致的层级混乱。对于工业 HMI,尤其要注意属性绑定的依赖关系,确保设备状态变化时界面能及时、准确地刷新。