本章提供 QML 的概述,Qt 5 中使用的声明性用户界面语言。我们将讨论 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: :
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。
元素通过使用它们的元素名称来声明,但通过使用它们的属性或通过创建自定义属性来定义。 一个属性是一个简单的键值对,例如 宽度: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"
}
让我们来看看属性的不同特征:
- id 是一个非常特殊的类似属性的值,它用于引用 QML 文件(在 QML 中称为“文档”)中的元素。 id 不是字符串类型,而是标识符和 QML 语法的一部分。 id 在文档中必须是唯一的,不能重置为不同的值,也不能被查询。 (它的行为很像 C++ 世界中的引用。)
- 属性可以设置为一个值,具体取决于它的类型。如果没有为属性指定值,则将选择初始值。您需要查阅特定元素的文档以获取有关属性初始值的更多信息。
- 一个属性可以依赖于一个或多个其他属性。这称为绑定。绑定属性已更新。当它的依赖属性发生变化时。它像合同一样工作,在这种情况下,高度应始终是宽度的两倍。
- 使用属性限定符后跟类型、名称和可选的初始值(属性
: )将自己的属性添加到元素。如果没有给出初始值,则选择系统初始值。
注意:如果没有给出属性名称,您也可以通过在属性声明前加上 default 关键字来将一个属性声明为默认属性。 这用于例如当您添加子元素时,如果子元素是可见元素,它们会自动添加到类型为 list 的默认属性 children 中。- 声明属性的另一种重要方式是使用别名关键字(属性别名
: )。 alias 关键字允许我们将对象的属性或对象本身从类型内转发到外部范围。稍后在定义组件以将内部属性或元素 ID 导出到根级别时,我们将使用此技术。属性别名不需要类型,它使用引用的属性或对象的类型。 - text属性依赖于int类型的自定义属性times。基于 int 的值会自动转换为字符串类型。表达式本身是另一个绑定示例,它会导致每次时间属性更改时都会更新文本。
- 一些属性是分组属性。当属性更加结构化并且相关属性应该组合在一起时,使用此功能。另一种编写分组属性的方法是 font { family:"Ubuntu";像素大小:24}。
- 一些属性附加到元素本身。这适用于在应用程序中仅出现一次的全局相关元素(例如键盘输入)。写作是
.property>: 。 - 对于每个属性,您都可以提供一个信号处理程序。在属性更改后调用此处理程序。例如,在这里我们希望在高度变化时得到通知,并使用内置控制台将消息记录到系统。
警告:元素 id 只能用于引用文档中的元素(例如当前文件)。 QML 提供了一种称为动态作用域的机制,之后加载的文档会覆盖之前加载的文档中的元素 id。 如果之前加载的文档尚未被覆盖,这使得可以从早期加载的文档中引用元素 id。 这就像创建全局变量。 不幸的是,这在实践中经常导致非常糟糕的代码,其中程序取决于执行顺序。 不幸的是,此功能无法关闭。 请谨慎使用它,甚至最好不要使用这种机制。 最好使用文档根元素上的属性将要提供给外界的元素导出。
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
}
}
- 文本更改处理程序 onTextChanged 每次文本因按下空格键而更改时打印当前文本
- 当文本元素接收到空格键(因为用户按下了键盘上的空格键)时,我们调用 JavaScript 函数 increment()。
- 以 function
( ) { ... } 的形式定义一个 JavaScript 函数,这增加了我们的计数器 spacePressed。 每次 spacePressed 增加时,绑定的属性也会更新。
注意:QML : (binding) 和 JavaScript = (assignment) 之间的区别在于,绑定是一种契约,并且在绑定的生命周期内保持真实,而 JavaScript 赋值 (=) 是一次性值赋值。当一个新的绑定被设置到属性时,甚至当一个 JavaScript 值被分配给该属性时, 绑定的生命周期结束。 例如,将 text 属性设置为空字符串的键处理程序会破坏我们的增量显示:
Keys.onEscapePressed: { label.text = '' }
按下 Escape 后,按下空格键将不再更新显示,因为先前绑定的文本属性(文本:“按下空格:”+ spacePresses +“次”)已被破坏。
当您在这种情况下更改属性的策略相互冲突时(通过绑定更改属性增量来更新文本,并通过 JavaScript 赋值清除文本),那么您不能使用绑定! 您需要在两个属性更改路径上都使用赋值,因为绑定将被赋值破坏(违反协议!)。
元素可以分为视觉元素和非视觉元素。 视觉元素(如矩形)具有几何形状,通常在屏幕上呈现一个区域。 非可视元素(如计时器)提供一般功能,通常用于操作可视元素。
目前,我们将专注于基本的视觉元素,例如 Item、Rectangle、Text、Image 和 MouseArea。 然而,通过使用 Qt Quick Controls 2 模块,可以创建由标准平台组件(如按钮、标签和滑块)构建的用户界面。
Item 是所有视觉元素的基础元素,因此所有其他视觉元素都继承自 Item。 它本身不绘制任何东西,但定义了所有视觉元素共有的所有属性:
组 | 属性 |
Geometry | x 和 y 定义元素扩展的左上角位置、宽度和高度,以及 z 堆叠顺序以将元素从其自然顺序向上或向下提升。 |
Layout handling | anchors(左、右、上、下、垂直和水平中心)以相对于其他元素及其margins定位元素 |
Key handling | 附加的 Key 和 KeyNavigation 属性来控制键处理和输入focus属性来首先启用键处理 |
Transformation | scale和rotate以及 x、y、z 变换的通用transform属性列表及其 transformOrigin 点 |
Visual | opacity 用于控制透明度,可见以显示/隐藏元素,clip 以限制对元素边界的绘制操作和smooth以提高渲染质量 |
State definition | states列表属性与支持的状态列表和当前states属性以及transitions列表属性以动画状态更改 |
为了更好地理解不同的属性,我们将尝试在本章介绍的元素的上下文中介绍它们。 请记住,这些基本属性在每个视觉元素上都可用,并且在这些元素中的作用相同。
注意:Item 元素通常用作其他元素的容器,类似于 HTML 中的 div 元素。
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 的颜色标记了该位置的颜色。
注意:没有设置宽度/高度的矩形将不可见。 当您有几个相互依赖的矩形宽度(高度)并且您的组合逻辑出现问题时,通常会发生这种情况。 所以小心!
无法创建有角度的渐变。 为此,最好使用预定义的图像。 一种可能性是仅使用渐变旋转矩形,但请注意旋转矩形的几何形状不会改变,因此会导致混乱,因为元素的几何形状与可见区域不同。 从作者的角度来看,在这种情况下使用设计的渐变图像确实更好。
要显示文本,您可以使用 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 属性,而在后一种情况下,您想操作元素几何形状或使用锚点。
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 创建自己的图像提供程序。 这使您可以即时创建图像并进行线程图像加载。
为了与这些元素进行交互,您通常会使用 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 等元素使用,并提供更大的控制和灵活性。
这个想法是在每个处理程序实例中处理一个交互方面,而不是将来自给定源的所有事件的处理集中在单个元素中,这是以前的情况。
组件是一个可重用的元素,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" } ... }
使用这种技术,很容易创建一系列可重用的组件。
变换操纵对象的几何形状。 通常,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 出现在代码的后面。 这也适用于鼠标区域。 代码中后面的鼠标区域将与代码前面的鼠标区域重叠(并因此抓住鼠标事件)。
请记住:文档中元素的顺序很重要。
有许多用于定位项目的 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 元素来可视化当前索引。
注意:更大模型和动态视图的更高级处理包含在自己的模型视图章节中。 当要呈现少量静态数据时,最好使用中继器。
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 等几何变化更强,因此拖动受锚定线的限制。 我们稍后会在讨论动画时看到这种效果。
我们已经使用 MouseArea 作为鼠标输入元素。 接下来,我们将专注于键盘输入。 我们从文本编辑元素开始:TextInput 和 TextEdit。
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。
焦点作用域声明最后一个具有焦点的子元素:如果焦点作用域接收到焦点,则为 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 个组件之间切换焦点,并且组件内的正确子元素被聚焦。
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"
}
}
附加属性 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;
}
}
}
QML 和 Javascript 是解释型语言。 这意味着它们在执行之前不必由编译器处理。 相反,它们在执行引擎中运行。 然而,由于解释是一项成本高昂的操作,因此使用各种技术来提高性能。
QML 引擎使用即时 (JIT) 编译来提高性能。 它还缓存中间输出以避免重新编译。 作为开发人员,这可以无缝地为您工作。 唯一的痕迹是可以在源文件旁边找到以 qmlc 和 jsc 结尾的文件。
如果您想避免初始解析引起的初始启动损失,您还可以预编译您的 QML 和 Javascript。 这需要您将代码放入 Qt 资源文件中,并且在 Qt 文档的 Compiling QML Ahead of Time 一章中有详细描述。