每一个QML对象类型都包含一组定义好的特性。
每个对象类型的实例在创建时都会包含一组特性,这些特性是在该对象类型中定义的。一个QML文档中的对象声明定义了一个新的类型,其中可以包含如下特性:
- id特性
- 属性(property)特性
- 信号(signal)特性
- 信号处理器(signal handler)特性
- 方法(method)特性
- 附加属性(attahed properties)和附加信号处理器(attached signal handler)特性
- 枚举(enumeration)特性
下面将介绍它们,也可以在Qt帮助中通过QML QObject Attributes关键字查看。
一、id特性
每个对象可以指定一个唯一的id,可以在其他对象中识别并引用该对象。
这个特性是语言本身提供的,不能被QML对象类型进行重定义或重写。
可以在一个对象所在组件中的任何位置使用该对象的id来引用这个对象。因此id值在一个组件的作用域中必须是唯一的。
下面通过id来设置第2个Text拥有与第一个Text一样的text属性值:
Row{
Text{
id: text1
text: "Hello World"
}
Text{ text: text1.text}
}
id必须使用小写字母或者下划线开头,且由字母、数字、下划线组成。
二、属性特性
可以分配静态的属性,也可以分配绑定一个动态表达式。
属性的值可以被其他对象获取。一般而言,属性的值也可以被其他对象修改,除非显示声明不允许这么做(声明为只读属性)。
1. 声明属性特性
属性可以在C++中通过线先注册一个类的Q_PROPERTY
宏,再注册到QML类型系统中进行创建。
此外,还可以在QML文档中使用下面的语法声明一个属性:
[default] [required] [readonly] property <propertyType> <propertyName>
使用这种机制可以很容易地将属性值暴露给外部对象或维护对象的内部状态。
与id类似,属性的名字propertyName
也必须以小写字母开始,可以包含字母、数字和下划线。另外JavaScript不能作为属性的名字。前面的修饰符是可选的。
- default:默认属性
- required:必须属性
- readonly:只读属性
声明一个自定义的属性,则会隐式地为该属性创建一个值改变信号以及一个相应的信号处理器on<PropertyName>Changed
。其中<PropertyName>
是自定义属性的名字,首字母大写。
例如:
import QtQuick
Rectangle {
property color previousColor;
property color nextColor;
onNextColorChanged:
console.log("The next color will be: " + nextColor.toString());
nextColor:"red"
width:400; height:300; color: nextColor
MouseArea {
anchors.fill: parent
onClicked: nextColor = "yellow"
}
}
这个例子中,从Rectangle基类型派生了一个新类型,它包含两个新属性:previousColor
和nextColor
。
我们希望在nextColor
属性发生改变时得到通知,所有增加了一个信号处理器onNextColroChanged
来达到目的。
除了enumeration
外,QML的基本类型都可以用作自定义属性的类型。
例如,下面的声明都是正确的:
Item{
property int someNumber
property string someString
property url someUrl
}
由于enumeration
其实就是整型int,所有当需要enumeration
的时候,可以选择用int类型替代。
对于QML的其他基本类型,只要导入相应模块,也可以作为属性类型。需要注意的是var类型:var是一种通用的占位符类型,类似于QVariant
,它可以包含任意类型的值,包括列表和对象。
例如:
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内置的对象类型,还可以将自定义的对象类型作为属性类型使用。
2. 初始化和赋值
qml属性的值可以通过初始化或者赋值操作来给出,这两种途径都可以直接给定一个静态数据值或一个表达式。
2.1 初始化
属性可以在初始化时直接赋值:
<propertyName> : <value>
也可以将属性声明和属性初始化结合成一条语句:
[default] property <propertyType> <propertyName> : <value>
下面看一个例子:
- color属性使用了初始化语句;
- nextColor则将属性声明与属性初始化结合在一起。
import QtQuick
Rectangle{
color: "yellow"
property color nextCOlor: "blue"
}
2.2 代码中赋值
赋值操作与JavaScript相同,使用赋值运算符(=)完成。其语法是:
[<objectId>.]<propertyName> = value
例子(将rect.colr的值赋成red):
import QtQuick
Rectangle{
id: root
Component.onCompleted:{
rect.color = "red
}
}
2.3 有效的属性值
一个属性可以分配两种类型的值:
- 静态值
- 与属性要求的类型相匹配(或者可以转换成属性要求的类型)的值。
- 绑定表达式的值
- 该JavaScript表达式可以运算出结果,并且运算结果的类型与属性要求的类型相匹配(或者可以转换成属性要求的类型)。
- 当绑定表达式的值发生变化时,表达式可以自动更新运算结果。
例子:
import QtQuick
Rectangle{
//静态值初始化
width: 400
height: 200
color: "red"
Rectangle{
//使用绑定表达式初始化
width: parent.width/2
height: parent.height
}
}
2.4 类型安全
前面已经强调,QML属性的初始化或赋值时,类型必须匹配或能够转换成匹配的类型。但是,上面的代码却将字符串"red"赋值给了color类型的属性。
这是因为QML提供了一系列转换器,能够将string转换成很多其他的属性类型。正因为有这些转换器的存在,才可以将"red"转换成颜色类型。由此可以看出,QML属性是类型安全的,属性值的类型必须与属性要求的类型相匹配。
3. 对象列表属性
可以将一个QML类型值列表赋值给一个列表类型的属性,其语法如下所示:
[<item>, <item2>, ...]
列表被包含在一对方括号中,使用都好分割列表中的对象。
例如,Item类型有一个states
属性,用于保存一个State
类型对象的列表。下面的代码片段给出如何初始化这个states
属性:
Item{
states: [
State{name: "loading"},
State{name: "running"},
State{name: "stopped"}
]
}
如果列表仅包含一个对象,也可以省略方括号:
Item{
states: State{name: "running"}
}
可以使用下面的语法在对象声明时指定一个列表类型属性:
[default] property list<<objectType>> propertyName
而且与其他属性声明类似,在属性声明时也可以使用下面的语法进行属性初始化:
[default] property list<<objectType>> propertyName: <value>
例子:
Rectangle {
//只声明,不初始化
property list<Rectangle> siblingRects
//声明并且初始化
property list<Rectangle> childRects: [
Rectangle {color: "red"},
Rectangle {color: "blue"}
]
width: 400; height: 300; color: childRects[1].color
MouseArea{
anchors.fill: parent
onClicked:{
for(var i=0;i<childRects.length; ++i){
console.log("color", i, childRects[i].color);
}
}
}
}
说明一下:可以使用length属性来获取列表中对象数量,可以通过[]index
语法来获取列表中的指定值。如果要声明一个用来存储一列值的属性,而不是使用QML对象类型的值,那么可以使用var
属性。
4. 属性组
QML属性可以按照逻辑关系进行分组。属性可以是一个包含子属性特性的逻辑组,而子属性特性也可以使用点标记或者组标记来赋值。例如,Text
类型的font
属性是一个属性组。
代码示例(第一个Text
使用点标记初始化font值,第二个则使用组标记的形式):
Row {
Text {
//点标记
font.pixelSize: 12; font.bold: true
text: "text1"
}
Text {
//组标记
font: {pixelSize:12; bold: true}
text: "text2"
}
}
5. 属性别名
属性别名类似C++的引用。与普通的属性声明不同,属性别名不需要分配一个新的唯一的存储空间,而是将新声明的属性(称为别名属性,the aliasing property)作为一个已经存在的属性(称为被别名的属性,the aliased property)的直接引用。
我们可以给属性定义一个别名,以后就可以利用这个别名操作这个属性。属性别名的声明与属性的声明类似,但是需要使用alias
关键字代替属性类型,而且在属性声明的右侧必须是一个有效的别用引用。
语法:
[default] property alias <name> : <alias reference>
与普通属性不同,别名只能引用到其声明处的类型作用域中的一个对象或一个对象的属性。它不能包含任何JavaScript表达式,也不能引用类型作用域之外的对象。
还要注意右侧的alias reference不是可选的,这与普通属性性声明中可选的默认值不同。
而且当第一次声明别名时alias reference必须提供,这一点与C++引用也非常相似。
例子:
//Button.qml
import QtQuick
Rectangle{
property alias buttonText: textItem.text
width: 100; height: 30; color: "yellow"
Text: {id: textItem}
}
这里定义了一个Button类型。Button有一个buttonText的属性别名,指向其Text
子对象的text
属性。在其他QML文档文档中使用Button类型时,可以之际使用如下语句定义其Text子对象的文本:
Button {buttonText: "click Me"}
由于buttonText
属性仅仅是一个别名,任何针对buttonText
的修改斗湖i直接反映到textItem.text
。同样,任何对textIten.text
的修改都会反映到buttonText
,这是一个双向绑定。
由此可以看出,属性别名与绑定表达式不同:
- 如果
buttonText
不是一个别名,相当于将textIten.text
绑定到buttonText
。 textItem.text
时,buttonText
会随之改变,但是buttonText
的变化却不会影响到textItem.text
在使用属性别名时需要注意下面几点:
(1) 属性别名在整个组件初始化完毕之后才是可用的
代码是从上向下执行的,因此一个常见的错误是,在引用所指向的属性还没有初始化的时候就使用了别名。例如,下面的代码无法工作:
property alias buttonText: textItem.text
//下面的代码会报错,因为diamond执行到这里时整个组件还没有完成初始化
buttonText: "some text"
另外,别名不能使用同一个组件中声明的另一个别名,例如,下面的代码是无法工作的:
id: root
property alias buttonText: textItem.text
//下面的代码会报错,因为代码执行到这里,buttonText是一个未定义的值
property alias buttonText2: root.buttonText
正确的初始化方法应该是这样的:
Component.onCompleted: buttonText = "some text"
这里的Component.onCompleted
会在组件创建完成时执行。
(2) 属性别名可以与现有的属性同名,但会覆盖现有属性
如下面的例子:
import QtQuick
Rectangle {
id: coloredRectangle
property alias color: blueRectangle.color
function setInternalColor() {
color = "#111100";
}
width: 400
height: 300
Component.onCompleted: {
console.log(coloredRectangle.color);
setInternalColor();
console.log(coloredRectangle.color);
coloredRectangle.color = "#884646";
console.log(coloredRectangle.color);
}
Rectangle {
id: blueRectangle
color: "#1234FF"
width: 100
height: 100
}
}
这里在Rectangle中定义了一个color别名属性,它的名称与内建的Rectangle::color
属性相同。
使用该组件的任何对象引用color属性时(如代码中额coloredRectangle.color)都使用别名,而不是一般的Rectangle.color
属性,而在组件内部直接使用color
属性引用的是真实定义的属性,而不是别名。
(3) 引用深度最多为两层,超过两层的引用无效。
例如下面的代码片段的引用是有效的:
property alias color: rectangle.border.color
Rectangle{
id: rectangle
}
而下面的代码片段的引用是无效的:
property alias color: myItem.myRect.border.color
Item{
id: myItem
property Rectagle myRect
}
需要说明,属性别名在开发组件的时候特别有用。
QML组件通常是一系列基本类型的有序堆积。一个组件可能有很多子对象,对于组件的使用者,处于封装的考虑,不应该知道这些子对象。
然而,组件使用者又不可避免地需要设置某些子对象的属性。此时,可以给子对象属性设置一个别名,把它作为整个组件的属性在外部使用,这样既解决了子对象封装的问题,又将有用的属性暴露出来。
6.默认属性
前面提到,对象声明可以有一个默认属性,默认属性至多有一个。
当声明对象时,如果其子对象没有明确指定它要分配到的属性名,那么这个子对象就被赋值给默认属性。
声明默认属性,只要在属性声明语句的前方加上default
修饰符即可。例如:
//MyLabel.qml
Text{
default property var someText
text: "Hello, " + someText.text
}
可以像这样在其他.qml
文件中使用MyLabel:
import QtQuick
Rectangle{
width:360; height: 360
MyLabel{
anchors.centerIn: parent
Text{text: "world!"}
}
}
这里Text对象自动成为MyLabel
的默认属性的值。这段代码其实等价于:
MyLabel{
someText: Text{text: "world!"}
}
由于someText是MyLabel的默认属性,所以无需显式给出这个属性的名字。
其实,在前面的例子已经见过默认属性,例如:
Rectangle{
id: ret
Text{
text: "Hello, world!"
}
}
注意这里的Text对象没有明确指出这个对象赋值给Rectangle
的哪一个属性,因此它就会自动成为Rectangle的默认属性的值。
所有基于Item的类型都有一个默认属性dara:list<QtObject>
,该属性允许将可视化子对象和资源自由添加到Item
对象中。
- 如果添加的式可视化子对象,那么将作为
children
; - 如果添加的是其他对象类型,那么将作为
resource
。
例如:
Item{
Text{}
Rectangle{}
Timer{}
}
相当于
Item{
children:[
Text{},
Rectangle{}
]
resources:[
Timer{}
]
}
正因为如此,不需要显式指出将子对象添加到data属性。
7. 必需属性
对象声明中,可以通过required
关键字声明一个必需属性,其语法如下:
required property <propertyType> <propertyName>
当创建一个对象的实例时,必需属性是必须要设置的。也可以使用如下语法来使现有的属性成为必需属性:
required <propertyName>
例如下面的代码片段,在自定义的ColorRectanle
类型中,颜色属性必需进行设置,不然无法运行:
//ColorRectangle.qml
Rectangle{
required color
}
注意,必需属性在模型视图程序中扮演特殊角色:
如果视图的委托具有与视图模型的角色名称相同的属性,则这些属性将使用模型的相应值进行初始化。
8. 只读属性
到目前,声明的属性都是可读可写的。
有时需要使用只读属性,通过指定readonly
关键字就可以定义一个只读属性,语法:
readonly property <propertyType> <propertyName> : <initialValue>
只读属性必需给出初始值,否则这个属性是没有意义的。一旦只读属性初始化完毕,属性值就不允许再更改。
例如下面的代码是非法的:
Item{
readonly property int someNumber: 10
Component.onCompleted: someNumber = 20 // 错误
}
注意,只读属性不允许是默认属性,也不允许有别名。
9. 属性修饰符对象(Property Modifier Objects)
一个属性可以拥有与之关联的属性修饰符对象,声明与特定属性相关联的属性修饰符对象的语法如下:
<PropertyModifierTypeName> on <propertyName>
这个通常被称为"on"语法。需要注意的是这个语法实际上是一个对象声明,它会实例化一个对象,而该对象作用于一个已经存在的属性。
典型的用法是动画类型,例如:
Rectangle{
width:100; height:100
color: "red"
NumberAnimation on x{to: 50; duration: 1000}
}
参考书籍:《Qt Quick快速入门 》(霍亚飞)