QML Object Attributes
每个QML对象类型有一组已定义的属性. 对象类型的每个实例创建时, 具有这个对象类型的一组属性. 如下所述, 可以指定几种不同类型的属性.
对象中定义的属性
An 在QML文档中, 一个对象声明定义一个新类型. 它还声明一个对象层次结构, 如果创建这个对象实例, 对象层次结构也被实例化.
QML对象类型的属性类型如下所示:
- the id 属性
- property 属性
- signal 属性
- signal 处理程序属性
- method 属性
- 附加属性和附加signal处理程序属性
- 枚举属性
下面详细讨论这些属性.
id 属性
每个QML对象类型仅有一个 id 属性. 这个属性由语言本身提供, 任何QML对象类型不用重新定义或重写这个属性.
id属性可以赋值, 以便其他对象识别和引用该对象. id必须以小写字母或下划线开头, 不能包含字母, 数字和下划线之外的字符.
下面代码声明一个 TextInput 对象和一个 Text 对象. TextInput 对象的 id
设为 "myTextInput". Text 对象通过 myTextInput.text
获取文本属性, 将其与自身的text
属性关联. 现在, 两个对象显示相同的文本:
import QtQuick 2.0 Column { width: 200; height: 200 TextInput { id: myTextInput; text: "Hello World" } Text { text: myTextInput.text } }
对象可以通过id在有效范围的任何位置引用. 因此, id在有效范围内始终唯一. 详见 Scope and Naming Resolution.
一旦创建对象实例, id属性将不可改变. id属性不是一个普通属性, 并且具有特殊语义; 例如, 上述示例无法访问myTextInput.id
.
Property 属性
propety
是对象的一种属性, 可以指定静态值或绑定动态表达式. propety
的值可以由其他对象读取. 通常, 它可以由其他对象修改, 除非使用特定的QML类型明确禁止对其修改.
定义property属性
在C++中, 通过 Q_PROPERTY 将成员变量注册到QML类型系统. 或者, 如下所示语法, 在QML文档中自定义property:
[default] property <propertyType> <propertyName>
通过这种方式, 对象声明可以更容易地向外部对象公开特定的值或维护某些内部状态.
属性名称必须以小写字母开头, 且只能包含字母、数字和下划线. JavaScript 保留关键字 不是有效的属性名称. 默认
k关键字, 必需关键字和只读关键字是可选的, 并修改所声明的属性的语义. 详见 default properties.
声明自定义属性会隐式创建这个属性的值改变 信号, 以及调用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
模块提供, 除非导入模块, 否则不能作为属性类型. 详见 QML Basic Types.
注意: var 是一种通用占位符类型, 可以容纳任何类型的值, 包括列表和对象:
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类型 . 如果QML类型在ColorfulButton.qml
中定义(随后由客户端导入目录), 则ColorfulButton
类型也将有效.
属性赋值
对象实例的属性值有两种赋值方式:
- 初始化赋值
- 命令式赋值
任何情况下, 这个值都可以是静态值或绑定表达式.
Value Assignment on Initialization
初始化赋值的语法如下:
<propertyName> : <value>
初始化值赋值可以与对象声明中的属性定义相结合. 这种情况下, 属性定义的语法变为:
[default] property <propertyType> <propertyName> : <value>
初始化赋值的示例如下:
import QtQuick 2.0 Rectangle { color: "red" property color nextColor: "blue" // combined property declaration and initialization }
命令式赋值
命令式赋值是将属性值(静态值或绑定表达式)赋予JavaScript代码的属性. 命令式赋值的语法只是JavaScript赋值运算符, 如下所示:
[<objectId>.]<propertyName> = value
命令时赋值语法如下:
import QtQuick 2.0 Rectangle { id: rect Component.onCompleted: { rect.color = "red" } }
静态值或绑定表达式
如前所述, 属性赋值有两种方式: 静态 值和 绑定表达式 值. 后者也称为 属性绑定.
Kind | Semantics |
---|---|
Static Value | A constant value which does not depend on other properties. |
Binding Expression | A JavaScript expression which describes a property's relationship with other properties. The variables in this expression are called the property's dependencies. The QML engine enforces the relationship between a property and its dependencies. When any of the dependencies change in value, the QML engine automatically re-evaluates the binding expression and assigns the new result to the property. |
下面示例展示两种不同的赋值方式:
import QtQuick 2.0 Rectangle { // both of these are static value assignments on initialization width: 400 height: 200 Rectangle { // both of these are binding expression value assignments on initialization width: parent.width / 2 height: parent.height } }
注意: 若强制分配绑定表达式, 绑定表达式必须包含在传递到 Qt.binding()函数中, 然后必须将Qt.binding()返回值分配给属性. 相反, 初始化分配绑定表达式时, 不能使用Qt.binding(). 详见 Property Binding.
类型安全
属性是类型安全的. 只能为属性分配与属性类型匹配的值.
例如, 如果一个属性是实数, 并且尝试为其分配字符串, 则会出现错误:
property int volume: "four" // generates an error; the property's object will not be loaded
同样, 如果在运行时为属性分配错误类型的值, 则不会分配新值, 并生成错误.
某些属性类型没有自然值表示, 对于这些属性类型, QML引擎会自动执行字符串到类型化值的转换. 因此, 例如, 即使颜色类型的属性存储颜色而不是字符串, 你也可以将字符串 "red"
分配给颜色属性, 而不会报告错误.
有关默认支持的属性类型的列表, 参见 QML Basic Types. 此外, 任何可用的 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" } ] }
如果list包含单个元素, 方括号可以省略:
import QtQuick 2.0 Item { states: State { name: "running" } }
使用下列语法指定 list 类型:
[default] property list<<objectType>> propertyName
与其他属性声明一样,属性初始化可以与具有以下语法的属性声明相结合:
[default] property list<<objectType>> propertyName: <value>
list属性声明示例如下:
import QtQuick 2.0 Rectangle { // declaration without initialization property list<Rectangle> siblingRects // declaration with initialization property list<Rectangle> childRects: [ Rectangle { color: "red" }, Rectangle { color: "blue"} ] }
如果你希望声明一个属性, 存储非QML对象类型的链表, 则应该声明一个 var 属性.
分组属性
在某些情况下, 属性包含子属性的逻辑组. 子属性可以使用点表示法或组表示法.
例如, Text 类型具有 font 组属性. 下面, 第一个 Text 对象使用点表示法初始化其 font
值, 而第二个对象使用组表示法:
Text { //dot notation font.pixelSize: 12 font.b: true } Text { //group notation font { pixelSize: 12; b: true } }
分组属性类型是具有子属性的类型. 如果分组的属性类型是对象类型(而不是值类型), 则包含该类型的属性必须是只读的. 这是为了防止替换子属性所属的对象. 详见 QML Basic Types.
属性别名
属性别名是对另一个属性的引用. 普通属性为属性分配唯一存储空间, 属性别名是对现有属性(别名属性)的直接引用.
属性别名声明看起来像普通的属性定义, 只是它需要 alias
关键字而不是属性类型, 并且属性声明的右侧必须是有效的别名引用:
[default] property alias <name>: <alias reference>
与普通属性不同, 别名属性具有以下限制:
别名属性只能引用在声明别名的类型范围内的对象或对象的属性.它不能包含JavaScript表达式. 它不能引用在其类型范围外声明的对象. 与普通属性的可选默认值不同, 别名引用不是可选的; 首次声明别名时必须提供别名引用. 它不能引用附加的属性.
例如, 下面是一个具有buttonText
别名属性的Button
类型, 这个属性连接到text
子级的文本对象:
// Button.qml import QtQuick 2.0 Rectangle { property alias buttonText: textItem.text width: 100; height: 30; color: "yellow" Text { id: textItem } }
下面代码创建一个 Button
, 它具有一个初始化文本字串的 Text 对象:
Button { buttonText: "Click Me" }
这里, 修改 buttonText
直接修改 buttonText
的值, 它不会更改其他值, 然后更新textItem.text
. 如果buttonText
不是别名, 则更改其值实际上根本不会更改显示的文本, 因为属性绑定不是双向的: 如果 buttonText
更改, buttonText
值会更改, 但反过来不会.
属性别名的注意事项
只有在组件完全初始化后, 别名才会被激活. 引用未初始化的别名时会生成错误. 同样, 对别名属性进行别名也会导致错误.
property alias widgetLabel: label //will generate an error //widgetLabel.text: "Initial text" //will generate an error //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" } }
任何使用此类型并引用其颜色属性的对象都将引用别名, 而不是普通的 Rectangle::color 属性. 但是, 在内部, Rectangle可以正确设置其颜色属性, 并引用实际定义的属性而不是别名.
默认属性
一个对象定义可以有一个 默认 属性. 如果在另一个对象的定义中声明了一个对象, 而没有将其声明为特定属性的值, 则默认属性是将值赋给特定属性.
使用可选的default
关键字声明属性会将其标记为默认属性. 例如, 假设有一个文件MyLabel.qml具有默认属性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的类型, 而无需将它们显式添加到 子 属性. 这是因为 Item 的默认属性是它的 data
属性, 并且为项添加到此列表的任何 Item 都会自动添加到 子列表中.
默认属性可用于重新分配item的子对象. 参见 TabWidget Example, 这个示例使用默认属性自动将TabWidget的子对象重新分配为内部 ListView的子对象. 也可参见 Extending QML.
只读属性
对象使用 readonly
关键字声明只读属性, 语法如下:
readonly property <propertyType> <propertyName> : <initialValue>
初始化时, 只读属性必需赋值. 初始化完成后, 只读属性无法再赋值.
如下所示, Component.onCompleted
中的代码无效:
Item { readonly property int someNumber: 10 Component.onCompleted: someNumber = 20 // doesn't work, causes an error }
注意: 只读属性不能是 默认 属性.
Property Modifier Objects
Properties can have property value modifier objects associated with them. The syntax for declaring an instance of a property modifier type associated with a particular property is as follows:
<PropertyModifierTypeName> on <propertyName> { // attributes of the object instance }
It is important to note that the above syntax is in fact an object declaration which will instantiate an object which acts on a pre-existing property.
Certain property modifier types may only be applicable to specific property types, however this is not enforced by the language. For example, the NumberAnimation
type provided by QtQuick
will only animate numeric-type (such as int
or real
) properties. Attempting to use a NumberAnimation
with non-numeric property will not result in an error, however the non-numeric property will not be animated. The behavior of a property modifier type when associated with a particular property type is defined by its implementation.
信号属性
信号是对象发出某个事件发生的通知: 例如, 属性已更改, 动画已启动或停止, 或者图像已下载. 例如, MouseArea 类型有一个 clicked 信号, 用户在鼠标区域内点击时发出该信号.
无论何时发出特定信号, 都可以通过 信号处理程序 通知对象. 信号处理程序是用 on<Signal> 语法声明的, 其中 <Signal> 是信号的名称, 第一个字母大写. 信号处理程序必须在发出信号的对象的定义中声明, 并且处理程序应该包含在调用信号处理程序时要执行的JavaScript代码块.
例如, 下面的 onClicked 信号处理程序在 MouseArea 对象定义中声明, 并在单击 MouseArea 时调用, 在控制台打印消息:
import QtQuick 2.0 Item { width: 100; height: 100 MouseArea { anchors.fill: parent onClicked: { console.log("Click!") } } }
定义信号属性
在C++中, 信号可能通过 Q_SIGNAL 定义, 然后被QML类型系统注册. 或者, 在QML中采用如下方式自定义信号:
signal <signalName>[([<type> <parameter name>[, ...]])]
在同一个模块中, 声明两个相同名称的信号或方法是错误的. 然而, 新信号可能重用已存在信号的名称. (小心这类操作, 已存在信号可能被隐藏, 导致无法访问.)
下面示例展示三种信号声明方式:
import QtQuick 2.0 Item { signal clicked signal hovered() signal actionPerformed(string action, var actionResult) }
I如果信号没有参数, "()" 是可选的. 如果使用参数, 则必须声明参数类型, 如上面actionPerformed
信号的参数类型是 string
和 var
. 参数类型允许与 Defining Property Attributes 列出的参数类型相同.
发出信号与调用函数一样. 信号发出时, 任何相关的 信号处理程序 都将被调用, 处理程序可以使用定义的信号参数名称访问相应的参数.
属性更改信号
QML类型还提供内置的属性更改信号. 每当属性值发生更改时就会发出信号, 如前面 property attributes所述. 有关这些信号为什么有用以及如何使用它们的更多信息, 参见 property change signal handlers.
信号处理属性
信号处理是一种特定顺序的 方法属性, 这个方法在信号发生时, 被QML引擎调用. 在QML中, 在一个对象中添加信号, QML自动生成对应的信号处理程序. 默认情况下, 这个对象定义空实现. 客户端可以提供实现, 以实现程序逻辑.
考虑下面的 SquareButton
类型, 这个定义在 SquareButton.qml
, 实现 activated
和 deactivated
:
// SquareButton.qml Rectangle { id: root signal activated(real xPosition, real yPosition) signal deactivated property int side: 100 width: side; height: side MouseArea { anchors.fill: parent onPressed: root.activated(mouse.x, mouse.y) onReleased: root.deactivated() } }
这些信号可以由同一目录中另一个QML文件中的任何 SquareButton
对象接收, 其中信号处理程序的实现由客户端提供:
// myapplication.qml SquareButton { onActivated: console.log("Activated at " + xPosition + "," + yPosition) onDeactivated: console.log("Deactivated!") }
有关如何使用信号的更多信息, 详见 Signal and Handler Event System.
属性更改信号处理函数
属性更改的信号处理函数语法是 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) }
方法
对象的方法是一个函数, 执行某些处理或触发事件. 方法可以连接信号, 在信号发出时自动调用. 详见 Signal and Handler Event System.
定义方法
在C++中使用 Q_INVOKABLE 或 Q_SLOT 定义的方法被QML类型系统自动注册. 或者, 使用如下语法, 在QML文档添加自定义方法:
function <functionName>([<parameterName>[, ...]]) { <body> }
方法可以添加到QML对象中, 封装独立的, 可重用的JavaScript代码块. 方法可以在内部调用, 也可以被外部对象调用.
与信号不同, 方法的参数类型可以不声明, 它们默认是var
类型. 但是, 你应该声明它们, 这样, qmlcachegen
可以生成性能更高的代码, 也便于维护.
在同一个代码块声明两个相同名称的方法或信号是错误的. 但是, 新方法可能重用已存在方法的名称.(谨慎操作, 已存在方法可能被隐藏并无法访问.)
下面是一个定义calculateHeight()
方法的 Rectangle. 这个方法计算height
值:
import QtQuick 2.0 Rectangle { id: rect function calculateHeight() { return rect.width / 2; } width: 100 height: calculateHeight() }
If the method has parameters, they are accessible by name within the method. Below, when the MouseArea is clicked it invokes the moveTo()
method which can then refer to the received newX
and newY
parameters to reposition the text:
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 , 该属性可用于 ListView中的每个委托对象. 每个单独的代理对象都可以使用此选项来确定它是否是视图中当前选定的item:
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
.
附加的信号处理程序以相同的方式被引用. 例如, Component.onCompleted 附加的信号处理程序通常在组件的创建过程完成后执行一些JavaScript代码. 在下面的示例中, 一旦 ListModel 被完全创建, 它的 Component.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
, 信号是 completed 信号, 因此信号处理函数是 Component.onCompleted
.
关于访问附加属性和信号处理程序的注意事项
A一个常见的错误是: 假定附加属性和信号处理程序可以从附加属性对象的子对象直接访问. 事实并非如此, 附着类型的实例仅附着到特定对象, 而不附着到对象及其所有子对象.
如下所示, 代理是一个 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
无法工作, 它仅附加到根对象, 而不是子对象. 由于 Rectangle 是委托的子对象, 而不是委托本身, 因此它不能访问作为ListView.isCurrentItem
附加的 isCurrentItem
属性. 因此, Rectangle应该通过根委托访问 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
是正确的.