QML快速入门(Quick Starter)

注意

最后一次构建:2014年1月20日下午18:00。

这章的源代码能够在assetts folder找到。

这章概述了QML语言,Qt5中大量使用了这种声明用户界面的语言。我们将会讨论QML语言,一个树形结构的元素,跟着是一些最基本的元素概述。然后我们会简短的介绍怎样创建我们自己的元素,这些元素被叫做组件,并如何使用属性操作来转换元素。最后我们会介绍如何对元素进行布局,如何向用户提供输入。

QML语法(QML Syntax)

QML是一种描述用户界面的声明式语言。它将用户界面分解成一些更小的元素,这些元素能够结合成一个组件。QML语言描述了用户界面元素的形状和行为。用户界面能够使用JavaScript来提供修饰,或者增加更加复杂的逻辑。从这个角度来看它遵循HTML-JavaScript模式,但QML是被设计用来描述用户界面的,而不是文本文档。

从QML元素的层次结构来理解是最简单的学习方式。子元素从父元素上继承了坐标系统,它的x,y坐标总是相对应于它的父元素坐标系统。

让我们开始用一个简单的QML文件例子来解释这个语法。

// rectangle.qml

import QtQuick 2.0

// The root element is the Rectangle
Rectangle {
    // name this element root
    id: root

    // properties: <name>: <value>
    width: 120; height: 240

    // color property
    color: "#D8D8D8"

    // Declare a nested element (child of root)
    Image {
        id: rocket

        // reference the parent
        x: (parent.width - width)/2; y: 40

        source: 'assets/rocket.png'
    }

    // Another child of root
    Text {
        // un-named element

        // reference element by id
        y: rocket.y + rocket.height + 20

        // reference root element
        width: root.width

        horizontalAlignment: Text.AlignHCenter
        text: 'Rocket'
    }
}
  • import声明导入了一个指定的模块版本。一般来说会导入QtQuick2.0来作为初始元素的引用。

  • 使用//可以单行注释,使用/**/可以多行注释,就像C/C++和JavaScript一样。

  • 每一个QML文件都需要一个根元素,就像HTML一样。

  • 一个元素使用它的类型声明,然后使用{}进行包含。

  • 元素拥有属性,他们按照name:value的格式来赋值。

  • 任何在QML文档中的元素都可以使用它们的id进行访问(id是一个任意的标识符)。

  • 元素可以嵌套,这意味着一个父元素可以拥有多个子元素。子元素可以通过访问parent关键字来访问它们的父元素。

建议

你会经常使用id或者关键字parent来访问你的父对象。有一个比较好的方法是命名你的根元素对象id为root(id:root),这样就不用去思考你的QML文档中的根元素应该用什么方式命名了。

提示

你可以在你的操作系统命令行模式下使用QtQuick运行环境来运行这个例子,比如像下面这样:

$ $QTDIR/bin/qmlscene rectangle.qml

将$QTDIR替换为你的Qt的安装路径。qmlscene会执行Qt Quick运行环境初始化,并且解释这个QML文件。

在Qt Creator中你可以打开对应的项目文件然后运行rectangle.qml文档。

4.1.1 属性(Properties)

元素使用他们的元素类型名进行声明,使用它们的属性或者创建自定义属性来定义。一个属性对应一个值,例如 width:100,text: ‘Greeting’, 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 neeed to receive key events
    focus: true

    // change color based on focus value
    color: focus?"red":"black"
}

让我们来看看不同属性的特点:

  1. id是一个非常特殊的属性值,它在一个QML文件中被用来引用元素。id不是一个字符串,而是一个标识符和QML语法的一部分。一个id在一个QML文档中是唯一的,并且不能被设置为其它值,也无法被查询(它的行为更像C++世界里的指针)。

  2. 一个属性能够设置一个值,这个值依赖于它的类型。如果没有对一个属性赋值,那么它将会被初始化为一个默认值。你可以查看特定的元素的文档来获得这些初始值的信息。

  3. 一个属性能够依赖一个或多个其它的属性,这种操作称作属性绑定。当它依赖的属性改变时,它的值也会更新。这就像订了一个协议,在这个例子中height始终是width的两倍。

  4. 添加自己定义的属性需要使用property修饰符,然后跟上类型,名字和可选择的初始化值(property : )。如果没有初始值将会给定一个系统初始值作为初始值。注意如果属性名与已定义的默认属性名不重复,使用default关键字你可以将一个属性定义为默认属性。这在你添加子元素时用得着,如果他们是可视化的元素,子元素会自动的添加默认属性的子类型链表(children property list)

  5. 另一个重要的声明属性的方法是使用alias关键字(property alias : )。alias关键字允许我们转发一个属性或者转发一个属性对象自身到另一个作用域。我们将在后面定义组件导出内部属性或者引用根级元素id会使用到这个技术。一个属性别名不需要类型,它使用引用的属性类型或者对象类型。

  6. text属性依赖于自定义的timers(int整型数据类型)属性。int整型数据会自动的转换为string字符串类型数据。这样的表达方式本身也是另一种属性绑定的例子,文本结果会在times属性每次改变时刷新。

  7. 一些属性是按组分配的属性。当一个属性需要结构化并且相关的属性需要联系在一起时,我们可以这样使用它。另一个组属性的编码方式是 font{family: “UBuntu”; pixelSize: 24 }。

  8. 一些属性是元素自身的附加属性。这样做是为了全局的相关元素在应用程序中只出现一次(例如键盘输入)。编码方式.:

  9. 对于每个元素你都可以提供一个信号操作。这个操作在属性值改变时被调用。例如这里我们完成了当height(高度)改变时会使用控制台输出一个信息。

警告

一个元素id应该只在当前文档中被引用。QML提供了动态作用域的机制,后加载的文档会覆盖之前加载文档的元素id号,这样就可以引用已加载并且没有被覆盖的元素id,这有点类似创建全局变量。但不幸的是这样的代码阅读性很差。目前这个还没有办法解决这个问题,所以你使用这个机制的时候最好仔细一些甚至不要使用这种机制。如果你想向文档外提供元素的调用,你可以在根元素上使用属性导出的方式来提供。

4.1.2 脚本(Scripting)

QML与JavaScript是最好的配合。在JavaScrpit的章节中我们将会更加详细的介绍这种关系,现在我们只需要了解这种关系就可以了。

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
    }
}
  1. 文本改变操作onTextChanged会将每次空格键按下导致的文本改变输出到控制台。

  2. 当文本元素接收到空格键操作(用户在键盘上点击空格键),会调用JavaScript函数increment()。

  3. 定义一个JavaScript函数使用这种格式function (){….},在这个例子中是增加spacePressed的计数。每次spacePressed的增加都会导致它绑定的属性更新。

注意

QML的:(属性绑定)与JavaScript的=(赋值)是不同的。绑定是一个协议,并且存在于整个生命周期。然而JavaScript赋值(=)只会产生一次效果。当一个新的绑定生效或者使用JavaScript赋值给属性时,绑定的生命周期就会结束。例如一个按键的操作设置文本属性为一个空的字符串将会销毁我们的增值显示:

Keys.onEscapePressed: {
    label.text = ''
}

在点击取消(ESC)后,再次点击空格键(space-bar)将不会更新我们的显示,之前的text属性绑定(text: “Space pressed:” + spacePresses + “times”)被销毁。

当你对改变属性的策略有冲突时(文本的改变基于一个增值的绑定并且可以被JavaScript赋值清零),类似于这个例子,你最好不要使用绑定属性。你需要使用赋值的方式来改变属性,属性绑定会在赋值操作后被销毁(销毁协议!)。

基本元素(Basic Elements)

元素可以被分为可视化元素与非可视化元素。一个可视化元素(例如矩形框Rectangle)有着几何形状并且可以在屏幕上显示。一个非可视化元素(例如计时器Timer)提供了常用的功能,通常用于操作可视化元素。

现在我们将专注于几个基础的可视化元素,例如Item(基础元素对象),Rectangle(矩形框),Text(文本),Image(图像)和MouseArea(鼠标区域)。

4.2.1 基础元素对象(Item Element)

Item(基础元素对象)是所有可视化元素的基础对象,所有其它的可视化元素都继承自Item。它自身不会有任何绘制操作,但是定义了所有可视化元素共有的属性:

Group(分组) Properties(属性)
Geometry(几何属性) x,y(坐标)定义了元素左上角的位置,width,height(长和宽)定义元素的显示范围,z(堆叠次序)定义元素之间的重叠顺序。
Layout handling(布局操作) anchors(锚定),包括左(left),右(right),上(top),下(bottom),水平与垂直居中(vertical center,horizontal center),与margins(间距)一起定义了元素与其它元素之间的位置关系。
Key handlikng(按键操作) 附加属性key(按键)和keyNavigation(按键定位)属性来控制按键操作,处理输入焦点(focus)可用操作。
Transformation(转换) 缩放(scale)和rotate(旋转)转换,通用的x,y,z属性列表转换(transform),旋转基点设置(transformOrigin)。
Visual(可视化) 不透明度(opacity)控制透明度,visible(是否可见)控制元素是否显示,clip(裁剪)用来限制元素边界的绘制,smooth(平滑)用来提高渲染质量。
State definition(状态定义) states(状态列表属性)提供了元素当前所支持的状态列表,当前属性的改变也可以使用transitions(转变)属性列表来定义状态转变动画。

为了更好的理解不同的属性,我们将会在这章中尽量的介绍这些元素的显示效果。请记住这些基本的属性在所有可视化元素中都是可以使用的,并且在这些元素中的工作方式都是相同的。

注意

Item(基本元素对象)通常被用来作为其它元素的容器使用,类似HTML语言中的div元素(div element)。

4.2.2 矩形框元素(Rectangle Element)

Rectangle(矩形框)是基本元素对象的一个扩展,增加了一个颜色来填充它。它还支持边界的定义,使用border.color(边界颜色),border.width(边界宽度)来自定义边界。你可以使用radius(半径)属性来创建一个圆角矩形。

Rectangle {
    id: rect1
    x: 12; y: 12
    width: 76; height: 96
    color: "lightsteelblue"
}
Rectangle {
    id: rect2
    x: 112; y: 12
    width: 76; height: 96
    border.color: "lightsteelblue"
    border.width: 4
    radius: 8
}

注意

颜色的命名是来自SVG颜色的名称(查看http://www.w3.org/TR/css3-color/#svg-color可以获取更多的颜色名称)。你也可以使用其它的方法来指定颜色,比如RGB字符串(’#FF4444’),或者一个颜色名字(例如’white’)。

此外,填充的颜色与矩形的边框也支持自定义的渐变色。

Rectangle {
    id: rect1
    x: 12; y: 12
    width: 176; height: 96
    gradient: Gradient {
        GradientStop { position: 0.0; color: "lightsteelblue" }
        GradientStop { position: 1.0; color: "slategray" }
    }
    border.color: "slategray"
}

一个渐变色是由一系列的梯度值定义的。每一个值定义了一个位置与颜色。位置标记了y轴上的位置(0 = 顶,1 = 底)。GradientStop(倾斜点)的颜色标记了颜色的位置。

注意

一个矩形框如果没有width/height(宽度与高度)将不可见。如果你有几个相互关联width/height(宽度与高度)的矩形框,在你组合逻辑中出了错后可能就会发生矩形框不可见,请注意这一点。

注意

这个函数无法创建一个梯形,最好使用一个已有的图像来创建梯形。有一种可能是在旋转梯形时,旋转的矩形几何结构不会发生改变,但是这会导致几何元素相同的可见区域的混淆。从作者的观点来看类似的情况下最好使用设计好的梯形图形来完成绘制。

4.2.3 文本元素(Text Element)

显示文本你需要使用Text元素(Text Element)。它最值得注意的属性时字符串类型的text属性。这个元素会使用给出的text(文本)与font(字体)来计算初始化的宽度与高度。可以使用字体属性组来(font property group)来改变当前的字体,例如font.family,font.pixelSize,等等。改变文本的颜色值只需要改变颜色属性就可以了。

Text {
    text: "The quick brown fox"
    color: "#303030"
    font.family: "Ubuntu"
    font.pixelSize: 28
}

文本可以使用horizontalAlignment与verticalAlignment属性来设置它的对齐效果。为了提高文本的渲染效果,你可以使用style和styleColor属性来配置文字的外框效果,浮雕效果或者凹陷效果。对于过长的文本,你可能需要使用省略号来表示,例如A very … long text,你可以使用elide属性来完成这个操作。elide属性允许你设置文本左边,右边或者中间的省略位置。如果你不想’….’省略号出现,并且希望使用文字换行的方式显示所有的文本,你可以使用wrapMode属性(这个属性只在明确设置了宽度后才生效):

Text {
    width: 40; height: 120
    text: 'A very long text'
    // '...' shall appear in the middle
    elide: Text.ElideMiddle
    // red sunken text styling
    style: Text.Sunken
    styleColor: '#FF4444'
    // align text to the top
    verticalAlignment: Text.AlignTop
    // only sensible when no elide mode
    // wrapMode: Text.WordWrap
}

一个text元素(text element)只显示的文本,它不会渲染任何背景修饰。除了显示的文本,text元素背景是透明的。为一个文本元素提供背景是你自己需要考虑的问题。

注意

知道一个文本元素(Text Element)的初始宽度与高度是依赖于文本字符串和设置的字体这一点很重要。一个没有设置宽度或者文本的文本元素(Text Element)将不可见,默认的初始宽度是0。

注意

通常你想要对文本元素布局时,你需要区分文本在文本元素内部的边界对齐和由元素边界自动对齐。前一种情况你需要使用horizontalAlignment和verticalAlignment属性来完成,后一种情况你需要操作元素的几何形状或者使用anchors(锚定)来完成。

4.2.4 图像元素(Image Element)

一个图像元素(Image Element)能够显示不同格式的图像(例如PNG,JPG,GIF,BMP)。想要知道更加详细的图像格式支持信息,可以查看Qt的相关文档。source属性(source property)提供了图像文件的链接信息,fillMode(文件模式)属性能够控制元素对象的大小调整行为。

Image {
    x: 12; y: 12
    // width: 48
    // height: 118
    source: "assets/rocket.png"
}
Image {
    x: 112; y: 12
    width: 48
    height: 118/2
    source: "assets/rocket.png"
    fillMode: Image.PreserveAspectCrop
    clip: true
}

注意

一个URL可以是使用’/‘语法的本地路径(”./images/home.png”)或者一个网络链接(”http://example.org/home.png“)。

注意

图像元素(Image element)使用PreserveAspectCrop可以避免裁剪图像数据被渲染到图像边界外。默认情况下裁剪是被禁用的(clip:false)。你需要打开裁剪(clip:true)来约束边界矩形的绘制。这对任何可视化元素都是有效的。

建议

使用QQmlImageProvider你可以通过C++代码来创建自己的图像提供器,这允许你动态创建图像并且使用线程加载。

4.2.5 鼠标区域元素(MouseArea Element)

为了与不同的元素交互,你通常需要使用MouseArea(鼠标区域)元素。这是一个矩形的非可视化元素对象,你可以通过它来捕捉鼠标事件。当用户与可视化端口交互时,mouseArea通常被用来与可视化元素对象一起执行命令。

Rectangle {
    id: rect1
    x: 12; y: 12
    width: 76; height: 96
    color: "lightsteelblue"
    MouseArea {
        id: area
        width: parent.width
        height: parent.height
        onClicked: rect2.visible = !rect2.visible
    }
}

Rectangle {
    id: rect2
    x: 112; y: 12
    width: 76; height: 96
    border.color: "lightsteelblue"
    border.width: 4
    radius: 8
}

注意

这是QtQuick中非常重要的概念,输入处理与可视化显示分开。这样你的交互区域可以比你显示的区域大很多。

组件(Compontents)

一个组件是一个可以重复使用的元素,QML提供几种不同的方法来创建组件。但是目前我们只对其中一种方法进行讲解:一个文件就是一个基础组件。一个以文件为基础的组件在文件中创建了一个QML元素,并且将文件以元素类型来命名(例如Button.qml)。你可以像任何其它的QtQuick模块中使用元素一样来使用这个组件。在我们下面的例子中,你将会使用你的代码作为一个Button(按钮)来使用。

让我们来看看这个例子,我们创建了一个包含文本和鼠标区域的矩形框。它类似于一个简单的按钮,我们的目标就是让它足够简单。

Rectangle { // our inlined button ui
    id: button
    x: 12; y: 12
    width: 116; height: 26
    color: "lightsteelblue"
    border.color: "slategrey"
    Text {
        anchors.centerIn: parent
        text: "Start"
    }
    MouseArea {
        anchors.fill: parent
        onClicked: {
            status.text = "Button clicked!"
        }
    }
}

Text { // text changes when button was clicked
    id: status
    x: 12; y: 76
    width: 116; height: 26
    text: "waiting ..."
    horizontalAlignment: Text.AlignHCenter
}

用户界面将会看起来像下面这样。左边是初始化的状态,右边是按钮点击后的效果。

我们的目标是提取这个按钮作为一个可重复使用的组件。我们可以简单的考虑一下我们的按钮会有的哪些API(应用程序接口),你可以自己考虑一下你的按钮应该有些什么。下面是我考虑的结果:

// my ideal minimal API for a button
Button {
    text: "Click Me"
    onClicked: { // do something }
}

我想要使用text属性来设置文本,然后实现我们自己的点击操作。我也期望这个按钮有一个比较合适的初始化大小(例如width:240)。
为了完成我们的目标,我创建了一个Button.qml文件,并且将我们的代码拷贝了进去。我们在根级添加一个属性导出方便使用者修改它。

我们在根级导出了文本和点击信号。通常我们命名根元素为root让引用更加方便。我们使用了QML的alias(别名)的功能,它可以将内部嵌套的QML元素的属性导出到外面使用。有一点很重要,只有根级目录的属性才能够被其它文件的组件访问。

// Button.qml

import QtQuick 2.0

Rectangle {
    id: root
    // export button properties
    property alias text: label.text
    signal clicked

    width: 116; height: 26
    color: "lightsteelblue"
    border.color: "slategrey"

    Text {
        id: label
        anchors.centerIn: parent
        text: "Start"
    }
    MouseArea {
        anchors.fill: parent
        onClicked: {
            root.clicked()
        }
    }
}

使用我们新的Button元素只需要在我们的文件中简单的声明一下就可以了,之前的例子将会被简化。

Button { // our Button component
    id: button
    x: 12; y: 12
    text: "Start"
    onClicked: {
        status.text = "Button clicked!"
    }
}

Text { // text changes when button was clicked
    id: status
    x: 12; y: 76
    width: 116; height: 26
    text: "waiting ..."
    horizontalAlignment: Text.AlignHCenter
}

现在你可以在你的用户界面代码中随意的使用Button{ …}来作为按钮了。一个真正的按钮将更加复杂,比如提供按键反馈或者添加一些装饰。

注意

就个人而言,可以更进一步的使用基础元素对象(Item)作为根元素。这样可以防止用户改变我们设计的按钮的颜色,并且可以提供出更多相关控制的API(应用程序接口)。我们的目标是导出一个最小的API(应用程序接口)。实际上我们可以将根矩形框(Rectangle)替换为一个基础元素(Item),然后将一个矩形框(Rectangle)嵌套在这个根元素(root item)就可以完成了。

Item {
    id: root
    Rectangle {
        anchors.fill parent
        color: "lightsteelblue"
        border.color: "slategrey"
    }
    ...
}

使用这项技术可以很简单的创建一系列可重用的组件。

简单的转换(Simple Transformations)

转换操作改变了一个对象的几何状态。QML元素对象通常能够被平移,旋转,缩放。下面我们将讲解这些简单的操作和一些更高级的用法。
我们先从一个简单的转换开始。用下面的场景作为我们学习的开始。

简单的位移是通过改变x,y坐标来完成的。旋转是改变rotation(旋转)属性来完成的,这个值使用角度作为单位(0~360)。缩放是通过改变scale(比例)的属性来完成的,小于1意味着缩小,大于1意味着放大。旋转与缩放不会改变对象的几何形状,对象的x,y(坐标)与width/height(宽/高)也类似。只有绘制指令是被转换的对象。

在我们展示例子之前我想要介绍一些东西:ClickableImage元素(ClickableImage element),ClickableImage仅仅是一个包含鼠标区域的图像元素。我们遵循一个简单的原则,三次使用相同的代码描述一个用户界面最好可以抽象为一个组件。

// ClickableImage.qml

// Simple image which can be clicked

import QtQuick 2.0

Image {
    id: root
    signal clicked

    MouseArea {
        anchors.fill: parent
        onClicked: root.clicked()
    }
}

我们使用我们可点击图片元素来显示了三个火箭。当点击时,每个火箭执行一种简单的转换。点击背景将会重置场景。

// transformation.qml


import QtQuick 2.0

Item {
    // set width based on given background
    width: bg.width
    height: bg.height

    Image { // nice background image
        id: bg
        source: "assets/background.png"
    }

    MouseArea {
        id: backgroundClicker
        // needs to be before the images as order matters
        // otherwise this mousearea would be before the other elements
        // and consume the mouse events
        anchors.fill: parent
        onClicked: {
            // reset our little scene
            rocket1.x = 20
            rocket2.rotation = 0
            rocket3.rotation = 0
            rocket3.scale = 1.0
        }
    }

    ClickableImage {
        id: rocket1
        x: 20; y: 100
        source: "assets/rocket.png"
        onClicked: {
            // increase the x-position on click
            x += 5
        }
    }

    ClickableImage {
        id: rocket2
        x: 140; y: 100
        source: "assets/rocket.png"
        smooth: true // need antialising
        onClicked: {
            // increase the rotation on click
            rotation += 5
        }
    }

    ClickableImage {
        id: rocket3
        x: 240; y: 100
        source: "assets/rocket.png"
        smooth: true // need antialising
        onClicked: {
            // several transformations
            rotation += 5
            scale -= 0.05
        }
    }
}

火箭1在每次点击后X轴坐标增加5像素,火箭2每次点击后会旋转。火箭3每次点击后会缩小。对于缩放和旋转操作我们都设置了smooth:true来增加反锯齿,由于性能的原因通常是被关闭的(与剪裁属性clip类似)。当你看到你的图形中出现锯齿时,你可能就需要打开平滑(smooth)。

注意

为了获得更好的显示效果,当缩放图片时推荐使用已缩放的图片来替代,过量的放大可能会导致图片模糊不清。当你在缩放图片时你最好考虑使用smooth:true来提高图片显示质量。

使用MouseArea来覆盖整个背景,点击背景可以初始化火箭的值。

注意

在代码中先出现的元素有更低的堆叠顺序(叫做z顺序值z-order),如果你点击火箭1足够多次,你会看见火箭1移动到了火箭2下面。z轴顺序也可以使用元素对象的z-property来控制。

由于火箭2后出现在代码中,火箭2将会放在火箭1上面。这同样适用于MouseArea(鼠标区域),一个后出现在代码中的鼠标区域将会与之前的鼠标区域重叠,后出现的鼠标区域才能捕捉到鼠标事件。

请记住:文档中元素的顺序很重要。

定位元素(Positioning Element)

有一些QML元素被用于放置元素对象,它们被称作定位器,QtQuick模块提供了Row,Column,Grid,Flow用来作为定位器。你可以在下面的插图中看到它们使用相同内容的显示效果。

注意

在我们详细介绍前,我们先介绍一些相关的元素,红色(red),蓝色(blue),绿色(green),高亮(lighter)与黑暗(darker)方块,每一个组件都包含了一个48乘48的着色区域。下面是关于RedSquare(红色方块)的代码:

// RedSquare.qml

import QtQuick 2.0

Rectangle {
    width: 48
    height: 48
    color: "#ea7025"
    border.color: Qt.lighter(color)
}

请注意使用了Qt.lighter(color)来指定了基于填充色的边界高亮色。我们将会在后面的例子中使用到这些元素,希望后面的代码能够容易读懂。请记住每一个矩形框的初始化大小都是48乘48像素大小。

Column(列)元素将它的子对象通过顶部对齐的列方式进行排列。spacing属性用来设置每个元素之间的间隔大小。

// column.qml

import QtQuick 2.0

DarkSquare {
    id: root
    width: 120
    height: 240

    Column {
        id: column
        anchors.centerIn: parent
        spacing: 8
        RedSquare { }
        GreenSquare { width: 96 }
        BlueSquare { }
    }
}

// M1<<

Row(行)元素将它的子对象从左到右,或者从右到左依次排列,排列方式取决于layoutDirection属性。spacing属性用来设置每个元素之间的间隔大小。

// row.qml

import QtQuick 2.0

BrightSquare {
    id: root
    width: 400; height: 120

    Row {
        id: row
        anchors.centerIn: parent
        spacing: 20
        BlueSquare { }
        GreenSquare { }
        RedSquare { }
    }
}

Grid(栅格)元素通过设置rows(行数)和columns(列数)将子对象排列在一个栅格中。可以只限制行数或者列数。如果没有设置它们中的任意一个,栅格元素会自动计算子项目总数来获得配置,例如,设置rows(行数)为3,添加了6个子项目到元素中,那么会自动计算columns(列数)为2。属性flow(流)与layoutDirection(布局方向)用来控制子元素的加入顺序。spacing属性用来控制所有元素之间的间隔。

// grid.qml

import QtQuick 2.0

BrightSquare {
    id: root
    width: 160
    height: 160

    Grid {
        id: grid
        rows: 2
        columns: 2
        anchors.centerIn: parent
        spacing: 8
        RedSquare { }
        RedSquare { }
        RedSquare { }
        RedSquare { }
    }

}

最后一个定位器是Flow(流)。通过flow(流)属性和layoutDirection(布局方向)属性来控制流的方向。它能够从头到底的横向布局,也可以从左到右或者从右到左进行布局。作为加入流中的子对象,它们在需要时可以被包装成新的行或者列。为了让一个流可以工作,必须指定一个宽度或者高度,可以通过属性直接设定,或者通过anchor(锚定)布局设置。

// flow.qml

import QtQuick 2.0

BrightSquare {
    id: root
    width: 160
    height: 160

    Flow {
        anchors.fill: parent
        anchors.margins: 20
        spacing: 20
        RedSquare { }
        BlueSquare { }
        GreenSquare { }
    }
}

通常Repeater(重复元素)与定位器一起使用。它的工作方式就像for循环与迭代器的模式一样。在这个最简单的例子中,仅仅提供了一个循环的例子。

// repeater.qml

import QtQuick 2.0

DarkSquare {
    id: root
    width: 252
    height: 252
    property variant colorArray: ["#00bde3", "#67c111", "#ea7025"]


    Grid{
        anchors.fill: parent
        anchors.margins: 8
        spacing: 4
        Repeater {
            model: 16
            Rectangle {
                width: 56; height: 56
                property int colorIndex: Math.floor(Math.random()*3)
                color: root.colorArray[colorIndex]
                border.color: Qt.lighter(color)
                Text {
                    anchors.centerIn: parent
                    color: "#f0f0f0"
                    text: "Cell " + index
                }
            }
        }
    }
}

在这个重复元素的例子中,我们使用了一些新的方法。我们使用一个颜色数组定义了一组颜色属性。重复元素能够创建一连串的矩形框(16个,就像模型中定义的那样)。每一次的循环都会创建一个矩形框作为repeater的子对象。在矩形框中,我们使用了JS数学函数Math.floor(Math.random()*3)来选择颜色。这个函数会给我们生成一个0~2的随机数,我们使用这个数在我们的颜色数组中选择颜色。注意之前我们说过JavaScript是QtQuick中的一部分,所以这些典型的库函数我们都可以使用。

一个重复元素循环时有一个index(索引)属性值。当前的循环索引(0,1,2,….15)。我们可以使用这个索引值来做一些操作,例如在我们这个例子中使用Text(文本)显示当前索引值。

注意

高级的大数据模型处理和使用动态代理的动态视图会在模型与视图(model-view)章节中讲解。当有一小部分的静态数据需要显示时,使用重复元素是最好的方式。

布局元素(Layout Items)

QML使用anchors(锚)对元素进行布局。anchoring(锚定)是基础元素对象的基本属性,可以被所有的可视化QML元素使用。一个anchors(锚)就像一个协议,并且比几何变化更加强大。Anchors(锚)是相对关系的表达式,你通常需要与其它元素搭配使用。

一个元素有6条锚定线(top顶,bottom底,left左,right右,horizontalCenter水平中,verticalCenter垂直中)。在文本元素(Text Element)中有一条文本的锚定基线(baseline)。每一条锚定线都有一个偏移(offset)值,在top(顶),bottom(底),left(左),right(右)的锚定线中它们也被称作边距。对于horizontalCenter(水平中)与verticalCenter(垂直中)与baseline(文本基线)中被称作偏移值。

  1. 元素填充它的父元素。

    GreenSquare {
     BlueSquare {
         width: 12
         anchors.fill: parent
         anchors.margins: 8
         text: '(1)'
     }
    }
  2. 元素左对齐它的父元素。

    GreenSquare {
     BlueSquare {
         width: 48
         y: 8
         anchors.left: parent.left
         anchors.leftMargin: 8
         text: '(2)'
     }
    }
  3. 元素的左边与它父元素的右边对齐。

    GreenSquare {
     BlueSquare {
         width: 48
         anchors.left: parent.right
         text: '(3)'
     }
    }
  4. 元素中间对齐。Blue1与它的父元素水平中间对齐。Blue2与Blue1中间对齐,并且它的顶部对齐Blue1的底部。

    GreenSquare {
     BlueSquare {
         id: blue1
         width: 48; height: 24
         y: 8
         anchors.horizontalCenter: parent.horizontalCenter
     }
     BlueSquare {
         id: blue2
         width: 72; height: 24
         anchors.top: blue1.bottom
         anchors.topMargin: 4
         anchors.horizontalCenter: blue1.horizontalCenter
         text: '(4)'
     }
    }
  5. 元素在它的父元素中居中。

    GreenSquare {
     BlueSquare {
         width: 48
         anchors.centerIn: parent
         text: '(5)'
     }
    }
  6. 元素水平方向居中对齐父元素并向后偏移12像素,垂直方向居中对齐。

    GreenSquare {
     BlueSquare {
         width: 48
         anchors.horizontalCenter: parent.horizontalCenter
         anchors.horizontalCenterOffset: -12
         anchors.verticalCenter: parent.verticalCenter
         text: '(6)'
     }
    }

注意

我们的方格都打开了拖拽。试着拖放几个方格。你可以发现第一个方格无法被拖拽因为它每个边都被固定了,当然第一个方格的父元素能够被拖拽是因为它的父元素没有被固定。第二个方格能够在垂直方向上拖拽是因为它只有左边被固定了。类似的第三个和第四个方格也只能在垂直方向上拖拽是因为它们都使用水平居中对齐。第五个方格使用居中布局,它也无法被移动,第六个方格与第五个方格类似。拖拽一个元素意味着会改变它的x,y坐标。anchoring(锚定)比几何变化(例如x,y坐标变化)更强大是因为锚定线(anchored lines)的限制,我们将在后面讨论动画时看到这些功能的强大。

输入元素(Input Element)

我们已经使用过MouseArea(鼠标区域)作为鼠标输入元素。这里我们将更多的介绍关于键盘输入的一些东西。我们开始介绍文本编辑的元素:TextInput(文本输入)和TextEdit(文本编辑)。

4.7.1 文本输入(TextInput)

文本输入允许用户输入一行文本。这个元素支持使用正则表达式验证器来限制输入和输入掩码的模式设置。

// textinput.qml

import QtQuick 2.0

Rectangle {
    width: 200
    height: 80
    color: "linen"

    TextInput {
        id: input1
        x: 8; y: 8
        width: 96; height: 20
        focus: true
        text: "Text Input 1"
    }

    TextInput {
        id: input2
        x: 8; y: 36
        width: 96; height: 20
        text: "Text Input 2"
    }
}

用户可以通过点击TextInput来改变焦点。为了支持键盘改变焦点,我们可以使用KeyNavigation(按键向导)这个附加属性。

// textinput2.qml

import QtQuick 2.0

Rectangle {
    width: 200
    height: 80
    color: "linen"

    TextInput {
        id: input1
        x: 8; y: 8
        width: 96; height: 20
        focus: true
        text: "Text Input 1"
        KeyNavigation.tab: input2
    }

    TextInput {
        id: input2
        x: 8; y: 36
        width: 96; height: 20
        text: "Text Input 2"
        KeyNavigation.tab: input1
    }
}

KeyNavigation(按键向导)附加属性可以预先设置一个元素id绑定切换焦点的按键。

一个文本输入元素(text input element)只显示一个闪烁符和已经输入的文本。用户需要一些可见的修饰来鉴别这是一个输入元素,例如一个简单的矩形框。当你放置一个TextInput(文本输入)在一个元素中时,你需要确保其它的元素能够访问它导出的大多数属性。

我们提取这一段代码作为我们自己的组件,称作TLineEditV1用来重复使用。

// TLineEditV1.qml

import QtQuick 2.0

Rectangle {
    width: 96; height: input.height + 8
    color: "lightsteelblue"
    border.color: "gray"

    property alias text: input.text
    property alias input: input

    TextInput {
        id: input
        anchors.fill: parent
        anchors.margins: 4
        focus: true
    }
}

注意

如果你想要完整的导出TextInput元素,你可以使用property alias input: input来导出这个元素。第一个input是属性名字,第二个input是元素id。

我们使用TLineEditV1组件重写了我们的KeyNavigation(按键向导)的例子。

Rectangle {
    ...
    TLineEditV1 {
        id: input1
        ...
    }
    TLineEditV1 {
        id: input2
        ...
    }
}

尝试使用Tab按键来导航,你会发现焦点无法切换到input2上。这个例子中使用focus:true的方法不正确,这个问题是因为焦点被转移到input2元素时,包含TlineEditV1的顶部元素接收了这个焦点并且没有将焦点转发给TextInput(文本输入)。为了防止这个问题,QML提供了FocusScope(焦点区域)。

4.7.2 焦点区域(FocusScope)

一个焦点区域(focus scope)定义了如果焦点区域接收到焦点,它的最后一个使用focus:true的子元素接收焦点,它将会把焦点传递给最后申请焦点的子元素。我们创建了第二个版本的TLineEdit组件,称作TLineEditV2,使用焦点区域(focus scope)作为根元素。

// TLineEditV2.qml

import QtQuick 2.0

FocusScope {
    width: 96; height: input.height + 8
    Rectangle {
        anchors.fill: parent
        color: "lightsteelblue"
        border.color: "gray"

    }

    property alias text: input.text
    property alias input: input

    TextInput {
        id: input
        anchors.fill: parent
        anchors.margins: 4
        focus: true
    }
}

现在我们的例子将像下面这样:

Rectangle {
    ...
    TLineEditV2 {
        id: input1
        ...
    }
    TLineEditV2 {
        id: input2
        ...
    }
}

按下Tab按键可以成功的在两个组件之间切换焦点,并且能够正确的将焦点锁定在组件内部的子元素中。

4.7.3 文本编辑(TextEdit)

文本编辑(TextEdit)元素与文本输入(TextInput)非常类似,它支持多行文本编辑。它不再支持文本输入的限制,但是提供了已绘制文本的大小查询(paintedHeight,paintedWidth)。我们也创建了一个我们自己的组件TTextEdit,可以编辑它的背景,使用focus scope(焦点区域)来更好的切换焦点。

// TTextEdit.qml

import QtQuick 2.0

FocusScope {
    width: 96; height: 96
    Rectangle {
        anchors.fill: parent
        color: "lightsteelblue"
        border.color: "gray"

    }

    property alias text: input.text
    property alias input: input

    TextEdit {
        id: input
        anchors.fill: parent
        anchors.margins: 4
        focus: true
    }
}

你可以像下面这样使用这个组件:

// textedit.qml

import QtQuick 2.0

Rectangle {
    width: 136
    height: 120
    color: "linen"

    TTextEdit {
        id: input
        x: 8; y: 8
        width: 120; height: 104
        focus: true
        text: "Text Edit"
    }
}

4.7.4 按键元素(Key Element)

附加属性key允许你基于某个按键的点击来执行代码。例如使用up,down按键来移动一个方块,left,right按键来旋转一个元素,plus,minus按键来缩放一个元素。

// keys.qml

import QtQuick 2.0

DarkSquare {
    width: 400; height: 200

    GreenSquare {
        id: square
        x: 8; y: 8
    }
    focus: true
    Keys.onLeftPressed: square.x -= 8
    Keys.onRightPressed: square.x += 8
    Keys.onUpPressed: square.y -= 8
    Keys.onDownPressed: square.y += 8
    Keys.onPressed: {
        switch(event.key) {
            case Qt.Key_Plus:
                square.scale += 0.2
                break;
            case Qt.Key_Minus:
                square.scale -= 0.2
                break;
        }

    }
}

高级用法(Advanced Techniques)

后续添加。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 2291184112@qq.com

×

喜欢就点赞,疼爱就打赏