《Qt5 Cadaques》学习笔记(四):快速入门

本章提供 QML 的概述,Qt 5 中使用的声明性用户界面语言。我们将讨论 QML 语法,它是一个元素树,然后是最重要的基本元素的概述。 稍后我们将简要介绍如何创建我们自己的元素(称为组件)以及如何使用属性操作来转换元素。 最后,我们将研究如何在布局中将元素排列在一起,最后看一下用户可以提供输入的元素。

4.1 QML 语法

QML 是一种声明性语言,用于描述您的应用程序的用户界面。 它将用户界面分解为更小的元素,这些元素可以组合成组件。 QML 描述了这些用户界面元素的外观和行为。 可以使用 JavaScript 代码丰富此用户界面描述,以提供简单但也更复杂的逻辑。 从这个角度来看,它遵循 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 语句导入特定版本的模块。
  • 可以使用// 单行注释或/* */ 多行注释进行注释。 就像在 C/C++ 和 JavaScript 中一样
  • 每个 QML 文件都需要只有一个根元素,例如 HTML
  • 元素按其类型声明,后跟{ }
  • 元素可以有属性,它们的形式为“名称:值”
  • QML 文档中的任意元素可以通过使用它们的 id(不带引号的标识符)来访问
  • 元素可以嵌套,即父元素可以有子元素。 可以使用 parent 关键字访问父元素

import 语句是你导入一个特定版本的模块。 对于 Qt 附带的 QML 模块,版本链接到您打算使用的 Qt 版本。 版本号越低,可以使用越早的Qt版本。 import 语句的小版本与 Qt 版本的小版本相匹配,因此 Qt 5.11 对应 QtQuick 2.11,Qt 5.12 对应 QtQuick 2.12 等等。 在 Qt 5.11 之前,Qt 附带的 QML 模块有自己的版本控制序列,这意味着 QtQuick 遵循 Qt 版本,而 Qt Quick.Controls 从 Qt 5.7 的 2.0 版本开始,到 Qt 5.11 的版本为 2.4。

提示:通常您希望通过 id 访问特定元素或使用 parent 关键字访问父元素。 因此,使用 id:root 将根元素命名为“root”是一种很好的做法。 然后,您不必考虑如何在 QML 文档中命名根元素。

提示:您可以使用 Qt Quick 运行时从操作系统的命令行运行该示例,如下所示:

$ $QTDIR/bin/qmlscene RectangleExample.qml

您需要将 $QTDIR 替换为 Qt 安装路径的位置。 qmlscene 可执行文件初始化 Qt Quick 运行时并解释提供的 QML 文件。
在Qt Creator中,可以打开对应的工程文件,运行文件RectangleExample.qml。

4.1.1 属性

元素通过使用它们的元素名称来声明,但通过使用它们的属性或通过创建自定义属性来定义。 一个属性是一个简单的键值对,例如 宽度:100,文字:'问候',颜色:'#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. 一个属性可以依赖于一个或多个其他属性。这称为绑定。绑定属性已更新。当它的依赖属性发生变化时。它像合同一样工作,在这种情况下,高度应始终是宽度的两倍。
  4. 使用属性限定符后跟类型、名称和可选的初始值(属性 <type> <name> : <value>)将自己的属性添加到元素。如果没有给出初始值,则选择系统初始值。
    注意:如果没有给出属性名称,您也可以通过在属性声明前加上 default 关键字来将一个属性声明为默认属性。 这用于例如当您添加子元素时,如果子元素是可见元素,它们会自动添加到类型为 list 的默认属性 children 中。
  5. 声明属性的另一种重要方式是使用别名关键字(属性别名<name>:<reference>)。 alias 关键字允许我们将对象的属性或对象本身从类型内转发到外部范围。稍后在定义组件以将内部属性或元素 ID 导出到根级别时,我们将使用此技术。属性别名不需要类型,它使用引用的属性或对象的类型。
  6. text属性依赖于int类型的自定义属性times。基于 int 的值会自动转换为字符串类型。表达式本身是另一个绑定示例,它会导致每次时间属性更改时都会更新文本。
  7.  一些属性是分组属性。当属性更加结构化并且相关属性应该组合在一起时,使用此功能。另一种编写分组属性的方法是 font { family:"Ubuntu";像素大小:24}。
  8.  一些属性附加到元素本身。这适用于在应用程序中仅出现一次的全局相关元素(例如键盘输入)。写作是 <Element>.property>:<value>。
  9. 对于每个属性,您都可以提供一个信号处理程序。在属性更改后调用此处理程序。例如,在这里我们希望在高度变化时得到通知,并使用内置控制台将消息记录到系统。

警告:元素 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
    }
}
  1. 文本更改处理程序 onTextChanged 每次文本因按下空格键而更改时打印当前文本
  2. 当文本元素接收到空格键(因为用户按下了键盘上的空格键)时,我们调用 JavaScript 函数 increment()。
  3. 以 function <name>(<parameters>) { ... } 的形式定义一个 JavaScript 函数,这增加了我们的计数器 spacePressed。 每次 spacePressed 增加时,绑定的属性也会更新。

注意:QML : (binding) 和 JavaScript = (assignment) 之间的区别在于,绑定是一种契约,并且在绑定的生命周期内保持真实,而 JavaScript 赋值 (=) 是一次性值赋值。当一个新的绑定被设置到属性时,甚至当一个 JavaScript 值被分配给该属性时, 绑定的生命周期结束。 例如,将 text 属性设置为空字符串的键处理程序会破坏我们的增量显示:

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

按下 Escape 后,按下空格键将不再更新显示,因为先前绑定的文本属性(文本:“按下空格:”+ spacePresses +“次”)已被破坏。
当您在这种情况下更改属性的策略相互冲突时(通过绑定更改属性增量来更新文本,并通过 JavaScript 赋值清除文本),那么您不能使用绑定! 您需要在两个属性更改路径上都使用赋值,因为绑定将被赋值破坏(违反协议!)。

4.2 基本要素

元素可以分为视觉元素和非视觉元素。 视觉元素(如矩形)具有几何形状,通常在屏幕上呈现一个区域。 非可视元素(如计时器)提供一般功能,通常用于操作可视元素。
目前,我们将专注于基本的视觉元素,例如 Item、Rectangle、Text、Image 和 MouseArea。 然而,通过使用 Qt Quick Controls 2 模块,可以创建由标准平台组件(如按钮、标签和滑块)构建的用户界面。

4.2.1 项目元素

Item 是所有视觉元素的基础元素,因此所有其他视觉元素都继承自 Item。 它本身不绘制任何东西,但定义了所有视觉元素共有的所有属性:

属性
Geometryx 和 y 定义元素扩展的左上角位置、宽度和高度,以及 z 堆叠顺序以将元素从其自然顺序向上或向下提升。
Layout handlinganchors(左、右、上、下、垂直和水平中心)以相对于其他元素及其margins定位元素
Key handling附加的 Key 和 KeyNavigation 属性来控制键处理和输入focus属性来首先启用键处理
Transformationscale和rotate以及 x、y、z 变换的通用transform属性列表及其 transformOrigin 点
Visualopacity 用于控制透明度,可见以显示/隐藏元素,clip 以限制对元素边界的绘制操作和smooth以提高渲染质量
State definitionstates列表属性与支持的状态列表和当前states属性以及transitions列表属性以动画状态更改

为了更好地理解不同的属性,我们将尝试在本章介绍的元素的上下文中介绍它们。 请记住,这些基本属性在每个视觉元素上都可用,并且在这些元素中的作用相同。

注意:Item 元素通常用作其他元素的容器,类似于 HTML 中的 div 元素。

4.2.2 矩形元素

Rectangle 扩展了 Item 并为其添加了填充颜色。 此外,它支持由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)。 您可以通过不同的方式在 QML 中提供颜色,但最常见的方式是 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 的颜色标记了该位置的颜色。

注意:没有设置宽度/高度的矩形将不可见。 当您有几个相互依赖的矩形宽度(高度)并且您的组合逻辑出现问题时,通常会发生这种情况。 所以小心!

无法创建有角度的渐变。 为此,最好使用预定义的图像。 一种可能性是仅使用渐变旋转矩形,但请注意旋转矩形的几何形状不会改变,因此会导致混乱,因为元素的几何形状与可见区域不同。 从作者的角度来看,在这种情况下使用设计的渐变图像确实更好。

4.2.3 文本元素

要显示文本,您可以使用 Text 元素。 它最显着的属性是 string 类型的 text 属性。
元素根据给定的文本和使用的字体计算其初始宽度和高度。 可以使用字体属性组(例如 font.family、font.pixelSize、...)来影响字体。 要更改文本的颜色,只需使用 color 属性。

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 元素是透明的。 为文本元素提供合理的背景是整体设计的一部分。

注意:注意文本初始宽度(高度)取决于文本字符串和字体集。 没有设置宽度且没有文本的 Text 元素将不可见,因为初始宽度将为 0。

通常当您想要布局文本元素时,您需要区分对齐文本元素边界框内的文本或对齐元素边界框本身。 在前一种情况下,您想使用 HorizontalAlignment 和 VerticalAlignment 属性,而在后一种情况下,您想操作元素几何形状或使用锚点。

4.2.4 图像元素

Image 元素能够显示各种格式的图像(例如 PNG、JPG、GIF、BMP、WEBP)。 有关支持的图像格式的完整列表,请参阅 Qt 文档。 除了提供图像 URL 的明显源属性外,它还包含一个控制调整大小行为的 fillMode。

Image {
    x: 12; y: 12
    // width: 72
    // height: 72
    source: "assets/triangle_red.png"
}
Image {
    x: 12+64+12; y: 12
    // width: 72
    height: 72/2
    source: "assets/triangle_red.png"
    fillMode: Image.PreserveAspectCrop
    clip: true
}

 注意:URL 可以是带有正斜杠的本地路径(“./images/home.png”)或网络链接(例如“http://example.org/home.png”)。

使用 PreserveAspectCrop 的图像元素也应该启用剪辑以避免图像数据
在图像边界之外渲染。 默认情况下,剪辑是禁用的(‘‘剪辑:假‘‘)。 您需要启用剪辑('' clip: true'')以将绘画限制在元素边界矩形内。 这可以用于任何视觉元素。

提示:使用 C++,您可以使用 QQuickImageProvider 创建自己的图像提供程序。 这使您可以即时创建图像并进行线程图像加载。

4.2.5 鼠标区域元素

为了与这些元素进行交互,您通常会使用 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
}

注意:这是 Qt Quick 的一个重要方面,输入处理与视觉呈现是分开的。 通过这种方式,您可以向用户显示界面元素,但交互区域可以更大。

对于更复杂的交互,Qt Quick Input Handlers 在 Qt 5.12 中引入。 它们旨在代替 MouseArea 和 Flickable 等元素使用,并提供更大的控制和灵活性。
这个想法是在每个处理程序实例中处理一个交互方面,而不是将来自给定源的所有事件的处理集中在单个元素中,这是以前的情况。

4.3 组件

组件是一个可重用的元素,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
}

UI 看起来与此类似。 左侧是初始状态的用户界面,右侧是单击按钮后的状态。

我们现在的任务是在可重用组件中提取按钮 UI。 为此,我们很快会考虑为我们的按钮提供一个可能的 API。 您可以通过想象其他人应该如何使用您的按钮来做到这一点。 以下是我的想法:

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

我想使用 text 属性设置文本并实现我自己的点击处理程序。 另外,我希望按钮具有合理的初始大小,我可以覆盖它(例如,宽度:240)。
为了实现这一点,我们创建了一个 Button.qml 文件并将我们的按钮 UI 复制到其中。 此外,我们需要导出用户可能想要在根级别更改的属性。

// Button.qml

import QtQuick 2.5

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()
        }
    }
}

我们已经导出了文本并在根级别单击了信号。 通常我们将根元素命名为 root 以使引用更容易。 我们使用 QML 的别名功能,这是一种将嵌套 QML 元素内的属性导出到根级别并使其可供外部世界使用的方法。 重要的是要知道,只有根级别的属性才能被其他组件从这个文件外部访问。
要使用我们的新 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
}

现在您可以在 UI 中使用任意数量的按钮,只需使用 Button { ... }。 真正的按钮可能更复杂,例如在单击时提供反馈或显示更好的装饰。

注意:如果您愿意,您甚至可以更进一步,将项目用作根元素。 这可以防止用户更改我们设计的按钮的颜色,并为我们提供对导出 API 的更多控制。 目标应该是导出一个最小 API。 实际上,这意味着我们需要将根 Rectangle 替换为一个 Item,并使该矩形成为根项中的嵌套元素。

Item {
    id: root
    width: 116; height: 26
    property alias text: label.text
    signal clicked
    Rectangle {
        anchors.fill parent
        color: "lightsteelblue"
        border.color: "slategrey"
    }
    ...
}

使用这种技术,很容易创建一系列可重用的组件。

4.4 简单变换

变换操纵对象的几何形状。 通常,QML 项可以平移、旋转和缩放。 这些操作有一种简单的形式和一种更高级的方式。
让我们从简单的转换开始。 这是我们的场景作为我们的起点。
通过更改 x,y 位置可以完成简单的平移。 使用rotation 属性进行旋转。
该值以度为单位 (0 .. 360)。 使用 scale 属性进行缩放,值 <1 表示元素按比例缩小,>1 表示元素按比例放大。 旋转和缩放不会改变您的几何形状。 项目 x、y 和宽度/高度没有改变。 只是绘画指令被转换。
在展示示例之前,我想介绍一个小帮手:ClickableImage 元素。 ClickableImage 只是一个带有鼠标区域的图像。 这带来了一个有用的经验法则——如果你已经复制了一段代码三次,将它提取到一个组件中。

// ClickableImage.qml
// Simple image which can be clicked

import QtQuick 2.5

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

我们使用可点击的图像来呈现三个对象(框、圆、三角形)。 单击时,每个对象都会执行简单的转换。 单击背景将重置场景。

// transformation.qml

import QtQuick 2.5

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
            circle.x = 84
            box.rotation = 0
            triangle.rotation = 0
            triangle.scale = 1.0
        }
    }

    ClickableImage {
        id: circle
        x: 84; y: 68
        source: "assets/circle_blue.png"
        antialiasing: true
        onClicked: {
            // increase the x-position on click
            x += 20
        }
    }

    ClickableImage {
        id: box
        x: 164; y: 68
        source: "assets/box_green.png"
        antialiasing: true
        onClicked: {
            // increase the rotation on click
            rotation += 15
        }
    }

    ClickableImage {
        id: triangle
        x: 248; y: 68
        source: "assets/triangle_red.png"
        antialiasing: true
        onClicked: {
            // several transformations
            rotation += 15
            scale += 0.05
        }
    }

    function _test_transformed() {
        circle.x += 20
        box.rotation = 15
        triangle.scale = 1.2
        triangle.rotation = -15
    }

    function _test_overlap() {
        circle.x += 40
        box.rotation = 15
        triangle.scale = 2.0
        triangle.rotation = 45
    }
}

 圆圈会在每次单击时增加 x 位置,并且框将在每次单击时旋转。 每次单击时,三角形都会旋转并放大图像,以演示组合变换。 对于缩放和旋转操作,我们设置 antialiasing: true 以启用抗锯齿,出于性能原因,它默认为关闭状态(与剪辑属性 clip 相同)。 在您自己的工作中,当您在图形中看到一些光栅化边缘时,您可能应该平滑地打开。

注意:为了在缩放图像时获得更好的视觉质量,建议缩小而不是放大图像。 使用较大的缩放因子放大图像将导致缩放伪影(图像模糊)。 在缩放图像时,您应该考虑使用“antialiasing: true”来启用更高质量的滤镜。

背景 MouseArea 覆盖整个背景并重置对象值。

注意:在代码中较早出现的元素具有较低的堆叠顺序(称为 z-order)。 如果您在圆圈上单击足够长的时间,您会看到它移动到框下方。 z-order 也可以通过 Item 的 z-property 来操作。

这是因为 box 出现在代码的后面。 这也适用于鼠标区域。 代码中后面的鼠标区域将与代码前面的鼠标区域重叠(并因此抓住鼠标事件)。
请记住:文档中元素的顺序很重要。

 4.5 定位元素

有许多用于定位项目的 QML 元素。 这些称为定位器,QtQuick 模块行、列、网格和流中提供了以下内容。 在下图中可以看到它们显示相同的内容。

注意:在我们详细介绍之前,让我介绍一些辅助元素。 红色、蓝色、绿色、浅色和深色方块。 这些组件中的每一个都包含一个 48x48 像素的彩色矩形。 作为参考,这里是 RedSquare 的源代码:

// RedSquare.qml

import QtQuick 2.5

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

请注意使用 Qt.lighter(color) 根据填充颜色生成较浅的边框颜色。 我们将在接下来的示例中使用这些帮助程序,以使源代码更加紧凑并希望具有可读性。 请记住,每个矩形的初始大小为 48x48 像素。

Column 元素通过将子项堆叠在一起,将它们排列成一列。 spacing属性可用于将每个子元素彼此分开。

// column.qml

import QtQuick 2.5

DarkSquare {
    id: root
    width: 120
    height: 240
    Column {
        id: row
        anchors.centerIn: parent
        spacing: 8
        RedSquare { }
        GreenSquare { width: 96 }
        BlueSquare { }
    }
}

根据 layoutDirection 属性,Row 元素将其子项从左到右或从右到左彼此相邻放置。 同样,spacing用于分隔子项。

// row.qml
import QtQuick 2.5

BrightSquare {
    id: root
    width: 400; height: 120
    Row {
        id: row
        anchors.centerIn: parent
        spacing: 20
        BlueSquare { }
        GreenSquare { }
        RedSquare { }
    }
}

Grid 元素将其子元素排列在一个网格中,通过设置 rows 和 columns 属性,可以限制行数或列数。 通过不设置其中任何一个,另一个是根据子项的数量计算的。 例如,将行设置为 3 并添加 6 个子项将导致 2 列。 属性 flow 和 layoutDirection 用于控制项目添加到网格的顺序,而spacing控制分隔子项目的空间量。

// grid.qml
import QtQuick 2.5

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 控制流的方向。 它可以横向运行,也可以从上到下运行。 它也可以从左到右或相反方向运行。 当项目添加到流中时,它们会根据需要进行包装以形成新的行或列。 为了使流程工作,它必须具有宽度或高度。 这可以直接设置,也可以通过锚布局设置。

// flow.qml
import QtQuick 2.5

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.5

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 个,由模型定义)。 对于每个循环,他创建由中继器的子级定义的矩形。 在矩形中,我们使用 JS 数学函数 Math.floor(Math.random()*3) 选择颜色。 这给了我们一个 0..2 范围内的随机数,我们用它来从颜色数组中选择颜色。 如前所述,JavaScript 是 Qt Quick 的核心部分,因此我们可以使用标准库。
转发器将索引属性注入转发器。 它包含当前循环索引。 (0,1,..15)。 我们可以使用它来根据索引做出自己的决定,或者在我们的例子中使用 Text 元素来可视化当前索引。

注意:更大模型和动态视图的更高级处理包含在自己的模型视图章节中。 当要呈现少量静态数据时,最好使用中继器。

4.6 布局项

QML 提供了一种使用锚点布局项目的灵活方式。 锚定的概念是 Item 基本属性的一部分,可用于所有可视 QML 元素。 锚点的作用就像上下文一样,比相比较的几何变化更强大。 锚是相对性的表达,你总是需要一个相关的元素来锚定。

一个元素有 6 条主要的锚线(上、下、左、右、水平中心、垂直中心)。 另外还有文本元素中文本的基线锚点。 每条锚线都有一个偏移量。 在顶部、底部左侧和右侧的情况下,它们被称为边距。 对于水平中心、垂直中心和基线,它们被称为偏移量。 

 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 也是水平居中的,但在 Blue 1 上,其顶部与 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. 元素使用水平和垂直中心线在父元素上以左偏移为中心

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

注意:我们的方块已经增强,可以拖动。 试试这个例子并拖动一些方块。
您会看到 (1) 无法拖动,因为它已锚定在所有侧面,确保您可以拖动 (1) 的父项,因为它根本没有锚定。 (2) 可以垂直拖动,因为只有左侧被锚定。 类似地适用于(3)。 (4) 只能垂直拖动,因为两个方块都是水平居中的。 (5) 以父级为中心,因此不能拖动,与 (7) 类似。 拖动元素意味着改变它们的 x,y 位置。 由于锚定比 x,y 等几何变化更强,因此拖动受锚定线的限制。 我们稍后会在讨论动画时看到这种效果。

4.7 输入元素

我们已经使用 MouseArea 作为鼠标输入元素。 接下来,我们将专注于键盘输入。 我们从文本编辑元素开始:TextInput 和 TextEdit。

4.7.1 文本输入

TextInput 允许用户输入一行文本。 该元素支持输入约束,例如验证器、inputMask 和 echoMode。

// textinput.qml
import QtQuick 2.5

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.5

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 绑定到在给定按键上切换焦点。
文本输入元素除了闪烁的光标和输入的文本之外没有视觉呈现。 为了让用户能够将元素识别为输入元素,它需要一些视觉装饰,例如,一个简单的矩形。 将 TextInput 放置在需要的元素中时,请确保导出希望其他人能够访问的主要属性。
我们将这段代码移动到我们自己的名为 TLineEditV1 的组件中以供重用。

// TLineEditV1.qml
import QtQuick 2.5

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
        focus: true
    }
}

注意:如果要完全导出 TextInput,可以使用属性别名 input: input 导出元素。 第一个输入是属性名,第二个输入是元素 id。

我们用新的 TLineEditV1 组件重写了 KeyNavigation 示例。

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

 并尝试使用 tab 键进行导航。 您将体验到焦点不会更改为 input2。 仅使用 focus: true 是不够的。 问题出现了,焦点被转移到了 input2 元素
TlineEditV1(我们的 Rectangle)内的顶级项目获得焦点,但没有将焦点转发到 TextInput。 为了避免这种情况,QML 提供了 FocusScope。

4.7.2 焦点范围

焦点作用域声明最后一个具有焦点的子元素:如果焦点作用域接收到焦点,则为 true 接收焦点。 因此,它将焦点转发到最后一个请求焦点的子元素。 我们将创建 TLineEdit 组件的第二个版本,称为 TLineEditV2,使用焦点范围作为根元素。
 

// TLineEditV2.qml
import QtQuick 2.5

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 键可以成功地在 2 个组件之间切换焦点,并且组件内的正确子元素被聚焦。

4.7.3 文本编辑

TextEdit 与 TextInput 非常相似,支持多行文本编辑字段。 它没有文本约束属性,因为这取决于查询文本的绘制大小(paintedHeight、paintedWidth)。
我们还创建了自己的名为 TTextEdit 的组件来提供编辑背景并使用焦点范围来更好地进行焦点转发。

// TTextEdit.qml
import QtQuick 2.5

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
    }
}

您可以像 TLineEdit 组件一样使用它

// textedit.qml
import QtQuick 2.5

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 键元素

附加属性 Keys 允许基于某些按键执行代码。 例如,要移动一个正方形并进行缩放,我们可以使用上、下、左、右键来平移元素,使用加号、减号键来缩放元素。

// keys.qml
import QtQuick 2.5

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;
        }
    }
}

 4.8 高级技术

4.8.1 QML 的性能

QML 和 Javascript 是解释型语言。 这意味着它们在执行之前不必由编译器处理。 相反,它们在执行引擎中运行。 然而,由于解释是一项成本高昂的操作,因此使用各种技术来提高性能。
QML 引擎使用即时 (JIT) 编译来提高性能。 它还缓存中间输出以避免重新编译。 作为开发人员,这可以无缝地为您工作。 唯一的痕迹是可以在源文件旁边找到以 qmlc 和 jsc 结尾的文件。
如果您想避免初始解析引起的初始启动损失,您还可以预编译您的 QML 和 Javascript。 这需要您将代码放入 Qt 资源文件中,并且在 Qt 文档的 Compiling QML Ahead of Time 一章中有详细描述。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值