QML 对象属性

QML对象类型属性的描述

每个QML对象类型都有一组定义的属性。每个对象类型的实例都是使用为该对象类型定义的属性集创建的。可以指定几种不同类型的属性,如下所述。

对象声明中的属性

在QML文档中,对象声明定义了一个新类型。它还声明了一个对象层次结构,当创建该新定义类型的实例时,该层次结构将被实例化。

QML对象类型属性类型的集合如下:

  • the id 属性

  • 属性特性

  • 信号属性

  • 信号处理程序属性

  • 方法属性

  • 附加属性和附加信号处理程序属性

  • 枚举属性

这些属性将在下面详细讨论。

ID

属性

每个QML对象类型都有一个id属性。这个属性由语言本身提供,任何QML对象类型都不能重新定义或覆盖它。

可以为对象实例的id属性赋值,以便其他对象可以识别和引用该对象。此id必须以小写字母或下划线开头,并且不能包含除字母、数字和下划线以外的字符。

下面是一个TextInput对象和一个Text对象。TextInput对象的id值设置为“myTextInput”。Text对象通过引用myTextInput.text将其text属性设置为与TextInput的text属性相同的值。现在,两个项目将显示相同的文本:

对象可以通过其id在声明它的组件作用域内的任何地方被引用。因此,id值在其组件作用域内必须始终是唯一的。有关更多信息,请参见Scope and Naming Resolution

一旦创建了对象实例,其id属性的值就不能更改。虽然它看起来像一个普通的属性,但id属性不是一个普通的property属性,并且有特殊的语义适用于它;例如,在上面的例子中,无法访问myTextInput.id

属性特性

属性是对象的一个属性,可以被赋予一个静态值或绑定到一个动态表达式。属性的值可以被其他对象读取。通常它也可以被另一个对象修改,除非特定的QML类型明确禁止了对特定属性的修改。

定义属性特性

在C++中,可以通过注册一个类的Q_PROPERTY来为类型定义一个属性,然后将其注册到QML类型系统中。或者,可以在QML文档的对象声明中使用以下语法定义对象类型的自定义属性:

[default] [required] [readonly] property <propertyType> <propertyName>

通过这种方式,对象声明可以向外部对象暴露特定值或更容易地维护某些内部状态。

属性名称必须以小写字母开头,并且只能包含字母、数字和下划线。JavaScript的保留字不能作为有效的属性名称。defaultrequiredreadonly关键字是可选的,它们修改了所声明属性的语义。有关它们各自含义的更多信息,请参阅即将到来的关于默认属性必需属性只读属性的部分。

声明一个自定义属性会隐式地为该属性创建一个值更改信号,以及一个名为onChanged的关联信号处理程序,其中是属性的名称,首字母大写。

例如,以下对象声明定义了一个从Rectangle基类型派生的新类型。它有两个新属性,并为其中一个新属性实现了信号处理器

自定义属性定义中的有效类型

除了枚举类型之外,任何QML 值类型都可以用作自定义属性类型。例如,以下都是有效的属性声明:

(枚举值只是整数值,可以用int类型来引用。)

一些值类型由QtQuick模块提供,因此除非导入该模块,否则不能用作属性类型。有关更多详细信息,请参阅QML值类型文档。

注意var值类型是一个通用占位符类型,可以容纳任何类型的值,包括列表和对象:

property var someNumber: 1.5
property var someString: "abc"
property var someBool: true
property var someList: [1, 2, "three", "four"]
property var someObject: Rectangle { width: 100; height: 100; color: "red" }

此外,任何QML对象类型都可以用作属性类型。例如:

property Item someItem
property Rectangle someRectangle

这也适用于自定义QML类型。如果一个QML类型是在名为ColorfulButton.qml的文件中定义的(在一个随后被客户端导入的目录中),那么类型为ColorfulButton的属性也是有效的。

为属性特性赋值

对象实例的属性值可以通过两种不同的方式指定:

  • 初始化时的值赋值

  • 一个命令式的值赋值

在任何情况下,该值可以是静态值或绑定表达式值。

初始化时的值分配

初始化时为属性赋值的语法是:

<propertyName> : <value>

如果需要,可以在对象声明中将初始化值赋值与属性定义结合起来。在这种情况下,属性定义的语法变为:

[default] property <propertyType> <propertyName> : <value>

属性值初始化的示例如下:

命令式值分配

命令式值赋值是指从命令式 JavaScript 代码中将属性值(静态值或绑定表达式)分配给属性。命令式值赋值的语法就是 JavaScript 赋值运算符,如下所示:

[<objectId>.]<propertyName> = value

以下是一个命令式赋值的示例:

静态值和绑定表达式值

如前所述,可以分配给属性的值有两种:静态值和绑定表达式值。后者也被称为属性绑定

类型

语义

静态值

一个不依赖于其他属性的常量值。

绑定表达式

一个描述属性与其他属性关系的JavaScript表达式。此表达式中的变量称为属性的依赖项

QML引擎强制执行属性与其依赖项之间的关系。当任何依赖项的值发生变化时,QML引擎会自动重新评估绑定表达式并将新结果分配给属性。

这里是一个示例,展示了两种值被分配给属性的情况:

注意

要强制分配绑定表达式,绑定表达式必须包含在传递给binding()的函数中,然后必须将Qt.binding()返回的值分配给属性。相反,在初始化时分配绑定表达式时不得使用Qt.binding()。有关更多信息,请参见属性绑定

类型安全

属性是类型安全的。属性只能被赋予与其类型匹配的值。

例如,如果一个属性是实数类型,而你尝试将一个字符串赋值给它,你将会得到一个错误:

property int volume: "four"  // generates an error; the property's object will not be loaded

同样,如果在运行时为属性分配了错误类型的值,新值将不会被分配,并且会生成错误。

某些属性类型没有自然的值表示形式,对于这些属性类型,QML引擎会自动执行字符串到类型值的转换。因此,例如,即使color类型的属性存储的是颜色而不是字符串,你也可以将字符串"red"分配给颜色属性,而不会报告错误。

请参阅QML值类型以获取默认支持的属性类型列表。此外,任何可用的QML对象类型也可以用作属性类型。

特殊属性类型

对象列表属性属性

一个列表类型的属性可以被赋予一系列QML对象类型的值。定义对象列表值的语法是用方括号包围的逗号分隔列表:

[ <item 1>, <item 2>, ... ]

例如,Item 类型有一个 states 属性,用于保存 State 类型对象的列表。下面的代码将此属性的值初始化为包含三个 State 对象的列表:

如果列表包含单个项目,可以省略方括号:

一个列表类型的属性可以在对象声明中使用以下语法指定:

[default] property list<<ObjectType>> propertyName

并且,与其他属性声明一样,属性初始化可以与属性声明结合使用,语法如下:

[default] property list<<ObjectType>> propertyName: <value>

列表属性声明的示例如下:

如果您希望声明一个属性来存储不一定为QML对象类型值的值列表,您应该声明一个var属性来代替。

分组属性

在某些情况下,属性包含一组逻辑子属性。这些子属性可以使用点符号或组符号进行赋值。

例如,Text 类型有一个字体组属性。下面,第一个 Text 对象使用点符号初始化其 font 值,而第二个使用组符号:

Text {
    //dot notation
    font.pixelSize: 12
    font.b: true
}

Text {
    //group notation
    font { pixelSize: 12; b: true }
}

分组属性类型是具有子属性的类型。如果分组属性类型是对象类型(而不是值类型),则持有它的属性必须是只读的。这是为了防止您替换子属性所属的对象。

属性别名

属性别名是持有对另一个属性的引用的属性。与普通的属性定义不同,普通的属性定义会为属性分配一个新的、唯一的存储空间,而属性别名将新声明的属性(称为别名属性)作为对现有属性(被别名的属性)的直接引用连接起来。

属性别名声明看起来像普通的属性定义,只是它需要使用alias关键字而不是属性类型,并且属性声明的右侧必须是一个有效的别名引用:

[default] property alias <name>: <alias reference>

与普通属性不同,别名具有以下限制:

  • 它只能引用在声明别名的type范围内的对象或对象的属性。

  • 它不能包含任意的JavaScript表达式

  • 它不能引用在其类型范围之外声明的对象。

  • 与普通属性的可选默认值不同,别名引用不是可选的;在首次声明别名时必须提供别名引用。

  • 它不能引用附加属性

  • 它不能引用深度为3或更深的层次结构中的属性。以下代码将无法工作:

    property alias color: myItem.myRect.border.color
    
    Item {
        id: myItem
        property Rectangle myRect
    }
    

    然而,对于深度不超过两层的属性别名是有效的。

    property alias color: rectangle.border.color
    
    Rectangle {
        id: rectangle
    }
    

例如,下面是一个Button类型,它有一个buttonText别名属性,该属性连接到Text子元素的text对象:

以下代码将创建一个带有为子文本对象定义的文本字符串的Button

在这里,直接修改buttonText会直接修改textItem.text的值;它不会改变其他值然后更新textItem.text。如果buttonText不是别名,改变它的值实际上不会改变显示的文本,因为属性绑定不是双向的:如果textItem.text被改变,buttonText的值会改变,但反之则不然。

属性别名的注意事项

别名属性可能与现有属性同名,从而有效地覆盖现有属性。例如,以下QML类型具有一个名为color的别名属性,与内置的Rectangle::color属性同名:

Rectangle {
    id: coloredrectangle
    property alias color: bluerectangle.color
    color: "red"

    Rectangle {
        id: bluerectangle
        color: "#1234ff"
    }

    Component.onCompleted: {
        console.log (coloredrectangle.color)    //prints "#1234ff"
        setInternalColor()
        console.log (coloredrectangle.color)    //prints "#111111"
        coloredrectangle.color = "#884646"
        console.log (coloredrectangle.color)    //prints #884646
    }

    //internal function that has access to internal properties
    function setInternalColor() {
        color = "#111111"
    }
}

任何使用此类型并引用其color属性的对象都将引用别名,而不是普通的Rectangle::color属性。然而,在内部,矩形可以正确设置其color属性,并引用实际定义的属性,而不是别名。

属性别名和类型

属性别名不能有显式的类型声明。属性别名的类型是它所引用的属性或对象的声明类型。因此,如果你创建了一个通过id引用的对象的别名,并且该对象有内联声明的额外属性,那么这些额外属性将无法通过别名访问:

你不能从组件外部初始化 inner.extraProperty,因为 inner 只是一个 Item

然而,如果你将内部对象提取到一个单独的组件中,并使用一个专用的 .qml 文件,你可以实例化该组件,并通过别名访问其所有属性:

默认属性

对象定义可以有一个单一的默认属性。默认属性是指如果在一个对象的定义中声明了另一个对象,而没有将其声明为特定属性的值,则该值将被分配给默认属性。

使用可选的default关键字声明属性会将其标记为默认属性。例如,假设有一个文件MyLabel.qml,其中有一个默认属性someText

someText 值可以分配给 MyLabel 对象定义,如下所示:

这与以下内容具有完全相同的效果:

然而,由于someText属性已被标记为默认属性,因此无需显式地将Text对象分配给此属性。

您会注意到,子对象可以添加到任何基于Item的类型中,而无需显式地将它们添加到children属性中。这是因为Item的默认属性是它的data属性,任何添加到Item的此列表中的项目都会自动添加到其子对象列表中。

默认属性对于重新分配项目的子项非常有用。例如:

通过将默认属性alias设置为inner.children,任何被分配为外部项目子项的对象都会自动重新分配为内部项目的子项。

警告

设置元素默认列表属性的值可以隐式或显式完成。在单个元素的定义中,这两种方法不能混合使用,因为这会导致列表中元素的顺序未定义。

必需属性

对象声明可以使用required关键字将属性定义为必需的。语法如下:

required property <propertyType> <propertyName>

顾名思义,创建对象实例时必须设置必需的属性。违反此规则将导致QML应用程序在静态检测到的情况下无法启动。对于动态实例化的QML组件(例如通过createComponent()),违反此规则将导致警告并返回空值。

可以使用以下方法使现有属性成为必需

required <propertyName>

以下示例展示了如何创建一个自定义的矩形组件,其中颜色属性始终需要指定。

注意

你不能从QML中为必需属性分配初始值,因为这将直接违背必需属性的预期用途。

必需的属性在模型-视图-委托代码中扮演着特殊角色:如果视图的委托具有与视图模型的角色名称匹配的必需属性,那么这些属性将使用模型的相应值进行初始化。有关更多信息,请访问Qt Quick页面中的模型和视图。

请参阅 createWithInitialPropertiessetInitialProperties 和 QQuickView::setInitialProperties,了解从 C++ 初始化所需属性的方法。

只读属性

对象声明可以使用readonly关键字定义一个只读属性,语法如下:

readonly property <propertyType> <propertyName> : <value>

只读属性必须在初始化时分配一个静态值或绑定表达式。只读属性初始化后,您不能再更改其静态值或绑定表达式。

例如,下面Component.onCompleted块中的代码是无效的:

注意

只读属性不能同时是default属性。

属性修饰符对象

属性可以拥有与之关联的属性值修饰符对象。声明与特定属性关联的属性修饰符类型实例的语法如下:

<PropertyModifierTypeName> on <propertyName> {
    // attributes of the object instance
}

这通常被称为“on”语法。

需要注意的是,上述语法实际上是一个对象声明,它将实例化一个作用于预先存在属性的对象。

某些属性修饰符类型可能仅适用于特定的属性类型,但这并不是由语言强制执行的。例如,QtQuick提供的NumberAnimation类型仅会动画化数字类型(如intreal)的属性。尝试将NumberAnimation与非数字属性一起使用不会导致错误,但非数字属性将不会被动画化。属性修饰符类型与特定属性类型关联时的行为由其实现定义。

信号属性

信号是从对象发出的通知,表示某个事件已经发生:例如,属性已更改,动画已开始或停止,或者图像已下载完成。例如,MouseArea类型有一个clicked信号,当用户在鼠标区域内点击时会发出该信号。

每当发出特定信号时,可以通过信号处理器通知对象。信号处理器的声明语法为on,其中是信号的名称,首字母大写。信号处理器必须在发出信号的对象定义内声明,并且处理器应包含在调用信号处理器时要执行的JavaScript代码块。

例如,下面的onClicked信号处理程序在MouseArea对象定义中声明,并在点击MouseArea时调用,导致控制台消息被打印:

定义信号属性

在C++中,可以通过注册一个类的Q_SIGNAL来为类型定义一个信号,然后将其注册到QML类型系统中。或者,可以在QML文档的对象声明中使用以下语法为对象类型定义一个自定义信号:

signal <signalName>[([<parameterName>: <parameterType>[, ...]])]

在同一类型块中尝试声明两个具有相同名称的信号或方法是一个错误。然而,新信号可以重用类型上现有信号的名称。(这应谨慎进行,因为现有信号可能会被隐藏并变得无法访问。)

以下是信号声明的三个示例:

你也可以使用属性风格的语法来指定信号参数:

为了与方法声明保持一致,您应该优先使用冒号进行类型声明。

如果信号没有参数,“()”括号是可选的。如果使用参数,则必须声明参数类型,如上面actionPerformed信号的stringint参数所示。允许的参数类型与本页定义属性属性下列出的类型相同。

要发出信号,将其作为方法调用。当信号发出时,任何相关的信号处理程序将被调用,处理程序可以使用定义的信号参数名称来访问相应的参数。

属性更改信号

QML 类型还提供了内置的属性更改信号,每当属性值发生变化时就会发出这些信号,正如之前在属性属性部分所述。有关这些信号为何有用以及如何使用它们的更多信息,请参阅即将到来的属性更改信号处理程序部分。

信号处理程序属性

信号处理程序是一种特殊的方法属性,每当关联的信号被发出时,QML引擎就会调用该方法的实现。在QML中向对象定义添加信号时,会自动向对象定义添加一个关联的信号处理程序,默认情况下,该处理程序具有空的实现。客户端可以提供实现,以实现程序逻辑。

考虑以下SquareButton类型,其定义在SquareButton.qml文件中提供,如下所示,带有信号activateddeactivated

这些信号可以被同一目录下另一个QML文件中的任何SquareButton对象接收,其中信号处理程序的实现由客户端提供:

信号处理程序不需要声明它们的参数类型,因为信号已经指定了它们。上面显示的箭头函数语法不支持类型注释。

有关信号使用的更多详细信息,请参见信号和处理器事件系统

属性更改信号处理程序

属性更改信号的处理程序采用语法形式 onChanged,其中 是属性的名称,首字母大写。例如,尽管 TextInput 类型文档没有记录 textChanged 信号,但由于 TextInput 具有 text 属性,因此该信号是隐式可用的,因此可以编写一个 onTextChanged 信号处理程序,以便在每次此属性更改时调用:

方法属性

对象类型的方法是一个可以被调用来执行某些处理或触发进一步事件的函数。一个方法可以连接到一个信号,以便每当信号发出时自动调用它。有关更多详细信息,请参见信号和处理器事件系统

定义方法属性

在C++中,可以通过标记类的函数来为类型定义一个方法,然后使用Q_INVOKABLE将其注册到QML类型系统中,或者将其注册为类的Q_SLOT。另外,可以使用以下语法将自定义方法添加到QML文档中的对象声明中:

function <functionName>([<parameterName>[: <parameterType>][, ...]]) [: <returnType>] { <body> }

可以向QML类型添加方法,以定义独立的、可重用的JavaScript代码块。这些方法可以在内部或由外部对象调用。

与信号不同,方法参数类型不需要声明,因为它们默认为var类型。然而,为了帮助qmlcachegen生成更高效的代码,并提高可维护性,您应该声明它们。

在同一类型块中尝试声明两个具有相同名称的方法或信号是错误的。然而,新方法可以重用类型上现有方法的名称。(这应谨慎进行,因为现有方法可能会被隐藏并变得无法访问。)

下面是一个带有calculateHeight()方法的矩形,该方法在分配height值时被调用:

如果方法有参数,它们可以在方法内通过名称访问。下面,当点击MouseArea时,它会调用moveTo()方法,然后可以引用接收到的newXnewY参数来重新定位文本:

附加属性和附加信号处理程序

附加属性附加信号处理程序是一种机制,使对象能够被注释上额外的属性或信号处理程序,这些属性或处理程序通常是对象无法使用的。特别是,它们允许对象访问与特定对象特别相关的属性或信号。

QML 类型的实现可以选择在 C++ 中使用特定的属性和信号来附加类型 :ref:` `。然后可以在运行时创建此类型的实例并将其附加到特定对象,使这些对象能够访问附加类型的属性和信号。这些属性和相应的信号处理程序通过附加类型的名称作为前缀来访问。

对附加属性和处理程序的引用采用以下语法形式:

<AttachingType>.<propertyName>
<AttachingType>.on<SignalName>

例如,ListView 类型有一个附加属性 ListView.isCurrentItem,它对 ListView 中的每个委托对象都可用。每个单独的委托对象可以使用此属性来确定它是否是视图中当前选中的项目:

在这种情况下,附加类型的名称是ListView,而相关的属性是isCurrentItem,因此附加属性被称为ListView.isCurrentItem

附加的信号处理程序以相同的方式引用。例如,onCompleted 附加信号处理程序通常用于在组件的创建过程完成后执行一些 JavaScript 代码。在下面的示例中,一旦 ListModel 完全创建,其 Component.onCompleted 信号处理程序将自动调用以填充模型:

由于附加类型的名称为Component,并且该类型具有completed信号,因此附加的信号处理程序被称为Component.onCompleted

关于访问附加属性和信号处理程序的说明

一个常见的错误是假设附加属性和信号处理程序可以直接从这些属性附加到的对象的子对象访问。事实并非如此。附加类型的实例仅附加到特定对象,而不是对象及其所有子对象。

例如,下面是一个修改过的早期示例,涉及附加属性。这次,委托是一个Item,而着色的矩形是该项目的子项:

这没有按预期工作,因为ListView.isCurrentItem仅附加到根委托对象,而不是其子对象。由于矩形是委托的子对象,而不是委托本身,它无法以ListView.isCurrentItem的方式访问isCurrentItem附加属性。因此,矩形应通过根委托访问isCurrentItem

现在 delegateItem.ListView.isCurrentItem 正确地引用了委托的 isCurrentItem 附加属性。

枚举属性

枚举提供了一组固定的命名选择。它们可以在QML中使用enum关键字声明:

如上所示,枚举类型(例如 TextType)和值(例如 Normal)必须以大写字母开头。

值通过...引用。

有关QML中枚举用法的更多信息可以在QML值类型 enumeration文档中找到。

在Qt 5.10中引入了在QML中声明枚举的能力。