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 提供两类布局方式:定位器(Positioners)和布局管理器(Layouts)。定位器包括 Row、Column、Grid、Flow,它们按顺序自动排列子元素;布局管理器包括 RowLayout、ColumnLayout、GridLayout,支持权重、对齐和尺寸约束,更适合自适应界面。
| 方式 | 典型元素 | 适用场景 |
|---|---|---|
| 锚定 | anchors | 元素相对父级或兄弟元素定位 |
| 定位器 | Row / Column / Grid / Flow | 简单顺序排列、固定间距 |
| 布局管理器 | RowLayout / ColumnLayout / GridLayout | 自适应、可伸缩、复杂界面 |
| 绝对定位 | x / y | 精确坐标,少用 |
锚定适合“贴边、居中、填充”等关系;定位器适合一组按钮或标签的快速排列;布局管理器适合窗口缩放时需要重新分配空间的场景。实际项目中常将三者结合:外层用布局管理器划分区域,内部用锚定或定位器细化。
六、案例与常见误区
假设要做一个计数器界面:一个 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 的作用域,跨文件引用失败。复盘时应优先检查属性绑定是否被破坏、布局是否使用了合适的容器、信号连接是否重复触发。