1. QML (Qt Meta-Object Language,Qt元对象语言)
QML
是一种基于CSS
及JavaScript
,用于描述对象间关系
的声明式
语言。其属性部分是CSS
风格的键值对,行为部分则使用JavaScript
实现。
注:QML是Qt Meta-Object Language,而不是Qt Markup Language的缩写。
Image {
id: root
...
MouseArea {
anchors.fill: parent
onClicked: wheel.rotation += 90
}
...
}
1.1. 元素
元素是构建QML应用的基本构件的模板。
- 每个
QML文件
都有且只有一个根元素 - 元素由其类型加
{}
声明 - 元素可以有属性,通过
name: value
形式使用 - 通过
id
可以访问QML文档中的任意元素 - 元素可以嵌套,关键字
parent
用于访问父元素
import QtQuick
// 以Rectangle为根元素
Rectangle {
// 命名此元素为root
id: root
// 属性: <键>: <值>
width: 120; height: 240
// color属性
color: "#4A4A4A"
// 定义一个嵌套元素 (root的子元素)
Image {
id: triangle
// 引用父元素
x: (parent.width - width)/2; y: 40
source: 'assets/triangle_red.png'
}
// root的其它子元素
Text {
// 匿名元素
// 通过id引用元素
y: triangle.y + triangle.height + 20
// 引用根元素
width: root.width
color: 'white'
horizontalAlignment: Text.AlignHCenter
text: 'Triangle'
}
}
通常情况下,使用root
作为根类型id
。
1.2. 属性
元素通过其元素名称声明,通过使用属性或创建自定义属性来定义。属性是一个有明确类型、可以有默认值的简单键值对。
Text {
// (1) 标识符
id: thisLabel
// (2) 设置x, y坐标
x: 24; y: 16
// (3) 绑定 height 为 2 * width
height: 2 * width
// (4) 自定义属性
property int times: 24
// (5) 属性别名
property alias anotherTimes: thisLabel.times
// (6) text属性包含一个值
text: "Greetings " + times
// (7) 字体是分组属性
font.family: "Ubuntu"
font.pixelSize: 24
// font { family: "Ubuntu"; pixelSize: 24 }
// (8) KeyNavigation是附加属性
KeyNavigation.tab: otherLabel
// (9) 属性变化信号处理程序
onHeightChanged: console.log('height:', height)
// focus属性表示是否接收按键事件
focus: true
// 依赖focus属性改变颜色
color: focus ? "red" : "black"
}
1.2.1. id
id
是由QML语法定义,不可重置,用于引用QML文件内元素的唯一标识符。(1)
1.2.2. 属性设置
属性可以设置成与其类型相关的值。如果没有设置,则为初始值。 (2)
1.2.3. 绑定
-
属性绑定 属性可以依赖一个或多个属性。绑定属性会随其依赖属性变化而变化。
(3)
-
表达式绑定 属性还可以依赖表达式。绑定属性会随依赖表达式变化而变化。
(6)
1.2.4. 自定义属性
通过属性限定符按如下格式添加自定义属性:
property <类型> <名称>: <初始值>
其中初始值可选,如果没有初始值,将会使用默认初始值。 (4)
1.2.5. 默认属性
通过default
关键字声明一个属性为默认属性。如果子元素未明确绑定到某个属性,则该元素将绑定到默认属性。
1.2.6. 别名
通过以下方式将对象属性或对象本身从类型内部转发到外部作用域:
property alias <名称>: <引用>
通过别名,我们可以将内部属性、元素id输出到根级别。别名声明不需要类别,它使用引用的属性或对象类型。 (5)
1.2.7. 分组属性
当一个属性结构比较复杂时,就会用到分组属性。 (7)
1.2.8. 附加属性
有些属性属于元素类本身。这种属性适用于在应用程序中只出现一次的全局设置元素。 (8)
<元素>.<属性>: <值>
1.2.9. 信号处理程序
每个属性都可以拥有一个信号处理程序。信号处理程序将在属性发生变化后调用。 (9)
1.3. 脚本
QML与JavaScript(ECMAScript)关系密切。
Text {
id: label
x: 24; y: 24
// 自定义属性记录空格键按压次数
property int spacePresses: 0
text: "Space pressed: " + spacePresses + " times"
// (1) text变化处理程序需要使用函数来捕获参数
onTextChanged: function(text) {
console.log("text changed to:", text)
}
// 接收按键事件
focus: true
// (2) 使用JavaScript处理
Keys.onSpacePressed: {
increment()
}
// 退出键按下时清空
Keys.onEscapePressed: {
label.text = ''
}
// (3) JavaScript函数
function increment() {
spacePresses = spacePresses + 1
}
}
1.3.1. 函数语法的处理程序
通过函数语法处理程序能够接收信号传递的参数。通常有以下两种形式:
function(args...) {}
(1)
- 箭头函数
((args...) => {})
第一种更易于阅读,因此使用更为广泛。
1.3.2. 调用JavaScript函数
需要使用JavaScript函数处理时,直接调用即可。 (2)
1.3.3. JavaScript函数定义
JavaScript函数以如下形式定义:
function <函数名>(<参数>) { ... }
(3)
1.4. 绑定
QML绑定:
与JavaScript赋值=
的区别在于,绑定在其生命周期内一直生效,而赋值只生效一次。
1.4.1. 生命周期
绑定的生命周期持续到下一次赋值或绑定为止。
1.4.2. 重新绑定
通过如下方法为属性建立新的绑定:
<属性> = Qt.binding(<表达式>)
1.5. 组件
组件是可重复使用的元素。QML组件通常由组件文件(QML文件)定义,而Component
元素能够在QML文件内嵌定义QML组件。
为了方便描述,我们将由文件定义的组件称为顶级组件(top-level component)
,由Component元素定义的组件称为内联组件(in-line component)
。
注:描述可能过时,因为在最新的Qt文档中无此表达。
1.5.1. 顶级组件
一个QML文件即为一个组件,元素类型即文件名。并且,对于文件组件而言,只有根级别的属性才能被其它文件的组件访问。
1.5.2. 内联组件
2. QML扩展
QML运行时采用C++开发,并且可以通过C++来扩展运行时功能。
2.1. 运行时
运行时是QML的执行环境,它由QtQml模块提供。它由引擎、上下文、组件组成。其中
- 引擎负责执行QML
- 上下文保存每个组件可访问的全局属性
- 组件表示可从QML实例化的QML元素。
#include <QtGui>
#include <QtQml>
int main(int argc, char **argv)
{
QGuiApplication app(argc, argv);
QUrl source(QStringLiteral("qrc:/main.qml"));
QQmlApplicationEngine engine;
engine.load(source);
return app.exec();
}
2.2. 注册QML类型
将C++类型注册为QML类型,使得开发者能够在QML中控制C++对象的生命周期,并且不会污染全局命名空间。但是所有的类型都要先注册才能使用,因此所有库都需要在应用程序启动时就链接。 假设CurrentTime继承于QObject,则可通过以下方法将CurrentTime注册为QML类型,并在QML中使用:
QQmlApplicationEngine engine();
qmlRegisterType<CurrentTime>("org.example", 1, 0, "CurrentTime");
engine.load(source);
import org.example 1.0
CurrentTime {
// 访问属性、方法、信号
}
2.3. 添加上下文属性
如果不需要在QML中实例化新类,则可以通过如下方式将C++对象添加到QML上下文属性中:
QScopedPointer<CurrentTime> current(new CurrentTime());
QQmlApplicationEngine engine();
engine.rootContext().setContextProperty("current", current.value())
engine.load(source);
import QtQuick
import QtQuick.Window
Window {
visible: true
width: 512
height: 300
Component.onCompleted: {
console.log('current: ' + current)
}
}
由于上下文属性的继承关系,添加到上下文属性的对象在QML代码的任何地方都可以使用。
2.4. QML扩展插件 (待完成)
QML扩展插件是比注册QML类型、上下文属性更加灵活的QML扩展方式。插件会在第一个QML文件导入标识符是加载,并在插件中注册类型。通过使用QML单例,不会污染全局命名空间。并且插件可以在不同的项目中重复使用。
当我们通过import
关键字导入模块时,QML运行时会在QML导入路径中查找并加载这些模块。由此模块提供的新类型将提供给QML环境。
2.4.1. 插件内容
插件是一个可按需加载、具有指定接口的库。在QML中,插件接口由类QQmlExtensionPlugin
提供。