3. QML语法基础
QML 是一种声明式语言,用于描述用户界面的结构、外观与行为。与命令式语言(如 C++、JavaScript)不同,QML 的核心思想是“声明对象及其属性”,由 Qt 运行时负责解析并渲染。本章将系统讲解 QML 的语法基石:对象声明、属性绑定、基本数据类型、变量与表达式、注释规则,以及这些元素如何组合成可维护的界面代码。理解这些基础语法,是后续编写自定义组件、处理交互逻辑、优化界面性能的前提。
上图展示了 QML 语法中几个核心概念之间的关系。对象声明是骨架,属性与类型是血肉,表达式与注释是逻辑与说明,变量与数据类型则是数据流动的载体。下面我们逐一深入剖析。
3.1 对象声明:QML 的骨架
QML 文件的基本单元是对象声明。一个对象声明由类型名和一对花括号组成,花括号内包含该对象的属性、子对象、信号处理器等。对象声明可以嵌套,形成父子层级关系,这与 Qt 对象树的概念一致。
// 一个简单的 Rectangle 对象声明
Rectangle {
width: 200
height: 100
color: "lightblue"
// 子对象:Text
Text {
text: "Hello QML"
anchors.centerIn: parent
}
}
在上述代码中,Rectangle 是根对象,Text 是其子对象。子对象通过缩进和花括号嵌套在父对象内部。对象声明时,可以同时设置初始属性值,也可以后续通过 JavaScript 动态修改。
Window、Rectangle 或自定义组件。3.2 属性:对象的特征描述
属性是 QML 对象的核心数据成员。每个属性都有类型和值。属性分为三类:内置属性(如 width、height、color)、自定义属性(通过 property 关键字声明)、附加属性(如 Keys.onReturnPressed)。
3.2.1 属性绑定
属性绑定是 QML 最强大的特性之一。它允许一个属性的值依赖于另一个属性或表达式,当依赖项变化时,绑定属性会自动更新。绑定使用冒号 : 而非赋值号 =。
Rectangle {
width: 200
height: width * 0.5 // 高度始终为宽度的一半
color: mouseArea.pressed ? "orange" : "lightblue"
MouseArea {
id: mouseArea
anchors.fill: parent
}
}
这里 height 绑定到 width * 0.5,color 绑定到条件表达式。当 width 变化或鼠标按下状态改变时,相关属性自动刷新。注意:绑定是单向的,从被依赖项到绑定项。如果后续对 height 进行赋值(height = 100),绑定会被破坏。
= 给已绑定的属性赋值会移除绑定关系。若需要保留绑定,应使用 Qt.binding() 函数重新创建绑定,或避免直接赋值。3.2.2 自定义属性
使用 property 关键字可以声明自定义属性,格式为 property type name : value。自定义属性可以指定类型,也可以使用 var 表示任意类型。
Item {
property int count: 0
property string userName: "guest"
property var dataList: [1, 2, 3]
property bool isVisible: true
}
自定义属性常用于组件内部状态管理或对外暴露接口。当属性值变化时,可以通过 onCountChanged 信号处理器监听变化。
3.3 基本数据类型与变量
QML 支持多种基本数据类型,这些类型在属性声明、函数参数和变量中广泛使用。下表列出了常用类型及其说明:
| 类型 | 说明 | 示例 |
|---|---|---|
| int | 整数 | width: 100 |
| real | 浮点数 | opacity: 0.5 |
| string | 字符串 | text: "Hello" |
| bool | 布尔值 | visible: true |
| color | 颜色值 | color: "#FF8800" |
| var | 通用类型,可存任意 JS 值 | property var obj: {a: 1} |
| list<T> | 元素类型为 T 的列表 | property list<int> nums: [1,2,3] |
| url | 资源定位符 | source: "images/logo.png" |
| date | 日期时间 | property date today: new Date() |
在 QML 中声明变量通常使用 property 或 var。在 JavaScript 函数内部,可以使用 var、let、const 声明局部变量。QML 是动态类型语言,变量可以随时改变类型。
// 在信号处理器中使用变量
MouseArea {
onClicked: {
var temp = width * 0.3
parent.width = temp
console.log("新宽度: " + temp)
}
}
int、color)而非 var,这样 Qt 可以提前优化,且代码可读性更好。var 适合存储复杂对象或临时数据。3.4 表达式:QML 的逻辑引擎
表达式是 QML 中实现动态行为的关键。表达式可以是简单的算术运算、函数调用、条件判断,也可以是 JavaScript 代码块。表达式通常出现在属性绑定、信号处理器、函数体等位置。
Rectangle {
width: Math.max(parent.width * 0.5, 200) // 取较大值
height: width > 300 ? 200 : 100 // 条件表达式
color: ["red", "green", "blue"][index % 3] // 数组索引
property int index: 0
NumberAnimation on index { from: 0; to: 10; duration: 2000; loops: Animation.Infinite }
}
表达式可以访问对象自身的属性、父对象属性、全局对象(如 Math、Qt)以及已定义的 id。QML 表达式遵循 JavaScript 语法,但有一些限制:不能使用 var 声明语句(在绑定中),不能包含分号分隔的多条语句(除非用 {} 包裹)。
3.5 注释:代码的说明书
QML 支持两种注释风格:单行注释 // 和多行注释 /* ... */。注释不参与执行,仅用于说明代码意图。
// 这是单行注释
Rectangle {
/* 这是多行注释
可以跨越多行 */
width: 100 // 行尾注释
height: 50
}
// 设置宽度。3.6 实战案例:构建一个可交互的计数器组件
下面通过一个完整的计数器组件,综合运用本章语法。该组件包含一个显示数字的文本、两个按钮(增加/减少),并演示属性绑定、自定义属性、信号处理器和表达式。
// Counter.qml
import QtQuick 2.15
Rectangle {
id: root
width: 200
height: 80
color: "#f0f0f0"
radius: 8
// 自定义属性:计数值
property int count: 0
// 自定义信号:当计数变化时发出
signal countChanged(int newCount)
// 显示区域
Text {
id: countText
anchors.centerIn: parent
font.pixelSize: 28
font.bold: true
text: root.count // 绑定到根对象的 count 属性
color: root.count > 0 ? "#2e7d32" : (root.count < 0 ? "#c62828" : "#333333")
}
// 增加按钮
Rectangle {
id: incBtn
width: 40; height: 40
anchors.right: parent.right
anchors.rightMargin: 10
anchors.verticalCenter: parent.verticalCenter
radius: 20
color: mouseInc.pressed ? "#43a047" : "#66bb6a"
Text { text: "+"; anchors.centerIn: parent; font.pixelSize: 24; color: "white" }
MouseArea {
id: mouseInc
anchors.fill: parent
onClicked: {
root.count++ // 修改属性
root.countChanged(root.count) // 发出信号
}
}
}
// 减少按钮
Rectangle {
id: decBtn
width: 40; height: 40
anchors.left: parent.left
anchors.leftMargin: 10
anchors.verticalCenter: parent.verticalCenter
radius: 20
color: mouseDec.pressed ? "#e53935" : "#ef5350"
Text { text: "-"; anchors.centerIn: parent; font.pixelSize: 24; color: "white" }
MouseArea {
id: mouseDec
anchors.fill: parent
onClicked: {
root.count--
root.countChanged(root.count)
}
}
}
// 监听 count 变化(演示 onPropertyChanged 语法)
onCountChanged: {
console.log("计数器新值: " + newCount)
}
}
在另一个 QML 文件中使用该组件:
// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
width: 300; height: 200
visible: true
Counter {
anchors.centerIn: parent
onCountChanged: {
// 外部响应信号
title = "当前计数: " + newCount
}
}
}
该案例展示了:对象嵌套(Rectangle 内包含 Text、MouseArea)、属性绑定(text 绑定到 count)、自定义属性(count)、自定义信号(countChanged)、表达式(条件颜色)、注释(代码说明)。运行后,点击加减按钮,数字实时变化,窗口标题同步更新。
3.7 常见误区与总结
学习 QML 语法时,初学者容易陷入以下误区:
- 混淆绑定与赋值:在
onClicked中直接写width = 100会破坏原有绑定。应使用width = Qt.binding(function() { return ... })或避免对已绑定属性赋值。 - 忽略 id 作用域:id 在声明它的 QML 文档内全局可见,但外部文件无法直接访问。跨文件通信应通过属性或信号。
- 类型不匹配:给
int属性赋字符串值会静默失败或产生警告。使用var时注意类型检查。 - 注释过度或不足:注释应解释“为什么”,而非逐行翻译代码。
- 表达式过于复杂:在绑定中写大量逻辑会降低可读性和性能。建议将复杂逻辑提取到函数中。
Behavior 动画或减少绑定层级。总结本章,QML 语法以对象声明为骨架,属性与绑定赋予界面动态性,类型系统保证数据正确性,表达式提供逻辑能力,注释提升可维护性。掌握这些基础后,下一阶段将学习布局管理、组件封装与动画系统,逐步构建复杂应用。