4. QML语法基础

QML 是 Qt Quick 的声明式 UI 语言,它把界面拆成“对象树 + 属性 + 表达式 + 事件”四件事。掌握这四件事,就能从“能跑”进入“可维护、可交付”的阶段。本节围绕元素、属性、绑定、信号与槽、组件化与模块导入六个关键词展开,并用一张结构关系图把它们的依赖关系串起来。

QML 语法核心关系图 元素 → 属性 → 绑定 → 信号与槽 → 组件化 → 模块导入 元素 Element Item / Rectangle / Text 对象树父子关系 属性 Property id / width / color 自定义 property 绑定 Binding width: parent.width * 0.5 依赖追踪自动更新 信号与槽 signal clicked() onClicked: { ... } 组件化 Component MyButton.qml 可复用 UI 单元 模块导入 import import QtQuick 2.15 import "./components" 交付视角的关键约束 • 元素必须有 id 才能被外部引用;属性绑定是单向数据流,避免在绑定表达式中写副作用。 • 信号与槽是事件入口,槽函数中不要做重计算,否则会阻塞渲染线程。 • 组件化后通过 import 路径暴露,模块导入顺序影响同名类型解析优先级。

一、元素与对象树:QML 的“骨架”

QML 文件本质上是一棵对象树。根元素通常是 Item 或 Window,子元素通过缩进或花括号嵌套成为其 children。每个元素都有类型名、可选的 id、一组属性以及可选的子元素。例如:

import QtQuick 2.15

Rectangle {
    id: root
    width: 320; height: 200
    color: "#f0f4f8"

    Text {
        id: title
        anchors.centerIn: parent
        text: "交付看板"
        font.pixelSize: 20
    }
}

这里 root 是根元素,title 是子元素。父元素负责布局与生命周期,子元素通过 parent 访问父级。交付项目中,建议给需要被外部引用的元素都加 id,但不要滥用全局 id,避免命名冲突。

二、属性与绑定:让界面“活”起来

属性是元素的静态特征,如 width、color、text。绑定则是把属性值写成表达式,QML 引擎会自动追踪表达式依赖的属性,一旦依赖变化就重新求值。这是 QML 区别于传统命令式 UI 的核心机制。

Rectangle {
    id: card
    width: 200; height: 120

    Rectangle {
        width: parent.width * 0.8
        height: parent.height * 0.5
        color: card.width > 180 ? "#3aa87a" : "#e0a63a"
    }
}

绑定是单向的:表达式 → 属性。若在 JavaScript 中直接给属性赋值,会破坏绑定。交付中常见做法是:展示型属性用绑定,交互型属性用信号槽显式赋值,二者边界清晰。

绑定表达式应保持“纯函数”风格:只读取属性、不做网络请求、不修改其他对象状态。否则会出现难以追踪的循环绑定或更新风暴。

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

信号是元素发出的通知,槽是响应函数。QML 中既可以使用内置信号(如 MouseArea.clicked),也可以自定义信号。槽函数通常写成 on<SignalName>: { ... } 形式。

Rectangle {
    id: btn
    width: 120; height: 40
    color: "#4a90d9"

    signal submitted(string payload)

    MouseArea {
        anchors.fill: parent
        onClicked: btn.submitted("confirm")
    }

    onSubmit: console.log("收到:", payload)
}

交付项目中,信号槽常用于:按钮点击、列表项选中、异步任务完成回调。注意槽函数应短小,耗时逻辑交给 C++ 或 WorkerScript,避免阻塞 UI 线程。

四、组件化与模块导入:从单文件到工程

当界面重复出现时,应抽成独立 .qml 文件,即组件。组件文件名首字母大写,内部根元素即组件类型。使用时直接写组件名,并通过属性暴露配置项。

// MyButton.qml
import QtQuick 2.15
Rectangle {
    property alias text: label.text
    signal clicked()
    width: 120; height: 40
    color: "#3aa87a"
    Text { id: label; anchors.centerIn: parent; color: "white" }
    MouseArea { anchors.fill: parent; onClicked: parent.clicked() }
}
// main.qml
import QtQuick 2.15
import "./components"

MyButton {
    text: "提交"
    onClicked: console.log("提交成功")
}

模块导入使用 import,可以是 Qt 官方模块(QtQuick 2.15)、本地目录("./components")或自定义 URI。导入顺序决定同名类型的解析优先级,后导入的会覆盖先导入的。交付时建议把组件目录统一放在 qml/components,并在 qmldir 中声明模块,便于版本管理。

语法要素作用交付注意点
元素 Element构成对象树,决定层级与生命周期id 命名统一前缀,避免全局污染
属性 Property描述静态特征与可配置项对外暴露的属性加 property alias
绑定 Binding依赖追踪,自动更新保持纯表达式,避免副作用
信号与槽事件响应与解耦槽函数短小,耗时逻辑下沉
组件化复用 UI 单元文件名大写,接口稳定
模块导入组织代码与依赖qmldir 声明,路径统一
常见误区:在绑定表达式中调用 Math.random() 或修改其他对象属性,会导致绑定失效或无限循环;组件化后仍用绝对路径导入,换机器后编译失败。交付前应统一检查 import 路径与 qmldir 配置。

五、复盘:把语法变成交付能力

语法本身不难,难的是在多人协作和长期维护中保持一致性。建议在项目初期就约定:id 命名规范、组件目录结构、属性暴露方式、信号命名风格。把本节六个要素当成检查清单,每新增一个 QML 文件都过一遍,能显著降低后期联调成本。