QML 对象属性
每个QML对象类型都有一组定义的属性。对象类型的每个实例都使用为该对象类型定义的一组属性创建。可以指定几种不同类型的属性,如下所述。
QML文档中的对象声明定义了一个新类型。它还声明了一个对象层次结构,如果创建了该新定义类型的实例,该对象层次结构将被实例化。
QML对象类型属性类型集如下:
- id 属性;
- 特性属性
- 信号属性
- 信号处理程序属性
- 方法属性
- 附加属性和附加信号处理程序属性
- 枚举属性
下面将详细讨论这些属性。
- id属性
每个QML对象类型都有一个id属性。此属性由语言本身提供,不能被任何QML对象类型重新定义或覆盖。
可以将值分配给对象实例的id属性,以允许其他对象标识和引用该对象。此id必须以小写字母或下划线开头,并且不能包含字母、数字和下划线以外的字符。
下面是一个TextInput对象和一个Text对象。TextInput对象的id值被设置为“myTextInput”。Text对象通过引用myTextInput.text将其Text属性设置为与TextInput的Text属性具有相同的值。现在,这两个项目将显示相同的文本:
import QtQuick 2.0
Column {
width: 200; height: 200
TextInput { id: myTextInput; text: "Hello World" }
Text { text: myTextInput.text }
}
对象可以通过其id从声明它的组件范围内的任何位置引用。因此,id值必须在其组件范围内始终是惟一的。有关更多信息,请参见范围和命名解析。(单独一章介绍)
一旦创建了对象实例,就不能更改其id属性的值。虽然它看起来像一个普通的属性,但是id属性不是一个普通的属性,并且应用了特殊的语义,例如在上面的例子中不能使用myTextInput.id。
- 特性属性
特性是对象的属性,可以为其分配静态值或绑定到动态表达式。性能的值可以其他对象读取。一般情况下,它也可以由另一个对象修改,除非特定的QML类型对特定属性显式地禁止这样做。
定义方式:
在c++中,可以通过注册类的Q_PROPERTY来为类型定义属性,然后将该类注册到QML类型系统中。或者,对象类型的自定义属性可以在QML文档的对象声明中定义,语法如下:
[default] property <propertyType> <propertyName>
通过这种方式,对象声明可以向外部对象公开特定的值,或者更容易地维护某些内部状态。
特性名称必须以小写字母开头,并且只能包含字母、数字和下划线。JavaScript保留字不是有效的属性名。default关键字是可选的,它修改要声明的属性的语义。有关默认属性修饰符的更多信息,请参阅后面关于默认属性的部分。
隐式声明自定义属性会为该属性创建值更改信号,以及在<PropertyName>上调用的关联信号处理程序。其中<PropertyName>是属性的名称,第一个字母大写。
例如,下面的对象声明定义了一个派生自矩形基类型的新类型。它有两个新属性,其中一个实现了信号处理程序:
property color previousColor
property color nextColor
onNextColorChanged: console.log("The next color will be: " + nextColor.toString())
}
自定义属性定义中的有效类型
除了枚举类型外,任何QML基本类型都可以用作自定义属性类型。例如,这些都是有效的属性声明:
Item {
property int someNumber
property string someString
property url someUrl
}
注意:(枚举值只是整数值,可以用int类型引用。)
一些基本类型由QtQuick模块提供,因此不能用作属性类型,除非导入模块。有关详细信息,请参阅QML基本类型文档。
注意var basic类型是一个通用的占位符类型,它可以容纳任何类型的值,包括列表和对象:
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类型。如果在名为ColorfulButton的文件中定义了QML类型。然后,ColorfulButton类型的属性也将是有效的。
为特性的属性赋值:
对象实例的属性值可以用两种不同的方式指定:
- 初始化时的值赋值
- 命令式值赋值
在这两种情况下,值可以是静态值,也可以是绑定表达式值。
属性初始化时赋值的语法为:
<propertyName> : <value>
如果需要,可以将初始化值赋值与对象声明中的属性定义相结合。在这种情况下,属性定义的语法变成::
[default] property <propertyType> <propertyName> : <value>
属性值初始化的一个例子如下:
import QtQuick 2.0
color: "red"
property color nextColor: "blue"
}
命令式赋值:
命令式值分配是指从命令式JavaScript代码中将属性值(静态值或绑定表达式)分配给属性。命令式值赋值语法只是JavaScript赋值操作符,如下所示:
[<objectId>.]<propertyName> = value
命令式赋值的一个例子如下所示:
import QtQuick 2.0
id: rect
Component.onCompleted: {
rect.color = "red"
}
}
静态值和绑定表达式值:
如前所述,有两种类型的值可以分配给属性:静态值和绑定表达式值。后者也称为属性绑定。
注意:要强制分配绑定表达式,绑定表达式必须包含在传递给Qt.binding()的函数中,然后必须将Qt.binding()返回的值分配给属性。相反,在初始化时分配绑定表达式时,不能使用Qt.binding()。有关更多信息,请参见属性绑定。(单独章节介绍)
类型安全性:
属性是类型安全的。属性只能分配与属性类型匹配的值。例如,如果一个属性是实数,如果你试图给它分配一个字符串,你会得到一个错误:
property int volume: "four" // generates an error; the property's object will //not be loaded
同样,如果在运行时为属性分配了错误类型的值,则不会分配新值,并将生成错误。
有些属性类型没有自然值表示,对于这些属性类型,QML引擎会自动执行字符串到类型值的转换。因此,例如,即使color类型的属性存储颜色而不是字符串,您也能够将字符串“red”分配给color属性,而不会报告错误。
有关默认支持的属性类型的列表,请参见QML基本类型。此外,任何可用的QML对象类型也可以用作属性类型。(QML基本类型将单独讲解)
特殊的属性类型:
对象列表属性:可以为list类型属性分配QML对象类型值的列表。定义对象列表值的语法是一个逗号分隔的列表,由方括号包围:
[ <item 1>, <item 2>, ... ]
例如,项目类型具有一个状态属性,用于保存状态类型对象列表。下面的代码将该属性的值初始化为一个包含三个状态对象的列表:
import QtQuick 2.0
Item {
states: [
State { name: "loading" },
State { name: "running" },
State { name: "stopped" }
]
}
如列表只包含一项,则可省略方括号:
import QtQuick 2.0
Item {
states: State { name: "running" }
}
列表类型属性可以在对象声明中使用以下语法指定:
[default] property list<<objectType>> propertyName
而且,与其他属性声明一样,属性初始化可以与属性声明结合使用以下语法:
[default] property list<<objectType>> propertyName: <value>
列表属性声明的一个例子如下:
import QtQuick 2.0
Rectangle {
// declaration without initialization
property list<Rectangle> siblingRects
// declaration with initialization
property list<Rectangle> childRects: [
Rectangle { color: "red" },
Rectangle { color: "blue"}
]
}
注意:如果希望声明一个属性来存储不一定是QML对象类型值的值列表,则应该声明一个var属性。
分组属性:
在某些情况下,属性包含一组逻辑子属性特性。这些子属性特性可以指定为使用点符号或组符号。
例如,文本类型具有字体组属性。下面,第一个Text对象使用点符号初始化它的字体值,而第二个使用组符号:
Text {
//dot notation
font.pixelSize: 12
font.b: true
}
Text {
//group notation
font { pixelSize: 12; b: true }
}
分组属性类型是具有子属性的基本类型。这些基本类型中有一些是由QML语言提供的,而其他类型可能只在导入Qt Quick模块时才使用。有关更多信息,请参阅有关QML基本类型的文档。
属性别名
属性别名是包含对另一个属性的引用的属性。与为属性分配新的惟一存储空间的普通属性定义不同,属性别名将新声明的属性(称为别名属性)连接起来,作为对现有属性(别名属性)的直接引用。
属性别名声明看起来像一个普通的属性定义,只是它需要别名关键字而不是属性类型,而且属性声明的右侧必须是一个有效的别名引用:
[default] property alias <name>: <alias reference>
与普通属性不同,别名有以下限制:
- 它只能引用在声明别名的类型范围内的对象或对象的属性。
- 它不能包含任意的JavaScript表达式。
- 它不能引用类型范围之外声明的对象。
- 别名引用不是可选的,不像普通属性的可选默认值;别名引用必须在首次声明别名时提供。
- 它不能引用附加属性。
- 不能引用分组属性;下列程式码将不适用:
property alias color: rectangle.border.color
Rectangle {
id: rectangle
}
但是,值类型属性的别名可以:
property alias rectX: object.rectProperty.x
Item {
id: object
property rect rectProperty
}
例如,下面是一个带有buttonText别名属性的按钮类型,它连接到text子对象的text对象:(此处代码涉及到qml文件的引用,将在以后单独讲解)
// Button.qml
import QtQuick 2.0
Rectangle {
property alias buttonText: textItem.text
width: 100; height: 30; color: "yellow"
Text { id: textItem }
}
下面的代码将为子文本对象创建一个定义了文本字符串的按钮:
Button { buttonText: "Click Me" }
在这里,修改buttonText直接修改textItem.text值,它不改变一些其他值,然后更新textIte.text。如果buttonText不是别名,改变它的值实际上不会改变显示的文本,因为属性绑定不是双向的(我目前的理解相当于c++引用)
属性别名的注意事项:
别名只有在组件完全初始化之后才会被激活。当引用未初始化的别名时,将生成错误。同样,混叠属性也会导致错误。
property alias widgetLabel: label
//will generate an error
//widgetLabel.text: "Initial text"
//will generate an error
//property alias widgetLabelText: //widgetLabel.text
Component.onCompleted: widgetLabel.text = "Alias completed Initialization"
然而,当在根对象中导入具有属性别名的QML对象类型时,该属性作为常规Qt属性出现,因此可以在别名引用中使用。
别名属性有可能具有与现有属性相同的名称,从而有效地覆盖现有属性。例如,下面的QML类型有一个color alias属性,其名称与内置的矩形::color属性相同:
import QtQuick 2.12
Rectangle {
id: coloredrectangle
property alias color: bluerectangle.color
//property alias width: bluerectangle.width
color: "blue"
width:200
height:400
Rectangle {
id: bluerectangle
width:20
height:40
anchors.centerIn: parent
color: "red"
}
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属性的对象都将引用别名,而不是普通的矩形::color属性。但是,在内部矩形可以正确地设置它的color属性,并引用实际定义的属性而不是别名。(程序测试结果现象是red并未设置成功)如图:属性覆盖成功,但内部设置color为red失败。
默认属性:
对象定义可以有一个默认属性,如果在一个对象(父)的定义中声明了另一个对象(子),而没有将其声明为特定属性的值,则默认属性是赋值给该属性的属性。
使用可选的default关键字声明属性将其标记为默认属性。例如,假设有一个文件MyLabel.qml具有默认属性someText:
// MyLabel.qml
import QtQuick 2.0
Text {
default property var someText
text: "Hello, " + someText.text
}
someText值可以在MyLabel对象定义中赋值,如下所示:
MyLabel {
Text { text: "world!" }
}
这与下面的效果完全相同:
MyLabel {
someText: Text { text: "world!" }
}
然而,因为someText属性已经被标记为默认属性,所以没有必要显式地将Text对象分配给该属性。
您将注意到,可以将子对象添加到任何基于项的类型,而无需显式地将它们添加到子属性。这是因为Item的默认属性是它的data属性,为一个条目添加到这个列表中的任何条目都会自动添加到它的子列表中。
默认属性对于重新分配项的子元素非常有用。参见TabWidget示例,该示例使用默认属性自动将TabWidget的子元素重新分配为内部列表视图的子元素。参见扩展QML。(将在单独章节说明)
只读属性:
对象声明可以使用readonly关键字定义只读属性,语法如下:
readonly property <propertyType> <propertyName> : <initialValue>
只读属性必须在初始化时赋值。初始化只读属性之后,就不能再给它赋值了,无论是来自命令式代码还是其他。
例如,组件中的代码。下面的oncomplete块无效:
Item {
readonly property int someNumber: 10
Component.onCompleted: someNumber = 20 // doesn't work, causes an error
}
注意:只读属性不能是默认属性。
属性修改器对象:
属性可以具有与其关联的属性值修饰符对象。声明与特定属性关联的属性修饰符类型的实例的语法如下:
<PropertyModifierTypeName> on <propertyName> {
// attributes of the object instance
}
需要注意的是,上面的语法实际上是一个对象声明,它将实例化作用于预先存在属性的对象。
某些属性修饰符类型可能只适用于特定的属性类型,但是该语言并不强制执行。例如,QtQuick提供的NumberAnimation类型将只对数字类型(例如int或real)属性进行动画。尝试使用带有非数值属性的NumberAnimation将不会导致错误,但是该非数值属性将不会被动画化。当与特定属性类型关联时,属性修饰符类型的行为由其实现定义。
信号属性:
信号是来自对象的某个事件已经发生的通知:例如,属性已经更改,动画已经启动或停止,或者图像已经下载。例如,MouseArea类型具有一个单击信号,当用户在鼠标区域内单击时发出该信号。
无论何时发出特定的信号,都可以通过信号处理程序通知对象。信号处理程序使用< signal >语法声明,其中< signal >是信号的名称,第一个字母大写。信号处理程序必须在发出信号的对象的定义中声明,并且处理程序应该包含在调用信号处理程序时要执行的JavaScript代码块。
例如,下面的onClicked信号处理程序在MouseArea对象定义中声明,并在单击MouseArea时调用,从而打印控制台消息:
import QtQuick 2.0
Item {
width: 100; height: 100
anchors.fill: parent
onClicked: {
console.log("Click!")
}
}
}
定义信号属性:
在c++中,可以为类型定义一个信号,方法是注册一个类的Q_SIGNAL,然后将该类注册到QML类型系统中。另外,对象类型的自定义信号可以在QML文档的对象声明中定义,语法如下(c++中将在单独章节讲解)
signal <signalName>[([<type> <parameter name>[, ...]])]
试图在同一类型块中声明具有相同名称的两个信号或方法是错误的。但是,新信号可以重用类型上现有信号的名称。(这必须小心,因为现有的信号可能被隐藏,无法访问。
下面是三个信号声明的例子:
import QtQuick 2.0
Item {
signal clicked
signal hovered()
signal actionPerformed(string action, var actionResult)
}
如果信号没有参数,“()”括号是可选的。如果使用参数,则必须声明参数类型,就像上面actionPerformed信号的字符串和var参数一样。允许的参数类型与在本页定义属性项下列出的参数类型相同。
若要发出信号,请将其作为方法调用。当发出信号时,将调用任何相关的信号处理程序,并且处理程序可以使用定义的信号参数名称访问相应的参数。
属性改变的信号:
QML类型还提供内置的属性更改信号,当属性值发生更改时,就会发出这些信号,如前面关于属性属性的部分所述。有关这些信号为何有用以及如何使用这些信号的更多信息,请参阅即将介绍属性更改信号处理程序的部分。
信号处理程序:
信号处理程序是一种特殊类型的方法属性,当发出相关信号时,QML引擎将调用方法实现。向QML中的对象定义添加信号将自动向对象定义添加关联的信号处理程序,默认情况下,对象定义具有空实现。客户端可以提供实现,实现程序逻辑。
了解下面的SquareButton类型,它的定义在SquareButton中提供。qml文件如下图所示,信号激活和关闭:
// SquareButton.qml
id: root
signal activated(real xPosition, real yPosition)
signal deactivated
property int side: 100
width: side; height: side
anchors.fill: parent
onPressed: root.activated(mouse.x, mouse.y)
onReleased: root.deactivated()
}
}
这些信号可以被同一目录下另一个QML文件中的任何SquareButton对象接收,其中信号处理程序的实现由客户端提供:
// myapplication.qml
SquareButton {
onActivated: console.log("Activated at " + xPosition + "," + yPosition)
onDeactivated: console.log("Deactivated!")
}
有关信号使用的详细信息,请参阅信号和处理程序事件系统。
属性更改信号处理程序:
属性更改信号的信号处理程序采用< property >Changed上的语法形式,其中< property >是属性的名称,第一个字母大写。例如,虽然TextInput类型文档不记录textChanged信号,但是这个信号是隐式可用的,因为TextInput有一个text属性,因此可以编写一个onTextChanged信号处理程序,以便在该属性发生更改时调用:
import QtQuick 2.0
text: "Change this!"
onTextChanged: console.log("Text has changed to:", text)
}
方法属性:
对象类型的方法是一个函数,可以调用该函数来执行某些处理或触发进一步的事件。方法可以连接到信号,以便在发出信号时自动调用该方法。有关详细信息,请参见信号和处理程序事件系统。
定义方法的属性:
在c++中,可以为类型定义方法,方法是为类的函数加上标签,然后用Q_INVOKABLE将该类的函数注册到QML类型系统中,或者将其注册为类的Q_SLOT。或者,可以使用以下语法将自定义方法添加到QML文档中的对象声明中
function <functionName>([<parameterName>[, ...]]) { <body> }
方法可以添加到QML类型中,以便定义独立的、可重用的JavaScript代码块。这些方法可以由内部调用,也可以由外部对象调用。
与信号不同,方法参数类型不需要声明,因为它们默认为var类型。
试图在同一类型块中声明具有相同名称的两个方法或信号是错误的。但是,新方法可以重用类型上现有方法的名称。(这应该谨慎进行,因为现有的方法可能是隐藏的,并且变得不可访问。)
下面是一个矩形,它带有calculateHeight()方法,当分配height值时调用该方法:
import QtQuick 2.0
id: rect
function calculateHeight() {
return rect.width / 2;
}
width: 100
height: calculateHeight()
}
如果方法有参数,则可以通过方法中的名称访问它们。下面,当鼠标区域被点击时,它调用mo否决权()方法,该方法可以引用接收到的newX和newY参数来重新定位文本:
import QtQuick 2.0
Item {
width: 200; height: 200
anchors.fill: parent
onClicked: label.moveTo(mouse.x, mouse.y)
}
Text {
id: label
function moveTo(newX, newY) {
label.x = newX;
label.y = newY;
}
text: "Move me!"
}
}
附加属性和附加信号处理程序:
附加属性和附加信号处理程序是一种机制,它允许使用附加属性或信号处理程序对对象进行注释,否则对象无法使用这些附加属性或信号处理程序。特别是,它们允许对象访问特定于单个对象的属性或信号。
QML类型实现可以选择用c++创建具有特定属性和信号的附加类型。然后可以在运行时创建此类型的实例并将其附加到特定对象,从而允许这些对象访问附加类型的属性和信号。通过在属性和各自的信号处理程序前面加上附加类型的名称来访问这些函数。
对附加属性和处理程序的引用采用以下语法形式:
<AttachingType>.<propertyName>
<AttachingType>.on<SignalName>
例如,ListView类型有一个附加的属性ListView.isCurrentItem,并且列表视图中每个委托对象都可以使用的isCurrentItem。每个委托对象都可以使用它来确定它是否是视图中当前选中的项目:
import QtQuick 2.0
ListView {
width: 240; height: 320
model: 3
delegate: Rectangle {
width: 100; height: 30
color: ListView.isCurrentItem ? "red" : "yellow"
}
}
在本例中,附加类型的名称是ListView,所涉及的属性是isCurrentItem,因此所附加的属性被称为ListView.isCurrentItem。
以相同的方式引用附加的信号处理程序。例如,组件。onCompleted附加信号处理程序通常用于在组件的创建过程完成时执行一些JavaScript代码。在下面的示例中,一旦完全创建了ListModel,它的组件。onCompleted信号处理器将自动调用填充模型
import QtQuick 2.0
ListView {
width: 240; height: 320
model: ListModel {
id: listModel
Component.onCompleted: {
for (var i = 0; i < 10; i++)
listModel.append({"Name": "Item " + i})
}
}
delegate: Text { text: index }
}
由于附加类型的名称是Component,并且该类型有一个已完成的信号,所以附加的信号处理程序称为Component. oncompleted。
关于访问附加属性和信号处理程序的说明:
例如,下面是前面示例的修改版本,其中包含附加属性。这一次,委托是一个项目,而彩色矩形是该项目的一个子项目:
import QtQuick 2.0
ListView {
width: 240; height: 320
model: 3
delegate: Item {
width: 100; height: 30
width: 100; height: 30
color: ListView.isCurrentItem ? "red" : "yellow" // WRONG! This won't work.
}
}
}
这并不像预期的那样工作,因为ListView.isCurrentItem只附加到根委托对象,而不是其子对象。由于矩形是委托的子元素,而不是委托本身,所以它不能访问作为ListView.isCurrentItem附加属性的isCurrentItem。因此,矩形应该通过根委托访问isCurrentItem:
ListView {
//....
delegate: Item {
id: delegateItem
width: 100; height: 30
width: 100; height: 30
color: delegateItem.ListView.isCurrentItem ? "red" : "yellow" // correct
}
}
}
现在delegateItem.ListView。正确地引用委托的isCurrentItem附加属性。
枚举属性:
枚举提供一组固定的命名选项。它们可以在QML中使用enum关键字声明:
// MyText.qml
Text {
enum TextType {
Normal,
Heading
}
}
如上所示,枚举类型(例如TextType)和值(例如Normal)必须以大写字母开头。值通过<Type>.<EnumerationType>.<Value> o或<Type>.<Value>。
// MyText.qml
Text {
enum TextType {
Normal,
Heading
}
property int textType: MyText.TextType.Normal
font.bold: textType == MyText.TextType.Heading
font.pixelSize: textType == MyText.TextType.Heading ? 24 : 12
}