概述
每一个QML对象类型都包含一组已定义的特性。当进行实例时都会包含一组特性,这些特性是在对象类型中定义的。
一个QML文档中的对象类型声明了一个新的类型,即实例出一个类型。
其中包含以下特性。
- the id attribute : id特性
- property attributes : 属性特性
- signal attributes : 信号特性
- signal handler attributes : 信号处理器特性
- method attributes : 方法特性
- attached properties and attached signal handler attributes : 附加属性和附加信号处理器特性
- enumeration attributes : 枚举特性
id特性
每个QML对象类型都有一个唯一的id属性。这个属性是由语言本身提供的,不能被任何QML对象类型重新定义或覆盖。
可以为对象实例的id属性赋值,以便其他对象可以识别和引用该对象。这个id必须以小写字母或下划线开头,且不能包含除字母、数字和下划线以外的字符。
下面是一个TextInput对象和一个Text对象。TextInput对象的id值设置为"myTextInput"。Text对象将其text属性设置为与TextInput的text属性相同的值,通过引用myTextInput.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类型明确禁止对特定属性进行修改。
定义属性特性
通过注册类的Q_PROPERTY宏,可以在c++中为类型定义属性,然后在QML类型系统中注册。另外,对象类型的自定义属性可以在QML文档中的对象声明中定义,语法如下:
[default] property <propertyType> <propertyName>
通过这种方式,对象声明可以更容易地向外部对象公开特定值或维护某些内部状态。
属性名称必须以小写字母开头,并且只能包含字母、数字和下划线。JavaScript保留字不是有效的属性名。默认关键字是可选的,用于修改所声明属性的语义。
隐式声明自定义属性会为该属性创建一个值更改信号,以及一个名为on<PropertyName>Changed
的关联信号处理程序,其中<PropertyName>
是属性的名称,第一个字母大写。
例如,下面的对象声明定义了一个派生自Rectangle基类型的新类型。它有两个新属性,并为其中一个新属性实现了信号处理程序:
Rectangle { 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模块提供的,因此除非导入模块,否则不能用作属性类型。
注意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类型。qml(在随后由客户端导入的目录中),那么ColorfulButton类型的属性也将是有效的。
Property ColorfulButton colorBtn
为属性赋值
对象实例的属性值可以用两种不同的方式指定:
- 初始化时的赋值
- 命令式值赋值
在这两种情况下,值都可以是静态值或绑定表达式值。
初始化赋值
在初始化时为属性赋值的语法是:
<propertyName> : <value>
如果需要,可以将初始化值赋值与对象声明中的属性定义结合在一起。在这种情况下,属性定义的语法变为:
[default] property <propertyType> <propertyName> : <value>
例如:
import QtQuick 2.0
Rectangle
{
color: "red"
property color nextColor: "blue" // 初始化并定义
}
命令式赋值
命令式值赋值是指将属性值(静态值或绑定表达式)从命令式JavaScript代码分配给属性。命令式值赋值的语法就是JavaScript赋值操作符,如下所示:
[<objectId>.]<propertyName> = value
例如:
import QtQuick 2.0
Rectangle {
id: rect
Component.onCompleted: { rect.color = "red" }
}
静态值和属性绑定
如前所述,有两种类型的值可以分配给属性:静态值和绑定表达式值。后者也称为属性绑定。
属性绑定:
对象的属性可以被赋一个静态值,该值在显式赋给新值之前保持不变。然而,为了充分利用QML及其对动态对象行为的内置支持,大多数QML对象使用属性绑定。
属性绑定是QML的一个核心特性,它允许开发人员指定不同对象属性之间的关系。当属性的依赖项的值发生变化时,该属性将根据指定的关系自动更新。
在幕后,QML引擎监视属性的依赖项(即绑定表达式中的变量)。当检测到更改时,QML引擎重新计算绑定表达式并将新结果应用于属性。
类型 | 描述 |
---|---|
Static Value | 描述属性与其他属性之间关系的JavaScript表达式。此表达式中的变量称为属性的依赖项。 |
Binding Expression | QML引擎强制属性与其依赖项之间的关系。当任何依赖项的值发生变化时,QML引擎自动重新计算绑定表达式,并将新结果分配给属性。 |
示例如下:
import QtQuick 2.0
Rectangle {
// 静态值初始化
width: 400
height: 200
Rectangle {
// 属性绑定,当parent.width变化时,width会自动变化
width: parent.width / 2
height: parent.height
}
}
注意:要命令式地分配绑定表达式,绑定表达式必须包含在传递给Qt.binding()的函数中,然后必须将Qt.binding()返回的值分配给属性。相反,在初始化时赋值绑定表达式时,不能使用Qt.binding()。
带有绑定的属性会根据需要自动更新。但是,如果稍后从JavaScript语句中为该属性分配静态值,则绑定将被删除。
例如,下面的矩形最初确保其高度始终是其宽度的两倍。但是,当空格键被按下时,width*3的当前值将作为静态值赋给height。在此之后,即使宽度改变,高度也将保持固定在此值。静态值的赋值将删除绑定。
import QtQuick 2.0
Rectangle {
width: 100
height: width * 2
focus: true
Keys.onSpacePressed: {
height = width * 3
}
}
如果目的是给矩形一个固定的高度并停止自动更新,那么这不是问题。然而,如果目的是在宽度和高度之间建立一个新的关系,那么新的绑定表达式必须包装在Qt.binding()函数中:
import QtQuick 2.0
Rectangle {
width: 100
height: width * 2
focus: true
Keys.onSpacePressed: {
height = Qt.binding(function() { return width * 3 })
}
}
现在,在按下空格键后,矩形的高度将继续自动更新,始终是其宽度的三倍。
类型安全
属性是类型安全的。只能为属性分配与属性类型匹配的值。
例如,如果一个属性是实数,如果试图给它赋值一个字符串,会报一个错误:
property int volume: "four" // 该属性的对象将不会被加载
同样,如果在运行时为属性分配了错误类型的值,则不会分配新值,并且会生成错误。
有些属性类型没有自然的值表示,对于这些属性类型,QML引擎会自动执行字符串到类型值的转换。因此,例如,即使color类型的属性存储颜色而不是字符串,您也可以将字符串“red”分配给color属性,而不会报告错误。
此外,任何可用的QML对象类型也可以用作属性类型。
特殊属性类型
list对象属性类型
可以为列表类型属性分配一个QML对象类型值列表。定义对象列表值的语法是一个用方括号括起来的逗号分隔的列表:
[ <item 1>, <item 2>, ... ]
例如,Item类型有一个states属性,用于保存State类型对象的列表。下面的代码将这个属性的值初始化为一个包含三个State对象的列表:
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 {
// 声明,但不初始化
property list<Rectangle> siblingRects
// 声明并初始化
property list<Rectangle> childRects: [
Rectangle { color: "red" },
Rectangle { color: "blue"}
]
}
如果你希望声明一个属性来存储不一定是QML对象类型值的值列表,那么应该声明一个var属性。
group对象属性类型
在某些情况下,属性包含子属性属性的逻辑组。这些子属性可以使用点表示法或组表示法进行分配。
例如,Text类型有一个字体组属性。下面,第一个Text对象使用点表示法初始化它的字体值,而第二个使用组表示法:
Text {
//dot notation
font.pixelSize: 12
font.b: true
}
Text {
//group notation
font { pixelSize: 12; b: true }
}
分组属性类型是具有子属性的基本类型。其中一些基本类型是由QML语言提供的,而其他类型只有在导入Qt Quick模块时才能使用。
属性别名
属性别名是包含对另一个属性的引用的属性。普通属性定义为属性分配一个新的、唯一的存储空间,而属性别名则不同,它将新声明的属性(称为别名属性)连接起来,作为对现有属性(别名属性)的直接引用。
属性别名声明看起来像普通的属性定义,不同之处在于它需要alias关键字而不是属性类型,并且属性声明的右侧必须是有效的别名引用:
[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别名属性的Button类型,它连接到Text子元素的text对象:
// Button.qml
import QtQuick 2.0
Rectangle {
property alias buttonText: textItem.text
width: 100; height: 30; color: "yellow"
Text { id: textItem }
}
下面的代码将为子Text对象创建一个带有定义文本字符串的Button:
Button { buttonText: "Click Me" }
在这里,修改buttonText会直接修改textItem.text 值;它不会改变其他一些值,然后更新textItem.text。如果buttonText不是别名,那么改变它的值实际上根本不会改变显示的文本,因为属性绑定不是双向的:如果是textItem.text改变, buttonText值就会改变。反之,则不会。
属性别名的注意事项
别名只有在组件完全初始化后才会被激活。当引用未初始化的别名时将生成错误。同样,给别名属性再次进行别名也会导致错误。
property alias widgetLabel: label
//下面代码将会报错
//widgetLabel.text: "Initial text"
//下面代码将会报错
//property alias widgetLabelText: widgetLabel.text
Component.onCompleted: widgetLabel.text = "Alias completed Initialization"
但是,当在根对象中导入带有属性别名的QML对象类型时,该属性显示为常规Qt属性,因此可以在别名引用中使用。
别名属性可能与现有属性具有相同的名称,从而有效地覆盖现有属性。例如,下面的QML类型有一个颜色别名属性,命名与内置的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属性。但是,在内部,矩形可以正确地设置其颜色属性,并引用实际定义的属性,而不是别名。
默认属性
对象定义可以有一个默认属性。如果在另一个对象的定义中声明对象而没有将其声明为特定属性的值,则默认属性是为其赋值的属性。
使用可选的default关键字声明属性将其标记为默认属性。例如,假设有一个文件MyLabel。使用默认属性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的类型中,而无需显式地将它们添加到children属性中。这是因为Item的默认属性是它的data属性,并且为Item添加到此列表中的任何项都会自动添加到其子列表中。
默认属性可用于重新分配项的子元素。
只读属性
对象声明可以使用readonly关键字定义只读属性,语法如下:
readonly property <propertyType> <propertyName> : <initialValue>
只读属性必须在初始化时赋值。初始化只读属性后,就不可能再给它赋值了,无论是通过命令式代码还是其他方式。
例如,组件中的代码。下面的onCompleted块无效:
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类型有一个单击信号,当用户在鼠标区域内单击时发出该信号。
每当发出特定信号时,可以通过信号处理程序通知对象。信号处理程序的声明语法为on<Signal>
;<Signal>
是信号的名称,第一个字母大写。信号处理程序必须在发出信号的对象的定义中声明,并且处理程序应该包含调用信号处理程序时要执行的JavaScript代码块。
例如,下面的onClicked信号处理程序在MouseArea对象定义中声明,并在单击MouseArea时调用,从而打印控制台消息:
import QtQuick 2.0
Rectangle {
visible: true
width: 640
height: 480
border.color: "red"
Rectangle {
anchors.centerIn: parent
visible: true
width: 100
height: 100
border.color: "red"
MouseArea {
anchors.fill: parent
onClicked: {
console.log("Click!")
}
}
}
}
定义信号属性
通过注册类的Q_SIGNAL,可以为c++中的类型定义信号,然后在QML类型系统中注册该类。另外,对象类型的自定义信号可以在QML文档中的对象声明中定义,语法如下:
signal <signalName>[([<type> <parameter name>[, ...]])]
试图在同一类型块中声明两个具有相同名称的信号或方法是错误的。但是,新信号可以重用该类型上已有信号的名称。(这应该谨慎进行,因为现有的信号可能会被隐藏并无法访问。)
下面是三个信号声明的例子:
import QtQuick 2.0
Item {
signal clicked
signal hovered()
signal actionPerformed(string action, var actionResult)
}
如果信号没有参数,“()”括号是可选的。如果使用参数,则必须声明参数类型,就像上面actionPerformed信号的string和var参数一样。允许的参数类型与此页面上定义属性属性下列出的参数类型相同。
要发出信号,请将其作为方法调用。当信号发出时,任何相关的信号处理程序都将被调用,处理程序可以使用定义的信号参数名称来访问相应的参数。
属性更改信号
QML类型还提供内置的属性更改信号,每当属性值发生更改时,就会发出这些信号,如前面关于属性属性部分所述。
信号处理特性
信号处理程序是一种特殊类型的方法属性,每当发出相关信号时,QML引擎就会调用该方法实现。在QML中向对象定义添加信号将自动向对象定义添加关联的信号处理程序,该对象定义在默认情况下具有空实现。客户端可以提供一个实现,来实现程序逻辑。
如下QML文件所示,其中信号激活和未激活:
import QtQuick 2.0
Rectangle {
visible: true
width: 640
height: 480
border.color: "red"
Rectangle {
id: aaaaaa
border.color: "blue"
anchors.centerIn: parent
signal activated(real xPosition, real yPosition)
signal deactivated
property int side: 100
width: side; height: side
MouseArea {
anchors.fill: parent
onPressed: aaaaaa.activated(mouse.x, mouse.y)
onReleased: aaaaaa.deactivated()
}
onActivated: {
console.log("onActivated")
}
onDeactivated: {
console.log("onDeactivated")
}
}
}
属性更改信号处理
属性更改信号的信号处理程序的语法形式为on<Property>Changed
;其中<Property>
是属性的名称,第一个字母大写。例如,虽然TextInput类型文档没有记录textChanged信号,但这个信号是隐式可用的,因为TextInput有一个text属性,因此可以编写一个onTextChanged信号处理程序,以便在该属性更改时调用:
import QtQuick 2.0
TextInput {
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()方法:
import QtQuick 2.0
Rectangle {
id: rect
function calculateHeight() {
return rect.width / 2;
}
width: 100
height: calculateHeight()
}
如果方法有参数,则可以在方法内通过名称访问它们。下面,当单击MouseArea时,它调用moveTo()方法,该方法可以引用接收到的newX和newY参数来重新定位文本:
import QtQuick 2.0
Item {
width: 200; height: 200
MouseArea {
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
在每个委托对象。这可以被每个单独的委托对象用来确定它是否是视图中当前选中的项:
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。
关于访问附加属性和信号处理的注意事项
一个常见的错误是假设附加的属性和信号处理程序可以从附加了这些属性的对象的子对象直接访问。事实并非如此。附加类型的实例只附加到特定对象,而不是对象及其所有子对象。
例如,下面是先前示例的修改版本,涉及附加属性。这一次,委托是一个Item,Rectangle是该Item的子元素:
import QtQuick 2.0
ListView {
width: 240; height: 320
model: 3
delegate: Item {
width: 100; height: 30
Rectangle {
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
Rectangle {
width: 100; height: 30
color: delegateItem.ListView.isCurrentItem ? "red" : "yellow" // correct
}
}
}
现在,delegateItem.ListView.isCurrentItem正确地引用了委托的isCurrentItem附加属性。