yafeilinux 发表于 2019-2-27 22:10:28

第4篇 Qt Quick入门教程之基础(四)QML语法2

http://files.qter.org/forum/201902/18/111113cqvyyamqcic8r5yk.jpg.thumb.jpg

# 导语

上一篇内容中我们提到了QML中一些最基本的概念和语法,也许有的同学短时间内还无法完全理解透彻,那么,也没有必要再去死抠那些术语了,因为更多的概念和内容已经到来!

下面的内容比较多,我们无心让大家为了学习语法而学习,所以,咱们的主线是设计一个小程序。真正想理解一个概念必须把它放到实例中去理解,虽然,大家学习的很多时候也有示例,但是,那只是简短的几行代码,并不能完全展示应有的语境。

这次我们换一种全新的方式,在编写实例程序的同时来学习QML语法,下面咱们开始!



# 让界面美观起来

上一篇教程中写的程序界面过于简单,完全影响了我们学习的心情和进一步探索的好奇心,所以,咱们先来对界面进行一些美化。



## 1.美化字体和圆形

首先在 text1 对象定义中添加如下代码:

```
font.bold: true
font { pointSize: 14; capitalization: Font.AllUppercase }

Behavior on rotation {
    NumberAnimation { duration: 500 }
}
```

这里使用 font 设置了 text1 的文本字体。然后下面为 text1 的 rotation 旋转属性设置了动画效果,这个咱们后面学习动画的时候再详细介绍。

然后将 colorRect 对象的定义修改如下:

```
Rectangle {
    id: colorRect
    width: 20 * 2
    height: width
    radius: 20
    border.color: "green"

    anchors.left: text1.right
    anchors.leftMargin: 10
    anchors.verticalCenter: text1.verticalCenter

    MouseArea {
      anchors.fill: parent
      onClicked: {
            console.debug("colorRect: ", parent.border.color)
            text1.rotation += 360
            text1.color = parent.border.color
      }
    }

    Rectangle {
      width: 12 * 2
      height: width
      radius: 12
      color: parent.border.color
      anchors.centerIn: parent
    }
}
```

这里主要是将以前绿色圆形修改为了绿色圆环,然后在其中间添加了一个小的绿色圆形对象;在MouseArea 点击处理中让 text1 旋转360度,并且设置 text1 的文本颜色为圆形的颜色。现在运行的效果如下图所示。




- **属性组**

QML 属性可以按照逻辑关系进行分组,这样的属性会包含一些子属性,子属性可以通过点标记法或者组标记法来赋值。例如代码中的 font 属性,既可以使用 `font.bold` 这样的点标记方式为单一子属性赋值;也可以通过 `font { }` 这样的形式同时为多个子属性赋值。前面看到的anchors、border也都是属性组。我们在帮助文档处可以明显看到组提示:





## 2.添加装饰图片



为了让界面更加美观,我们添加几张图片来做装饰。首先将需要的图片放到源码目录,一般是新建一个目录单独存放,比如这里放到了 images 目录中。然后在Qt Creator左上角项目文件列表的 qml.qrc 上右击,选择 `Open in Editor` 菜单项,如下图所示。







然后在资源文件编辑器界面下方选择添加文件,选中images目录中所有图片,完成后在资源文件编辑器界面按下Ctrl + S 快捷键保存修改。这样就可以在左侧项目文件列表中看到 images 目录以及其中添加的图片文件了。如下图所示。







下面在 Window 根对象最后添加一个 Image类型的图片对象定义:

```
Image {
    id: backImg
    source: "images/bg1.png"
    width: parent.width
    anchors.bottom: parent.bottom
    fillMode: Image.PreserveAspectFit
}
```

Image类型在后面也会详细讲解,一般只需要指定它的大小、路径source属性、填充模式fillMode属性即可。使用资源系统中的文件,需要指明路径,比如这里的 `"images/bg1.png"` ;填充模式 `Image.PreserveAspectFit` 表明在缩放图片时会保持宽高比不变,这样图片不会被拉伸变形。下面是运行效果。



接下来我们在colorRect 对象的MouseArea 子对象的 `onClicked: { }` 中添加一行代码实现点击colorRect 切换装饰图片的效果:

```
backImg.source = "images/bg1.png"
```



## 3.实现圆形的交互特效

现在点击圆形可以让文本变换颜色并旋转,并且底部的图片也会更换。为了点击这个圆形时能给用户一个好的反馈,我们为其添加鼠标指针进入和移出时不同效果的设置,其实也挺简单的,只是在 MouseArea 的最后面添加如下代码:

```
hoverEnabled: true
onEntered: {
    parent.width = 32
    parent.color = "black"
}
onExited: {
    parent.width = 40
    parent.color = "white"
}
```

因为MouseArea默认是鼠标点击后才会有效果,为了让鼠标指针进入MouseArea区域就可以进行相应设置,需要使用 `hoverEnabled: true` 开启悬停。然后分别设置了鼠标进入、移出时圆形的不同效果。



## 4.复制圆形按钮

现在我们的绿色圆形已经具有了简单的按钮功能,下面我们再复制两个这样的按钮到text1文本的不同位置,其实就是复制colorRect的定义代码,然后简单更改 `id、border.color、anchors、backImg.source` 等处代码,最终运行效果如下图所示。因为复制后代码较多,就不再列出。





到这里程序要实现的功能已经完成了,不过,代码写成这样会妨碍我们走得更远,所以,教程到这里只是开始,下面我们继续!



# 自定义组件

前面的代码写完已经150多行了,在main文件中写这么多代码并不是什么好习惯。下面我们将 Text 和所有 Rectangle 对象的代码都剪切到单独的文件中。

## 1.自定义对象类型和组件

在项目中添加新的qml文件,因为现在qml文件默认都在资源文件中,所以我们需要向qml.qrc文件中添加新文件。在项目文件列表 qml.qrc 文件下面的“/”目录上右击,在弹出菜单中选择 “Add New” ,如下图所示。



在弹出的新建文件对话框中,选择Qt分类下的 QML File (Qt Quick 2)模板,如下图所示。



文件名称设置为 `ColorText` ,注意首字母大写。完成后ColorText.qml文件内容如下:

```
// ColorText.qml
import QtQuick 2.9

Item {
      id: root
}
```

自动生成的 import 语句导入的可能是 QtQuick 2.0,这里修改为了 2.9 。Item 类型是Qt Quick中所有可视类型的基类型,比如前面的 Text、Rectangle 等类型都继承自 Item 。因为这里Item作为根对象,所以我们为其设置了 id为 root 。

接下来,我们把 main.qml 中的 Text 和所有 Rectangle 类型对象的代码都剪切到 Item 中。

完成后需要将代码中 `backImg.source = "images/bg1.png"` 这样的三行代码删除掉。

然后到main.qml文件中,在 Window 对象的最后添加如下代码:

```
ColorText {
    anchors.centerIn: parent
}
```

可以看到我们能够直接使用 ColorText.qml 文件的名称作为类型来创建对象。



- **使用QML文件定义对象类型**

像这种将一个QML文档放到一个以 `TypeName.qml` 命名的文件中,就定义了一个对象类型,类型名称就是 `TypeName`,名称必须以大写字母开头,不能包含除字母、数字和下划线以外的字符。这个文档会被QML引擎自动识别为一个QML类型的定义,在同一个目录中的其他QML文件会被自动设置为可用的。所以,这里我们可以像使用一个普通QML对象类型一样来使用 ColorText 。

- **组件(Component)**

组件是可重用的、封装好的、并提供了已定义接口的QML类型,最常见的组件就是一个单独的QML文件,比如这里的 ColorText.qml 文件就定义了一个组件。除此之外,还可以使用Component类型在文档中定义组件,这里就不再开展讲述了。

为什么要将代码放到一个单独的文件中,除了前面说的可以简化 main.qml 文件以外,还有可封装、可重用等好处。到这里我们的组件已经可用了,但是在外面使用封装好的组件,要实现一定的功能,还必须提供接口。



## 2.自定义信号

我们在 ColorText.qml 的 Item 根对象前面添加一个信号定义:

```
Item {
    id: root
    signal clicked(string buttonColor)
    ... ...
}
```

然后在下面三个Rectangle对象的MouseArea子对象的onClicked中分别调用该信号:

```
root.clicked(parent.border.color)

```

现在回到 main.qml 文件中,将ColorText 对象定义更改如下:

```
ColorText {
    anchors.centerIn: parent
    onClicked: {
      console.log("colorButton: ", buttonColor)
      var value = buttonColor
      switch(value) {
      case "#ff0000": backImg.source = "images/bg2.png"; break;
      case "#0000ff": backImg.source = "images/bg3.png"; break;
      default: backImg.source = "images/bg1.png";
      }
    }
}

```

当信号发射后,可以在 onClicked 信号处理器中执行需要的操作,这里根据信号发射参数的不同,显示不同的图片。现在运行程序,已经恢复了我们在第一节完成的代码功能。

- **信号特性(Signal Attributes)和信号处理器特性(Signal Handler Attributes)**

如果大家以前有Qt的基础,那么对信号和槽应该不陌生。在QML中,信号是发生事件的对象发出的通知,比如前面我们用到的 MouseArea 对象被点击了,会发射 clicked 信号,这时可以在对应的信号处理器( Qt C++ 中叫做槽)中获得通知,信号处理器的格式是 on<Signal> ,其中 <Signal> 是信号的名称(首字母必须大写),所以前面我们一直在 onClicked 中进行点击按钮后的操作。

在QML文档中定义一个信号可以使用如下语法:

```
signal <signalName>[([<type> <parameter name>[, ...]])]

```

例如这里定义的 clicked 信号:

```
signal clicked(string buttonColor)

```

如果信号有参数,那么参数类型必须声明,如果没有参数,那么可以省略后面的小括号。

发射一个信号,就是调用该信号。当一个信号被发射后,其对应的信号处理器就会被调用,在其中可以直接访问信号的参数。

另外,还可以使用connect()函数将信号与一个方法或者另外的信号进行关联,语法是:` 对象id.信号.connect (方法或者信号)` 。



- **属性改变信号**

QML类型提供了内建的属性改变信号,每当属性值改变时都会发射该信号,可以在对应的 `on<PropertyName>Changed` 信号处理器中进行相应的操作。



## 3.自定义属性

一个组件中只有根对象的属性可以在外部被调用,也就是说,ColorText.qml文件中创建的 Text 对象和其他 Rectangle对象中的属性是无法在main.qml中定义的 ColorText 对象中调用的。要想在外部访问组件内子对象的属性,需要通过在根对象中定义属性来实现。

我们在ColorText.qml文件的 Item 根对象中添加如下属性定义:

```
Item {
    id: root
    property string colorText // 定义一个string类型的colorText属性
    signal clicked(string buttonColor)
    ... ...

```

然后修改 Text 对象中 text 属性:

```
Text {
    id: text1
    text: colorText
    ... ...

```

这样,就可以到main.qml文件,在 ColorText 对象中修改文本的内容了:

```
ColorText {
      colorText: qsTr("hello world!")
      ... ...

```

当然,这里只是将自定义的属性当做了一个桥梁,如果只是这个目的,那么也可以不使用自定义属性,而是使用属性别名。属性别名更适合将组件内子对象属性暴露给外部对象。我们在Item根对象中继续添加这样一行代码:

```
property alias textFont: text1.font

```

然后就可以在 main.qml 的 ColorText 对象处这样来使用:

```
textFont.underline: true

```

可以看到,对于导出组件中的属性,使用属性别名更方便。



- **定义属性特性(Property Attributes)**

可以使用如下语法来自定义一个属性:

```
property <propertyType> <propertyName> : <value>

```

第一个default修饰符是可选的,如果有则表明定义了一个默认属性; propertyType是属性类型,QML的基本类型、对象类型都可以作为属性的类型;属性名称 propertyName 的命名规则与 id 类似,必须以小写字母开头,可以包含字母、数字和下划线。在定义属性的同时可以为其初始化,如果不需要初始化,那么后面的 `: <value>`可以省略。



- **默认属性**

一个对象至多有一个默认属性,当为该对象添加子对象时,如果没有明确指定子对象要分配到的属性名,那么子对象就会被赋值给默认属性。前面我们看到子对象都是直接写的,一般不会赋值给一个属性,这是因为,所有基于Item 的类型都有一个默认属性 data 。



- **属性别名(Property Aliases)**

与普通的定义属性不同,属性别名不需要分配一个新的唯一的存储空间,而只是将新声明的属性(称为别名属性)作为已存在属性(称为被别名的属性)的直接引用。相比较语法而言,定义属性别名需要使用alias关键字代替属性类型,并且右侧的赋值(被别名的属性)不可省略:

```
property alias <name>: <alias reference>

```



## 4.自定义方法

对于复杂的操作我们可以在函数中进行,QML中的方法特性可以让我们自定义方法。我们在 Item 根对象中添加如下代码:

```
function changeDuration(duration) {
    animation.duration = duration
}

```

这里定义了一个函数来更改动画的持续时间。为了可以访问text1对象中的动画对象,需要为其设置id:

```
NumberAnimation { id: animation; duration: 500 }

```

现在就可以在main.qml的 ColorText 对象中调用该函数来修改动画持续时间了:

```
onClicked: {
    changeDuration(2000)
    ... ...

```

现在可以运行程序,测试下效果。大家可能会发现,第一次点击按钮后,动画的持续时间并没有改变,等第二次点击按钮时才会生效。这是因为,当ColorText对象被实例化的时候,`duration: 500` 已经生效了,当按钮点击信号发**的时候,动画已经开始执行了,所以在这里设置的值没有对动画立即生效。

怎么能在 ColorText 对象创建完成的时候就执行changeDuration()函数呢,可以通过 `Component.onCompleted` 来完成。在main.qml 的 ColorText 对象定义中添加如下代码:

```
Component.onCompleted: {
    changeDuration(2000)
}

```

这样当对象被创建以后就会立即执行,所以我们编译运行程序后,动画持续时间已经改变了。

为了不留遗憾,虽然我们要讲的知识已经讲完了,但是我们还是再修改下 ColorText 对象中的相关代码:

```
onClicked: {
    console.log("colorButton: ", buttonColor)
    var value = buttonColor
    switch(value) {
    case "#ff0000":
      backImg.source = "images/bg2.png";
      changeDuration(500);
      break;
    case "#0000ff":
      backImg.source = "images/bg3.png";
      changeDuration(1000);
      break;
    default:
      backImg.source = "images/bg1.png";
      changeDuration(2000);
    }
}

```

​      这样,当点击不同的按钮,会更改不同的图片并为文本设置新的旋转速度。最后,附上该程序在手机端运行效果的截图。





- **方法特性(Method Attributes)**

一个对象类型的方法就是一个函数,可以用来执行一些处理或者触发其他事件。QML的方法可以用来定义相对独立的可重用的 JavaScript 代码块,这些方法可以在内部调用,也可以被外部对象调用。定义方法的语法如下:

```
function <functionName>([<parameterName>[, ...]]) { <body> }

```

functionName是函数名称,一般首字母小写;参数是可选的,如果有,不需要指明参数类型,默认是var类型,可以在函数体中通过参数名称来访问这些参数。



- **在启动时执行操作(Component.onCompleted)**

每一个QML对象类型都包含一个附加的 Component 属性,在对象被实例化完成后该属性会发射completed信号,其对应的onCompleted 处理器会在QML环境完全建立以后执行。所以,我们想要在程序启动后执行一些操作,可以放到 Component.onCompleted 中执行。



# 结语



这篇教程内容比较多,本来可以分成两篇文章来写,但是整篇的内容是一个有机整体,为了不打断学习的思路,我们放到了一篇教程中呈现给大家。也希望真正想学习的读者可以从头自己敲一遍代码,不要嫌费事,这篇文章的每一个字包括代码都是我亲自手敲的,本来有现成的东西可以复制粘贴,但是为了理顺思路,获得灵感,还是要亲力亲为的。作为初学者,如果连代码都懒得敲,那么也就没有必要学下去了。

------
源码下载:

返回目录

duliningmissyou 发表于 2019-2-28 16:15:27

跟着霍老师一口气学完了这篇教程,收获蛮多的,谢谢老师!

yafeilinux 发表于 2019-2-28 18:53:00

duliningmissyou 发表于 2019-2-28 16:15
跟着霍老师一口气学完了这篇教程,收获蛮多的,谢谢老师!

有些东西就得一气呵成!:lol

michael_zh 发表于 2019-3-10 08:52:10

谢谢,获益良多。。。。。。。。。

一叶知秋 发表于 2019-3-21 11:04:56

打卡

千载不变 发表于 2019-4-13 14:25:51

按照,这个代码,出来的第二个并不是个圆环...

yafeilinux 发表于 2019-4-14 21:04:59

千载不变 发表于 2019-4-13 14:25
按照,这个代码,出来的第二个并不是个圆环...

可以下载源码试试!

千载不变 发表于 2019-4-17 07:37:37

yafeilinux 发表于 2019-4-14 21:04
可以下载源码试试!

霍老师,我自己根据您的讲解,自己写了代码,也下载了您的这个代码

但是有问题:

先点击蓝色 文字一种转的速度
再点击红色 文字一种转的速度
但是,如果再点击蓝色 文字又是另一种速度,此时文字的转动速度和第一次点击蓝色的时候的转动速度是不一样的
我自己也调试了,但是没发现问题在哪里

yafeilinux 发表于 2019-4-17 19:49:08

千载不变 发表于 2019-4-17 07:37
霍老师,我自己根据您的讲解,自己写了代码,也下载了您的这个代码

但是有问题:


这样,当点击不同的按钮,会更改不同的图片并为文本设置新的旋转速度。
这样理解:代码设置时已经开始旋转了,速度是上次设置的那个值,而这次设置的值会在下次生效。

fireflyfang 发表于 2019-4-23 20:49:35

学习一下声明性编程语言,帮助很大~

liuyuananfang 发表于 2019-5-8 23:10:15

写得通俗易懂,非常好

qadou 发表于 2019-10-11 19:43:21

写得通俗易懂,非常好

qadou 发表于 2019-10-11 19:48:02

按照,这个代码,加载背景图片后看不到原来的文本和rectangle.
看下源码理解下

qadou 发表于 2019-10-11 19:54:46

看了代码理解了,我家加载的是jpg格式的,不透明,原有的文本和rectangle被挡住了.化成PNG的就OK了.
谢谢yf老师

k60405003 发表于 2019-11-28 16:26:44

好东西,支持一下跟着霍老师一口气学完了这篇教程,收获蛮多的,谢谢老师!

tushong8 发表于 2019-12-30 14:49:06

一直想学,但是没毅力学,一直用的qwidget。现在准备毕设用quick,加油!

formatmdqt 发表于 2020-1-30 21:05:16

要结合例子代码看,没看代码,字体旋转实现不了,

yafeilinux 发表于 2020-1-31 10:54:19

formatmdqt 发表于 2020-1-30 21:05
要结合例子代码看,没看代码,字体旋转实现不了,

可以下载源码试试

formatmdqt 发表于 2020-2-8 10:38:27

yafeilinux 发表于 2020-1-31 10:54
可以下载源码试试

下了代码,可以实现了,谢谢.

zzwantqt 发表于 2020-8-23 18:49:31

感谢,讲解很详细,新手福利呀
页: [1] 2
查看完整版本: 第4篇 Qt Quick入门教程之基础(四)QML语法2