1. QML (Qt Meta-Object Language,Qt元对象语言)

QML是一种基于CSSJavaScript,用于描述对象间关系声明式语言。其属性部分是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提供。

2.4.2. 创建插件