解读QML之四
QML对象属性
每一个QML对象类型都定义了一系列属性。每创建一个该对象类型的实例,该实例的这些属性也自动被创建了。接下来我们讨论几种不同类型的属性。
id属性
每一个QML对象类型都有一个唯一确定的id属性。这个属性是由QML语言自身提供的,并且在QML对象类型中不能被重定义和重载。
我们必须为id属性指定一个值允许该对象被唯一标示并且可用于被其它对象引用。Id属性值必须以小写字母或者下划线开始,只能包含字母,数字和下划线等字符。
下面是一个TextInput对象和一个Text对象,TextInput对象的id属性值为”myTextInput”。Text对象的text属性值通过myTextInput.text被设置为和TextInput对象的text属性值一样。
我们以后可以在该组件可见域内通过id属性引用该对象。因此id属性值在组件可见域内必须是唯一的。
一旦对象实例被创建了,那么id属性值就不能被改变。Id属性看起来像普通属性,但是并非是真正的普通属性,我们对它应用特殊的语义,例如:在上面的例子中我们不能使用myTextInput.id。
property属性
一个property是对象的一个属性,可以被赋为静态值或者是绑定到动态表达式上。一个property的值可以被其它的对象读取。一般情况下,property属性也可以被其它对象修改,除非该QML类型明确指定该property属性不能被修改。
【定义property属性】
一个property属性可以在C++中定义,并且通过Q_PROPERTY注册到QML类型系统。当然,我们也可以在QML文档中通过如下语法自定义对象的property属性:
通过这种方式,一个对象可以将一些特定的值暴露给其它对象,或者是更加简便的维护一些内部状态。
Property属性的名称必须以小写字母开头,且只能包含字母,数字和下划线。JavaScript的保留关键字不能作为property属性的名称。Default关键字是可选的,对于default以及default属性修改者的详细信息稍后讨论。
定义一个自定义的property属性也就为该property属性隐式的创建了一个value-change信号,也就是关联了一个名为on<PropertyName>Changed的signal handler。<PropertyName>就是property属性的名称,而且首字母要大写。
例如:下面就定义了两个property属性,并且实现了其signalhandler:
【自定义property属性的合法类型】
QML基本类型中的枚举类型都可以作为自定义property属性类型。例如:下面都是合法的property属性声明:
一些QtQuick模块提供的基本类型是不能作为property类型的,除非在QML文档中导入QtQuick模块。
var基本类型是通用的类型,可以保存任意类型的值,包括lists和objects:
另外,任何的QML对象类型都可以被用作property属性类型。例如:
这对于自定义QML类型也是适用的。如果在ColorfulButton.qml文件中定义了一个QML类型,那么ColorfulButton类型的property属性也是合法的。
【合法的property属性值】
我们可以通过两种方式为定义的property属性的值:
*初始化
*赋值
值可以是静态值也可以是绑定表达式。
{初始化}
Property属性初始化:
我们可以在定义property属性的时候,也进行初始化赋值:
初始化赋值举例如下:
{赋值}
我们可以使用JavaScript代码给property属性赋值,如下:
举例如下:
【合法的property值】
正如之前提到的,我们可以给property属性赋两类值:静态值和绑定表达式:
示例:
在许多情况下,string类型的值可以自动被转换为许多不同类型的值,因为QML提供了string类型到其它类型的转换(这也就是为什么你可以给color属性赋值”red”)。
必须特别注意,绑定到一个表达式的情况下,右边的表达式必须的Qt.binding()函数的返回值,该函数返回合适的值类型。一个表达式可以在property属性初始化的时候直接赋值,而不需要使用那个函数(实际上使用函数还会导致错误)。
【类型安全】
属性都是类型安全的。给属性赋值必须是类型匹配的。
例如,下面的赋值就会导致一个错误:
如果在运行时的时候给属性赋类型错误的值,那么赋值也不会成功还会产生一个错误。
正如在之前提到过的,一些属性类型并没有很好的类型来表达其值,这个时候QML引擎提供了很好的string类型转换。例如:color属性,该属性存储的值类型应该是color类型而不是string类型,但是你却可以给它指定string类型的值,而不会产生错误。
【特殊的property属性类型】
对象列表类型的property属性
List类型的property属性也可以被赋予QML对象类型列表的值。赋值的形式如下:
例如:Item类型有一个状态属性,可以用于保存State对象类型的列表。下面的代码用于初始化该列表:
如果列表仅仅包含一项,那么方括号可以省略:
我们可以像下面这样定义对象列表类型的property属性:
List类型的属性声明举例如下:
如果你希望声明一个属性用于存储列表值,但不一定是存储QML对象类型值,这个时候你就需要声明var的property属性。
【分组的属性】
在某些情况下,我们可以将属性根据逻辑分为子属性组。这些子属性可以通过”.”或者是组来赋值。
例如:Text类型有一个font的组属性。在下面,第一个Text对象使用”.”初始化font的值,第二个则是使用组来赋值:
组属性类型都是基本的类型。这些基本类型一部分是由QML语言提供的,另外一部分则是由Qt Quick模块提供的。
【属性别名】
属性别名就是保存对另一个属性的引用。不像普通属性的定义,普通属性的定义需要分配一个新的,唯一的存储空间,而一个属性别名仅仅是连接到了属性上。
属性别名的定义根属性定义差不多,只是属性别名需要使用alias关键字代替属性定义中的property类型,右边的值必须是合法的引用别名:
不像普通的属性,一个别名仅仅可以引用对象或者是对象的属性。
例如:下面的Button类型有一个buttonText属性别名,链接到子Text对象的text属性:
下面的代码会创建一个Button,并且定义文本字符串:
修改了buttonText,就直接修改了textItem.text的值,它不会修改其它的值。如果buttonText不是属性别名,那么修改它的值是不会修改显示的文本的,因为属性绑定不是双向的。
【考虑属性别名】
只有在组件完全初始化之后属性别名才会被激活。如果未初始化的别名被引用了就会产生错误。另外,为一个属性别名产生属性别名也会导致错误:
我们可以为一个已经存在属性创建一个同名的属性别名,这就会覆盖已经存在的属性。例如:下面的QML类型有一个color属性别名,跟Rectangle内建的属性Rectangle::color属性:
【default属性】
一个对象定义可以包含一个default属性。如果一个对象()子对象定义在另一个对象(父对象)之内而没有赋值给该父对象的任何属性,那么该子对象就是该父对象default属性的值。声明为default属性需要使用default关键字。
例如下面的MyLabel.qml文件中的对象就有一个someText的默认属性:
我们可以在MyLabel对象定义中为someText默认属性赋值:
上面两个等同于下面一个:
然而,由于someText属性被标记为默认属性,因此我们就没有必要显示的将Text对象赋值给这个默认属性。
你也可能注意到了子对象可以添加到任何基于Item的类型,而不需要明确将它们添加到子属性上。这是因为Item的默认属性是data属性,任何添加到Item中的对象都自动添加到它的子对象列表。
默认属性对于重新指定子对象是十分有用的。可以看看TabWidget示例,该示例就是使用默认属性自动重新指定TabWidget的子对象作为它的子对象列表。
【只读属性】
任何对象的定义都可以使用readonly关键字定义只读属性,使用下面的语法:
只读属性必须在初始化的时候指定值。一旦只读属性被初始化了,它就不可能再被赋值了,无论是赋值(使用”=”)还是其它的方式。
例如,下面的Component.onCompleted代码块就是非法的:
注意:一个只读的属性是不能声明为默认属性或者是属性别名的。
【属性修改对象】
属性可以有属性值修改对象与它们关联。我们可以像如下的这样定义属性修改对象实例,与特定的属性关联:
需要注意的是,上面的语法实际上一个对象声明,将会实例化一个对象操作已存在的属性。
特定的属性修改类型只能应用到特定的属性类型上,但是这并不是被语言强制的。例如:QtQuick模块提供的NumberAnimation类型仅仅影响数字类型(例如int或者real)属性。如果将NumberAnimation使用到非数字类型属性将不会引起错误,但是非数字属性将不会产生动画。属性修改类型的动作是与特定属性的实现紧密相关的。
Signal属性
信号就是当某些事件发生的时候从对象类型中发出通知:例如,一个属性改变,一个动画开始或者停止,或者当一个图片下载完成。例如,MouseArea类型当用户点击的时候就会发射一个点击信号。
当一个信号发射了,对象可以通过signal handler被通知。一个signalhandler的定义语法为on<Signal>,<Signal>是信号的名称,首字母要大写。Signalhandler必须在发射该信号的对象定义的内部实现,并且signalhandler必须包括JavaScript代码块,当signal handler被调用的时候该代码块就会被执行。
例如,MouseArea对象的定义中可以定义onClicked类型的signal handler,当MouseArea被点击了该signal handler就会被调用,使得控制台打印出消息:
【定义Signal属性】
我们可以使用Q_SIGNAL在C++的类中定义Signal属性并注册到QML类型系统。可选的,我们也可以在QML文档中使用如下语法定义signal属性:
在一个类型块中定义两个同名的信号或者方法将会产生错误。然而,我们可以使用已经存在的信号类型定义新的信号(这会导致之前的信号不可见)
下面就是一个信号定义的示例:
如果一个signal没有参数,那么”()”就是可选的。如果使用了参数,那么参数的类型就必须指定,例如上面的actionPerformed信号中的string和val参数。允许使用的参数类型根之前描述的定义property属性中的一样。
要发射一个信号,那么就会调用一个方法。任何和信号关联的signal handler都会在信号被发射的时候执行,并且handlers可以使用定义的信号参数的名字和获取预期的参数。
【属性改变信号】
QML类型也提供内建的属性改变信号,当属性值被改变的时候这些信号就会被发射。接下来我们就来介绍信号的好处以及如何使用。
【Signal Handler属性】
Signal handlers是特殊的方法属性类型,当信号发射了,与信号关联的特定方法就会被调用。在QML中添加一个信号就会自动添加一个与之关联的signalhandler,默认情况下该函数就会是空实现。客户端可以提供自己的实现来实现程序逻辑:
考虑下面的SquareButton类型,该类型是SquareButton.qml文件中定义的,定义了两个信号:activated和deactived:
这些信号可以在同一目录下的其他QML文件中的任何SquareButton对象获取,客户端也提供了signalhandler实现:
【属性改变Signal Handler】
属性改变的Signal handler语法如下on<Property>Changed,<Property>就是属性的名称,首字母要大写。例如,尽管TextInput类型文档没有定义textChanged信号,但是这个信号因为TextInput有一个text属性而可用。因此我们也就可以实现onTextChanged signal handler,当属性值被改变的时候就会调用该signal handler:
方法属性
一个对象的方法就是在执行一些处理或者触发一些事件的时候被调用的。当一个方法连接到一个信号时,当信号被发射的时候,这些方法就会被执行。
【定义方法属性】
我们可以通过在C++的类的函数使用Q_INVOKABLE注册到QML类型系统或者是在C++类中注册为Q_SLOT。可选的,我们也可以在QML文档中自定义方法添加到对象中:
方法可以添加到QML类型以定义单独的可重用的JavaScript代码。这些方法可以在内部被调用或者是被外部对象调用。
不像信号,这里不用定义参数的类型,因为它们默认为var类型。
在同一个类型块中视图定义两个同名的方法或者是信号都会导致错误。然而,我们可以使用已存在的方法的名称定义新的方法(这会导致已存在的方法不可用)
下面的Rectangle类型有一个calculateHeight()方法,当指定height值的时候该方法就会被调用。
如果方法有参数,那么我们可以在方法中通过参数名访问它们。例如,MouseArea被点击,那么就会调用moveTo()方法,可以使用newX和newY设置文本的新位置:
附加属性和附加SignalHandler
附加属性和附加的信号处理机制允许对象使用那些对象不可引用的外部属性和信号处理者。这允许对象访问那些与单个对象关联的属性和信号。
一个QML类型实现可以选择创建附加的特定属性和信号。实例化这种类型就会在运行时创建,允许这些对象访问属性和信号。
引用附加的属性和handlers语法如下:
例如:ListView类型有一个附加的属性ListView.isCurrentItem,该属性可以被ListView的每一个代理访问。该属性可以被每一个独立的代理对象来决定当前选择的是哪一个条目:
在这种情况下,附加类型的名称是ListView和属性isCurrentItem,附加属性使用ListView.isCurrentItem引用。
附加的signal handler也可以使用这种方式引用。例如:附加的signal handler是Component.isCompleted,当组件的创建过程完成后可以被用来执行一些JavaScript代码。在下面的示例中,一旦ListModel被完全创建,Component.onCompleted signal handler就会被自动执行:
附加的类型名是Component以及completed信号,附加的signal handler可以这样引用:Component.isCompleted。
【访问附加属性和Signal Handler的注意点】
一个常见的错误是将设附加的属性和signal handler可以直接从子对象中访问。这并不是问题。附加类型的实例仅仅添加到特定的对象上,而不是对象以及其所有子对象之上。
例如:下面的示例是之前的附加属性的改编版示例。在这次中,代理是一个Item,Rectangle就是该Item的子对象:
这并不像预期中那样工作,因为ListView.isCurrentItem仅仅是附加到根代理对象,而不是它的子对象。由于Rectangle是代理的子对象,而不是代理本身,他不能使用ListView.isCurrentItem访问isCurrentItem附加属性。因此,rectangle必须通过根代理对象访问isCurrentItem属性:
现在就可以通过 delegateItem.ListView.isCurrentItem正确引用到代理的isCurrentItem附加属性。