目录
-
- [1. QML 语法](#1. QML 语法)
-
- [1.1 概述](#1.1 概述)
- [1.2 属性](#1.2 属性)
- [1.3 脚本](#1.3 脚本)
- [1.4 绑定](#1.4 绑定)
1. QML 语法
1.1 概述
QML 是一种声明式语言,用于描述对象和对象之间的关系。QtQuick 是一个基于 QML 构建的框架,用于构建应用程序的用户界面。它将用户界面分解成更小的元素,这些元素可以组合成组件。QtQuick 描述了这些用户界面元素的外观和行为。这种用户界面描述可以通过 JavaScript 代码进行丰富,从而提供简单或更复杂的逻辑。从这个角度来看,它遵循 HTML-JavaScript 模式,但 QML 和 QtQuick 从一开始就被设计用于描述用户界面,而不是文本文档。
QtQuick 最简单的用法是创建元素层级结构。
子元素继承父元素的坐标系。x,y 坐标始终相对于父元素。
提示:
QtQuick 基于 QML 构建。QML 语言本身只包含元素、属性、信号和绑定。
QtQuick 是一个基于 QML 的框架。通过使用默认属性,可以优雅地构建 QtQuick 元素的层次结构。
让我们从一个简单的 QML 文件示例开始,来解释不同的语法。
// RectangleExample.qml
// import 语句用于导入模块。可以添加一个可选的版本号,格式为 <major>.<minor>
import QtQuick
// 可以使用 // 进行单行注释,或使用 /* */ 进行多行注释。这与 C/C++ 和 JavaScript 类似。
// 每个 QML 文件都必须只有一个根元素,就像 HTML 文件一样。
// The root element is the Rectangle
// 元素由其类型后跟 { } 声明。
Rectangle {
// 可以使用 id (未加引号的标识符)访问 QML 文档中的任意元素。
// name this element root
id: root
// 元素可以拥有属性,属性格式为 name: value
// properties: <name>: <value>
width: 120; height: 240
// color property
color: "#4A4A4A"
// 元素可以嵌套,也就是说,一个父元素可以有子元素。可以使用 parent 关键字访问父元素。
// 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'
}
}
通常情况下,您需要通过 ID 访问特定元素,或者使用 parent 关键字访问 parent 元素。因此,最佳实践是将根元素命名为"root",并使用 id: root 。这样,您就无需考虑如何在 QML 文档中命名根元素了。
1.2 属性
元素通过元素名称声明,但通过属性或创建自定义属性来定义。属性是一个简单的键值对,例如 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"
}
- (1)
id是一个非常特殊的类属性值,它用于引用 QML 文件(在 QML 中称为"文档")内部的元素。id不是字符串类型,而是一个标识符,是 QML 语法的一部分。id在文档内部必须是唯一的,它不能被重置为其他值,也无法被查询。(它的行为非常类似于 C++ 世界中的引用。) - (2) 属性可以根据其类型被赋予一个值。如果没有为属性指定值,则会选择一个初始值。您需要查阅特定元素的文档,以获取有关属性初始值的更多信息。
- (3) 一个属性可以依赖于一个或多个其他属性。这被称为绑定。当所依赖的属性发生变化时,绑定的属性也会随之更新。它的工作机制类似于一种契约,在这种情况下,
height应该始终是width的两倍。 - (4) 使用
property限定符后跟类型、名称和可选的初始值 (property <type> <name> : <value>),即可向元素添加新属性。如果没有给出初始值,则会选择一个默认初始值。 - (5) 声明属性的另一种重要方式是使用
alias关键字(property alias <name>: <reference>)。alias关键字允许我们将类型内部的对象属性或对象本身转发到外部作用域。稍后在定义组件时,我们将使用这种技术将内部属性或元素 ID 导出到根级。属性别名不需要指定类型,它直接使用所引用的属性或对象的类型。 - (6)
text属性依赖于 int 类型的自定义属性times。基于int的值会自动转换为 string 类型。该表达式本身是绑定的另一个示例,其结果是每当times属性发生变化时,文本都会随之更新。 - (7) 某些属性是分组属性。当一个属性具有更复杂的结构,且相关属性应当组合在一起时,就会使用此特性。编写分组属性的另一种方式是
font { family: "Ubuntu"; pixelSize: 24 }。 - (8) 某些属性属于元素类本身。这适用于在应用程序中仅出现一次的全局设置元素(例如键盘输入)。其写法是
<Element>.<property>: <value>。 - (9) 对于每一个属性,你都可以提供一个信号处理器。该处理器会在属性发生变化后被调用。例如,在这里我们希望在高度变化时获得通知,并使用内置的控制台向系统记录一条消息。
1.3 脚本
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. Need to use function to capture parameters
onTextChanged: function(text) {
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
}
}
- (1) 文本更改处理器
onTextChanged会在每次因按下空格键导致文本更改时打印当前文本。由于我们使用了信号注入的参数,因此这里需要使用函数语法。虽然也可以使用箭头函数((text) => {}),但我们认为function(text) {}的可读性更高。 - (2) 当文本元素接收到空格键(因为用户按下了键盘上的空格键)时,我们会调用一个 JavaScript 函数
increment()。 - (3) 以
function <name>(<parameters>) { ... }形式定义的 JavaScript 函数,用于递增我们的计数器spacePresses。每当spacePresses递增时,绑定的属性也会随之更新。
1.4 绑定
QML : (绑定)与 JavaScript = (赋值)的区别在于:绑定是一种契约,并在绑定的生命周期内始终保持有效;而 JavaScript 赋值( = )则是一次性的数值分配。
当为属性设置新的绑定,甚至当为属性分配一个 JavaScript 数值时,绑定的生命周期就会结束。例如,一个将 text 属性设置为空字符串的按键处理器,将会破坏我们的增量显示:
Keys.onEscapePressed: {
label.text = ''
}
按下 Esc 键后,再按空格键将不再更新显示内容,因为之前对 text 属性的绑定(text: "Space pressed: " + spacePresses + " times")已被破坏。
当遇到如本例中这种冲突的属性更改策略时(文本既通过绑定随属性增量更新,又通过 JavaScript 赋值被清空),你就不能使用绑定!你需要在两条属性更改路径上都使用赋值,因为绑定会被赋值操作破坏(契约失效!)。