Qt Quick:Import、ID 与 QML 属性详解

Qt Quick:Import、ID 与 QML 属性详解

  • [Qt Quick 学习笔记:Import、ID 与 QML 属性详解](#Qt Quick 学习笔记:Import、ID 与 QML 属性详解)
  • 二、Import:导入模块与资源
    • [2.1 Import 是什么](#2.1 Import 是什么)
    • [2.2 Import 与 include 的区别](#2.2 Import 与 include 的区别)
      • [C/C++ 的 include](#C/C++ 的 include)
      • [QML 的 import](#QML 的 import)
      • 二者对比
    • [2.3 模块导入的基本语法](#2.3 模块导入的基本语法)
    • [2.4 Qt 5 与 Qt 6 的版本号写法](#2.4 Qt 5 与 Qt 6 的版本号写法)
    • [2.5 使用 as 指定命名空间](#2.5 使用 as 指定命名空间)
      • [使用 as 的常见场景](#使用 as 的常见场景)
  • [三、导入自定义 QML 文件](#三、导入自定义 QML 文件)
    • [3.1 当前目录中的 QML 文件](#3.1 当前目录中的 QML 文件)
    • [3.2 导入其他目录中的 QML 文件](#3.2 导入其他目录中的 QML 文件)
    • [3.3 给目录设置别名](#3.3 给目录设置别名)
    • [3.4 模块导入与目录导入对比](#3.4 模块导入与目录导入对比)
  • [四、QML 中的 id](#四、QML 中的 id)
    • [4.1 id 是什么](#4.1 id 是什么)
    • [4.2 id 的主要作用](#4.2 id 的主要作用)
  • [五、id 的四个重要特性](#五、id 的四个重要特性)
    • [5.1 唯一性](#5.1 唯一性)
    • [5.2 只读性](#5.2 只读性)
    • [5.3 id 不是普通对象属性](#5.3 id 不是普通对象属性)
    • [5.4 id 的命名规则](#5.4 id 的命名规则)
  • 六、parent:引用父对象
  • [七、QML 属性基础](#七、QML 属性基础)
    • [7.1 什么是属性](#7.1 什么是属性)
    • [7.2 自定义属性的声明格式](#7.2 自定义属性的声明格式)
    • [7.3 常见属性类型](#7.3 常见属性类型)
    • [7.4 属性命名规则](#7.4 属性命名规则)
  • 八、属性修饰符
    • [8.1 default 属性](#8.1 default 属性)
    • [8.2 required 属性](#8.2 required 属性)
    • [8.3 readonly 属性](#8.3 readonly 属性)
  • 九、属性初始化与赋值
    • [9.1 使用冒号进行初始化](#9.1 使用冒号进行初始化)
    • [9.2 在 JavaScript 代码块中使用等号赋值](#9.2 在 JavaScript 代码块中使用等号赋值)
    • [9.3 类型需要匹配](#9.3 类型需要匹配)
  • 十、属性绑定
    • [10.1 什么是属性绑定](#10.1 什么是属性绑定)
    • [10.2 固定赋值与属性绑定的区别](#10.2 固定赋值与属性绑定的区别)
    • [10.3 绑定到表达式](#10.3 绑定到表达式)
    • [10.4 绑定到函数](#10.4 绑定到函数)
    • [10.5 直接赋值可能破坏绑定](#10.5 直接赋值可能破坏绑定)
  • 十一、属性变化信号
    • [11.1 属性会自动生成变化信号](#11.1 属性会自动生成变化信号)
    • [11.2 信号处理器的命名规则](#11.2 信号处理器的命名规则)
    • [11.3 属性变化信号示例](#11.3 属性变化信号示例)
    • [11.4 内置属性也有变化信号](#11.4 内置属性也有变化信号)
  • [十二、对象列表 property list](#十二、对象列表 property list)
    • [12.1 对象列表是什么](#12.1 对象列表是什么)
    • [12.2 完整示例](#12.2 完整示例)
    • [12.3 length 与下标](#12.3 length 与下标)
    • [12.4 列表书写注意事项](#12.4 列表书写注意事项)
  • 十三、属性组
    • [13.1 什么是属性组](#13.1 什么是属性组)
    • [13.2 点语法](#13.2 点语法)
    • [13.3 属性组语法](#13.3 属性组语法)
    • [13.4 两种写法对比](#13.4 两种写法对比)
  • [十四、属性别名 property alias](#十四、属性别名 property alias)
    • [14.1 什么是属性别名](#14.1 什么是属性别名)
    • [14.2 属性别名示例](#14.2 属性别名示例)
    • [14.3 属性别名的双向关系](#14.3 属性别名的双向关系)
    • [14.4 属性别名与普通绑定的区别](#14.4 属性别名与普通绑定的区别)
    • [14.5 使用 alias 暴露子组件属性](#14.5 使用 alias 暴露子组件属性)
  • [十五、只读属性 readonly property](#十五、只读属性 readonly property)
    • [15.1 定义只读属性](#15.1 定义只读属性)
    • [15.2 只读属性不能重新赋值](#15.2 只读属性不能重新赋值)
    • [15.3 只读属性可以绑定表达式](#15.3 只读属性可以绑定表达式)
    • [15.4 readonly 与 C++ const 的理解对比](#15.4 readonly 与 C++ const 的理解对比)
  • 十六、综合示例
    • 十七、综合示例解析
      • [1. 导入模块](#1. 导入模块)
      • [2. 使用 id 标识窗口](#2. 使用 id 标识窗口)
      • [3. 声明普通属性](#3. 声明普通属性)
      • [4. 声明只读属性](#4. 声明只读属性)
      • [5. 声明属性别名](#5. 声明属性别名)
      • [6. 监听属性变化](#6. 监听属性变化)
      • [7. 使用属性组](#7. 使用属性组)
  • 十八、常见错误整理
    • [18.1 重复使用相同 id](#18.1 重复使用相同 id)
    • [18.2 id 使用引号](#18.2 id 使用引号)
    • [18.3 id 以数字开头](#18.3 id 以数字开头)
    • [18.4 在对象声明中使用等号](#18.4 在对象声明中使用等号)
    • [18.5 属性类型不匹配](#18.5 属性类型不匹配)
    • [18.6 修改只读属性](#18.6 修改只读属性)
    • [18.7 混淆 alias 与普通属性绑定](#18.7 混淆 alias 与普通属性绑定)
  • 十九、核心知识对比表
  • 二十、快速复习口诀
  • 二十一、复习问题
  • 二十二、本节总结

Qt Quick 学习笔记:Import、ID 与 QML 属性详解

本文整理 Qt Quick 开发中的三个基础知识点:importid 和 QML 属性。

这三个部分虽然属于基础语法,但会直接影响后续的组件拆分、属性绑定、对象通信和项目结构设计。


一、本节内容概览

本文主要包含以下内容:

  • import 的作用和基本语法
  • import 与 C/C++ 中 include 的区别
  • 模块导入与目录导入
  • as 命名空间限定符
  • QML 中 id 的作用
  • id 的唯一性、只读性和命名规则
  • QML 自定义属性的声明
  • 属性初始化、赋值与属性绑定
  • 属性变化信号
  • 对象列表 property list
  • 属性组
  • 属性别名 property alias
  • 只读属性 readonly property

二、Import:导入模块与资源

2.1 Import 是什么

在 QML 中,import 用于告诉 QML 引擎:

当前 QML 文件需要使用哪些模块、组件、JavaScript 资源或其他目录中的 QML 文件。

例如:

qml 复制代码
import QtQuick
import QtQuick.Controls
import QtQuick.Window

不同模块提供不同类型的组件。

模块 常见组件或作用
QtQuick Rectangle、Text、Image、MouseArea 等
QtQuick.Controls Button、Label、TextField 等控件
QtQuick.Window Window 等窗口类型
QtQuick.Layouts RowLayout、ColumnLayout、GridLayout 等
QtQml QML 核心语言和对象支持

例如,下面的代码同时使用了窗口、矩形和按钮:

qml 复制代码
import QtQuick
import QtQuick.Controls
import QtQuick.Window

Window {
    width: 800
    height: 600
    visible: true

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

        Button {
            text: "确定"
            anchors.centerIn: parent
        }
    }
}

2.2 Import 与 include 的区别

QML 中的 import 和 C/C++ 中的 #include 看起来作用相似,但工作方式不同。

C/C++ 的 include

cpp 复制代码
#include <iostream>

C/C++ 的 #include 会在预处理阶段,将头文件内容引入当前源文件。

可以简单理解为:

text 复制代码
将头文件内容展开到当前位置

QML 的 import

qml 复制代码
import QtQuick

QML 的 import 不会把模块源码复制到当前 QML 文件。

它的作用更接近:

text 复制代码
告诉 QML 引擎去哪里寻找和加载需要的类型与资源

二者对比

对比项 C/C++ #include QML import
执行阶段 编译预处理阶段 QML 引擎加载阶段
是否复制代码 会进行文本展开 不会复制模块代码
主要作用 引入声明或定义 注册并访问 QML 类型和资源
常见对象 头文件 QML 模块、目录、JS 资源

可以用 include 帮助理解 import,但不要把二者完全等同。


2.3 模块导入的基本语法

传统的模块导入结构可以写成:

qml 复制代码
import 模块名称 版本号 as 限定名称

例如:

qml 复制代码
import QtQuick.Controls 2.15 as MyControls

其中:

组成部分 示例 说明
import import 导入关键字
模块名称 QtQuick.Controls Module Identifier
版本号 2.15 Version Number
限定名称 MyControls Qualifier

不过,版本号和 as 都可能省略。

最常见的写法是:

qml 复制代码
import QtQuick
import QtQuick.Controls

2.4 Qt 5 与 Qt 6 的版本号写法

Qt 5 项目中经常看到:

qml 复制代码
import QtQuick 2.15
import QtQuick.Controls 2.15

Qt 6 项目中通常可以使用无版本号写法:

qml 复制代码
import QtQuick
import QtQuick.Controls

课程中建议,在将 Qt 5 项目迁移到 Qt 6 时,优先检查并调整带版本号的导入语句。

实际开发时,应以当前 Qt 版本、项目配置和构建工具的要求为准。


2.5 使用 as 指定命名空间

as 可以给导入的模块指定一个局部名称。

例如:

qml 复制代码
import QtQuick.Controls as MyControls

使用组件时,需要加上限定名称:

qml 复制代码
MyControls.Button {
    text: "确定"
}

完整示例:

qml 复制代码
import QtQuick
import QtQuick.Window
import QtQuick.Controls as MyControls

Window {
    width: 800
    height: 600
    visible: true

    MyControls.Button {
        text: "确定"
        anchors.centerIn: parent
    }
}

使用 as 的常见场景

  • 不同模块中存在同名类型;
  • 希望明确组件来自哪个模块;
  • 导入自定义组件目录;
  • 避免污染当前文件的全局命名空间。

如果没有类型冲突,一般不需要使用 as


三、导入自定义 QML 文件

3.1 当前目录中的 QML 文件

如果自定义组件和当前文件位于同一个目录,通常可以直接使用,不需要再次写 import

例如,项目目录如下:

text 复制代码
MyProject/
├── Main.qml
└── CustomButton.qml

CustomButton.qml

qml 复制代码
import QtQuick
import QtQuick.Controls

Button {
    width: 140
    height: 45
    text: "自定义按钮"
}

Main.qml 中可以直接使用:

qml 复制代码
import QtQuick
import QtQuick.Window

Window {
    width: 800
    height: 600
    visible: true

    CustomButton {
        anchors.centerIn: parent
    }
}

3.2 导入其他目录中的 QML 文件

假设项目目录如下:

text 复制代码
MyProject/
├── Main.qml
└── components/
    └── CustomRectangle.qml

可以在 Main.qml 中导入 components 目录:

qml 复制代码
import QtQuick
import QtQuick.Window
import "components"

然后直接使用:

qml 复制代码
CustomRectangle {
    anchors.centerIn: parent
}

3.3 给目录设置别名

也可以使用 as 为目录指定别名:

qml 复制代码
import "components" as Components

使用组件时:

qml 复制代码
Components.CustomRectangle {
    anchors.centerIn: parent
}

完整示例:

qml 复制代码
import QtQuick
import QtQuick.Window
import "components" as Components

Window {
    width: 800
    height: 600
    visible: true

    Components.CustomRectangle {
        anchors.centerIn: parent
    }
}

这种方式可以清楚地看出组件来自哪个目录。


3.4 模块导入与目录导入对比

类型 示例 主要用途
模块导入 import QtQuick.Controls 导入已经注册的 QML 模块
目录导入 import "components" 导入本地目录中的 QML 文件
带别名导入 import "components" as Components 使用命名空间访问组件

四、QML 中的 id

4.1 id 是什么

id 是 QML 对象的唯一标识符。

例如:

qml 复制代码
Rectangle {
    id: redRectangle

    width: 200
    height: 100
    color: "red"
}

其他对象可以通过 redRectangle 访问该对象的属性:

qml 复制代码
Text {
    text: "矩形宽度:" + redRectangle.width
}

还可以在按钮事件中修改它:

qml 复制代码
Button {
    text: "增加宽度"

    onClicked: {
        redRectangle.width += 10
    }
}

4.2 id 的主要作用

id 最常见的作用有:

  1. 引用当前文件中的其他对象;
  2. 访问对象属性;
  3. 调用对象函数;
  4. 建立属性绑定;
  5. 在信号处理器中修改对象状态。

例如:

qml 复制代码
Rectangle {
    id: targetRectangle

    width: 100
    height: 100
    color: "orange"
}

Button {
    text: "变大"

    onClicked: {
        targetRectangle.width += 20
        targetRectangle.height += 20
    }
}

五、id 的四个重要特性

5.1 唯一性

在同一个 QML 作用域中,不能存在两个相同的 id

错误示例:

qml 复制代码
Rectangle {
    id: box
}

Rectangle {
    id: box
}

两个对象都使用了 box,QML 组件将无法正常加载。

正确写法:

qml 复制代码
Rectangle {
    id: redBox
}

Rectangle {
    id: blueBox
}

可以将 id 理解为对象在当前作用域中的"身份证号码"。


5.2 只读性

id 在声明后不能被重新赋值。

错误示例:

qml 复制代码
Rectangle {
    id: redRectangle

    Component.onCompleted: {
        redRectangle.id = blueRectangle
    }
}

不能在程序运行过程中修改一个对象的 id

因此,id 应当在对象定义时确定。


5.3 id 不是普通对象属性

下面这些属于对象属性:

qml 复制代码
width: 200
height: 100
color: "red"
visible: true

id 是 QML 语言提供的特殊标识符,不是普通属性。

因此,不能像修改 width 一样修改 id

qml 复制代码
redRectangle.width = 300   // 正确
redRectangle.id = newId    // 错误

5.4 id 的命名规则

id 必须:

  • 以小写字母开头;
  • 或者以下划线开头;
  • 后续可以包含字母、数字和下划线;
  • 不能以数字开头;
  • 不能包含空格;
  • 不需要使用引号。

正确示例:

qml 复制代码
id: redRectangle
id: button1
id: main_window
id: _privateObject

错误示例:

qml 复制代码
id: 1button
id: "redRectangle"
id: red-rectangle
id: red rectangle

推荐使用小驼峰命名:

qml 复制代码
id: mainWindow
id: loginButton
id: userNameLabel
id: contentRectangle

六、parent:引用父对象

QML 对象可以嵌套。

例如:

qml 复制代码
Rectangle {
    width: 400
    height: 300
    color: "lightgray"

    Rectangle {
        width: 100
        height: 100
        color: "blue"
    }
}

内层矩形的父对象是外层矩形。

子对象可以使用 parent 引用父对象:

qml 复制代码
Rectangle {
    width: parent.width / 2
    height: parent.height / 2
    color: "blue"
}

也可以让子对象在父对象中居中:

qml 复制代码
anchors.centerIn: parent

完整示例:

qml 复制代码
Rectangle {
    id: container

    width: 400
    height: 300
    color: "lightgray"

    Rectangle {
        width: parent.width / 2
        height: parent.height / 2
        color: "skyblue"

        anchors.centerIn: parent
    }
}

七、QML 属性基础

7.1 什么是属性

属性用于保存或描述对象的状态。

例如:

qml 复制代码
Rectangle {
    width: 200
    height: 100
    color: "red"
    visible: true
}

这里的:

  • width
  • height
  • color
  • visible

都是属性。

属性值可以是:

  • 固定值;
  • 表达式;
  • 其他对象的属性;
  • JavaScript 函数返回值;
  • 动态绑定结果。

7.2 自定义属性的声明格式

QML 中可以通过 property 声明自定义属性。

基本格式:

qml 复制代码
property 属性类型 属性名称: 初始值

例如:

qml 复制代码
property int count: 0
property string title: "Qt Quick"
property bool running: false
property real scaleValue: 1.5

完整示例:

qml 复制代码
Rectangle {
    id: root

    property int count: 10
    property string title: "QML 属性学习"
    property bool enabledState: true
    property real opacityValue: 0.8

    width: 300
    height: 200
    opacity: opacityValue
}

7.3 常见属性类型

属性类型 说明 示例
int 整数 property int count: 10
real 小数 property real ratio: 1.5
double 双精度小数 property double value: 3.14
bool 布尔值 property bool running: true
string 字符串 property string name: "QML"
color 颜色 property color themeColor: "blue"
url URL 地址 property url imageSource: "logo.png"
var 任意类型 property var data: []
list 对象列表 property list<Rectangle> items
alias 属性别名 property alias boxWidth: box.width

7.4 属性命名规则

属性名称通常应当:

  • 以小写字母开头;
  • 使用字母、数字和下划线;
  • 不使用空格;
  • 不以数字开头;
  • 不使用 JavaScript 保留关键字;
  • 推荐使用小驼峰命名。

正确示例:

qml 复制代码
property int userAge: 18
property string userName: "Tom"
property bool loginState: false

不推荐或错误示例:

qml 复制代码
property int 1age: 18
property string user-name: "Tom"
property bool var: false

八、属性修饰符

QML 属性可以搭配不同修饰符。

常见修饰符包括:

qml 复制代码
default
required
readonly

基本结构可以表示为:

qml 复制代码
[default] [required] [readonly] property 类型 名称: 初始值

这些修饰符一般不会同时全部使用,而是根据需求选择。


8.1 default 属性

default 表示对象的默认属性。

当子对象没有明确写入某个属性时,会自动进入默认属性。

例如,某些 QML 容器类型可以将子对象自动加入其默认数据属性。

自定义组件中也可以声明默认属性:

qml 复制代码
default property alias content: container.data

示例:

qml 复制代码
Item {
    default property alias content: container.data

    Item {
        id: container
        anchors.fill: parent
    }
}

使用时,子对象可以直接放在组件内部。


8.2 required 属性

required 表示该属性必须由组件使用者提供。

例如:

qml 复制代码
Rectangle {
    required property string title
    required property color backgroundColor

    color: backgroundColor

    Text {
        text: title
        anchors.centerIn: parent
    }
}

使用组件时,必须为这些属性赋值:

qml 复制代码
CustomCard {
    title: "用户信息"
    backgroundColor: "lightblue"
}

如果没有设置 required 属性,组件可能无法正常创建。


8.3 readonly 属性

readonly 表示属性只能读取,不能在外部重新赋值。

例如:

qml 复制代码
readonly property int maxCount: 100

错误写法:

qml 复制代码
maxCount = 200

只读属性适合保存:

  • 固定配置;
  • 计算结果;
  • 不允许外部修改的数据;
  • 组件内部状态的只读接口。

九、属性初始化与赋值

9.1 使用冒号进行初始化

在对象声明中,一般使用冒号初始化属性:

qml 复制代码
property int count: 10
width: 200
height: 100
color: "red"

推荐在冒号后添加一个空格:

qml 复制代码
width: 200

不推荐:

qml 复制代码
width:200

虽然两种写法通常都能运行,但第一种更易读。


9.2 在 JavaScript 代码块中使用等号赋值

在事件处理器或函数中,需要使用等号赋值:

qml 复制代码
Button {
    text: "增加"

    onClicked: {
        count = count + 1
    }
}

也可以写成:

qml 复制代码
count += 1

因此,需要区分两个场景。

声明和初始化

qml 复制代码
property int count: 0

运行期间修改

qml 复制代码
count = 10

9.3 类型需要匹配

属性值应当与属性类型匹配。

正确示例:

qml 复制代码
property int count: 10
property string title: "QML"
property bool running: true

错误示例:

qml 复制代码
property int count: "hello"
property bool running: 100

部分数值类型之间可能发生自动转换。

例如:

qml 复制代码
property real sizeValue: 10

整数 10 可以作为 real 类型的值使用。

但实际开发中仍应尽量保证类型清晰,避免依赖隐式转换。


十、属性绑定

10.1 什么是属性绑定

当一个属性的值依赖另一个属性时,QML 会建立动态绑定关系。

例如:

qml 复制代码
Rectangle {
    id: redRectangle

    width: 200
    height: 100
    color: "red"
}

Rectangle {
    width: redRectangle.width / 2
    height: 100
    color: "blue"
}

蓝色矩形的宽度绑定到了红色矩形:

qml 复制代码
width: redRectangle.width / 2

当红色矩形宽度变化时,蓝色矩形宽度会自动更新。


10.2 固定赋值与属性绑定的区别

固定值:

qml 复制代码
width: 100

属性绑定:

qml 复制代码
width: parent.width / 2

二者区别如下:

类型 示例 是否自动更新
固定值 width: 100
属性绑定 width: parent.width / 2
对象属性绑定 width: redBox.width
函数表达式绑定 width: calculateWidth() 依赖项变化时重新计算

10.3 绑定到表达式

属性可以绑定到一个表达式:

qml 复制代码
width: parent.width - 40
height: width / 2
visible: parent.width > 500

也可以使用三元表达式:

qml 复制代码
color: width > 200 ? "red" : "blue"

10.4 绑定到函数

qml 复制代码
Rectangle {
    id: box

    property int baseWidth: 100

    function calculateWidth() {
        return baseWidth * 2
    }

    width: calculateWidth()
    height: 100
}

当函数中依赖的 QML 属性发生变化时,绑定结果可能重新计算。


10.5 直接赋值可能破坏绑定

假设原来存在绑定:

qml 复制代码
Rectangle {
    id: blueBox

    width: redBox.width / 2
}

之后执行:

qml 复制代码
blueBox.width = 300

此时原来的绑定通常会被固定值 300 替代。

后续即使 redBox.width 改变,blueBox.width 也可能不再跟随变化。

因此需要区分:

qml 复制代码
width: redBox.width / 2

和:

qml 复制代码
blueBox.width = 300

前者是动态绑定,后者是直接赋值。


十一、属性变化信号

11.1 属性会自动生成变化信号

声明一个属性后,QML 会自动为它生成变化信号。

例如:

qml 复制代码
property int count: 0

会自动产生类似下面的变化信号:

qml 复制代码
countChanged

对应的信号处理器是:

qml 复制代码
onCountChanged

11.2 信号处理器的命名规则

属性名称首字母大写,并在前面添加 on,后面添加 Changed

属性名称 变化信号处理器
count onCountChanged
userName onUserNameChanged
currentValue onCurrentValueChanged
backgroundColor onBackgroundColorChanged

11.3 属性变化信号示例

qml 复制代码
Rectangle {
    property int count: 0

    onCountChanged: {
        console.log("count 发生变化,当前值:", count)
    }

    Button {
        text: "增加"

        onClicked: {
            count += 1
        }
    }
}

每次按钮点击后,count 发生变化,onCountChanged 会自动执行。


11.4 内置属性也有变化信号

例如:

qml 复制代码
Rectangle {
    width: 100

    onWidthChanged: {
        console.log("矩形宽度变化:", width)
    }
}

常见内置属性变化信号包括:

qml 复制代码
onWidthChanged
onHeightChanged
onXChanged
onYChanged
onVisibleChanged
onOpacityChanged

十二、对象列表 property list

12.1 对象列表是什么

对象列表用于在一个属性中保存多个 QML 对象。

基本语法:

qml 复制代码
property list<对象类型> 属性名称: [
    对象1 {},
    对象2 {},
    对象3 {}
]

例如:

qml 复制代码
property list<Rectangle> rectangles: [
    Rectangle {
        width: 100
        height: 100
        color: "red"
    },

    Rectangle {
        width: 100
        height: 100
        color: "blue"
    }
]

12.2 完整示例

qml 复制代码
Item {
    property list<Rectangle> rectangleList: [
        Rectangle {
            width: 100
            height: 100
            color: "red"
        },

        Rectangle {
            width: 120
            height: 100
            color: "green"
        },

        Rectangle {
            width: 140
            height: 100
            color: "blue"
        }
    ]

    Component.onCompleted: {
        console.log("列表长度:", rectangleList.length)
        console.log("第一个矩形颜色:", rectangleList[0].color)
        console.log("第二个矩形宽度:", rectangleList[1].width)
    }
}

12.3 length 与下标

对象列表常用两种操作:

获取列表长度

qml 复制代码
rectangleList.length

根据下标获取对象

qml 复制代码
rectangleList[0]
rectangleList[1]
rectangleList[2]

下标从 0 开始。

例如,一个长度为 3 的列表,其有效下标为:

text 复制代码
0、1、2

12.4 列表书写注意事项

建议使用下面的格式:

qml 复制代码
property list<Rectangle> items: [
    Rectangle {
        color: "red"
    },

    Rectangle {
        color: "blue"
    }
]

注意:

  • 每个元素之间使用逗号分隔;
  • 最后一个元素后通常不需要再写逗号;
  • 通过 [下标] 访问元素;
  • 使用 .length 获取列表长度。

十三、属性组

13.1 什么是属性组

某些属性内部还包含多个子属性。

例如,font 属性中包含:

  • pixelSize
  • bold
  • italic
  • family
  • underline

这些相关属性可以看作一个属性组。


13.2 点语法

可以通过点语法设置属性:

qml 复制代码
Text {
    text: "Qt Quick"

    font.pixelSize: 24
    font.bold: true
    font.italic: false
}

13.3 属性组语法

也可以使用属性组的写法:

qml 复制代码
Text {
    text: "Qt Quick"

    font {
        pixelSize: 24
        bold: true
        italic: false
    }
}

两种写法效果相同。


13.4 两种写法对比

点语法

qml 复制代码
font.pixelSize: 24
font.bold: true
font.family: "Microsoft YaHei"

属性组语法

qml 复制代码
font {
    pixelSize: 24
    bold: true
    family: "Microsoft YaHei"
}

如果只修改一个子属性,点语法更加简洁。

如果同时修改多个相关属性,属性组语法通常更整齐。


十四、属性别名 property alias

14.1 什么是属性别名

属性别名是对已有属性的直接引用。

基本格式:

qml 复制代码
property alias 别名名称: 对象id.属性名称

例如:

qml 复制代码
property alias boxWidth: box.width

这里的 boxWidth 并没有创建新的独立存储值,而是直接引用 box.width


14.2 属性别名示例

qml 复制代码
Rectangle {
    id: root

    property alias contentWidth: contentRectangle.width

    width: 400
    height: 300

    Rectangle {
        id: contentRectangle

        width: 200
        height: 100
        color: "lightblue"
    }
}

此时:

qml 复制代码
root.contentWidth

实际上就是:

qml 复制代码
contentRectangle.width

14.3 属性别名的双向关系

修改原属性:

qml 复制代码
contentRectangle.width = 300

别名读取到的值也会变成 300

修改别名:

qml 复制代码
root.contentWidth = 500

原属性也会变成:

qml 复制代码
contentRectangle.width === 500

因此,属性别名具有双向访问效果。


14.4 属性别名与普通绑定的区别

普通属性绑定:

qml 复制代码
property int copiedWidth: contentRectangle.width

属性别名:

qml 复制代码
property alias contentWidth: contentRectangle.width

二者对比如下:

对比项 普通属性绑定 属性别名
是否创建新属性存储
是否直接引用原属性
修改原属性 绑定值会更新 别名值同步
修改当前属性 可能破坏绑定 会直接修改原属性
典型用途 计算或同步值 暴露内部对象属性

14.5 使用 alias 暴露子组件属性

property alias 经常用于自定义组件。

例如,创建 CustomLabel.qml

qml 复制代码
import QtQuick

Rectangle {
    id: root

    property alias text: label.text
    property alias fontSize: label.font.pixelSize

    width: 240
    height: 60
    color: "lightgray"

    Text {
        id: label
        anchors.centerIn: parent
    }
}

在外部使用时:

qml 复制代码
CustomLabel {
    text: "欢迎学习 Qt Quick"
    fontSize: 20
}

外部虽然没有直接访问组件内部的 label,但可以通过别名控制它的属性。


十五、只读属性 readonly property

15.1 定义只读属性

只读属性通过 readonly property 声明:

qml 复制代码
readonly property int maxCount: 100

完整示例:

qml 复制代码
Rectangle {
    readonly property int maxCount: 100
    readonly property string appName: "Qt Quick Demo"
}

15.2 只读属性不能重新赋值

错误示例:

qml 复制代码
Button {
    text: "修改"

    onClicked: {
        maxCount = 200
    }
}

程序会提示只读属性不能被修改。


15.3 只读属性可以绑定表达式

只读不代表只能保存固定值。

例如:

qml 复制代码
Item {
    property int itemWidth: 200
    property int itemHeight: 100

    readonly property int area: itemWidth * itemHeight
}

area 不能被外部直接赋值,但会根据 itemWidthitemHeight 自动更新。

例如:

qml 复制代码
itemWidth = 300

此时:

qml 复制代码
area === 300 * itemHeight

这种写法适合暴露计算结果。


15.4 readonly 与 C++ const 的理解对比

可以把 readonly 暂时理解为类似 C++ 中的 const

cpp 复制代码
const int maxCount = 100;

不过二者不是完全相同的语言机制。

在学习阶段,可以先记住:

readonly property 允许读取和绑定,但不允许从外部直接重新赋值。


十六、综合示例

下面通过一个完整案例,将本节知识点组合起来。

qml 复制代码
import QtQuick
import QtQuick.Controls
import QtQuick.Window

Window {
    id: mainWindow

    width: 800
    height: 600
    visible: true
    title: "Import、ID 与属性综合示例"

    property int clickCount: 0
    property string message: "尚未点击按钮"

    readonly property int maxWidth: 500

    property alias targetWidth: targetRectangle.width

    onClickCountChanged: {
        console.log("点击次数发生变化:", clickCount)
    }

    Rectangle {
        id: targetRectangle

        width: 200
        height: 120
        color: clickCount % 2 === 0 ? "lightblue" : "lightgreen"
        radius: 10

        anchors.centerIn: parent

        Text {
            text: message
            anchors.centerIn: parent

            font {
                pixelSize: 18
                bold: true
            }
        }
    }

    Button {
        id: increaseButton

        text: "增加宽度"

        anchors.left: parent.left
        anchors.bottom: parent.bottom
        anchors.leftMargin: 20
        anchors.bottomMargin: 20

        onClicked: {
            clickCount += 1
            message = "已经点击 " + clickCount + " 次"

            if (targetRectangle.width < maxWidth) {
                targetWidth += 20
            }

            console.log("当前矩形宽度:", targetWidth)
        }
    }

    Button {
        id: resetButton

        text: "恢复默认"

        anchors.left: increaseButton.right
        anchors.bottom: parent.bottom
        anchors.leftMargin: 10
        anchors.bottomMargin: 20

        onClicked: {
            clickCount = 0
            targetWidth = 200
            message = "尚未点击按钮"

            console.log("界面已经恢复默认状态")
        }
    }
}

十七、综合示例解析

1. 导入模块

qml 复制代码
import QtQuick
import QtQuick.Controls
import QtQuick.Window

分别用于基础类型、控件和窗口。

2. 使用 id 标识窗口

qml 复制代码
id: mainWindow

当前文件中的其他对象可以通过 mainWindow 引用窗口。

3. 声明普通属性

qml 复制代码
property int clickCount: 0
property string message: "尚未点击按钮"

用于保存点击次数和提示文字。

4. 声明只读属性

qml 复制代码
readonly property int maxWidth: 500

用于限制矩形的最大宽度。

5. 声明属性别名

qml 复制代码
property alias targetWidth: targetRectangle.width

让窗口可以通过 targetWidth 直接访问矩形宽度。

6. 监听属性变化

qml 复制代码
onClickCountChanged: {
    console.log("点击次数发生变化:", clickCount)
}

每次 clickCount 改变时自动执行。

7. 使用属性组

qml 复制代码
font {
    pixelSize: 18
    bold: true
}

集中设置字体相关属性。


十八、常见错误整理

18.1 重复使用相同 id

错误:

qml 复制代码
Rectangle {
    id: box
}

Button {
    id: box
}

正确:

qml 复制代码
Rectangle {
    id: box
}

Button {
    id: actionButton
}

18.2 id 使用引号

错误:

qml 复制代码
id: "mainWindow"

正确:

qml 复制代码
id: mainWindow

18.3 id 以数字开头

错误:

qml 复制代码
id: 1button

正确:

qml 复制代码
id: button1

18.4 在对象声明中使用等号

错误:

qml 复制代码
width = 200

正确:

qml 复制代码
width: 200

在 JavaScript 事件中才使用等号:

qml 复制代码
onClicked: {
    width = 200
}

18.5 属性类型不匹配

错误:

qml 复制代码
property int count: "hello"

正确:

qml 复制代码
property int count: 10

18.6 修改只读属性

错误:

qml 复制代码
readonly property int maxCount: 100

onClicked: {
    maxCount = 200
}

只读属性只能读取,不能直接赋值。


18.7 混淆 alias 与普通属性绑定

普通绑定:

qml 复制代码
property int targetWidth: rectangle.width

别名:

qml 复制代码
property alias targetWidth: rectangle.width

普通绑定创建了一个新的属性,而别名是对原属性的直接引用。


十九、核心知识对比表

知识点 语法示例 核心作用
模块导入 import QtQuick 使用模块中的 QML 类型
目录导入 import "components" 使用目录中的自定义组件
导入别名 import "components" as UI 使用限定名称访问组件
对象 ID id: mainWindow 唯一标识当前作用域中的对象
自定义属性 property int count: 0 保存组件状态
必填属性 required property string title 要求使用者提供属性
只读属性 readonly property int max: 100 防止外部直接修改
属性别名 property alias text: label.text 暴露内部对象属性
属性绑定 width: parent.width / 2 根据依赖项自动更新
属性变化处理 onWidthChanged 响应属性变化
对象列表 property list<Item> items 保存多个 QML 对象
属性组 font { pixelSize: 20 } 集中设置相关子属性

二十、快速复习口诀

可以使用下面的方式快速记忆:

text 复制代码
import:告诉 QML 引擎去哪里找资源。

id:对象在当前作用域中的唯一标识。

property:保存和描述对象状态。

binding:依赖项变化,属性自动更新。

alias:不创建新值,直接引用原属性。

readonly:允许读取,不允许直接修改。

list:一个属性中保存多个对象。

onXxxChanged:属性变化时自动执行。

二十一、复习问题

学完本节后,可以尝试回答以下问题:

  1. QML 的 import 与 C++ 的 #include 有什么区别?
  2. 模块导入和目录导入有什么区别?
  3. as 关键字有什么作用?
  4. 同一个 QML 作用域中能否出现两个相同的 id
  5. id 是普通对象属性吗?
  6. id 能否在运行时修改?
  7. 自定义属性的基本声明格式是什么?
  8. 冒号赋值和等号赋值分别在什么场景使用?
  9. 什么是属性绑定?
  10. 直接给绑定属性赋值会发生什么?
  11. onCountChanged 是如何产生的?
  12. property list 如何获取列表长度?
  13. 点语法和属性组语法有什么区别?
  14. property alias 和普通属性绑定有什么区别?
  15. readonly property 能否绑定到表达式?

二十二、本节总结

本节最重要的内容可以概括为三部分。

Import

text 复制代码
负责导入模块、目录和资源,让 QML 引擎能够找到所需类型。

ID

text 复制代码
负责唯一标识当前作用域中的对象,方便其他对象引用和操作。

Property

text 复制代码
负责保存对象状态、建立动态绑定,并对外暴露组件接口。

将三者组合起来,可以得到 QML 组件开发的基本结构:

qml 复制代码
import QtQuick

Rectangle {
    id: root

    property int customWidth: 200
    readonly property int maxWidth: 500
    property alias labelText: label.text

    width: customWidth
    height: 100

    Text {
        id: label
        anchors.centerIn: parent
    }
}

一句话总结:

import 决定可以使用什么,id 决定如何找到对象,property 决定对象保存什么以及如何联动。