属性
QML中元素使用它们的元素类型名进行声明,使用它们的属性或者创建自定义属性来定义。一个属性对应一个值,以Text元素为例:
import QtQuick 2.3 import QtQuick.Window 2.2 Window { visible: true width: 640 height: 480 Text { id: thisLabel; x: 24; y: 16 height: 2 * width property int times: 24 property alias anotherTimes: thisLabel.times text: "Say hello " + times font.family: "sans-serif" font.pixelSize: 24 KeyNavigation.tab: otherLabel onHeightChanged: console.log('height: ', height) focus: true color: focus ? "red" : "black" } Text { id: otherLabel; x: 24; y: 50 height: 2 * width property int times: 24 property alias anotherTimes: otherLabel.times text: "Say hello " + times font.family: "sans-serif" font.pixelSize: 24 font.family: "sans-serif" font.pixelSize: 24 KeyNavigation.tab: thisLabel onHeightChanged: console.log('height: ', height) focus: false color: focus ? "red" : "black" } }
上面这段代码的实现效果如下方动图所示,每次按下tab键,当前选中的标签即发生改变,从而文本颜色发生变化。
属性的知识点
id是一个非常特殊的属性值,它在一个QML文件中被用来引用元素。id不是一个字符串,而是一个标识符和QML语法的一部分。一个id在一个QML文档中是唯一的,并且不能被设置为其它值,也无法被查询(它的行为就像C语言里的指针)。一个属性能够设置一个值,这个值依赖于它的类型。如果没有对一个属性赋值,那么它将会被初始化为一个默认值。可以查看特定元素的文档来获得这些初始值的信息。一个属性能够依赖一个或多个其它的属性,这种操作称作属性绑定。当它依赖的属性改变时,它的值也会更新。就像上面代码中的height始终是width的两倍。添加自定义的属性需要使用property修饰符,然后跟上类型,名字和可选择的初始化值。如果没有初始值,也将会给定一个系统初始化值作为初始值。注意如果属性名与已定义的默认属性名不重复,使用default关键字可以将一个属性定义为默认属性。这在你添加子元素时用得着,如果它们是可视化的元素,子元素会自动添加默认属性的子类型链表。另一个重要的声明属性的 是使用alias关键字。alias关键字允许我们转发一个属性或者转发一个属性对象自身到另一个作用域。我们将在后面定义组件导出内部属性或者引用根级元素id会介绍这个技术。一个属性别名不需要类型,它使用引用的属性类型或者对象类型、text属性依赖于自定义的times属性。int整型数据会自动的转换为string字符串类型数据。这样的表达方式本身也是另一种属性绑定的例子,文本结果会在times属性每次改变时刷新。一些属性是按组分配的属性。当一个属性需要结构化并且相关的属性需要联系在一起时,我们可以这样使用它。另一个组属性的编码方式是: font { family: "sans-serif"; pixelSize: 24}。一些属性是元素自身的附加属性。这样做是为了全局的相关元素在应用程序中只出现一次(例键盘输入、编码方式)。对于每个元素你都可以提供一个信号操作。这个操作在属性值改变时被调用。例如这里我们完成了当height改变时会使用控制台输出一个信息。属性的注意
一个元素id应该只在当前文档中被引用。QML提供了动态作用域的机制,后加载的文档会覆盖之前加载的元素的id,这样就可以引用已加载并且没有被覆盖的元素id,这有点类似创建全局变量。但这样的代码阅读性很差,目前还没有办法解决这个问题,所以你使用这个机制的时候更好仔细一些甚至不要使用。如果你想向文档外提供元素的调用,你可以在根元素上使用属性导出的方式来提供。
脚本
QML与JavaScript是更好的配合,在后面的文章中我也会更加详细的介绍这种关系,现在我们只需要了解这种关系即可,先来看个例子:
import QtQuick 2.3 import QtQuick.Window 2.2 Window { visible: true width: 640 height: 480 Text { id: label; x: 24; y: 24 height: 2 * width property int spacePresses: 0 text: "Space pressed: " + spacePresses + " times" onTextChanged: console.log('text changed to: ', text) focus: true Keys.onSpacePressed: { increment() } Keys.onEscapePressed: { spacePresses = 0 } function increment() { spacePresses = spacePresses + 1; } } }
运行效果如下图所示,每次按下空格键时数字加1,按下ESC键时,数字置0:
文本改变操作onTextChanged会将每次空格键按下导致的文本改变输出到控制台。当文本元素接收到空格键操作,会调用JavaScript函数increment()。定义一个JavaScript函数使用这种格式function() {},在上面的例子中是增加spacePressed的计数。每次spacePressed的增加都会导致它绑定的属性更新。脚本注意点
QML中属性绑定与JavaScirpt的=(赋值)是不同的。绑定是一个协议,并且存在于整个生命周期。然而JavaScript赋值只会产生一次效果。当一个新的绑定生效或者使用JavaScript赋值给属性时,绑定的什么周期就会结束。例如一个按键的操作设置文本属性为一个空的字符串,将会销毁我们的increment()增值显示:
Keys.onEscapePressed: { label.text = '' }
在点击ESC键后,再次点击空格键,将不会更新我们的显示,之前的text属性绑定:
text: "Space pressed: " + spacePresses + " times"
将被销毁。
所以当你对改变属性的策略有冲突时(文本的改变基于一个增值的绑定并且可以被JavaScript赋值清零),类似于上面的例子,更好就不要使用绑定属性。你需要使用赋值的方式来改变属性,属性绑定会在赋值操作后被销毁。所以在最开始的例子中,直接令spacePresses = 0,而不是令label.text = ''。