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 的区别
| 特性 | QML | Qt Widgets |
|---|---|---|
| 语言 | QML + JavaScript | C++ |
| 编程范式 | 声明式 | 命令式 |
| 动画支持 | 内置,简洁 | 需要额外代码 |
| 样式定制 | 样式表或 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)
}
参数处理的注意事项:
- 参数名不必与信号定义中的参数名相同,但建议使用有意义的名称
- 可以省略尾部的参数,但不能跳过中间的参数
- 不推荐使用代码块直接注入参数的方式(已弃用),应使用箭头函数
信号连接
使用 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("动画完成")
}
}
}
动画分组
使用 SequentialAnimation 和 ParallelAnimation 组织复杂动画:
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 的基础知识:
- QML 语法:声明式语法,对象树结构
- 属性系统:基本属性、属性绑定、自定义属性、属性别名
- 信号机制:内置信号、自定义信号、信号连接
- 布局方式:锚点布局、定位器(Row、Column、Grid、Flow)
- 基础元素:Item、Rectangle、Text、Image
- 动画系统:属性动画、行为动画、过渡动画
- 组件:文件组件、内联组件
QML 的声明式语法和属性绑定机制使得创建动态用户界面变得简单直观。接下来,我们将在 QML 组件 中学习更多内置组件和自定义组件的高级用法。
练习
- 创建一个带渐变背景的矩形,内部居中显示一段文字
- 使用锚点实现两个矩形左右并排排列,间距 10 像素
- 实现一个可点击的矩形,点击后颜色在红色和蓝色之间切换,带动画过渡
- 创建一个自定义按钮组件,支持设置文本和颜色
- 使用 Column 和 Row 实现一个简单的计算器界面布局