跳到主要内容

QML 基础

QML(Qt Meta Language)是一种声明式用户界面语言,专门用于设计和开发流畅、动态的用户界面。与传统的 Qt Widgets 相比,QML 更适合开发现代化的触屏应用、嵌入式界面和需要丰富动画效果的应用程序。

QML 简介

什么是 QML?

QML 是一种声明式语言,其语法类似于 JSON,但增加了 JavaScript 表达式的支持。QML 的核心思想是:描述用户界面应该是什么样子,而不是如何去创建它

// 一个简单的 QML 示例
import QtQuick

Rectangle {
width: 200
height: 100
color: "lightblue"

Text {
anchors.centerIn: parent
text: "Hello, QML!"
font.pixelSize: 20
}
}

这段代码声明了一个浅蓝色矩形,中间显示一段文字。QML 引擎会负责解析代码、创建对象并渲染到屏幕上。

QML 与 Qt Widgets 的区别

特性QMLQt Widgets
语言QML + JavaScriptC++
编程范式声明式命令式
动画支持内置,简洁需要额外代码
样式定制样式表或 QML 属性QSS 样式表
性能硬件加速渲染CPU 渲染
适用场景现代 UI、触屏、嵌入式传统桌面应用
学习曲线较平缓较陡峭

Qt Quick 模块

Qt Quick 是 QML 的标准库,提供了创建用户界面所需的基本类型:

  • 视觉项(Visual Items):Item、Rectangle、Text、Image 等
  • 用户输入:MouseArea、Keys、TextInput 等
  • 定位与布局:Row、Column、Grid、Flow、锚点系统
  • 动画与过渡:PropertyAnimation、NumberAnimation、Transition 等
  • 模型与视图:ListView、GridView、PathView 等

QML 语法基础

导入语句

每个 QML 文件都以导入语句开始,引入所需的模块:

// 导入 Qt Quick 模块(Qt 6 推荐省略版本号)
import QtQuick

// 导入指定版本
import QtQuick 2.15

// 导入 Qt Quick Controls(按钮、文本框等控件)
import QtQuick.Controls

// 导入 Qt Quick Layouts(布局管理)
import QtQuick.Layouts

// 导入自定义模块
import "components" as Components

// 导入 JavaScript 文件
import "utils.js" as Utils

对象声明

QML 使用类似 JSON 的语法声明对象,每个对象由类型名和大括号组成:

import QtQuick

// 声明一个 Rectangle 对象
Rectangle {
// 属性赋值
width: 200
height: 100
color: "steelblue"

// 嵌套子对象
Text {
text: "嵌套的文本"
anchors.centerIn: parent
}
}

对象树结构:QML 文件定义的是一个对象树。根对象可以包含子对象,子对象又可以包含自己的子对象。

import QtQuick

Item {
// 根对象

Rectangle {
// 子对象 1
color: "red"

Text {
// 孙对象
text: "嵌套层级"
}
}

Rectangle {
// 子对象 2
color: "blue"
}
}

注释

QML 支持两种注释方式:

Rectangle {
// 单行注释
width: 100 // 行尾注释

/*
* 多行注释
* 可以跨越多行
*/
height: 100
}

属性系统

基本属性

属性是对象特征的定义,通过 属性名: 值 的方式赋值:

Rectangle {
// 基本类型属性
width: 200 // 整数
height: 100.5 // 浮点数
color: "red" // 颜色字符串
visible: true // 布尔值

// 颜色的多种表示方式
// color: "#FF0000" // 十六进制
// color: "#80FF0000" // 带透明度
// color: Qt.rgba(1, 0, 0, 1) // RGBA 函数
}

属性绑定

属性绑定是 QML 的核心特性之一。当绑定的源值变化时,目标属性会自动更新:

import QtQuick

Rectangle {
id: root
width: 400
height: 200

Rectangle {
// 属性绑定:宽度始终是父矩形的一半
width: root.width / 2
height: parent.height
color: "lightblue"
}

Text {
// 绑定表达式可以使用 JavaScript
text: "宽度: " + root.width + "px"
anchors.centerIn: parent
}
}

绑定 vs 赋值

Item {
width: 100

Rectangle {
id: rect1
// 绑定:width 会随父元素变化
width: parent.width
}

Rectangle {
id: rect2
// 赋值:width 固定为 100,不会更新
width: 100

Component.onCompleted: {
// 在 JavaScript 中赋值会破坏绑定
// rect2.width = 200 // 绑定被破坏
}
}
}

绑定的生命周期

理解绑定的创建和破坏对于编写正确的 QML 代码至关重要:

import QtQuick

Rectangle {
id: root
width: 200
height: 200

property int baseValue: 10

Rectangle {
id: child
width: root.width / 2 // 这是一个绑定表达式

// 绑定在以下情况会被破坏:
// 1. 通过 JavaScript 赋值
function breakBinding() {
child.width = 100 // 绑定被破坏,width 不再跟随 root.width
}

// 2. 在信号处理器中赋值
MouseArea {
anchors.fill: parent
onClicked: {
// 每次点击都会破坏绑定(如果绑定还存在的话)
// parent.width = 50
}
}
}

// 使用 Qt.binding() 恢复绑定
function restoreBinding() {
child.width = Qt.binding(function() { return root.width / 2 })
}

// 条件性绑定
property bool useHalf: true
Rectangle {
width: Qt.binding(function() {
return useHalf ? root.width / 2 : root.width * 0.8
})
}
}

属性绑定的最佳实践

import QtQuick

Item {
id: root

// 1. 使用 id 明确引用(推荐)
property int derivedValue: root.baseValue * 2

property int baseValue: 10

Rectangle {
id: rect1

// 好:明确指定目标
width: root.width / 2

// 不推荐:使用 parent 可能导致意外行为
// height: parent.height / 2

// 2. 复杂表达式提取为属性
property real aspectRatio: width / height
property real area: width * height

// 3. 避免在绑定中进行耗时操作
// 不好的做法:
// property string result: heavyComputation()

// 好的做法:使用函数和信号更新
property string result: ""
function updateResult() {
// 异步处理耗时操作
result = heavyComputation()
}
}

// 4. 使用 required 属性明确依赖
// 子组件中使用
component MyItem : Item {
required property string title // 必须由父组件提供
required property int count

Text {
text: title + ": " + count
}
}

// 使用
MyItem {
title: "计数"
count: 42
}
}

信号与处理器

使用 property 关键字定义自定义属性:

Item {
// 基本类型属性
property int count: 0
property string name: "默认名称"
property real price: 99.9
property bool isActive: true
property color bgColor: "white"

// 对象类型属性
property Rectangle highlightRect

// 属性别名(指向已有属性)
property alias buttonText: textItem.text

Text {
id: textItem
text: "原始文本"
}

// 使用自定义属性
Rectangle {
color: bgColor
visible: isActive
}
}

属性别名:别名允许将内部属性暴露给外部,是组件接口设计的常用方式:

// Button.qml
Rectangle {
id: root
width: 100
height: 40

// 别名:外部可以直接访问内部 Text 的 text 属性
property alias text: label.text
property alias textColor: label.color

Text {
id: label
anchors.centerIn: parent
text: "按钮"
}
}

// 使用时
Button {
text: "自定义文本" // 设置内部 Text 的 text
textColor: "red" // 设置内部 Text 的 color
}

属性变化信号

当属性值变化时,会发出对应的信号:

Item {
property int count: 0

// 属性变化信号处理器
onCountChanged: {
console.log("count 变化:", count)
}

Component.onCompleted: {
count = 10 // 触发 onCountChanged
count = 20 // 再次触发
}
}

信号与处理器

内置信号

QML 类型提供内置信号,通过 onSignalName 格式处理:

import QtQuick

Item {
width: 200
height: 200

MouseArea {
anchors.fill: parent

// 点击信号处理器
onClicked: (mouse) => {
console.log("点击位置:", mouse.x, mouse.y)
console.log("按键:", mouse.button)
}

// 按下信号处理器
onPressed: {
console.log("鼠标按下")
}

// 释放信号处理器
onReleased: {
console.log("鼠标释放")
}
}
}

自定义信号

使用 signal 关键字声明自定义信号:

import QtQuick

Item {
id: root

// 无参数信号
signal clicked

// 带参数信号
signal moved(int x, int y)
signal messageReceived(string from, string text)

// 发射信号
function triggerClick() {
root.clicked()
}

function moveObject(newX, newY) {
x = newX
y = newY
moved(newX, newY)
}

// 信号处理器
onClicked: {
console.log("clicked 信号被触发")
}

onMoved: (x, y) => {
console.log("移动到:", x, y)
}
}

信号参数处理

当信号带有参数时,需要使用函数来接收参数。Qt 6 推荐使用箭头函数或命名函数来处理信号参数,这种方式更清晰、更高效。

import QtQuick

Item {
// 带参数的信号定义
signal positionChanged(real x, real y)
signal dataReceived(string message, int code, bool success)

// 推荐方式:使用箭头函数,参数名可以自定义
onPositionChanged: (posX, posY) => {
console.log("位置变化:", posX, posY)
}

// 只使用部分参数(可以省略后面的参数)
onDataReceived: (msg) => {
console.log("收到消息:", msg)
// code 和 success 参数被忽略
}

// 使用下划线表示忽略不需要的参数
onDataReceived: (_, statusCode, _) => {
console.log("状态码:", statusCode)
}

// 使用命名函数提高可读性
function handleDataReceived(message, code, success) {
if (success) {
console.log("成功:", message, "代码:", code)
} else {
console.log("失败:", message)
}
}

onDataReceived: (msg, code, success) => handleDataReceived(msg, code, success)
}

参数处理的注意事项

  1. 参数名不必与信号定义中的参数名相同,但建议使用有意义的名称
  2. 可以省略尾部的参数,但不能跳过中间的参数
  3. 不推荐使用代码块直接注入参数的方式(已弃用),应使用箭头函数

信号连接

使用 Connections 对象或 connect 方法连接信号:

import QtQuick

Item {
id: root

signal clicked

// 方式 1:声明式连接(推荐)
// Connections 允许在对象外部连接信号
Connections {
target: root
function onClicked() {
console.log("方式1: 点击了")
}
}

Component.onCompleted: {
// 方式 2:命令式连接
// 适用于动态创建对象或需要条件性连接的场景
root.clicked.connect(function() {
console.log("方式2: 点击了")
})

// 方式 3:连接到方法
root.clicked.connect(handleClick)
}

function handleClick() {
console.log("方法处理点击")
}
}

Connections 的优势

import QtQuick

Item {
id: container

// Connections 可以动态改变目标
property var currentTarget: button1

Button { id: button1; text: "按钮1" }
Button { id: button2; text: "按钮2" }

Connections {
id: connections
target: container.currentTarget

function onClicked() {
console.log("点击了:", target.text)
}
}

// 切换监听目标
function switchTarget() {
container.currentTarget = (container.currentTarget === button1) ? button2 : button1
}

// Connections 的 enabled 属性可以临时禁用
Connections {
target: button1
enabled: false // 禁用连接
function onClicked() {
// 不会被调用
}
}
}

信号到信号的连接

信号可以直接连接到另一个信号,实现信号转发:

import QtQuick

Item {
id: relay

// 定义转发信号
signal messageReceived(string text)
signal forwarded(string text)

Component.onCompleted: {
// 信号连接到信号:messageReceived 会触发 forwarded
messageReceived.connect(forwarded)
}

onMessageReceived: (text) => console.log("原始信号:", text)
onForwarded: (text) => console.log("转发信号:", text)

function send(msg) {
messageReceived(msg)
// 输出:
// 原始信号: hello
// 转发信号: hello
}
}

断开信号连接

import QtQuick

Item {
signal updated(int value)

property var handler: function(v) { console.log("值:", v) }

Component.onCompleted: {
// 连接
updated.connect(handler)

// 断开特定处理器
updated.disconnect(handler)

// 断开所有连接
// updated.disconnect()
}
}

锚点布局

锚点(Anchors)是 QML 中最常用的布局方式,通过指定元素相对于其他元素的位置来定位。

基本锚点

import QtQuick

Item {
width: 300
height: 200

Rectangle {
id: redRect
width: 100
height: 100
color: "red"

// 锚定到父元素的左上角
anchors.left: parent.left
anchors.top: parent.top

// 边距
anchors.leftMargin: 10
anchors.topMargin: 10
}

Rectangle {
id: blueRect
width: 100
height: 100
color: "blue"

// 锚定到父元素的中心
anchors.centerIn: parent
}

Rectangle {
id: greenRect
width: 100
height: 100
color: "green"

// 锚定到红色矩形右边
anchors.left: redRect.right
anchors.top: redRect.top
anchors.leftMargin: 10
}
}

锚点属性

锚点属性说明
anchors.left左边锚定
anchors.right右边锚定
anchors.top顶部锚定
anchors.bottom底部锚定
anchors.horizontalCenter水平中心锚定
anchors.verticalCenter垂直中心锚定
anchors.centerIn居中锚定(水平+垂直)
anchors.fill填充目标(四边锚定)

边距和偏移

Rectangle {
id: container
width: 200
height: 200
color: "lightgray"

Rectangle {
color: "steelblue"

// 填充父元素,但有边距
anchors.fill: parent
anchors.margins: 20 // 四边边距
// 或分别设置:
// anchors.leftMargin: 20
// anchors.rightMargin: 20
// anchors.topMargin: 20
// anchors.bottomMargin: 20
}

Rectangle {
width: 50
height: 50
color: "orange"

// 水平居中,但有偏移
anchors.horizontalCenter: parent.horizontalCenter
anchors.horizontalCenterOffset: 30 // 向右偏移

anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: -20 // 向上偏移
}
}

常见布局模式

import QtQuick

Item {
width: 400
height: 300

// 模式1:水平居中
Rectangle {
width: 100; height: 50
color: "coral"
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
}

// 模式2:垂直居中
Rectangle {
width: 100; height: 50
color: "teal"
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
}

// 模式3:完全居中
Rectangle {
width: 100; height: 50
color: "olive"
anchors.centerIn: parent
}

// 模式4:底部居中
Rectangle {
width: 100; height: 50
color: "purple"
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
}

// 模式5:左右排列
Rectangle {
id: leftRect
width: 100; height: 50
color: "navy"
anchors.left: parent.left
anchors.leftMargin: 10
anchors.top: parent.top
anchors.topMargin: 60
}

Rectangle {
width: 100; height: 50
color: "maroon"
anchors.left: leftRect.right
anchors.leftMargin: 10
anchors.top: leftRect.top
}
}

定位器

除了锚点,QML 还提供了定位器(Positioners)用于批量排列子元素。

Row(水平排列)

import QtQuick

Item {
width: 400
height: 100

Row {
spacing: 10 // 元素间距

Rectangle { width: 80; height: 80; color: "red" }
Rectangle { width: 80; height: 80; color: "green" }
Rectangle { width: 80; height: 80; color: "blue" }
}
}

Column(垂直排列)

Column {
spacing: 10

Rectangle { width: 80; height: 40; color: "red" }
Rectangle { width: 80; height: 40; color: "green" }
Rectangle { width: 80; height: 40; color: "blue" }
}

Grid(网格排列)

Grid {
columns: 3 // 列数
rows: 2 // 行数(可选)
spacing: 10 // 间距

Rectangle { width: 60; height: 60; color: "red" }
Rectangle { width: 60; height: 60; color: "green" }
Rectangle { width: 60; height: 60; color: "blue" }
Rectangle { width: 60; height: 60; color: "yellow" }
Rectangle { width: 60; height: 60; color: "purple" }
Rectangle { width: 60; height: 60; color: "orange" }
}

Flow(流式布局)

Flow {
width: 200 // 指定宽度
spacing: 10

// 元素会自动换行
Rectangle { width: 80; height: 40; color: "red" }
Rectangle { width: 80; height: 40; color: "green" }
Rectangle { width: 80; height: 40; color: "blue" }
Rectangle { width: 80; height: 40; color: "yellow" }
}

基础视觉元素

Item

Item 是所有视觉元素的基类,本身不绘制任何内容,常用作容器:

Item {
id: container
width: 200
height: 200

// 作为容器,使用 clip 裁剪超出内容
clip: true

Rectangle {
width: 300
height: 300
color: "red"
// 只有 200x200 的部分可见
}
}

Rectangle

矩形是最常用的视觉元素:

Rectangle {
width: 200
height: 100

// 填充颜色
color: "steelblue"

// 边框
border.color: "darkblue"
border.width: 2

// 圆角
radius: 10

// 渐变(与 color 互斥)
gradient: Gradient {
GradientStop { position: 0.0; color: "lightblue" }
GradientStop { position: 1.0; color: "blue" }
}
}

Text

文本显示元素:

Text {
text: "Hello QML"

// 字体设置
font.family: "Arial"
font.pixelSize: 24
font.bold: true
font.italic: true

// 颜色
color: "navy"

// 对齐
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter

// 换行
wrapMode: Text.WordWrap
width: 200

// 省略号
elide: Text.ElideRight

// 富文本
textFormat: Text.RichText
text: "<b>粗体</b> 和 <i>斜体</i>"
}

Image

图片显示元素:

Image {
source: "images/photo.png"

// 填充模式
fillMode: Image.PreserveAspectFit // 保持比例

// 可选值:
// Image.Stretch - 拉伸填充
// Image.PreserveAspectFit - 保持比例,留白
// Image.PreserveAspectCrop - 保持比例,裁剪
// Image.Tile - 平铺
// Image.TileVertically - 垂直平铺
// Image.TileHorizontally - 水平平铺

// 大小
width: 200
height: 150

// 加载状态
onStatusChanged: {
if (status === Image.Ready) {
console.log("图片加载完成")
} else if (status === Image.Error) {
console.log("图片加载失败")
}
}
}

动画基础

QML 的动画系统是其最强大的特性之一,可以轻松创建流畅的视觉效果。

属性动画

import QtQuick

Rectangle {
id: rect
width: 100
height: 100
color: "red"

// 属性动画
NumberAnimation on x {
from: 0
to: 200
duration: 1000 // 毫秒
running: true
loops: Animation.Infinite // 无限循环
}

// 颜色动画
ColorAnimation on color {
from: "red"
to: "blue"
duration: 2000
running: true
loops: Animation.Infinite
}
}

Easing 曲线

Easing 曲线定义了动画在开始和结束之间的过渡方式,可以创造出更自然、更有表现力的动画效果:

import QtQuick

Item {
width: 400
height: 400

// 常用的 Easing 类型示例
Repeater {
model: [
{ name: "Linear", type: Easing.Linear },
{ name: "InOutQuad", type: Easing.InOutQuad },
{ name: "OutBounce", type: Easing.OutBounce },
{ name: "OutElastic", type: Easing.OutElastic },
{ name: "InOutBack", type: Easing.InOutBack },
{ name: "OutCubic", type: Easing.OutCubic }
]

delegate: Rectangle {
width: 60
height: 60
y: 20 + index * 70
color: Qt.hsla(index * 0.15, 0.7, 0.5, 1)
radius: 30

Text {
anchors.left: parent.right
anchors.leftMargin: 10
anchors.verticalCenter: parent.verticalCenter
text: modelData.name
}

NumberAnimation on x {
from: 0
to: 300
duration: 2000
loops: Animation.Infinite
easing.type: modelData.type
}
}
}
}

常用的 Easing 类型说明

Easing 类型效果描述适用场景
Linear匀速运动机械运动、进度条
InOutQuad开始和结束慢,中间快自然移动、界面过渡
OutQuad / InQuad开始/结束减速弹出效果、滑入效果
OutBounce结束时弹跳落地效果、强调
OutElastic结束时弹性弹性效果、活泼动画
InOutBack超出目标后回弹特殊效果、吸引注意
OutCubic平滑减速优雅停止、柔和过渡

Easing 参数配置

NumberAnimation {
easing.type: Easing.OutElastic
easing.amplitude: 1.0 // 弹性幅度
easing.period: 0.5 // 弹性周期
}

NumberAnimation {
easing.type: Easing.OutBack
easing.overshoot: 2.0 // 超出目标的距离
}

动画控制

动画可以通过代码进行精确控制:

import QtQuick

Rectangle {
id: rect
width: 100
height: 100
color: "steelblue"

NumberAnimation on x {
id: anim
from: 0
to: 300
duration: 1000
running: false // 初始不运行
}

Column {
anchors.right: parent.right
spacing: 5

Button { text: "开始"; onClicked: anim.start() }
Button { text: "暂停"; onClicked: anim.pause() }
Button { text: "恢复"; onClicked: anim.resume() }
Button { text: "停止"; onClicked: anim.stop() }
Button { text: "重启"; onClicked: anim.restart() }
Button { text: "完成"; onClicked: anim.complete() } // 跳到结束状态
}

// 监听动画状态
Connections {
target: anim
function onRunningChanged() {
console.log("动画运行中:", anim.running)
}
function onFinished() {
console.log("动画完成")
}
}
}

动画分组

使用 SequentialAnimationParallelAnimation 组织复杂动画:

import QtQuick

Rectangle {
id: rect
width: 50
height: 50
color: "coral"

// 顺序动画:一个接一个执行
SequentialAnimation {
id: seqAnim
running: true
loops: Animation.Infinite

// 第一阶段:移动到右边
NumberAnimation {
target: rect
property: "x"
to: 300
duration: 500
easing.type: Easing.OutQuad
}

// 第二阶段:放大
ParallelAnimation {
NumberAnimation {
target: rect
property: "width"
to: 100
duration: 300
}
NumberAnimation {
target: rect
property: "height"
to: 100
duration: 300
}
}

// 第三阶段:改变颜色
ColorAnimation {
target: rect
to: "lightblue"
duration: 200
}

// 第四阶段:暂停
PauseAnimation { duration: 500 }

// 第五阶段:回到初始状态
ParallelAnimation {
NumberAnimation { target: rect; property: "x"; to: 0; duration: 500 }
NumberAnimation { target: rect; property: "width"; to: 50; duration: 300 }
NumberAnimation { target: rect; property: "height"; to: 50; duration: 300 }
ColorAnimation { target: rect; to: "coral"; duration: 200 }
}
}

// 并行动画:同时执行
ParallelAnimation {
id: parallelAnim

NumberAnimation {
target: rect
property: "y"
to: 100
duration: 1000
}

RotationAnimation {
target: rect
to: 360
duration: 1000
}
}
}

注意:分组动画中的子动画不能独立控制,必须通过父动画来启动和停止。

行为动画

为属性变化自动添加动画:

Rectangle {
id: rect
width: 100
height: 100
color: "blue"

// 为 x 属性添加行为动画
Behavior on x {
NumberAnimation { duration: 500 }
}

Behavior on color {
ColorAnimation { duration: 300 }
}

MouseArea {
anchors.fill: parent
onClicked: {
// 点击时,位置和颜色会动画过渡
rect.x = rect.x === 0 ? 200 : 0
rect.color = rect.color === "blue" ? "red" : "blue"
}
}
}

过渡动画

状态变化时的动画效果:

Rectangle {
id: rect
width: 100
height: 100
color: "lightblue"

states: [
State {
name: "expanded"
PropertyChanges {
target: rect
width: 200
height: 200
color: "steelblue"
}
}
]

transitions: Transition {
// 状态变化时的动画
NumberAnimation {
properties: "width,height"
duration: 300
easing.type: Easing.InOutQuad
}
ColorAnimation { duration: 300 }
}

MouseArea {
anchors.fill: parent
onClicked: {
rect.state = rect.state === "expanded" ? "" : "expanded"
}
}
}

组件基础

文件组件

将 QML 文件作为组件使用,文件名即组件名(首字母大写):

// 文件:Button.qml
import QtQuick

Rectangle {
id: root
width: 120
height: 40
color: mouseArea.pressed ? "#cccccc" : "#eeeeee"
radius: 5

// 暴露属性
property alias text: label.text

// 定义信号
signal clicked()

Text {
id: label
anchors.centerIn: parent
text: "按钮"
}

MouseArea {
id: mouseArea
anchors.fill: parent
onClicked: root.clicked()
}
}

// 使用组件
// 在其他 QML 文件中:
Button {
text: "自定义按钮"
onClicked: console.log("按钮被点击")
}

内联组件

使用 Component 类型定义可重用的内联组件:

import QtQuick

Item {
width: 400
height: 400

// 内联组件定义
Component {
id: rectComponent

Rectangle {
width: 50
height: 50
color: "red"
}
}

// 使用 Loader 加载组件
Loader {
sourceComponent: rectComponent
x: 100
y: 100
}

// 动态创建多个实例
Repeater {
model: 5

delegate: Loader {
sourceComponent: rectComponent
x: index * 60
}
}
}

QML 编码约定

遵循统一的编码规范可以提高代码的可读性和可维护性。以下是 Qt 官方推荐的 QML 编码约定:

对象属性顺序

QML 对象的属性建议按以下顺序排列:

Rectangle {
// 1. id(便于查找对象)
id: root

// 2. 自定义属性声明
property bool isActive: false
property alias labelText: label.text
property color backgroundColor: "white"

// 3. 信号声明
signal clicked()
signal stateChanged(string newState)

// 4. JavaScript 函数
function toggle() {
isActive = !isActive
}

function updateState(state) {
// 函数体
}

// 5. 对象属性(Qt 内置属性)
color: backgroundColor
width: 200
height: 100
radius: 5

// 6. 状态和过渡
states: [
State {
name: "active"
PropertyChanges { target: root; color: "lightblue" }
}
]

transitions: [
Transition {
ColorAnimation { duration: 200 }
}
]

// 7. 子对象
Text {
id: label
anchors.centerIn: parent
text: "Hello"
}

MouseArea {
anchors.fill: parent
onClicked: root.clicked()
}
}

分组属性

对于同一组属性,使用分组语法提高可读性:

// 推荐:分组语法
Rectangle {
anchors {
left: parent.left
top: parent.top
right: parent.right
margins: 10
}

font {
family: "Arial"
pixelSize: 14
bold: true
}
}

// 不推荐:点语法分散
Rectangle {
anchors.left: parent.left
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: 10
}

属性引用规范

始终通过 id 明确引用属性,避免使用隐式的 parent 引用:

// 推荐:明确引用
Item {
id: root

Rectangle {
id: child
width: root.width / 2 // 明确引用 root
height: root.height / 2
}
}

// 不推荐:隐式引用
Item {
Rectangle {
width: parent.width / 2 // 不清楚 parent 是谁
}
}

必需属性(Required Properties)

使用 required 关键字声明组件必须接收的属性:

// UserCard.qml
import QtQuick

Rectangle {
id: root
width: 200
height: 80

// 必需属性:使用组件时必须提供
required property string userName
required property string userEmail
required property int userAge

// 可选属性:有默认值
property bool showAge: true

Column {
anchors.centerIn: parent
Text { text: userName; font.bold: true }
Text { text: userEmail; color: "gray" }
Text { text: showAge ? userAge + "岁" : ""; visible: showAge }
}
}

// 使用时
UserCard {
userName: "张三"
userEmail: "[email protected]"
userAge: 25
showAge: true
}

必需属性的优势:

  • 编译时检查,缺少必需属性会报错
  • 提高性能(避免动态查找)
  • 提高代码可读性和可维护性

类型注解

为函数添加类型注解以提高代码可读性和工具支持:

// 推荐:带类型注解
function calculateArea(width: real, height: real) : real {
return width * height
}

function formatPrice(price: double, currency: string) : string {
return currency + price.toFixed(2)
}

// 不推荐:无类型注解
function calculateArea(width, height) {
return width * height
}

信号处理器规范

使用函数形式处理信号参数,并为参数命名:

// 推荐:箭头函数 + 命名参数
MouseArea {
onClicked: (mouse) => {
console.log(`点击位置: ${mouse.x}, ${mouse.y}`)
}
}

// 或使用命名函数
function handleClicked(mouse) {
console.log(`点击位置: ${mouse.x}, ${mouse.y}`)
}

MouseArea {
onClicked: handleClicked
}

代码格式化

// 每个属性单独一行
Rectangle {
color: "blue"
width: parent.width / 3
}

// 多行脚本使用块格式
Rectangle {
width: {
var w = parent.width / 3
console.debug(w)
return w
}
}

// 长函数提取为独立函数
function calculateWidth() : real {
var w = parent.width / 3
// 更多计算...
return w
}

Rectangle {
width: calculateWidth()
}

小结

本章介绍了 QML 的基础知识:

  1. QML 语法:声明式语法,对象树结构
  2. 属性系统:基本属性、属性绑定、自定义属性、属性别名
  3. 信号机制:内置信号、自定义信号、信号连接
  4. 布局方式:锚点布局、定位器(Row、Column、Grid、Flow)
  5. 基础元素:Item、Rectangle、Text、Image
  6. 动画系统:属性动画、行为动画、过渡动画
  7. 组件:文件组件、内联组件

QML 的声明式语法和属性绑定机制使得创建动态用户界面变得简单直观。接下来,我们将在 QML 组件 中学习更多内置组件和自定义组件的高级用法。

练习

  1. 创建一个带渐变背景的矩形,内部居中显示一段文字
  2. 使用锚点实现两个矩形左右并排排列,间距 10 像素
  3. 实现一个可点击的矩形,点击后颜色在红色和蓝色之间切换,带动画过渡
  4. 创建一个自定义按钮组件,支持设置文本和颜色
  5. 使用 Column 和 Row 实现一个简单的计算器界面布局

参考资源