3. QML语言基础

QML 是 Qt Quick 的声明式描述语言,它把界面拆成“元素树”,每个元素通过属性描述外观与行为,通过信号与槽连接交互逻辑,并允许直接嵌入 JavaScript 表达式完成动态计算。掌握 QML 语法、元素与属性、信号与槽、JavaScript 集成以及基本布局与定位器,是后续 C++ 与 QML 混合开发的地基。

一、QML 语法:声明式对象树

QML 文件通常以 .qml 结尾,顶层必须是一个根元素。元素用“类型名 + 花括号”声明,花括号内部写属性、子元素和信号处理器。属性采用 name: value 形式,子元素直接嵌套,形成父子层级。QML 的注释支持 // 与 /* */,与 JavaScript 一致。

import QtQuick 2.15

Rectangle {
    id: root
    width: 320; height: 240
    color: "#f5f7fa"

    Text {
        id: title
        anchors.centerIn: parent
        text: "QML 基础"
        font.pixelSize: 24
        color: "#1f2937"
    }
}

其中 id 是元素在文件内的唯一标识,用于跨元素引用;parent 指向父元素;anchors 是定位属性组。QML 的属性赋值支持绑定表达式,当依赖的属性变化时,绑定会自动重新求值,这是声明式 UI 的核心机制。

二、元素与属性:类型系统与属性绑定

QML 元素分为可视化元素(如 Rectangle、Text、Image)和非可视化元素(如 Timer、Connections、ListModel)。每个元素都有一组属性,属性可分为:

  • 基本属性:width、height、x、y、color、visible 等。
  • 分组属性:如 font.pixelSize、anchors.fill、border.width。
  • 附加属性:如 Keys.onPressed、Layout.fillWidth。
  • 自定义属性:通过 property type name: value 声明,例如 property int count: 0。

属性绑定是 QML 区别于传统命令式 UI 的关键。绑定表达式中的依赖项变化时,属性值自动更新。例如 width: parent.width * 0.5 会随父元素宽度变化而更新。若在 JavaScript 中直接给属性赋值,会破坏原有绑定,需要谨慎使用。

建议优先使用属性绑定而非手动赋值,让界面状态由数据驱动,减少“界面不同步”的问题。

三、信号与槽:交互的桥梁

QML 中信号用于表达“发生了某件事”,槽是响应信号的函数或处理器。元素内置信号如 clicked、pressed、textChanged,通过 on<SignalName> 形式编写处理器。自定义信号使用 signal 关键字声明,并通过 emit 或直接调用信号函数触发。

Rectangle {
    id: button
    width: 120; height: 40
    color: "#3b82f6"

    signal activated(string msg)

    MouseArea {
        anchors.fill: parent
        onClicked: button.activated("clicked")
    }

    onActivated: console.log("收到信号:", msg)
}

信号也可以连接到 JavaScript 函数或 C++ 槽函数。在 QML 内部,常用 Connections 元素监听其他对象的信号,避免直接修改对方代码。信号与槽让界面元素之间保持松耦合,是后续与 C++ 交互的通用通道。

四、JavaScript 集成:表达式与函数

QML 内置 JavaScript 引擎,属性绑定、信号处理器和函数体都可以写 JavaScript。常见用法包括:

  • 在属性绑定中做算术、字符串拼接和三元判断。
  • 在信号处理器中调用函数、修改属性、发起网络请求。
  • 使用 function 定义可复用函数,或引入外部 .js 文件。
  • 通过 Qt.include() 或 import "utils.js" as Utils 组织代码。
function formatCount(n) {
    return n > 99 ? "99+" : n.toString()
}

Text {
    text: formatCount(model.count)
}
QML 中的 JavaScript 适合处理界面逻辑和轻量计算,复杂业务、性能敏感或需要复用的算法应放到 C++ 中实现,再通过上下文属性或注册类型暴露给 QML。

五、基本布局与定位器

QML 提供两类布局方式:定位器(Positioners)和布局管理器(Layouts)。定位器包括 Row、Column、Grid、Flow,它们按顺序自动排列子元素;布局管理器包括 RowLayout、ColumnLayout、GridLayout,支持权重、对齐和尺寸约束,更适合自适应界面。

方式典型元素适用场景
锚定anchors元素相对父级或兄弟元素定位
定位器Row / Column / Grid / Flow简单顺序排列、固定间距
布局管理器RowLayout / ColumnLayout / GridLayout自适应、可伸缩、复杂界面
绝对定位x / y精确坐标,少用

锚定适合“贴边、居中、填充”等关系;定位器适合一组按钮或标签的快速排列;布局管理器适合窗口缩放时需要重新分配空间的场景。实际项目中常将三者结合:外层用布局管理器划分区域,内部用锚定或定位器细化。

QML 语言基础核心关系 QML 语法 类型 + 花括号 id / 属性 / 子元素 元素与属性 基本 / 分组 / 附加 属性绑定自动更新 信号与槽 on<Signal> 处理器 自定义 signal JavaScript 集成 表达式 / 函数 / .js 轻逻辑放 QML 布局与定位器 anchors / Row / Column Layout 自适应 绑定表达式 交互驱动 声明式对象树 + 属性绑定 + 信号驱动 + JS 逻辑 + 布局定位

六、案例与常见误区

假设要做一个计数器界面:一个 Text 显示数字,两个按钮分别加一和减一。可以用 Column 纵向排列,用 property int count: 0 保存状态,用 onClicked 修改 count,再用 text: count 自动刷新显示。这个例子同时覆盖了元素、属性、信号和 JavaScript 表达式。

Column {
    spacing: 12
    property int count: 0

    Text { text: "计数: " + count; font.pixelSize: 20 }

    Row {
        spacing: 8
        Button { text: "-"; onClicked: count-- }
        Button { text: "+"; onClicked: count++ }
    }
}

常见误区包括:在 JavaScript 中直接给已绑定的属性赋值,导致绑定失效;过度使用绝对坐标 x/y,窗口缩放后布局错乱;把复杂业务逻辑写进 QML,造成性能和维护问题;忽略 id 的作用域,跨文件引用失败。复盘时应优先检查属性绑定是否被破坏、布局是否使用了合适的容器、信号连接是否重复触发。

掌握 QML 的关键在于理解“声明式对象树 + 属性绑定 + 信号驱动”这三件事。语法是外壳,元素与属性是骨架,信号与槽是神经,JavaScript 是肌肉,布局与定位器是姿态。五者配合,才能写出可维护、可扩展的 Qt Quick 界面。