第五章:快速入门

第五章:快速入门

本章介绍的QML, 是在Qt 6中使用的一种声明式用户界面语言。我们将讨论QML语法,它是各种元素构成的树,接着将会介绍最重要的一些基本元素。稍后,我们将简要介绍如何创建自定义元素(称为组件),以及如何使用属性操作来转换元素。最后,我们将看看如何在布局中安排元素,最后看看可供用户输入的元素。

QML 语法

QML是描述元素间关系的声明式语言。QtQuick是基于QML的构建应用程序用户界面的框架,它将用户界面分解为组件,而组件由元素组成。QtQuick描述了这些用户界面元素的外观和行为。JavaScript可为用户界面添加或简洁或复杂的逻辑。从这个角度来看,它遵循HTML-JavaScript模式,但QML和QtQuick是为了描述用户界面-而不是文本文档,从头开始设计的。
在最简单的窗体中,QtQuick构建了元素间的层级关系。子元素从父元素中继续了坐标系。x,y坐标都是相对于父元素的。

注意
QtQuick构建于QML之上。QML语言只知道元素、属性、信号和绑定。QtQuick是一个构建在QML上的框架。使用默认属性,可以以一种优雅的方式构造QtQuick元素的层次结构。

 


 

以一个例子来展示QML的语法

  1. // RectangleExample.qml 
  2.  
  3. import QtQuick 
  4.  
  5. // The root element is the Rectangle 
  6. Rectangle { 
  7. // name this element root 
  8. id: root 
  9.  
  10. // properties: <name>: <value> 
  11. width: 120; height: 240 
  12.  
  13. // color property 
  14. color: "#4A4A4A" 
  15.  
  16. // Declare a nested element (child of root) 
  17. Image { 
  18. id: triangle 
  19.  
  20. // reference the parent 
  21. x: (parent.width - width)/2; y: 40 
  22.  
  23. source: 'assets/triangle_red.png' 
  24. } 
  25.  
  26. // Another child of root 
  27. Text { 
  28. // un-named element 
  29.  
  30. // reference element by id 
  31. y: triangle.y + triangle.height + 20 
  32.  
  33. // reference root element 
  34. width: root.width 
  35.  
  36. color: 'white' 
  37. horizontalAlignment: Text.AlignHCenter 
  38. text: 'Triangle' 
  39. } 
  40. } 
  • import 声明引入一个模块. 模块可以带版本号,形式是 . 。
  • 单行注释用 // ,多行注释用 /\* \*/ , 这和 C/C++ 及 JavaScript 相同。
  • 每个QML必须仅有一个根元素,就象 HTML 那样。
  • 元素的类型是通过在其后面跟{ }来完成的。
  • 元素可以有属性,是以名:值(name:value)的形式。
  • 可以通过id(唯一值)来找到QML中的任意元素。
  • 元素可以嵌套,即父元素可以有子元素。父元素可以通过parent关键字来访问。
    import声明会导入一个QML模块。在Qt5 中,必须指定模块的大版本和小版本号(如,2.15),在Qt 6中,这是可选的。本书中,将不写版本号,系统将从所选择的kit中选最新的可用版本。

注意
通常,您希望通过id访问特定的元素,或者使用parent关键字访问父元素。因此,使用id: root将根元素命名为“root”是一个很好的习惯。这样就不必考虑如何在QML文档中命名根元素。

注意
你可以像这样在你的操作系统的命令行运行Qt Quick运行时的例子:

$ $QTDIR/bin/qml RectangleExample.qml

您需要将$QTDIR替换为Qt安装的路径。qml可执行文件初始化Qt Quick运行时并解析所提供的qml文件。在Windows中可以很轻松地找到qml,但在Linux中,我还没找到,如果你找到了,请提醒我。
在Qt Creator中,您可以打开相应的项目文件并运行文档RectangleExample.qml

属性

通过使用元素名声明元素,通过属性或创建自定义属性来定义元素。一个属性即简单的“名值”对,比如:width: 100, text: 'Greetings', color: '#FF0000'。属性有定义良好的类型,也可以具有初始值。

  1. Text { 
  2. // (1) identifier 
  3. id: thisLabel 
  4.  
  5. // (2) set x- and y-position 
  6. x: 24; y: 16 
  7.  
  8. // (3) bind height to 2 * width 
  9. height: 2 * width 
  10.  
  11. // (4) custom property 
  12. property int times: 24 
  13.  
  14. // (5) property alias 
  15. property alias anotherTimes: thisLabel.times 
  16.  
  17. // (6) set text appended by value 
  18. text: "Greetings " + times 
  19.  
  20. // (7) font is a grouped property 
  21. font.family: "Ubuntu" 
  22. font.pixelSize: 24 
  23.  
  24. // (8) KeyNavigation is an attached property 
  25. KeyNavigation.tab: otherLabel 
  26.  
  27. // (9) signal handler for property changes 
  28. onHeightChanged: console.log('height:', height) 
  29.  
  30. // focus is need to receive key events 
  31. focus: true 
  32.  
  33. // change color based on focus value 
  34. color: focus ? "red" : "black" 
  35. } 

一起来看下不同的属性特征:

  • (1) id 是跟属性很象的值,它是用于标识QML文件中的元素的。id 并非字符类型,它是标识符,是QML语法的一部分。它在文件中必须保持唯一且不能被重置,也不能被遍历到 (这很象C++ 中的 引用 )。
  • (2) 属性可以被赋值,但要注意与其类型相匹配。如果没有被赋值,则会被赋予默认值。可以参考文档中更多关于元素初始化属性的部分。
  • (3) 一个属性可以依赖于一个或多个其他属性。这叫做绑定。绑定属性在其依赖属性更改时被更新。它就像一个公式,本例中,height 应该总是width的两倍。
  • (4) 添加自定义属性的格式是,在property后跟类型、名称 以及默认值(可选),格式如property <type> <name> : <value>。定义中如果没有初始值,则会被赋予默认值。

注意
还可以使用default关键字将一个属性声明为默认属性。如果在元素内部创建了另一个元素,并且没有显式地绑定到某个属性,那么它将绑定到默认属性。例如,在添加子元素时使用This。如果子元素是可见元素,则会自动添加到list类型的默认属性children元素中。这说起来比较抽象,后面找机会写个例子。

  • (5) 另一个重要的声明属性的方法是使用alias(别名)关键字(property alias <name>: <reference>)。alias 关键字允许引用一个对象的属性,或者类型之外的对象本身。我们在之后将使用这个技术:在定义组件时将内部属性或id暴露给根元素。别名属性不需要写明类型,它与所引用的属性或对象的类型相同。
  • (6) text属性依赖于int类型的自定义属性times ,这时,int基础类型被自动转换成了string类型。本表达式也是一个绑定的例子,每当times发生变化时,文本会跟着变。
  • (7) 有些属性是分组属性。当属性结构化特征明显且相关属性应该组合在一起时,将使用此特性。另一种编写分组属性的方式是font {family: "Ubuntu";pixelSize: 24}
  • (8) 有些属性属于元素类本身。这是在应用程序中只出现一次的全局设置元素(例如键盘输入)。写法是<元素>.<属性>:<值>
  • (9) 对于每个属性,都可以提供一个信号处理函数。此处理函数在属性更改后被调用。例如,在这里,我们希望在高度改变时得到通知,并使用内置控制台向系统输出一条消息。

警告
元素id只能用于引用你文档中的元素(例如当前文件)。QML提供了一种称为“动态作用域”的机制,在这种机制中,稍后加载的文档将覆盖先前加载的文档的元素id。这使得如果以前加载的文档还没有被覆盖,就可以引用它们的元素id。这就像创建全局变量。不幸的是,这在实践中经常导致非常糟糕的代码,其中程序依赖于执行的顺序。不幸的是,这是无法关闭的。请小心使用;或者,更好的是,完全不使用这种机制。最好使用文档根元素上的属性将希望提供给外部的元素导出。

脚本

QML和JavaScript(也称为ECMAScript)是最好的搭档。在JavaScript这一章中,我们将更详细地讨论这种共生关系。目前,你只需要了解他们有这种关系。

  1. Text { 
  2. id: label 
  3.  
  4. x: 24; y: 24 
  5.  
  6. // custom counter property for space presses 
  7. property int spacePresses: 0 
  8.  
  9. text: "Space pressed: " + spacePresses + " times" 
  10.  
  11. // (1) handler for text changes. Need to use function to capture parameters 
  12. onTextChanged: function(text) {  
  13. console.log("text changed to:", text) 
  14. } 
  15.  
  16. // need focus to receive key events 
  17. focus: true 
  18.  
  19. // (2) handler with some JS 
  20. Keys.onSpacePressed: { 
  21. increment() 
  22. } 
  23.  
  24. // clear the text on escape 
  25. Keys.onEscapePressed: { 
  26. label.text = '' 
  27. } 
  28.  
  29. // (3) a JS function 
  30. function increment() { 
  31. spacePresses = spacePresses + 1 
  32. } 
  33. } 
  • (1) 每当空格键被按下时,会触发执行onTextChanged函数,输出当前的文本。当信号中有参数传入时,这里就要使用function语法了。也可以使用箭头函数语法((text) => {}),但我觉得 function(text) {} 这种语法可读性更好些。
  • (2) 当Text元素接收到空格时(用户敲空格键),JavaScript函数increment()就会被调用。
  • (3) 以格式 function <name>(<parameters>) { ... }来定义JavaScript 函数,这里的函数增加了空格键点击的记录次数spacePressed。 每当spacePressed 增加,绑定的属性也会被更新。

Binding绑定

QML中的绑定符:与JavsScript中的赋值符=的区别在于,绑定符是在绑定生命周期内一直有效的约定(期间随绑定的值的变化而变化),而JavaScript的赋值是一次性的。
绑定的生命周期在属性上设置新绑定时结束,或者甚至在为属性分配 JavaScript 值时结束。例如,在Esc按键处理函数中将 text 属性设置为空字符串,会破坏我们的增量显示:

  1. Keys.onEscapePressed: { 
  2. label.text = '' 
  3. } 

按下 Esc 后,按下空格键将不再更新显示,因为之前text属性的绑定(text:“Space press:”+spacePresses +“times”)被破坏了。
当更改属性的策略存在冲突(通过绑定更改属性增量来更新文本,并通过 JavaScript 赋值清除文本),那就不能使用绑定!您需要在两个属性的更改方式上都使用赋值,因为绑定将被赋值破坏(违反绑定约定!)。

核心元素

元素可以分为可见元素和非可见元素。可见元素(如Rectangle)有一个坐标系,一般在屏幕某块区域呈现。非可见元素(如Timer)提供常用的功能,用于操作可视元素。
目前,我们将专注于基本的视觉元素,例如Item、Rectangle、Text、ImageMouseArea。但是,通过使用 Qt Quick Controls 2 模块,可以创建由标准平台组件(如按钮、标签和滑块)构建的用户界面。

Item元素

Item是所有可视元素的基类,所有其它可视类都是从Item继承来的。它本身并不绘制可视内容,但它定义了所有在可视元素间都通用属性,如下(以下译文真是不知道该怎么翻了):

  • Geometry - xy 用于定义左上角位置,widthheight 定义元素的宽度与高度,z 定义元素堆叠的层次,以将元素从其默认顺序向前或向后调整。
  • Layout handling - anchors 锚点(left, right, top, bottom, vertical 以及 horizontal center)用于定位元素,它决定了相对于其他具有可选边距margins的元素之间的位置。
  • Key handling - KeyKeyNavigation 属性关联键处理,focus属性表示是否首先启用按键处理。
  • Transformation - scale缩放和rotate旋转变换以及用于x,y,z变换的通用变换属性列表,以及一个变换原点transformOrigin
  • Visual - opacity 控制透明度, visible 用于显示/隐藏元素, clip 限定绘制的边缘,smooth 用于增强渲染的品质。
  • State definition - states 列表属性,包括支持的状态列表、当前state状态属性和过渡列表transitions属性,以使状态变化成为动画。
    为了更好的理解这些属性,我们将在本章呈现的元素中介绍它们。再次强调 ,这些基础属性在每个可见元素中通用。

注意
Item元素经常用于其它元素的容器,类似HTML中的div

矩形元素 Rectangle Element

Rectangle 继承于 Item 并添加了填充颜色属性。此外,它还支持由border.colorborder.width 定义边框。可以使用 radius 属性来创建圆角矩形。

  1. Rectangle { 
  2. id: rect1 
  3. x: 12; y: 12 
  4. width: 76; height: 96 
  5. color: "lightsteelblue" 
  6. } 
  7. Rectangle { 
  8. id: rect2 
  9. x: 112; y: 12 
  10. width: 76; height: 96 
  11. border.color: "lightsteelblue" 
  12. border.width: 4 
  13. radius: 8 
  14. } 

 


 

注意
有效的颜色值是来自 SVG 颜色名称(参见 这里)。您可以在 QML 中以不同的方式设置颜色,但最常见的方式是 RGB 字符串(“#FF4444”)或颜色名称(例如“white”)。
可以使用一些 JavaScript 创建随机颜色:

  1. color: Qt.rgba( Math.random(), Math.random(), Math.random(), 1 ) 

除了填充颜色和边框外,矩形还支持自定义渐变:

  1. Rectangle { 
  2. id: rect1 
  3. x: 12; y: 12 
  4. width: 176; height: 96 
  5. gradient: Gradient { 
  6. GradientStop { position: 0.0; color: "lightsteelblue" } 
  7. GradientStop { position: 1.0; color: "slategray" } 
  8. } 
  9. border.color: "slategray" 
  10. } 

 


 

渐变由一系列渐变色标定义。每个停靠点都有位置和颜色。该位置标记 y 轴上的位置(0 = 顶部,1 = 底部)。 GradientStop 的颜色标记了该位置的颜色。

注意
没有设置width/height的矩形将不可见。当有几个矩形宽度(高度)属性相互依赖或某些逻辑出现问题时,通常会发生这种情况。所以小心!

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

文本元素 Text Element

使用Text元素来展示文本。它最显著的属性是类型为stringtext属性。元素会根据给定内容的字体来计算其高度与宽度。字体可以使用字体属性来设置(比如font.family,font.pixelSize等)。修改字体颜色使用颜色属性。

  1. Text { 
  2. text: "The quick brown fox" 
  3. color: "#303030" 
  4. font.family: "Ubuntu" 
  5. font.pixelSize: 28 
  6. } 

 



文本可以使用horizontalAlignment和verticalAlignment属性对齐到两边和中间。为了进一步增强文本呈现,您可以使用stylestyleColor属性,它们允许您以轮廓模式、凸出模式和下沉模式呈现文本。
对于较长的文本,您通常希望定义一个断点位置,如a very…long text,这可以使用elide属性来实现。elide属性允许您将省略位置设置为文本的左边、右边或中间。
如果你不希望省略模式的“…”出现,但仍然希望看到全文,你也可以使用wrapMode属性包装文本(只有当显式设置宽度时才有效):

 

  1. Text { 
  2. width: 40; height: 120 
  3. text: 'A very long text' 
  4. // '...' shall appear in the middle 
  5. elide: Text.ElideMiddle 
  6. // red sunken text styling 
  7. style: Text.Sunken 
  8. styleColor: '#FF4444' 
  9. // align text to the top 
  10. verticalAlignment: Text.AlignTop 
  11. // only sensible when no elide mode 
  12. // wrapMode: Text.WordWrap 
  13. } 

Text元素只显示给定的文本,它占用的剩余空间是透明的。这意味着它不渲染任何背景装饰,所以如果需要,可以由您指定合适的背景图片。

注意
请注意,Text项的初始宽度依赖于所设置的字体和文本字符串。没有设置宽度及文本的Text元素将不可见,因为初始宽度将为0。

注意
通常,当你想要布局文本元素时,你需要区分文本元素边界框内的文本对齐和元素边界框本身的对齐。在前一种情况下,需要使用horizontalAlignmentverticalAlignment属性,而在后一种情况下,需要操作元素坐标或使用锚点。

图象元素

Image元素可以呈现各种格式的图片(PNG,JPG,GIF,WEBP)。更多受支持的图片格式,可以参考Qt文档
除了提供图像URL的source属性外,它还包含一个fillMode来控制调整大小的行为。

  1. Image { 
  2. x: 12; y: 12 
  3. // width: 72 
  4. // height: 72 
  5. source: "assets/triangle_red.png" 
  6. } 
  7. Image { 
  8. x: 12+64+12; y: 12 
  9. // width: 72 
  10. height: 72/2 
  11. source: "assets/triangle_red.png" 
  12. fillMode: Image.PreserveAspectCrop 
  13. clip: true 
  14. } 

 


 

注意
URL可以是本地形式( “./images/home.png” ) ,也可以是远程网页形式(e.g. “http://example.org/home.png ”)

注意
使用 PreserveAspectCropImage元素还应该启用剪辑,以避免图像数据被渲染到Image边界之外。默认情况下,剪辑被禁用(clip: false)。需要启用剪辑 (clip: true) 以将绘画约束到元素边界内。这可以用于任何视觉元素,但应谨慎使用

注意
使用 C++,您可以使用 QQuickImageProvider 创建自己的图像提供程序。这允许您动态创建图像并使用线程加载图像。

鼠标区域元素 MouseArea Element

与元素交互通常需要用到MouseArea。它是一种可以捕捉鼠标事件的,矩形的不可见元素。鼠标区域元素通常与可视元素一起使用,当用户与可视元素互动时,它可以完成某些命令。

  1. Rectangle { 
  2. id: rect1 
  3. x: 12; y: 12 
  4. width: 76; height: 96 
  5. color: "lightsteelblue" 
  6. MouseArea { 
  7. id: area 
  8. width: parent.width 
  9. height: parent.height 
  10. onClicked: rect2.visible = !rect2.visible 
  11. } 
  12. } 
  13.  
  14. Rectangle { 
  15. id: rect2 
  16. x: 112; y: 12 
  17. width: 76; height: 96 
  18. border.color: "lightsteelblue" 
  19. border.width: 4 
  20. radius: 8 
  21. } 

 


 

 


 

注意
这是Qt Quick中很重要的一点:输入响应与界面展示分离。这允许将交互区域设置的比与可视元素区域大。

提示
更多的复杂交互,参考Qt Quick输入处理。它们试图用于代替 MouseAreaFlickable 等元素,并提供更好的控制和灵活性。这个想法是在每个处理程序实例中处理一个交互方面,而不是像以前那样,将来自一个特定来源的所有事件集中在一个元素中处理。

组件

组件是可重用的元素。QML有多种创建组件的方式,本节只涉及最简单的方式:文件式组件。文件式组件的创建方式是,将QML的元素写在文件中,并将文件命名为元素名(如Button.qml)。可以象其它Qt Quick 组件一样使用文件式组件。本例,在代码中大概就象Button{...}
本例中,将会创建一个包括文本组件和鼠标区域的矩形。这就象一个简单按钮,能验证组件的一些用法而已,没有涉及更复杂的东西。

  1. Rectangle { // our inlined button ui 
  2. id: button 
  3. x: 12; y: 12 
  4. width: 116; height: 26 
  5. color: "lightsteelblue" 
  6. border.color: "slategrey" 
  7. Text { 
  8. anchors.centerIn: parent 
  9. text: "Start" 
  10. } 
  11. MouseArea { 
  12. anchors.fill: parent 
  13. onClicked: { 
  14. status.text = "Button clicked!" 
  15. } 
  16. } 
  17. } 
  18.  
  19. Text { // text changes when button was clicked 
  20. id: status 
  21. x: 12; y: 76 
  22. width: 116; height: 26 
  23. text: "waiting ..." 
  24. horizontalAlignment: Text.AlignHCenter 
  25. } 

形成的UI如下图所。第一张是初始状态样式,第二张是点击鼠标后的样式。

接下来,我们将按钮界面抽象成可重用的组件。本例中,要为按钮设计可能的API。想象下别人如何使用你设计的按钮,有助于帮你设计按钮。我是这么做的:

  1. // minimal API for a button 
  2. Button { 
  3. text: "Click Me" 
  4. onClicked: { /* do something */ } 
  5. } 

我想设置文本使用text属性,并实现自定义的单击处理程序。此外,我希望按钮有一个可以覆盖的合理的初始大小(例如,宽度:240)。
我们创建Button.qml文件并将按钮UI代码写入文件。而且,我们需要将用户要用到的属性(在树状结构的)最上层暴露出来。

  1. // Button.qml 
  2.  
  3. import QtQuick 
  4.  
  5. Rectangle { 
  6. id: root 
  7. // export button properties 
  8. property alias text: label.text 
  9. signal clicked 
  10.  
  11. width: 116; height: 26 
  12. color: "lightsteelblue" 
  13. border.color: "slategrey" 
  14.  
  15. Text { 
  16. id: label 
  17. anchors.centerIn: parent 
  18. text: "Start" 
  19. } 
  20. MouseArea { 
  21. anchors.fill: parent 
  22. onClicked: { 
  23. root.clicked() 
  24. } 
  25. } 
  26. } 

我们已经将文本属性和点击信号提取到顶层。通常将最顶层的元素命名为root,这样方便引用。这里使用了QML的alias特性,这是一种将QML中的元素属性导出到顶层的方式,如此便可以在元素外部使用(元素属性)了。只有根级/顶层的属性才能被文件外的其它组件引用,这一点很重要。
在文件中简单声明便要使用Button元素了。所以早先的例子可以再简洁一些:

  1. Button { // our Button component 
  2. id: button 
  3. x: 12; y: 12 
  4. text: "Start" 
  5. onClicked: { 
  6. status.text = "Button clicked!" 
  7. } 
  8. } 
  9.  
  10. Text { // text changes when button was clicked 
  11. id: status 
  12. x: 12; y: 76 
  13. width: 116; height: 26 
  14. text: "waiting ..." 
  15. horizontalAlignment: Text.AlignHCenter 
  16. } 

现在就可以象这样Button{...}在你的代码中使用很多按钮了。实际的按钮组件可能会更复杂,比如,有更好地点击反馈或更漂亮的外观。

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

  1. Item { 
  2. id: root 
  3. width: 116; height: 26 
  4.  
  5. property alias text: label.text 
  6. signal clicked 
  7.  
  8. Rectangle { 
  9. anchors.fill parent 
  10. color: "lightsteelblue" 
  11. border.color: "slategrey" 
  12. } 
  13. ... 
  14. } 

使用这个技术,很容易构建整套可用组件。

简单变换

变换就是操作修改了元素的坐标位置。QML 元素通常可以被移动、旋转、缩放,有简单的和相对复杂的方式。
我们就从比较简单的变换讲起。
一个简单的变换是通过改变x,y的坐标来完成的。旋转通过rotation属性实现,其取值是角度形式(0...360)。缩放通过scale实现,当取值小于1时缩小,大于1时放大。旋转和缩放不会改变元素的坐标值(x,y)和宽高比(width/height)。只是绘画指令发生了改变。
在展示示例之前,我想介绍一个小帮手:ClickableImage元素。ClickableImage 只是一个带有鼠标区域的图像。这带来了一个有用的经验法则——如果你已经复制了一段代码三次,将它提取到一个组件中。

  1. // ClickableImage.qml 
  2. // Simple image which can be clicked 
  3.  
  4. import QtQuick 
  5.  
  6. Image { 
  7. id: root 
  8. signal clicked 
  9.  
  10. MouseArea { 
  11. anchors.fill: parent 
  12. onClicked: root.clicked() 
  13. } 
  14. } 

 


 

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

  1. // transformation.qml 
  2.  
  3. import QtQuick 
  4.  
  5. Item { 
  6. // set width based on given background 
  7. width: bg.width 
  8. height: bg.height 
  9.  
  10. Image { // nice background image 
  11. id: bg 
  12. source: "assets/background.png" 
  13. } 
  14.  
  15. MouseArea { 
  16. id: backgroundClicker 
  17. // needs to be before the images as order matters 
  18. // otherwise this mousearea would be before the other elements 
  19. // and consume the mouse events 
  20. anchors.fill: parent 
  21. onClicked: { 
  22. // reset our little scene 
  23. circle.x = 84 
  24. box.rotation = 0 
  25. triangle.rotation = 0 
  26. triangle.scale = 1.0 
  27. } 
  28. } 
  29.  
  30. ClickableImage { 
  31. id: circle 
  32. x: 84; y: 68 
  33. source: "assets/circle_blue.png" 
  34. antialiasing: true 
  35. onClicked: { 
  36. // increase the x-position on click 
  37. x += 20 
  38. } 
  39. } 
  40.  
  41. ClickableImage { 
  42. id: box 
  43. x: 164; y: 68 
  44. source: "assets/box_green.png" 
  45. antialiasing: true 
  46. onClicked: { 
  47. // increase the rotation on click 
  48. rotation += 15 
  49. } 
  50. } 
  51.  
  52. ClickableImage { 
  53. id: triangle 
  54. x: 248; y: 68 
  55. source: "assets/triangle_red.png" 
  56. antialiasing: true 
  57. onClicked: { 
  58. // several transformations 
  59. rotation += 15 
  60. scale += 0.05 
  61. } 
  62. } 
  63.  
  64. // ... 

 



圆圈会在每次单击时增加 x 位置,矩形框会在每次单击时旋转一定角度。每次单击时,三角形都会做旋转和放大的组合变换。对于缩放和旋转操作,我们设置 antialiasing: true 以启用抗锯齿,因性能原因该属性默认是关闭的(与裁剪属性 clip 相同)。在自己的项目中,当看到图形边缘有锯齿时,应该打开该属性。

 

**注意 **
为了在缩放图像时获得更好的视觉质量,建议缩小而不是放大图片。使用较大的比例放大图像将导致缩放伪影(图像模糊)。缩放图像时,您应该考虑使用 smooth: true 以启用更高质量的过滤器,但会以性能为代价。

顶层的 MouseArea 覆盖了整个背景并重置ClickableImage元素中的MouseArea

**注意 **
在QML代码中出现越早的元素,其z轴的层级越低。如果点击圆形图片足够多次,它会跑到矩形下面,这是因为box在代码中出现的晚于circle。z轴的顺序也可以由元素的z属性来设置。

同样的规则也适用于鼠标区域。后定义的鼠标区域会覆盖早先定义的鼠标区域,也因此抢占了鼠标事件。

位置元素

在QML中有一些元素是用于定位其它元素的,被称为定位器。Qt Quick提供了如下定位器:Row, Column, Grid and Flow。可以在下图的各元素的不同位置关系进行验证。

注意
在详细讲解前,先引入一些辅助元素:红、蓝、绿、浅色和深色的小方块。每个组件都包含一个48×48象素的矩形色块。以下列出RedSquare源码以备引用:

  1. // RedSquare.qml 
  2.  
  3. import QtQuick 
  4.  
  5. Rectangle { 
  6. width: 48 
  7. height: 48 
  8. color: "#ea7025" 
  9. border.color: Qt.lighter(color) 
  10. } 

请注意Qt.lighter(color) 这种用法,它根据填充颜色设置了更浅的边框颜色。为了提高代码的紧凑性和可读性,我们将在后面的示例代码中直接引用这些辅助色块。谨记,每个矩形是48×48象素的初始大小。
Column元素将子元素自上而下排成一列,spacing属性用来定义元素间的间距。

  1. // column.qml 
  2.  
  3. import QtQuick 
  4.  
  5. DarkSquare { 
  6. id: root 
  7. width: 120 
  8. height: 240 
  9.  
  10. Column { 
  11. id: row 
  12. anchors.centerIn: parent 
  13. spacing: 8 
  14. RedSquare { } 
  15. GreenSquare { width: 96 } 
  16. BlueSquare { } 
  17. } 
  18. } 

Row 元素将其子项彼此相邻放置,从左到右或从右到左,具体取决于 layoutDirection 属性。同样,apacing间距用于分隔子项。

  1. // row.qml 
  2.  
  3. import QtQuick 
  4.  
  5. BrightSquare { 
  6. id: root 
  7. width: 400; height: 120 
  8.  
  9. Row { 
  10. id: row 
  11. anchors.centerIn: parent 
  12. spacing: 20 
  13. BlueSquare { } 
  14. GreenSquare { } 
  15. RedSquare { } 
  16. } 
  17. } 

 



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

 

  1. // grid.qml 
  2.  
  3. import QtQuick 
  4.  
  5. BrightSquare { 
  6. id: root 
  7. width: 160 
  8. height: 160 
  9.  
  10. Grid { 
  11. id: grid 
  12. rows: 2 
  13. columns: 2 
  14. anchors.centerIn: parent 
  15. spacing: 8 
  16. RedSquare { } 
  17. RedSquare { } 
  18. RedSquare { } 
  19. RedSquare { } 
  20. } 
  21. } 

最后一个定位器是 Flow。它将其子项添加到流中。使用 flowlayoutDirection 控制流的方向。它可以横向运行,也可以从上到下运行。它也可以从左到右或相反方向运行。当项目添加到流中时,它们会根据需要进行包装以形成新的行或列。为了使流生效,它必须具有宽度或高度。这可以直接设置,也可以通过锚布局设置。

  1. // flow.qml 
  2.  
  3. import QtQuick 
  4.  
  5. BrightSquare { 
  6. id: root 
  7. width: 160 
  8. height: 160 
  9.  
  10. Flow { 
  11. anchors.fill: parent 
  12. anchors.margins: 20 
  13. spacing: 20 
  14. RedSquare { } 
  15. BlueSquare { } 
  16. GreenSquare { } 
  17. } 
  18. } 
  19.  

Repeater元素经常与定位器一起使用。它像 for 循环一样工作并迭代模型。在最简单的用法,模型可以只提供循环次数。

  1. // repeater.qml 
  2.  
  3. import QtQuick 
  4.  
  5. DarkSquare { 
  6. id: root 
  7. width: 252 
  8. height: 252 
  9. property variant colorArray: ["#00bde3", "#67c111", "#ea7025"] 
  10.  
  11.  
  12. Grid{ 
  13. anchors.fill: parent 
  14. anchors.margins: 8 
  15. spacing: 4 
  16. Repeater { 
  17. model: 16 
  18. Rectangle { 
  19. width: 56; height: 56 
  20. property int colorIndex: Math.floor(Math.random()*3) 
  21. color: root.colorArray[colorIndex] 
  22. border.color: Qt.lighter(color) 
  23. Text { 
  24. anchors.centerIn: parent 
  25. color: "#f0f0f0" 
  26. text: "Cell " + index 
  27. } 
  28. } 
  29. } 
  30. } 
  31. } 

在这个复制器(Repeater)示例中,我们使用了一些新的技能。我们定义了自己的 colorArray 属性,它是一个颜色数组。复制器创建一系列矩形(16 个,由模型定义)。对于每个循环,它都会创建由复制器的子级定义的矩形。在矩形中,我们使用 JS 数学函数选择颜色:Math.floor(Math.random()\*3)。这给了我们一个 0..2 范围内的随机数,我们用它来从颜色数组中选择颜色。如前所述,JavaScript 是 Qt Quick 的核心部分,因此我们可以使用js标准函数。
复制器将index属性注入复制器。它包含当前循环索引 (0,1,..15)。我们可以使用它来根据索引选出相应颜色,或者在我们的例子中使用 Text 元素来显示当前索引值。

**注意 **
更大的模型、更复杂的处理、更灵活的视图等更高级用法,在模型视图章节中进行了介绍。当要呈现少量静态数据时,比较适合使用复制器。

布局元素

QML 提供了一种使用锚点布局项目的灵活方式。锚定的概念是 Item 的基础,适用于所有 QML 可视元素。锚点的作用就像契约一样,比坐标变换更强大。锚是相对性的表达;需要参照一个相关的元素来锚定。

一个元素有 6 条主要的锚线(top, bottom, left, right, horizontalCenter, verticalCenter)。此外,Text元素中的文本还有基线锚点。每条锚线都有一个偏移量,对于top, bottom, left, right ,它们被称为边距,对于horizontalCenter, verticalCenter, baseline,它们被称为偏移量。

  • (1)填充父元素的元素
  1. GreenSquare { 
  2. BlueSquare { 
  3. width: 12 
  4. anchors.fill: parent 
  5. anchors.margins: 8 
  6. text: '(1)' 
  7. } 
  8. } 
  • (2)停靠在父元素左侧的元素
  1. GreenSquare { 
  2. BlueSquare { 
  3. width: 48 
  4. y: 8 
  5. anchors.left: parent.left 
  6. anchors.leftMargin: 8 
  7. text: '(2)' 
  8. } 
  9. } 
  • (3)元素的左侧锚定在父元素右侧
  1. GreenSquare { 
  2. BlueSquare { 
  3. width: 48 
  4. anchors.left: parent.right 
  5. text: '(3)' 
  6. } 
  7. }  
  • (4)元素居中。Blue1 水平居中于父级。 Blue2也是水平居中的,但在 Blue1 上,它的顶部与 Blue1 的底线对齐。
  1. GreenSquare { 
  2. BlueSquare { 
  3. id: blue1 
  4. width: 48; height: 24 
  5. y: 8 
  6. anchors.horizontalCenter: parent.horizontalCenter 
  7. } 
  8. BlueSquare { 
  9. id: blue2 
  10. width: 72; height: 24 
  11. anchors.top: blue1.bottom 
  12. anchors.topMargin: 4 
  13. anchors.horizontalCenter: blue1.horizontalCenter 
  14. text: '(4)' 
  15. } 
  16. } 
  • (5)在父元素中居中
  1. GreenSquare { 
  2. BlueSquare { 
  3. width: 48 
  4. anchors.centerIn: parent 
  5. text: '(5)' 
  6. } 
  7. } 
  • (6)利用相对于水平中线和垂直中线的偏移量来锚定元素
  1. GreenSquare { 
  2. BlueSquare { 
  3. width: 48 
  4. anchors.horizontalCenter: parent.horizontalCenter 
  5. anchors.horizontalCenterOffset: -12 
  6. anchors.verticalCenter: parent.verticalCenter 
  7. text: '(6)' 
  8. } 
  9. } 

彩蛋

这些小方块被赋予了神奇的拖拽能力。试着拖动示例中的方块,你会发现(1)是不能被拖动的,因为它锚定了各个方向(但你可以拖动(1)的父元素,因为它根本没有锚定)。(2)可以竖向拖动,因为它只做了左侧锚定,(3)也是同理。(4)只能竖向拖动,因为两个方块都是水平居中。(5)相对于父元素居中,因此不能拖动,(7)也是同理。拖动元素意味着改变其x,y坐标。而锚定比设置x,y坐标有更强的控制力,拖动受限于锚定线。稍后讨论动画时,可以看到其效果。

输入元素

前面已经使用过鼠标区域MouseArea作为鼠标输入元素了。接下来,我们将聚焦于键盘输入。以文本编辑元素入手:TextInputTextEdit

TextInput

TextInput允许用户输入一行文本。该元素支持输入输入内容验证,如validator,inputMask,echoMode

  1. // textinput.qml 
  2.  
  3. import QtQuick 
  4.  
  5. Rectangle { 
  6. width: 200 
  7. height: 80 
  8. color: "linen" 
  9.  
  10. TextInput { 
  11. id: input1 
  12. x: 8; y: 8 
  13. width: 96; height: 20 
  14. focus: true 
  15. text: "Text Input 1" 
  16. } 
  17.  
  18. TextInput { 
  19. id: input2 
  20. x: 8; y: 36 
  21. width: 96; height: 20 
  22. text: "Text Input 2" 
  23. } 
  24. } 

 



用户可以点击InputText以改变焦点。可以通过KeyNavigation绑定属性来实现焦点的切换。

 

  1. // textinput2.qml 
  2.  
  3. import QtQuick 
  4.  
  5. Rectangle { 
  6. width: 200 
  7. height: 80 
  8. color: "linen" 
  9.  
  10. TextInput { 
  11. id: input1 
  12. x: 8; y: 8 
  13. width: 96; height: 20 
  14. focus: true 
  15. text: "Text Input 1" 
  16. KeyNavigation.tab: input2 
  17. } 
  18.  
  19. TextInput { 
  20. id: input2 
  21. x: 8; y: 36 
  22. width: 96; height: 20 
  23. text: "Text Input 2" 
  24. KeyNavigation.tab: input1 
  25. } 
  26. } 

KeyNavigation绑定属性支持预设一个按键,当按下这个按键时,切换焦点到绑定的元素上。
文本组件除了闪动的光标和用户输入的文本内容外,没有其它可视内容。为了让用户识别到文本输入元素,需要为其增加点辍,比如,一个简单的矩形。当将TextInput嵌入其它元素时,要确保将可能要访问的重要属性导出。
将以下代码写到可重用的自定义组件,并命名为TLineEditV1

  1. // TLineEditV1.qml 
  2.  
  3. import QtQuick 
  4.  
  5. Rectangle { 
  6. width: 96; height: input.height + 8 
  7. color: "lightsteelblue" 
  8. border.color: "gray" 
  9.  
  10. property alias text: input.text 
  11. property alias input: input 
  12.  
  13. TextInput { 
  14. id: input 
  15. anchors.fill: parent 
  16. anchors.margins: 4 
  17. focus: true 
  18. } 
  19. } 

重要
如果想全部导出TextInput,可以写成property alias input : input。第一个input是属性名,第二个input是元素id。

然后用TLineEditV1组件重写关于KeyNavigation的例子。

  1. Rectangle { 
  2. ... 
  3. TLineEditV1 { 
  4. id: input1 
  5. ... 
  6. } 
  7. TLineEditV1 { 
  8. id: input2 
  9. ... 
  10. } 
  11. } 

 



按下Tab键试试,你会看到焦点并未转移到input2focus: true并未生效。问题在于,当焦点转移到input2元素时,TLineEditV1内的顶层元素(Rectangle)接收到了焦点,而未将焦点转发到TextInput。QML提供了FocusScope来避免焦点争抢。

 

FocusScope

焦点范围规定,当焦点范围接收到焦点时,它将会把焦点转发至其内部具有focus: true的最末级子元素。我们将创建第二个版本的TLineEdit,名为TLineEditV2,使用focus scope作为根元素。

  1. // TLineEditV2.qml 
  2.  
  3. import QtQuick 
  4.  
  5. FocusScope { 
  6. width: 96; height: input.height + 8 
  7. Rectangle { 
  8. anchors.fill: parent 
  9. color: "lightsteelblue" 
  10. border.color: "gray" 
  11.  
  12. } 
  13.  
  14. property alias text: input.text 
  15. property alias input: input 
  16.  
  17. TextInput { 
  18. id: input 
  19. anchors.fill: parent 
  20. anchors.margins: 4 
  21. focus: true 
  22. } 
  23. } 

示例代码现在长这样:

  1. Rectangle { 
  2. ... 
  3. TLineEditV2 { 
  4. id: input1 
  5. ... 
  6. } 
  7. TLineEditV2 { 
  8. id: input2 
  9. ... 
  10. } 
  11. } 

这回按下Tab键,就能在两个组件间正常切换焦点了,且焦点落在组件内的正确的子元素上。

TextEdit

TextEditTextInput非常象,但可以录入多行文本。它没有文本约束属性,因为这取决于查询文本的绘制尺寸(paintedHeight, paintedWidth)。我们同样先创建一个自定义组件TTextEdit以提供文本录入区域,并使用focus scope实现更好的焦点转发。

  1. // TTextEdit.qml 
  2.  
  3. import QtQuick 
  4.  
  5. FocusScope { 
  6. width: 96; height: 96 
  7. Rectangle { 
  8. anchors.fill: parent 
  9. color: "lightsteelblue" 
  10. border.color: "gray" 
  11.  
  12. } 
  13.  
  14. property alias text: input.text 
  15. property alias input: input 
  16.  
  17. TextEdit { 
  18. id: input 
  19. anchors.fill: parent 
  20. anchors.margins: 4 
  21. focus: true 
  22. } 
  23. } 

可以象组件TLineEdit那样使用它。

  1. // textedit.qml 
  2.  
  3. import QtQuick 
  4.  
  5. Rectangle { 
  6. width: 136 
  7. height: 120 
  8. color: "linen" 
  9.  
  10. TTextEdit { 
  11. id: input 
  12. x: 8; y: 8 
  13. width: 120; height: 104 
  14. focus: true 
  15. text: "Text Edit" 
  16. } 
  17. } 

 


 

按键元素

Keys绑定属性允许在按下相应按键时,执行相应代码。比如,移动和缩放方块的操作,可以按上 下 左 右 键来移动它,按 加 减 键来缩放元素。

  1. // keys.qml 
  2.  
  3. import QtQuick 
  4.  
  5. DarkSquare { 
  6. width: 400; height: 200 
  7.  
  8. GreenSquare { 
  9. id: square 
  10. x: 8; y: 8 
  11. } 
  12. focus: true 
  13. Keys.onLeftPressed: square.x -= 8 
  14. Keys.onRightPressed: square.x += 8 
  15. Keys.onUpPressed: square.y -= 8 
  16. Keys.onDownPressed: square.y += 8 
  17. Keys.onPressed: { 
  18. switch(event.key) { 
  19. case Qt.Key_Plus: 
  20. square.scale += 0.2 
  21. break; 
  22. case Qt.Key_Minus: 
  23. square.scale -= 0.2 
  24. break; 
  25. } 
  26.  
  27. } 
  28. } 

 


 

技能进阶

QML的性能

QML和Javascript 是解释型语言,这意味着它们不需要在执行前就编译好。相应的,他们在一个执行引擎内运行。虽然解释操作很耗费资源,但有一些技术可以改善其性能。
QML引擎使用JIT(just-in-time)编译机制来改善性能。它也缓存中间结果来避免重复编译。对开发者来说,这很有效。这唯一的缺点就是在源文件里会生成以qmlcjsc后辍的文件。
如果你想避免因初始化而带来的程序启动迟缓,也可以预编译QML和Javascript。这需要将代码放到Qt源码文件中,具体参考Qt文档中的Compiling QML Ahead of Time章节。

posted @ 2022-02-22 14:55  sammy621  阅读(444)  评论(1编辑  收藏  举报