4. 出发
本章概述 Qt 5 中使用的声明式用户界面语言 QML。我们将讨论 QML 语法,这是一个元素树,其次是最重要的基本元素概述。稍后我们将简要介绍如何创建我们自己的元素,称为组件,以及如何使用属性操作来转换元素。最后,我们将看看如何在一个布局中排列元素,最后看看可以提供给用户进行输入的元素。
4.1. QML 语法
QML 是用于描述应用程序的声明式用户界面语言。它将用户界面分解成可以随意组合成组件的更小的元素。QML 描述了这些用户界面元素的外观和行为。该用户界面描述可以通过使用简单的 JavaScript 代码,以提供更丰富但也更复杂的逻辑。在这个角度来说,QML 遵循 HTML-JavaScript 模式,但 QML 是从头开始设计的,用于描述用户界面而不是文本文档的语言。
以最简单的方式理解,QML 是元素的层次结构语言。子元素从父元素继承坐标系。子元素的x,y 坐标始终相对于父对象。
我们从一个简单的 QML 文件示例开始,以解释不同的语法。
// RectangleExample.qml
import QtQuick 2.5
// The root element is the Rectangle
Rectangle {
// name this element root
id: root
// properties: <name>: <value>
width: 120; height: 240
// color property
color: "#4A4A4A"
// Declare a nested element (child of root)
Image {
id: triangle
// reference the parent
x: (parent.width - width)/2; y: 40
source: 'assets/triangle_red.png'
}
// Another child of root
Text {
// un-named element
// reference element by id
y: triangle.y + triangle.height + 20
// reference root element
width: root.width
color: 'white'
horizontalAlignment: Text.AlignHCenter
text: 'Triangle'
}
}
- import 语句用于导入特定版本的模块。一般来说,我们总是要将 QtQuick 2.0 作为初始模块导入
- 就像 C/C ++ 和 JavaScript 那样,我们可以使用
//
进行单行注释或者使用/* */
进行多行注释。 - 与 HTML 类似,每个 QML 文件都需要有一个根元素
- 元素由类型后跟
{ }
进行声明 - 元素可以具有自己的属性,它们的格式为
name: value
- QML 文档中的任意元素可以通过使用其 id(无引号标识符)来访问
- 元素可以嵌套,这意味着父元素可以有子元素。可以使用 parent 关键字访问父元素
提示:
你会经常使用 id 访问对象或者使用关键字 parent 来访问父对象。有一个比较好的方法是命名我们的根元素对象 id 为 root(id: root),这样就不用去思考我们的 QML 文档中的根元素应该用什么方式命名更好了。记住:尽早确定你的开发风格,并坚持它,是一个好习惯。
注意:
您可以使用操作系统的命令行中的 Qt Quick 运行环境运行示例:
$ $QTDIR/bin/qmlscene rectangle.qml
上述命令行中我们需要将 $QTDIR
替换为 Qt 的实际安装路径。qmlscene
可执行程序会初始化 Qt Quick 运行环境并解释执行我们提供的 QML 文件。
在 Qt Creator 中就更简单一些了,我们可以打开相应的项目文件并运行文档 RectangleExample.qml。
4.1.1. 属性
元素通过使用其元素类型名称进行声明,但通过使用其属性或创建自定义属性进行定义。属性是一个简单的键值对,例如。width: 100
,text: 'Greetings'
,color: '#FF0000'
。属性具有明确的类型,可以具有初始值。
Text {
// (1) identifier
id: thisLabel
// (2) set x- and y-position
x: 24; y: 16
// (3) bind height to 2 * width
height: 2 * width
// (4) custom property
property int times: 24
// (5) property alias
property alias anotherTimes: thisLabel.times
// (6) set text appended by value
text: "Greetings " + times
// (7) font is a grouped property
font.family: "Ubuntu"
font.pixelSize: 24
// (8) KeyNavigation is an attached property
KeyNavigation.tab: otherLabel
// (9) signal handler for property changes
onHeightChanged: console.log('height:', height)
// focus is need to receive key events
focus: true
// change color based on focus value
color: focus?"red":"black"
}
我们来看看属性的不同特征:
- id 是一个非常特殊的属性值,它用于引用 QML 文件中的元素(在 QML 中称为“文档”)。id 不是字符串类型,而是标识符和 QML 语法的一部分。文档中的 id 需要是唯一的,不能重新设置为不同的值,它的值也不能被查询。(它的行为更像是 C++ 世界中的一个指针)
- 属性可以设置为一个值,具体取决于其类型。如果没有给出属性值,将选择一个初始值。我们需要查阅特定元素的文档以获取有关属性初始值的更多信息。
- 属性可以依赖于一个或多个其他属性。这被称为绑定。当其依赖属性更改时,也会更新绑定属性。它的工作原理像是一个合同,在上面的例子中的情况下,高度应该是宽度的两倍。
- 使用属性限定符后跟类型,名称和可选的初始值(属性
<type> <name>: <value>
)来向元素添加自己的属性。 如果没有给出初始值,则选择系统初始值。
注意:
如果属性名与已定义的默认属性名不重复,使用default
关键字你可以将一个属性定义为默认属性。例如,当我们添加子元素时,如果他们是可视化的元素,子元素会自动的添加默认属性的子类型链表(children property list)。 - 声明属性的另一个重要方法是使用别名关键字(
alias <name>: <reference>
)。alias
关键字允许我们将对象或对象本身的属性从类型转发到另一个作用域。稍后在定义组件以将内部属性或元素 id 导出到根级别时,我们将使用此技术。属性别名不需要类型,它使用引用的属性或对象的类型。 - 上例中 text 属性的值取决于 int 类型的属性 times 的值,基于 int 的值将自动转换为字符串类型。这个表达式本身就是绑定的另一个实际使用例子,并且每次 times 属性更改时将更新文本属性 text 的结果。
- 一些属性是分组的属性。当属性更加结构化并且相关属性应该分组在一起时,将使用此功能。使用分组属性的另一种方法是
font {family: “Ubuntu”; pixelSize: 24}
- 上例中的第八点表示:一些附加到元素本身的属性。这适用于在应用程序中只出现一次的全局相关元素(例如键盘输入)。其使用语法是
<Element>.<property>: <value>
- 对于上例中的第九点,可以这样理解:对于每个属性,我们都可以提供一个信号处理程序。当属性更改后调用此处理程序。例如,在这里,我们希望在高度变化时收到通知,并使用内置控制台将该属性改变的消息记录到系统。
警告
一个元素 id 应该只在当前文档中被引用。QML 提供了动态作用域的机制,后加载的文档会覆盖之前加载文档的元素 id 标志,这样就可以引用已加载并且没有被覆盖的元素 id 标志,这有点类似创建全局变量。但不幸的是这样的代码阅读性很差。目前这个还没有办法解决这个问题,所以你使用这个机制的时候最好仔细一些,最好不要使用这种机制。如果我们想向文档外提供元素的调用,我们可以在根元素上使用属性导出的方式来提供。
4.1.2. 脚本
QML 和 JavaScript(也称为 ECMAScript)是最好的开发组合。在 JavaScript 章节中,我们将详细介绍这种共生关系。目前我们只需要简单了解这种关系。
Text {
id: label
x: 24; y: 24
// custom counter property for space presses
property int spacePresses: 0
text: "Space pressed: " + spacePresses + " times"
// (1) handler for text changes
onTextChanged: console.log("text changed to:", text)
// need focus to receive key events
focus: true
// (2) handler with some JS
Keys.onSpacePressed: {
increment()
}
// clear the text on escape
Keys.onEscapePressed: {
label.text = ''
}
// (3) a JS function
function increment() {
spacePresses = spacePresses + 1
}
}
- 文本改变的处理器 onTextChanged 用于在每次按下空格键时打印当前的文本
- 当文本元素接收到空格键(因为用户按下键盘上的空格键)消息时,我们调用一个 JavaScript 函数 increment()
- 我们需要以 function <name>(<parameters>) {...} 的形式来定义 JavaScript 函数。这个 increment() 函数会在每次我们按下空格键时,为计数器属性 spacePresses 加一,与之绑定的 text 属性的值也会随之改变
注意
QML :(绑定) 和 JavaScript =(赋值)之间的区别在于绑定是一个协议,并且存在于整个生命周期。然而 JavaScript 赋值(=)只会产生一次效果。当一个新的绑定生效或者使用 JavaScript 赋值给属性时,QML 绑定的生命周期就会结束。 例如,将文本属性设置为空字符串的键处理程序将会破坏我们的每次按下空格键时都会产生的增量显示效果:
Keys.onEscapePressed: {
label.text = ''
}
按下退出键后,按住空格键将不再更新文本的显示内容,因为之前的文本属性绑定(text: "Space pressed: " + spacePresses + " times")已经被破坏了。
当我们对改变属性的策略有冲突时(文本的改变基于一个增值的绑定并且可以被 JavaScript 赋值清零),类似于这个例子,我们最好不要使用绑定属性。我们需要使用赋值的方式来改变属性,属性绑定会在赋值操作后被销毁(销毁绑定协议!)。
本文参考链接:Quick Starter