qml编码规范与指导
说明
良好的编程规范可以大幅提高一个程序的可读性、可维护性。编码规范是大家达成一致的约定,这样大家的代码就可以互相看懂,维护起来更容易,思想更畅快的交流,经验更快的得到传播。
QML是一种声明方式设计的语言,用来设计应用程序的界面,包括式样和表现行为。在QML中,一个用户界面被制定为一棵树形的对象模型并且包含了对象的属性,这棵树树根对应的就是qml文件中的根组件。
JavaScript在QML中作为一种脚本语言,对QML进行逻辑方面的编程,也可以说QML是对JavaScript的扩展,提供了js主机环境。
QML中的JavaScript是特殊的JavaScript,是简化的JavaScript。
Item {
id:root
width:800
height:480
Rectangle {
x:200
y: 60
width:200
height:160
color:“red”
visible: false
}
}
Rectangle组件中一些语法的声明可以写成JavaScript的方式:
Item {
id:root
width:800
height:480
property bool visibleState: false
property bool lightState: false
Rectangle {
x: parent.widht/4
y: parent.height/8
width:parent.widht/4
height:parent.height/3
color:root.lightState ? “red” : "green"
visible: root.visibleState ? true : false
}
}
通过以上两段QML代码中Rectangle组件属性的赋值情况可以看出,组件的属性可以直接赋值,也可通过JavaScript代码赋值。
指导性原则
统一的代码风格,减少代码冗余,增强其代码可读性,还可以提升工作效率,让程序更清晰、健壮。
详细说明
文件命名
这里的文件分为 *.qml 文件和 *.js 文件。
qml文件命名
因每一个qml文件的使用方法可能不固定,在某些情况下作为程序的入口文件,文件名的首字母可以大写,也可以小写,但在另一些情况下可能作为Component(控件,类)来使用,首字母必须大写。例如:
mainScreen.qml是一个程序界面的入口文件,在后续的维护中,需要在mainScreen.qml中添加一系列的按钮,在这种情况下,可以重新写一个继承于mainScreen的界面入口文件main.qml,此时则需重新命名mainScreen.qml为MainScreen.qml。
为了方便qml文件的维护,避免上述情况的发生,qml文件的命名规则为:
- 仅由英文字母组成。
- 以大写字母开始,遵循大驼峰的命名规则,例如:
SwithcButton.qml ListButton.qml MainScreen.qml
js文件命名
- js文件名由小写的英文字母和_组成。
- js文件名一律以“ impl ”结尾。
例如:datetime_helper_impl.js
这样便于在qml中导入*.js_ 文件时,方便命名别名。
编写qml文件
import
import的意思是导入模块,类似java语言中的import包,用来导入qml文件所要用到的内置模块、自定义插件、控件、js文件等。
qml文件中,import模块的顺序为:
- 导入 Qt 内置模块
- 导入框架自定义 Qt Quick 模块
- 导入项目自定义 Qt Quick 模块
- 导入本地 *.qml 组件包
- javascript 导入脚本文件
- javascript 引用脚本模块
例如:
// ModuleA.qml
import QtQuick 2.6
import QtQuick.Controls 2.1
import net.phoneyou.EasyUI 1.0 as HUI
import net.phoneyou.EasyUI.Material 1.0 as HUIMaterial
import net.phoneyou.Roshan.MapRender 1.0 as MapRender2D
import your_project_name 1.0
import "path_to_package"
import "my_component_impl.js" as MyComponent
.import "utils.js" as Utils
为了避免不同模块之间的命名冲突,我们在导入模块时可以使用“as”关键字设置该模块在本 qml 文件中的“别名”。
对于"as"的相关规则说明:
- 导入Qt内置模块的版本号为对应的Qt版本的最高版本号。
- js文件导入到qml中必须要有别名。
- 在导入模块中,本身就有命名冲突时,需给其中至少一个模块都设置别名
- 不对 Qt 内置模块设置别名
- 对于自定义模块需设置别名
- 别名作为模块在所属 qml 文件中的代号,需保持文件内的唯一性
- 别名的命名规则采用“大驼峰”
为了保证 Qt 内部组件和命名空间不被污染以及保持代码可以顺利升级--比如升级 Qt 版本,需要确保不对 Qt 模块设置别名,而对其他,比如HUI、Roshan以及其他三方的或者项目内的模块,进行“别名”处理。尤其是那些对常见控件进行定制的模块。
根组件
qml文件描述的实际上一个由 “UI组件节点” 组成的“树”。而“根组件”也就是“根节点”,在qml文件有且只有一个。
根组件作为逻辑上的顶层组件,直接承担起了 qml 文件的所有定义,其属性、信号、方法即为该 qml 组件的属性、信号、方法。
其他组件只能作为其子节点存在,对外是不可见的,类似 private 作用域,他们的属性、信号、方法命名需添加双下划线("__")前缀,而根组件没有任何的前后缀。
关于根组件的类型:
- 如果该qml文件声明的是对已有组件进行改造的可复用的组件,比如 “ImageButton”,那么该根组件应该为“Button”或者其他的什么“Button”,对应的别名应该设置为“button”或“root”。
- 如果该qml文件声明的是一个复合的功能型界面,比如说“ReplaybackPanel”,“DataDelegateItem”,它是一系列组件的组合,一般将根组件设定为“Item”或者“Rectange”,其别名应设置为“root”。
根组件定义
- id
- 自定义属性property
- 信号signal
- JavaScript function
- 自带属性设置object properties
- 子控件child objects
- 状态states
- 动画animation、渐变transitions
例如:
Rectangle {
id: root
/*! 鼠标按下状态,当鼠标按下时为true */
property bool pressed: false
/*! 图片资源 */
property alias source: photoImage.source
/*! 鼠标点击事件 */
signal clicked
/*! 返回photoImage的width */
function doSomething()
{
return photoImage.width
}
width: {
if (photoImage.width > 200) {
photoImage.width;
} else {
200;
}
}
height: 480
color: "gray"
Rectangle {
id: photoRect
x: 20
y: 20
width: parent.width
height: 200
color: "white"
Image {
id: photoImage
anchors.centerIn: parent
}
}
MouseArea {
anchors.fill: parent
onPressed: {
root.pressed = true
}
onClicked: {
root.clicked()
}
}
states: State {
name: "selected"
when: root.pressed
PropertyChanges {
target: root.border
color: "red"
}
}
transitions: Transition {
from: ""
to: "selected"
ColorAnimation {
target: root.border
duration: 200
}
}
}
id
在声明组件时,可以同时声明其“id”属性。“id”属性是该组件的代号,声明该属性的好处是,可以被它的作用域为:。
同一qml文件中,每个控件都可以设置id属性,且不能相同,id相同会有书写错误提示。
具体命名规则如下:
-
由字母组成,首字母必须小写,以小驼峰的形式命名。
-
以控件类型结尾,例如:
将Button的id设置为:xxxxBtn 或 xxxxButton
将Text的id设置为: xxxxTxt 或 xxxxText
将ComboBox的id设置为: xxxxCmb 或 xxxxComboBox
这样根据id就可以知道其对应的是什么控件。
只有在明确知道其控件的缩写时,才能在id中使用缩小,否则一律使用控件全称。
部分控件的缩写详见“常用控件的缩写” -
控件默认不设置id属性,若某个控件的id需要被使用(控件间存在关联关系),才要设置id。
例如控件的布局存在相对位置的关系或控件间的属性在逻辑上存在依赖关系等,则需要设置id,例如:Item{ width: 200 height: 300 Button{ id: saveBtn width: 60 height: 30 text: "Save" } Button{ anchors { top: saveBtn.bottom topMargin: 10 left: saveBtn.left } width: 60 height: 30 visible: saveBtn.visible } }
代码中的Item和第二个Button没有被引用到,不必设置id,这样可避免设置过多的无用id,徒增命名的烦恼。
-
当子控件要使用父控件的属性或根据父控件设置相对位置时,不要使用父控件的id,用parent代替id。例如:
Item {
id: root
width: 800
height: 480
Rectangle{
id: testRect
anchors.centerIn: parent
width: parent.width/2
height: 200
color: "red"
Button{
id: testBtn
width: parent.width/2
height: 40
text: "1233"
highlighted: (parent.color === "red") ? false : true
}
}
}
- 一个qml文件中,设置id的个数不要超过7个,多余7个,则要进行qml代码的拆分,建议设置3到4个。
- 同一个qml文件中的控件id在文件中是通用的,但是delegate中的控件id除外,delegate中控件id只在delegate中有效,外部访问无效。
因为 delegate为Component类型,外界无法访问。
通俗点讲,就是delegate一般用来承载model数据的显示,一个id对于多个数据显示控件,外部在使用id时,无法定位当前使用是哪个数据控件的id。例如:
ListView {
id:listView
anchors.centerIn: parent
width: 160
height: 240
model: Qt.fontFamilies()
delegate: ItemDelegate {
id:fontDelegate
text: modelData
width: parent.width
highlighted: ListView.isCurrentItem
onClicked:{
listView.currentIndex = index
console.log("clicked:", modelData)
}
}
}
代码中ItemDelegate的id名称fontDelegate,是不能在ItemDelegate之外被使用的。
property属性
在qml代码中,为了方便外界使用本自定义控件或者便于qml文件内部控件之间的关联,常需要自定义一些属性。
一个property是对象的一个属性,可以被赋为静态值或者是绑定到动态表达式上。一个property的值可以被其它的对象读取。一般情况下,property属性也可以被其它对象修改,除非该QML类型明确指定该property属性不能被修改。
- 属性的命名规则:
属性名称必须是以小写字母开头,仅由字母、数字和下划线组成,由 名词或形容词+名词 的小驼峰形式组合而成。
当定义的属性仅限qml内部使用时,才会用到_,这样的私有属性的命名必须以两个下划线(__)开始,例如:
property var __locale: Qt.locale()
自定义属性
自定义属性的语法格式如下:
property <propertyType> <propertyName> : value
- 定义一个自定义的property属性也就为该property属性隐式的创建了一个value-change信号,也就是关联了一个名为 on
Changed的signal handler。 就是property属性的名称,而且首字母要大写。,如下所示:
Rectangle {
property string someText: “ ”
onSomeTextChanged: console.log("The someText will be: " + someText)
}
- 自定义的属性必需要有初始值,除了Object Types
- 常见自定义的属性类型:
bool,double,int,real,string,url,var,list - bool:true/false。
- double:双精度实数,有小数点。
- int:整数,可以是负数、0、正数。
- real:实数,有小数点。
- string:字符串。
- url:资源路径,可以是绝对路径或相对路径,相对路径时就会被转换为URL对象,如果使用了Qt资源系统,就要用“qrc:///”代替“😕”。
- var:基本类型是通用的类型,可以保存任意类型的值,包括lists和objects:
任何标准的JavaScript类型都可以使用通用的var类型创建和存储。
如果声明一个属性用于存储列表值,但不一定是存储QML对象类型值,这个时候你就需要声明var的property属性。
property var someNumber: 1.5
property var someString: "aaa"
property var someBool: true
property var someList: [2,5,"one","two"]
property var someObject: Rectangle {
width: 100
height: 100
color: "red"
}
property var theArray: new Array()
property var theDate: new Date()
另外,任何的QML对象类型都可以被用作property属性类型。例如:
property Item someItem
property Rectangle someRectangle
这对于自定义QML类型也是适用的。
- 自定义属性的调用方式:
属性所属控件id. propertyName
属性别名
- 属性别名就是保存对另一个属性的引用。不像普通属性的定义,普通属性的定义需要分配一个新的,唯一的存储空间,而一个属性别名仅仅是连接到了属性上。
属性别名的定义根属性定义差不多,只是属性别名需要使用alias关键字代替属性定义中的property类型,右边的值必须是合法的引用别名:
属性别名的语法格式如下:
property alias <name> : aliasreference
name是我们自定以的属性名,aliasreference是属性别名所引用的那个属性或对象,也就是说属性别名可以引用自一个单一的属性,也可以引用自一个复杂的对象。属性绑定是动态的,但不是双向的,而通过属性别名可达到动态、双向的效果,即修改name和aliasreference中任一个的值,与之关联的另一个属性值也会随之改变。
需要注意的是,如果属性别名与已有的属性名相同,就会把已有的属性覆盖掉,当我们再使用这个属性的时候,实际上使用的是我们自定义的那个属性,例如:
Rectangle {
id: rootRect
// 引用属性
property alias color: blueRect.color
// 引用对象
property alias theRect: blueRect
color: "red"
Rectangle {
id: blueRect
color: "#1234ff"
}
}
- 如果为一个已经存在属性创建一个同名的属性别名,这就会覆盖已经存在的属性。
上面的代码中定义的color属性,会替代Rectangle原有的color属性,为了避免已有属性被覆盖的问题,定义的属性别名不能与已有属性相同。 - 若所引用的属性不是已经存在属性,则定义的属性别名要与引用的属性名称相同。 例如:
Rectangle {
id: rootRect
property alias count: nameList.count
property alias currentIndex: nameList.currentIndex
color: "red"
ListView{
id: nameList
}
}
Rectangle没有count和currentIndex属性,则要将属性别名与引用属性名设置成一样。
- 只有在组件完全初始化之后属性别名才会被激活。如果未初始化的别名被引用了就会产生错误。
- 为一个属性别名产生属性别名也会导致错误,例如:
property alias sureBtn: testBtn
property alias text: sureBtn.text
Button{
id: testBtn
width: 100
height: 40
text: "1233"
}
Component.onCompleted: console.log("text="+text)
产生的错误提示: Invalid alias reference. Unable to find id "sureBtn"
只读属性
任何对象的定义都可以使用readonly关键字定义只读属性,使用下面的语法:
readonly property <propertyType> <propertyName> : <initialValue>
只读属性必须在初始化的时候指定值。一旦只读属性被初始化了,它就不可能再被赋值了,无论是赋值(使用”=”)还是其它的方式。
例如,下面的Component.onCompleted代码块就是非法的:
Item{
id: root
readonly property int chiledNum: 10
width: 100
height: 40
Component.onCompleted: root.chiledNum = 20
}
信号
信号就是当某些事件发生的时候从对象类型中发出通知:例如,一个属性改变,一个动画开始或者停止,当一个图片下载完成,或者MouseArea类型当用户点击的时候就会发射一个点击信号。
当一个信号发射了,对象可以通过signal handler被通知。一个signalhandler的定义语法为on
例如,MouseArea对象的定义中可以定义onClicked类型的signal handler,当MouseArea被点击了该signal handler就会被调用,使得控制台打印出消息:
Item{
id: root
width: 100
height: 40
MouseArea{
anchors.fill: parent
onClicked: console.log("Click!")
}
}
qml文件中,定义信号signal的语法:
signal <signalName>[([<type> <parameter name>[, ...]])]
信号一般是相关联的事件触发的,信号名由英文字母和_组成,已小驼峰的形式命名。
- 信号的命名规则遵循属性的命名规则,以小驼峰的形式命名。
- 如果信号不带参数,则一律不使用()
- 如果信号带参数,参数要写参数类型。
信号参数的命名规则:
msg+大驼峰形式
必须以“msg”开头,便于标识是信号参数。
例子:
signal clicked
signal hovered(string msgInfo)
signal activated(real msgXPosition, real msgYPosition)
信号的调用方式:信号所属控件id.信号
js代码
自定义的js函数,可以带参数,也可以不带出参数,且参数不用指明参数类型,因为函数的参数类型默认是万能的var类型。
函数名由字母和_组成,且首字母必须是小写,即以小驼峰的形式命名,例如:
function addZero(num){
if(num < 10)
return "0"+num
else
return num
}
- 函数返回时不要使用圆括号。
- 当定义的js函数,仅能在本qml文件使用,即使私有函数时,此函数的命名才用到_, 且是以双下划线(__)开始 + 小驼峰的方式命名,例如:
function __factorial(a) {
a = parseInt(a);
if (a <= 0)
return 1;
else
return a * factorial(a - 1);
}
- qml文件中js函数的调用:函数所在控件的id.函数名()
js文件
当一个qml文件中的js代码较多即:js函数代码超过80行时,应根据其分类或应用逻辑单独作成一个js文件,再将js文件导入qml文件。
js文件的导入方法:
- qml中导入的js文件必须要有别名,别名的命名规则:
以大驼峰的形式命名
由js文件名中除了_和impl以外的字母组成,例如:
import "jsfile_impl.js" as Jsflie
- 在js文件中导入另一个js文件的2种方法 :
.import "filename_imp.js" as Filename
Qt.include "filename.js"`
布尔表达式
- 在布尔表达式中,逻辑运算符 && 和|| 两边的条件表达式根据逻辑需要添加圆括号()
状态机
- 在qml中,状态是定义在state类型中的一系列属性配置,不同的配置可能有不同的作用,为了方便起见,State元素都有一个when属性,可以绑定表达式来改变状态,当绑定的表达式评估为true,状态改变;当表达式评估为false,状态会退回到default state。
- 显示一些UI控件,隐藏另一些。
StateGroup {
states: [
State {
name: "xxx"
when: visible
PropertyChanges {
target: xxxBtn
visible: true
}
PropertyChanges {
target: yyyBtn
visible: true
}
PropertyChanges {
target: zzzBtn
visible: fasle
}
}
]
}
- 启动、暂停、停止动画。
StateGroup {
states: [
State {
name: "start"
when: 0 == animationState
PropertyChanges {
target: imageAnimation
running: true
}
},
State {
name: "pause"
when: 1 == animationState
PropertyChanges {
target: imageAnimation
paused: true
}
},
State {
name: "stop"
when: 2 == animationState
PropertyChanges {
target: imageAnimation
running: false
}
}
]
}
- 改变某个特定Item的property的值。
StateGroup {
states: [
State {
name: "color"
when: bChanged
PropertyChanges {
target: rootRect
color: "red"
}
},
State {
name: "enabled"
when: disabled
PropertyChanges {
target: rootRect
enabled: false
}
}
]
}
- 显示一个不同的view或screen。
StateGroup {
states: [
State {
name: "xxx"
when: 0 == loaderState
PropertyChanges {
target: xxxLoader
source: "Xxx.qml"
}
},
State {
name: "yyy"
when: 1 == loaderState
PropertyChanges {
target: xxxLoader
source: "Yyy.qml"
}
},
State {
name: "zzz"
when: 2 == loaderState
PropertyChanges {
target: xxxLoader
source: "Zzz.qml"
}
}
]
}
- 在某种新的状态下执行某些脚本。
// statetest.js
function getColor()
{
return "green";
}
// test.qml
import "statetest.js" as StateTest
Rectangle{
id: rect
width: 50; height: 50
color: "red"
MouseArea {
anchors.fill: parent
onClicked: rect.state = "changedColor"
}
StateGroup {
states:
State {
name: "changedColor"
StateChangeScript{
name: "myScript"
script: rect.color = StateTest.getColor()
}
}
}
}
- 向用户呈现不同的操作和功能
Rectangle {
id: rect
width: 120; height: 120
color: "black"
Rectangle { id: myRect; width: 50; height: 50; color: "red" }
StateGroup {
states:
State {
name: "reanchored"
AnchorChanges { // 改变 myRect 的anchors属性
target: myRect
anchors.top: rect.top
anchors.bottom: rect.bottom
}
PropertyChanges {
target: myRect
anchors.topMargin: 10
anchors.bottomMargin: 10
}
}
}
// 鼠标事件
MouseArea {
anchors.fill: parent
onClicked: rect.state = "reanchored"
}
}
- 把指定的item换一个item父节点
Item {
width: 200
height: 100
Rectangle {
id: redRect
width: 100
height: 100
color: "red"
}
Rectangle {
id: blueRect
x: redRect.width
width: 50
height: 50
color: "blue"
states: State {
name: "reparented"
ParentChange {
target: blueRect
parent: redRect
x: 10
y: 10
}
}
MouseArea {
anchors.fill: parent
onClicked: blueRect.state = "reparented"
}
}
}
-
states中每个state之间的触发条件与PropertyChanges中的属性之间不要有关联,若有关联,可能会形成死循环。
-
在qml中使用states时,一定要加上父层StateGroup,这样可以避免与原始控件中state名相同的state相冲突,产生无法预料的问题。
-
能用条件运算符替代states的地方,尽量用条件运算符,不要用states。条件运算符的执行速度比state快。
StateGroup {
states:
State {
name: "name"
when: bChanged
PropertyChanges {
target: root
color: "red"
}
}
}
应修改为:
color: bChanged ? "red" : "white"`
- 同一个State中有多个PropertyChanges,PropertyChanges的target不能设置一样,否则只执行第一个PropertyChanges。
例如:
错误的写法
StateGroup {
states:
State {
name: "name"
when: bChanged
PropertyChanges {
target: root
color: "red"
}
PropertyChanges {
target: root
border.color: "red"
}
}
}
应该修改为:
StateGroup {
states:
State {
name: "name"
when: bChanged
PropertyChanges {
target: root
color: "red"
border.color: "red"
}
}
}
内部组件
与“根组件定义”相同,但是其属性、信号、方法命名需添加双下划线("__")前缀。
比如:
Item {
id: root
width: 800
height: 480
Item {
id: subItem
property bool __updateState:false
signal __clicked
function __doSomething(x)
{
return x+subItem.x
}
width: 400
height: 400
}
}
注释
为了提高qml代码的可读性和便于后期维护,注释是必不可少的。
文件功能注释
文件功能注释是对此qml大概功能的一个概述,若此qml文件还作为控件别的qml文件使用,则还有写出大概的使用方法。
例如下面是combobox.qml文件功能的注释:
/*!
将一个model的数据以下拉框列表的形式显示,通过选择下拉框列表数据,改变当前显示的数据
\qml
ComboBox {
width: 200
model: [ "Banana", "Apple", "Coconut" ]
}
\endqml
*/
注释以“/*!”开始,以“ */” 结束,包含的对文件功能的说明,和控件的使用方法,使用用例以“\qml”开始,以“\endqml”结束。
自定义属性、信号、方法注释
- 自定义的属性、信号、方法必须添加注释。
自定义属性、信号、方法注释格式为:“/*! */”,写在其定义的上一行,例如:
/*! ComboBox下拉框列表当前选中的index值 */
property int currentIndex: 0
其他注释
这里的其它注释是指除文件功能注释和自定义属性、信号、方法注释外的其它需要注释的代码。
代码注释格式为:“ // xxxxxxxxx ”,写在被注释代码上方,例如:
// temporarily set fillIndex to new item
fillIndex = __items.length
补充说明
object properties
- 一行写一条语句
一般属性设置的顺序为:
id: rootRect
x: 10
y: 10
width: 100
height: 100
...
组属性的设置
在对控进行属性设置时,如果有同一类组的多个属性需要设置,为了提高代码可读性,则使用组的提示符,而不是点的运算符号。
例如:
anchors.top: parent.top
anchors.left: parent.left
anchors.topMargin: 10
anchors.leftMargin: 10
应该改写成:
anchors {
top: parent.to
left: parent.left
topMargin: 10
leftMargin: 10
}
Qml中和c++中都可以处理的逻辑
在qml文件中,有许多的逻辑处理既可以在qml中完成,也可以在c++中处理,原则是qml代码只处理界面显示逻辑,其它逻辑能在c++中处理的,一律在c++中处理,这样有利于提高qml的加载速度 。
MouseArea{ anchors.fill: parent }的特殊用法
MouseArea除了处理Item的鼠标响应,还有一个典型的用途是,MouseArea { anchors.fill: parent }能够屏蔽鼠标事件的渗透。
例如:
// main.qml
Rectangle{
width: 800
height: 480
Button{
width: 150
height: 50
text: "Button"
onClicked: {
if(ld.source === “ ”){
ld.source= "TestChild.qml"
}
}
}
Loader{
id:ld
}
}
// TestChild.qml
Rectangle{
width: 800
height: 480
}
当TestChild界面加载后,在TestChild界面上与main界面的Button所处位置的重叠处点击,会触发main界面Button的点击事件,
这就是一个界面的鼠标事件参透到另一界面。为了避免鼠标渗透事件的发生,TestChild.qml应该修改为:
Rectangle{
width: 800
height: 480
MouseArea { anchors.fill: parent }
}
注意:MouseArea { anchors.fill: parent }要作为TestChild界面的第一个子控件,才能屏蔽整个TestChild的鼠标渗透,又不影响TestChild界面中其它可操作控件事件。
常用控件的缩写
1.Button -> btn
1.CheckBox -> chk
1.ComboBox -> cmb
1.RadioButton -> rdo
1.Text -> txt
1.Label -> lbl
1.Image -> img
1.DateTimePicker -> dtp
1.ListView -> lvw
1.ToolTip -> tip
1.GroupBox -> grp
1.Panel -> pnl
1.Calendar -> cal
1.TreeView ->trvw
1.ListView->lvw
1.GridView -> gvw
1.Repeater->rpt
1.Menu ->mnu
1.ProgressBar ->prg
属性的绑定
所谓属性绑定,就是建立目标对象对其它对象的依赖关系,当其它对象发生变化时,目标对象也会变化,自动更新相关的属性值,达到动态属性的效果,例如:
Item {
id: root
width: 800
height: 480
focus: true
Keys.enabled: true
// 重新绑定
Keys.onEnterPressed: redRect.width = Qt.binding(function() {return parent.width/2})
Rectangle {
id: redRect
anchors.fill: parent
width: parent.width/3 // 绑定
height: 100
color: "red"
}
Button{
width: 60
height: 30
text: “Button”
onClicked: redRect.width = 150 // 绑定解除
}
}
上述例子中,redRect的width属性最初绑定到了root的width属性上,redRect的width会随着root的width变化而变化;Button按下后,redRect的width属性绑定解除;Enter键按下后,redRect的width属性重新绑定;Button再次按下后,redRect的width属性绑定解除。
也就是说如果属性绑定已经完成,那么在别的地方重新给这个属性赋值时,不管是赋个静态值,还是想换个别的属性绑定表达式,都会破坏原来的绑定关系,而新的值也不会有动态属性的效果。
Keys的载体
Keys是专门用来处理键盘事件KeyEvent的,它定义了许多针对特定按键的信号,例如digit0Pressed(KeyEvent event)、spacePressed(KeyEvent event)等,不过使用pressed(KeyEvent event)和released(KeyEvent event)这两个普通的信号就可以处理大部分按键事件了,信号的参数类型是KeyEvent,参数名是event,包含了按键的详细信息。
这里说所的载体是指Keys所属的父组件,Keys的载体必须是Item,或从Item继承而来,不能是Window或从Window继承而来的组件,否则Keys无效。
例如:
Window {
visible: true
width: 360
height: 360
Keys.enabled: true;
Keys.onEscapePressed: Qt.quit() // 没有功能: 不退出
Window {
visible: true
width: 360
height: 360
Item{
anchors.fill: parent
focus: true
Keys.enabled: true;
Keys.onEscapePressed: Qt.quit() // 有功能: 退出
}
Loader
QML的Loader元素经常备用来动态加载QML组件。可以使用source属性或者sourceComponent属性加载。这个元素最有用的地方是它能在qml组件需要的时候再创建,即延迟创建QML的时间。
加载与被加载组件中都有相同的事件,那么需要设置Loader的属性focus为true,且设置被加载组件 focus: true才能让事件不被传播到被加载组件。
main.qml
Item {
property bool isFirst : false;
width: 200
height: 200
Loader {
id: pageLoader
source: "Page2.qml"
focus: true
}
Keys.onPressed: {
console.log("Captured: ", event.text);
event.accepted = true;
}
}
// Page2.qml
Rectangle {
width: 100
height: 62
Text {
anchors.centerIn: parent
text: "Page2 Test"
}
focus: true
Keys.onPressed: {
console.log("Loaded item captured: ", event.text)
event.accepted = true
}
}
若将main.qml中Item的focus设置为true,则上述代码的只有main响应按键消息。
-
如果source或者sourceComponent更改了,任何先前实例化的项目都将被自动销毁。
-
将source设置为空字符串或者sourceComponent设置为undefined,则销毁当前加载的项目,释放资源,并将Loader设置为空。
-
Loader出来的qml界面,在界面关闭时,一定要清空Loader资源。
qml的目录结构
在独立项目或者Roshan插件中,qml的目录结构都是类似的。
资源
qml.qrc
components
img
views
main.qml
- components文件夹用来放置项目或插件的专有控件。
- img文件夹用来放置项目或插件的图片资源。
- view文件夹用来放置用来放置除专有控件、图片资源和main.qml外的其他文件。
model
QML中,model是为ListView,GridView和Repeater等元素来提供要显示数据的数据模型.这些元素需要一个为模型中的每一项数据生成一个实例的代理组件(delegate component).给代理提供的数据通过叫做角色的数据绑定到代理.
例如下述代码中,ListModel有两个角色:text 和 age,与代理模型Text的属性text的名称相冲突。
在这种情况下,如果直接在代理中绑定角色名称,则代理Text的text不会显示模型中text角色值,而是显示它自身的属性值。为了避免这种冲突的产生,在角色前面加上model.即可正常显示。
Item{
width: 200
height: 200
ListView{
id: testList
anchors.fill: parent
model: testModel
delegate: Text{
width: lst.width
height: 30
// text: text + "-" + age
text: model.text + "-" + model.age
}
}
ListModel{
id: testModel
ListElement{
text: "ccw"
age: 12
}
ListElement{
text: "ym"
age: 13
}
ListElement{
text: "zfb"
age: 35
}
ListElement{
text: "ded"
age: 30
}
}
}
其它
-
js代码中,能够用条件运算符(? :)代替 if...else...地方,要用条件运算符,这样是代码更简洁明了。
-
js中“”和“=”的区别:
==用于一般比较,在比较的时候可以转换数据类型。
===用于严格比较,只要类型不匹配就返回flase。 -
当一个qml文件中,代码行达到200至300行时,要从其qml文件中分解出子qml文件。
这样可以避免出现控件关联复杂,篇幅较长的代码。 -
只有一条语句的代码块,写成一行,例如:
MouseArea { anchors.fill: parent }
在信号处理代码中,如果也只有一条执行语句,则写成一行,且不需要{},例如:
onPressed: {
rootRect.color= "gray"
}
应该写成:
onPressed: rootRect.color= "gray"