类型可以来自各种来源:

  • 由 QML 语言原生提供
  • QML 模块通过 C++ 注册
  • 由 QML 模块作为 QML 文档提供

此外,应用程序开发人员可以通过直接注册 C++ 类型或通过在 QML 文档中定义可重用组件然后可以导入的方式来提供他们自己的类型。无论类型定义来自何处,引擎都会为这些类型的属性和实例强制执行类型安全。

6.1 基本类型(Basic type)

Basic type 是指一个简单值的类型,如 int 或 string。而 QML Object Types 指的是具有属性、信号、方法等的对象。基本类型不能用于声明 QML 对象。

基本类型可以用来引用:

  • 一个值。例如 int 指单个数字,var 可以指单个项目列表。
  • 属性值。例如,size 是指具有 widthheight 属性的值。

当一个变量或属性持有一个基本类型并将其分配给另一个变量或属性时,就会生成该值的副本。在 JavaScript 中,这个值被称为原始值。

6.1.1 支持的基本类型

引擎默认支持一些基本类型,不需要使用 import 语句。以下基本类型不能作为 QML 中的属性类型:

  • list 必须与 QML 对象类型结合使用。
  • 枚举不能直接使用,因为枚举必须由注册的 QML 对象类型定义

    6.1.1.1 QML 语言提供的基本类型

    包括:
bool 布尔值,true、false
double 双精度浮点值
enumeration 枚举
int 整型
list QML 对象列表
real 浮点值
string 字符串
url 资源定位
var 通用原始类型

6.1.1.2 QML 模块提供的基本类型

QML 模块扩展了 QML 语言的基本类型:

color
date
font
matrix4x4
point
quaternion
rect
size
vector2d
vector3d
vector4d

Qt 全局对象提供的函数可以操作基本类型的值。

6.1.2 基本类型的属性变化行为

有些基本类型具有属性:例如,font 具有 pixelSizefamilybold 属性。与对象类型的属性不同,基本类型的属性不提供属性变化信号。只能为基本类型属性本身创建属性更改信号处理程序:

  1. Text {
  2. onFont.pixelSizeChanged: doSomething() //无效
  3. font {
  4. onPixelSizeChanged: doSomething() //无效
  5. }
  6. onFontChanged: doSomething() //有效
  7. }

但是,请注意,只要基本类型的任何属性发生更改,以及属性本身发生更改,就会发出基本类型的属性更改信号。示例:

  1. Text {
  2. onFontChanged: console.log("font changed")
  3. Text { id: otherText }
  4. focus: true
  5. // 改变font属性值,就会触发onFontChanged函数
  6. Keys.onDigit1Pressed: font.pixelSize += 1
  7. Keys.onDigit2Pressed: font.b = !font.b
  8. Keys.onDigit3Pressed: font = otherText.font
  9. }

相反,对象类型的属性发出它们自己的属性更改信号,并且对象类型属性的属性更改信号处理程序仅在该属性重新分配给不同的对象值时才被调用。

6.2 JavaScript 类型

QML 引擎支持 JavaScript 对象和数组。可以使用泛型 var 类型创建和存储任何标准 JavaScript 类型。例如:

  1. import QtQuick 2.0
  2. Item {
  3. property var theArray: []
  4. property var theDate: new Date()
  5. Component.onCompleted: {
  6. for (var i = 0; i < 10; i++)
  7. theArray.push("Item " + i)
  8. console.log("There are", theArray.length, "items in the array")
  9. console.log("The time is", theDate.toUTCString())
  10. }
  11. }

更多信息,请参阅 JavaScript Expressions in QML Documents

6.3 QML 对象类型

QML 对象类型是可以实例化的类型。在语法方面,QML 对象类型是一种可用于通过指定类型名称后跟一组包含该对象属性的花括号来声明对象的类型,这与基本类型不同。例如,Rectangle 是一个 QML 对象类型:它可以用来创建 Rectangle 类型的对象。这不能用像 int 和 bool 这样的原始类型来完成,它们用于保存简单的数据类型而不是对象。

可以创建 .qml 文件来自定义 QML 对象类型,如 Documents as QML object type definitionsDefining QML Types from C++ 所述。请注意,类型名必须以大写字母开头。

QML 对象类型源自 QtObject,由 QML 模块提供。程序可以导入这些模块以使用它们提供的对象类型。QtQuick 模块提供了在 QML 中创建用户界面所需的最常见的对象类型。

最后,每个 QML 文档都隐式定义了一个 QML 对象类型,可以在其他 QML 文档中重复使用。更多信息,请参阅 object types in the QML type system

6.3.1 定义对象类型(从 QML )

6.3.1.1 通过 QML 文档定义对象类型()

开发者可以以 QML 文档的形式定义类型。

创建 .qml 文件

要创建对象类型,创建 .qml 文件。类型名称要求:

  • 必须由字母、数字、下划线组成。
  • 必须以大写字母开头。

    写定义

    例如,以下 SquareButton.qml 文件声明了一个 Rectangle: ```javascript import QtQuick 2.0

Rectangle { property int side: 100 width: side; height: side color: “red”

  1. MouseArea {
  2. anchors.fill: parent
  3. onClicked: console.log("Button clicked!")
  4. }

}

  1. 由于该文件名为 SquareButton.qml,因此它现在可以被同目录中的其他 QML 文件用作名为 SquareButton 的类型。例如,同目录下的 myapplication.qml 文件可以引用 SquareButton 类型:
  2. ```javascript
  3. // myapplication.qml
  4. import QtQuick 2.0
  5. SquareButton {}

image.png
这将创建一个 100 x 100 的红色矩形,其中包含一个内部 MouseArea,如 SquareButton.qml 中所定义。当这个 myapplication.qml 文档被引擎加载时,它将 SquareButton.qml 文档作为一个组件加载并实例化它以创建一个 SquareButton 对象。

SquareButton 类型封装了在 SquareButton.qml 中的 QML 对象树。当 QML 引擎从此类型实例化 SquareButton 对象时,它正在实例化 SquareButton.qml 中声明的 Rectangle 树中的对象。

注意:文件名的字母大小写在某些(特别是 UNIX)文件系统上很重要。 建议文件名大小写与所需 QML 类型名称的大小写完全匹配。

6.3.1.2 使用组件(Component)定义匿名类型

在 QML 中创建对象类型的另一种方法是使用 Component 类型。这种方式适用于不需要公开类型,而只需要创建一个实例。语法是:

  1. component <component name> : BaseType {
  2. // declare properties and bindings here
  3. }

在声明内联组件的文件中,可以简单地通过其名称引用该类型:

  1. // Images.qml
  2. import QtQuick 2.15
  3. Item {
  4. component LabeledImage: Column {
  5. property alias source: image.source
  6. property alias caption: text.text
  7. Image {
  8. id: image
  9. width: 50
  10. height: 50
  11. }
  12. Text {
  13. id: text
  14. font.bold: true
  15. }
  16. }
  17. Row {
  18. LabeledImage {
  19. id: before
  20. source: "before.png"
  21. caption: "Before"
  22. }
  23. LabeledImage {
  24. id: after
  25. source: "after.png"
  26. caption: "After"
  27. }
  28. }
  29. property LabeledImage selectedImage: before
  30. }

在其他文件中,它必须以其包含组件的名称作为前缀:

  1. // LabeledImageBox.qml
  2. import QtQuick 2.15
  3. Rectangle {
  4. property alias caption: image.caption
  5. property alias source: image.source
  6. border.width: 2
  7. border.color: "black"
  8. Images.LabeledImage {
  9. id: image
  10. }
  11. }

注意:内联组件不与声明它们的组件共享它们的作用域。在以下示例中,当文件 B.qml 中的 A.MyInlineComponent 创建时,将发生 ReferenceError,因为 root 不作为 B 中的 id 存在 .qml。 因此,建议不要引用内联组件中不属于它的对象。

  1. // A.qml
  2. import QtQuick 2.15
  3. Item {
  4. id: root
  5. property string message: "From A"
  6. component MyInlineComponent : Item {
  7. Component.onCompleted: console.log(root.message)
  8. }
  9. }
  10. // B.qml
  11. import QtQuick 2.15
  12. Item {
  13. A.MyInlineComponent {}
  14. }

注意:内联组件不能嵌套。

或者

  1. Item {
  2. id: root
  3. width: 500; height: 500
  4. Component {
  5. id: myComponent
  6. Rectangle { width: 100; height: 100; color: "red" }
  7. }
  8. Component.onCompleted: {
  9. myComponent.createObject(root)
  10. myComponent.createObject(root, {"x": 200})
  11. }
  12. }

这里 myComponent 对象本质上定义了一个匿名类型,可以使用 Component::createObject 创建该匿名类型的对象。

内联组件共享常规顶级组件的所有特征,并使用与其包含的 QML 文档相同的导入列表。

请注意,每个 Component 对象声明都会创建自己的 component scope。在 Component 对象声明中使用和引用的任何 id 值在该范围内必须是唯一的,但在声明内联组件的文档中不需要是唯一的。因此,在 myComponent 对象声明中声明的 Rectangle 可以具有 root id,而不会与为同一文档中的 Item 对象声明的 root 冲突,因为这两个 id 值是在不同的组件范围内声明的。

更多信息,请参阅 Scope and Naming Resolution

导入在当前目录之外定义的类型

如果 SquareButton.qml 与 myapplication.qml 不在同一目录中,则需要通过 myapplication.qml 中的 import 语句专门使 SquareButton 类型可用。 它可以从文件系统上的相对路径导入,也可以作为已安装的模块导入;更多详细信息,请参阅 module

6.3.1.3 访问自定义类型的属性

.qml 文件中的 root object 定义定义了可用于 QML 类型的属性。属于这个根对象的所有属性、信号和方法——无论它们是自定义声明的,还是来自根对象的 QML 类型——都可以从外部访问,并且可以为这种类型的对象读取和修改。

例如,上面 SquareButton.qml 文件中的根对象类型是 Rectangle。这意味着可以为 SquareButton 对象修改由 Rectangle 类型定义的任何属性。下面的代码为 SquareButton 类型的根 Rectangle 对象的某些属性定义了三个具有自定义值的 SquareButton 对象:

  1. // application.qml
  2. import QtQuick 2.0
  3. Column {
  4. SquareButton { side: 50 }
  5. SquareButton { x: 50; color: "blue" }
  6. SquareButton { radius: 10 }
  7. }

image.png
自定义 QML 类型的对象可访问的属性包括为对象另外定义的任何自定义属性、方法和信号。例如,假设 SquareButton.qml 中的 Rectangle 已定义如下,并具有其他属性、方法和信号:

  1. // SquareButton.qml
  2. import QtQuick 2.0
  3. Rectangle {
  4. id: root
  5. property bool pressed: mouseArea.pressed
  6. signal buttonClicked(real xPos, real yPos)
  7. function randomizeColor() {
  8. root.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
  9. }
  10. property int side: 100
  11. width: side; height: side
  12. color: "red"
  13. MouseArea {
  14. id: mouseArea
  15. anchors.fill: parent
  16. onClicked: (mouse)=> root.buttonClicked(mouse.x, mouse.y)
  17. }
  18. }

任何 SquareButton 对象都可以使用已添加到根 Rectangle 的 press 属性、buttonClicked 信号和 randomizeColor() 方法:

  1. // application.qml
  2. import QtQuick 2.0
  3. SquareButton {
  4. id: squareButton
  5. onButtonClicked: (xPos, yPos)=> {
  6. console.log("Clicked", xPos, yPos)
  7. randomizeColor()
  8. }
  9. Text { text: squareButton.pressed ? "Down" : "Up" }
  10. }

请注意,SquareButton.qml 中定义的任何 id 值都不能被 SquareButton 对象访问,因为 id 值只能从声明组件的组件范围内访问。 上面的 SquareButton 对象定义不能为了引用 MouseArea 子对象而引用 mouseArea,如果它的 id 是 root 而不是 squareButton,这不会与 SquareButton.qml 中定义的 root 对象的相同值的 id 冲突 因为这两者将在不同的范围内声明。

6.3.2 定义对象类型(从 C++)

可以使用 QML 类型系统注册 C++ 类来扩展 QML,使之成为 QML 代码中的数据类型。虽然 QObject 派生类的属性、方法和信号都可以从 QML 访问,如 Exposing Attributes of C++ Types to QML 所述,但这样的类不能用作 QML 的数据类型,直到它在类型系统中注册。此外,注册可以提供其他功能,例如允许将类用作 QML 中的可实例化 QML 对象类型,或允许从 QML 导入和使用该类的单例实例。

此外,Qt QML 模块提供了实现 QML 特定功能的机制,例如 C++ 中的 attached property default property

请注意,所有声明 QML 类型的头文件都需要在没有任何前缀的情况下从项目的包含路径中访问。另外,Writing QML Extensions with C++ 教程中演示了本文档中涵盖的许多重要概念。

6.3.2.1 注册 C++ 类型

QML 类型系统可以注册 QObject 派生类。

该引擎允许注册可实例化和不可实例化的类型。1、注册可实例化类型使 C++ 类能够用作 QML 对象类型的定义,从而允许在 QML 代码的对象声明中使用它来创建这种类型的对象。注册还为引擎提供了额外的类型元数据,使类型(以及类声明的任何枚举)能够用作在 QML 和 C++ 之间交换的属性值、方法参数和返回值以及信号参数的数据类型。2、注册不可实例化的类型也会以这种方式将该类注册为数据类型,但该类型不能用于从 QML 实例化为 QML 对象类型。这很有用,例如,如果类型具有应该公开给 QML 的枚举,但类型本身不应该是可实例化的。

有关选择向 QML 公开 C++ 类型的正确方法的快速指南,请参阅 Choosing the Correct Integration Method Between C++ and QML

6.3.2.1.1 先决条件

下面提到的所有宏都可以从 qqmlregistration.h 头文件中获得。您需要将以下代码添加到使用它们的文件中,以使宏可用:

  1. #include <QtQml/qqmlregistration.h>

此外,您的类声明必须位于可通过项目的包含路径访问的头文件中。声明用于在编译时生成注册码,注册码需要包含包含声明的头文件。

6.3.2.1.2 注册一个可实例化的对象类型

QObject 派生类都可以注册为 QML object type。一旦在 QML 类型系统中注册了一个类,就可以像 QML 代码中的任何其他对象类型一样声明和实例化该类。创建后,可以从 QML 操作类实例;正如 Exposing Attributes of C++ Types to QML 所解释的那样,任何 QObject 派生类的属性、方法和信号都可以从 QML 代码访问。

要将 QObject 派生类注册为可实例化的 QML 对象类型,请将 QML_ELEMENT 或 QML_NAMED_ELEMENT() 添加到类声明中,并将 CONFIG += qmltypes、QML_IMPORT_NAME 和 QML_IMPORT_MAJOR_VERSION 添加到您的项目文件中。这会将类注册到给定主要版本下的类型命名空间中,使用类名或明确给定的名称作为 QML 类型名称。次要版本将从附加到属性、方法或信号的任何修订中派生。默认次要版本为 0。您可以通过将 QML_ADDED_IN_MINOR_VERSION() 宏添加到类声明中来明确限制类型只能从特定次要版本中使用。客户端可以导入命名空间的合适版本以使用该类型。

例如,假设有一个具有 author 和 creationDate 属性的 Message 类:

  1. class Message : public QObject
  2. {
  3. Q_OBJECT
  4. Q_PROPERTY(QString author READ author WRITE setAuthor NOTIFY authorChanged)
  5. Q_PROPERTY(QDateTime creationDate READ creationDate WRITE setCreationDate NOTIFY creationDateChanged)
  6. QML_ELEMENT
  7. public:
  8. // ...
  9. };

可以通过向项目文件添加适当的类型命名空间和版本号来注册此类型。 例如,要使 com.mycompany.messaging 命名空间中的类型可用 1.0 版:

  1. CONFIG += qmltypes
  2. QML_IMPORT_NAME = com.mycompany.messaging
  3. QML_IMPORT_MAJOR_VERSION = 1

如果无法从项目的包含路径访问类声明的头文件,您可能需要修改包含路径,以便可以编译生成的注册代码:

  1. INCLUDEPATH += com/mycompany/messaging

该类型可以在 QML 的 object declaration 中使用,并且可以读取和写入其属性,如下例所示:

  1. import com.mycompany.messaging 1.0
  2. Message {
  3. author: "Amelie"
  4. creationDate: new Date()
  5. }

6.3.2.1.3 注册不可实例化类型