QtQuick-QML类型系统-对象特性(id和属性)

55 篇文章 0 订阅

每一个QML对象类型都包含一组定义好的特性。

每个对象类型的实例在创建时都会包含一组特性,这些特性是在该对象类型中定义的。一个QML文档中的对象声明定义了一个新的类型,其中可以包含如下特性:

  1. id特性
  2. 属性(property)特性
  3. 信号(signal)特性
  4. 信号处理器(signal handler)特性
  5. 方法(method)特性
  6. 附加属性(attahed properties)和附加信号处理器(attached signal handler)特性
  7. 枚举(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基类型派生了一个新类型,它包含两个新属性:previousColornextColor
我们希望在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 有效的属性值

一个属性可以分配两种类型的值:

  1. 静态值
    • 与属性要求的类型相匹配(或者可以转换成属性要求的类型)的值。
  2. 绑定表达式的值
    • 该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,这是一个双向绑定。

由此可以看出,属性别名与绑定表达式不同:

  1. 如果buttonText不是一个别名,相当于将textIten.text绑定到buttonText
  2. 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快速入门 》(霍亚飞)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

barbyQAQ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值