Qt Quick 作为 QML 语言的标准库,提供了很多基本元素和控件来帮助我们构建 Qt Quick 应用,这节我们简要地介绍一些 Qt Quick 元素。
一、 基本可视化项
1.1 Item
Item(基本的项元素)是 Qt Quick 中所有可视元素的基类,虽然它自己什么也不绘制,但是它定义了绘制图元所需要的大部分通用属性,比如 x、y、width、height、锚定(anchoring)和按键处理。
-
Item 除了 x、y 属性,其实还有一个 z 属性,用来指定图元在场景中的 Z 序。z 属性的类 型是 real,数值越小,图元就越垫底(远离我们);数值越大,图元就越靠近我们。
-
Item 的属性 opacity 可以指定一个图元的透明度,取值在 0.0 到 1.0 之间。
-
虽然 Item 本身不可见,但你可以使用 Item 来分组其他的可视图元。分组后可以通过 Item 的 children 或 visibleChildren 属性来访问子对象元素。
import QtQuick 2.0
Rectangle {
300
height: 200
// 使用Item对Rectangle进行分组
Item {
id: gradientGroup
// 矩形子对象0
Rectangle {
x: 20
y: 20
120
height: 120
gradient: Gradient {
GradientStop { position: 0.0; color: "#202020" }
GradientStop { position: 1.0; color: "#A0A0A0" }
}
}
// 矩形子对象1
Rectangle {
x: 160
y: 20
120
height: 120
rotation: 90
gradient: Gradient {
GradientStop { position: 0.0; color: "#202020" }
GradientStop { position: 1.0; color: "#A0A0A0" }
}
}
// 分组后可以通过Item的children或visibleChildren属性来访问子对象元素
Component.onCompleted: {
console.log("visible children: ",
gradientGroup.visibleChildren.length) // 可见子对象长度,为2
console.log("children: ",gradientGroup.children.length) // 子对象长度,为2
// 访问两个子对象的x绝对坐标
for(var i=0; i<gradientGroup.children.length; i++){
console.log("child ", i, " x=",gradientGroup.children[i].x)
}
}
}
}
另外,你可能注意到了,x、y、width、height 四个属性结合起来,可以完成 QtQuick 应用的界面布局,不过这种采用绝对坐标的布局方式,不太容易适应多种多样的移动设备分辨率,也不太适应可变大小的窗口。可以采用一种全新的布局方式:锚布局。铺布局是通过 Item 的 anchors 属性实现的。
1.2 Rectangle
Rectangle(基本的可视化矩形元素)用来绘制一个填充矩形,可以带边框,也可以不带,可以使用纯色填充,也可以使用渐变色填充,甚至还可以不填充而只提供边框......
-
color 属性可以指定填充颜色。
-
gradient 属性则用来设置渐变色供填充使用,如果你同时指定了 color 和 gradient,那么 gradient 生效;如果你设置 color 属性为 transparent,那么就可以达到只绘制边框不填充的效果。
-
border.width 和 border.color 分别用来指定边框的宽度和颜色,radius 设置圆角矩形。
import QtQuick 2.0
Rectangle {
320
height: 480
color: Qt.rgba(0.4,0.6,0.4,1.0)
border. 2
border.color: "black"
radius: 4
}
将上面的代码片段保存到一个 QML 文件中,然后在 QML 文件目录下使用 qmlscene 加载它来看效果。
1.3 Text
Text 可以显示纯文本或者富文本。
-
可以使用Html标记:text: “<b>HELLO</b>” 。
-
换行:wrapMode属性,没有设置,则单行。
-
horizontalAlignment:水平对齐方式; verticalAlignment: 垂直对齐方式。
-
字体:font
- font.blod:true - 加粗,false - 不加粗
- font.family:字体族
- font.italic:true - 斜体 ,false - 不斜体
Text {
id: coloredText;
text: "Hello World!";
font.pixelSize: 32;
}
1.4 color
color(颜色元素)在 QML 中可以使用颜色名字,如 blue、red、green、transparent 等,也可以使用 “#RRGGBB” 或者 “#AARRGGBB” 来指定,还可以使用 Qt.rgba()、Qt.lighter() 等方法来构造。color 类型有 r、g、b、a 四个属性,分别表示一个颜色值的 red、green、blue、alpha 四个成分。
Rectangle {
color: "red"
// color: "#00AA00"
// color: "#800000B0"
// color: Qt.rgba(0.4,0.6,0.4,0.2)
}
1.5 Gradient(渐变色)
QML 中渐变色的类型是 Gradient ,渐变色通过两个或多个颜色值来指定,QML 会自动在你指定的颜色之间插值,进行无缝填充。Gradient 使用 GradientStop 元素来指定一个颜色值和它的位置(取值在 0.0 与 1.0 之间)。
Rectangle {
rotation: 90 // 默认是垂直方向的线性渐变,其它方向可通过rotation旋转获得
gradient: Gradient{
GradientStop { position: 0.0; color: "black" }
GradientStop { position: 0.33; color: "blue" }
GradientStop { position: 1.0; color: "white" }
}
}
1.6 Image
Image 可以显示一个图片,只要是 Qt 支持的,比如 JPG、PNG、BMP、GIF、SVG 等都可以显示。它只能显示静态图片,对于 GIF 等格式,只会把第一帧显示出来。如果要显示动 画,则可以使用 AnimatedSprite 或者 Animatedlmage 元素。
-
Image 的 width 和 height 属性用来设定图元的大小,如果没有设置它们,那么 Image 会使用图片本身的尺寸。如果设置了 width 和 height,那么图片就可能会被拉伸来适应这个尺寸。
-
Image 默认会阻塞式地加载图片,如果要显示的图片很小,则没什么问题,如果分辨率很高,那麻烦就来了。此时你可以设置 asynchronous 属性为 true 来开启异步加载模式,在这种模式下 Image 使用一个线程来加载图片,而你可以在界面上显示一个等待图标之类的小玩意儿来告诉用户他需要等会儿。然后,当 status (枚举值)的值为 Image.Ready 时再隐藏加载等待图元。
-
Image 支持从网络加载图片。它的 source 属性类型是 url,可以接受 Qt 支持的任意一种网络协议,比如 http、ftp 等。而当 Image 识别到你提供的 source 是网络资源 时,会自动启用异步加载模式。此时 Image 的 progress (取值范围是0.0〜1.0)、status (枚举 值)都会适时更新,你可以根据它们判断何时结束加载等候提示界面。
显示网络图片
下面显示网络上的图片,在下载和加载前显示一个转圈圈的 Loading 图标,图片加载成功后隐藏 Loading 图标,如果加载出错,则显示一个简单的错误消息。示例如下:
import QtQuick 2.2
import QtQuick.Controls 1.2
Rectangle {
id: text
480
height: 320
// 用来显示一个等待图元
BusyIndicator {
id: busy
running: true
anchors.centerIn: parent
z: 2
}
Text {
id: stateLabel
visible: false
anchors.centerIn: parent
z: 3
}
Image {
id: imageViewer
// 开启异步加载模式,专门使用一个线程来加载图片
asynchronous: true
// 图片较大的情况下,指定不缓存图像(cache默认为true)
cache: false
anchors.fill: parent
// 设置图片的填充模式为“等比缩放”
fillMode: Image.PreserveAspectFit
onStatusChanged: {
if (imageViewer.status === Image.Loading) {
busy.running = true; // 图片为“加载状态”,则显示“等待图元”
stateLabel.visible = false
}
else if(imageViewer.status === Image.Ready)
busy.running = false; // 图片为“准备好的状态”,则不再显示“等待图元”
else if(imageViewer.status === Image.Error) {
busy.running = false;
stateLabel.visible = true // 图片为“加载失败状态”,则显示“Error”文本
stateLabel.text = "Error"
}
}
// 上面都执行完了,再显示图片
Component.onCompleted: {
imageViewer.source = "https://www.cnblogs.com/images/cnblogs_com/linuxAndMcu/1348721/o_o_misaka.jpg"
}
}
}
Image对象,设置了 asynchronous属性为true,不过对于网络资源Image默认异步加载, 这个属性不起作用,只有你想异步加载本地资源时才需要设置它。cache属性设置为false, 告诉Image不用缓存图片。fillMode属性设置了等比缩放模式。
onStatusChanged是信号处理器,Image的status属性变化时会发射statusChanged()信号。属性变化触发的信号,对应的信号处理器格式为on<property>Changed, 所以这里的名字是onStatusChanged。在信号处理器的代码块中,我们通过Image对象的id访问它的status属性,根据不同的状态来更新界面。
1.7 Busylndicator(等待图元)
Busylndicator 用来显示一个等待图元,在进行一些耗时操作时你可以使用它来缓解用户的焦躁情绪。
Busylndicator 的 running 属性是个布尔值,为 true 时显示。style 属性允许你定制 Busylndicator。默认的效果就是一个转圈圈的动画。
二、 基本的交互项
2.1 Keys
前面提到 Item 可以处理按键,所有从 Item 继承的元素都可以处理按键,比如 Rectangle、 Button。Item 通过附加属性 Keys 来处理按键。
Keys 对象是 Qt Quick 提供的、专门供 Item 处理按键事件的对象。它定义了很多针对特定按键的信号,比如 pressed 和 released 信号,一般地,你可以使用这两个信号来处理按键(请对照 Qt C++ 中的 keyPressEvent 和 keyReleaseEvent 来理解)。它们有一个类型为 KeyEvent、名字是 event 的参数,包含了按键的详细信息。如果一个按键被处理,event.accepted 应该被设置为 true,以免它被继续传递。
这里举一个简单的例子,检测到 Escape 和 Back 键时退出应用,检测到数字键时,就通过 Text 来显示对应的数字。示例程序如下:
import QtQuick 2.0
Rectangle {
300
height: 200
focus: true
Keys.onEscapePressed: Qt.quit()
Keys.onBackPressed: Qt.quit()
Keys.onPressed: {
switch(event.key) {
case Qt.Key_0:
case Qt.Key_1:
case Qt.Key_2:
case Qt.Key_3:
case Qt.Key_4:
case Qt.Key_5:
case Qt.Key_6:
case Qt.Key_7:
case Qt.Key_8:
case Qt.Key_9:
event.accept = true
keyView.text = event.key - Qt.Key_0;
break;
}
}
Text {
id: keyView
anchors.centerIn: parent
font{ bold: true; pixelSize: 24}
text: qsTr("text");
}
}
2.2 Button
按钮可能是 GUI 应用中最常用的控件了。QML 中的 Button 和 QPushButton 类似,用户点击按钮会触发一个 clicked() 信号,在 QML 文档中可以为 clicked() 指定信号处理器(onClicked),响应用户操作。
要使用 Button,需要引入 import QtQuick.Controls 2.x() 先看一个简单的示例,button_quit.qml,点击按钮,退出应用。代码如下:
import QtQuick 2.0
import QtQuick.Controls 2.0
Rectangle {
300
height: 200
Button {
anchors.centerIn: parent
text: "Button"
// onClicked为信号处理器,处理clicked信号
onClicked: Qt.quit()
}
}
-
checkable 属性设置 Button 是否可选。如果 Button 可选,checked 属性则保存 Button 选中状态。
-
iconName 属性设定图标的名字,如果平台的图标主题中存在该名字对应的资源,Button 就可以加载并显示它。iconSource 则通过 URL 的方式来指定 icon 的位置。iconName 属性的优先级高于 iconSource。
-
isDefault 属性指定按钮是否为默认按钮,如果是默认的,用户按 Enter 键就会触发按钮的 clicked() 信号。(Qt Quick.Controls 2.0 已移除)。
-
menu属性允许你给按钮设置一个菜单(此时按钮可能会出现一个小小的下拉箭头),用 户点击按钮时会弹出菜单。默认是null。(Qt Quick.Controls 2.0 已移除)。
-
action属性允许你设定按钮的 action,action 可以定义按钮的 checked、text、tooltip、 iconSource 等属性,还可以绑定按钮的 clicked 信号。action 属性的默认值为 null。
-
style 属性用来定制按钮的风格,与它配套的有一个 ButtonStyle 类,允许你定制按钮的背景和文本。(Qt Quick.Controls 2.0 已移除)
接下来看看如何使用 ButtonStyle 来定制按钮外观。
2.3 ButtonStyle
要使用 ButtonStyle,需要引入 QtQuick.Controls.Styles 1.x。(Qt Quick.Controls 2.0 已移除)
ButtonStyle 类有 background、control、label 三个属性。background属性的类型是Component,用来绘制Button的背景。label属性的类型也是 Component,用于定制按钮的文本。control 属性指向使用 ButtonStyle 的按钮对象,你可以用它访问按钮的各种状态。示例程序如下:
import QtQuick 2.0
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.2
Rectangle {
300
height: 200
Button {
text: "Quit"
anchors.centerIn: parent
style: ButtonStyle {
background: Rectangle {
implicitWidth: 70;
implicitHeight: 25;
// 按下按键时,边框宽度增大为2
border. control.pressed ? 2 : 1;
// 鼠标覆盖或者按下按键时,边框颜色变为"green"
border.color: (control.hovered || control.pressed)
? "green" : "#888888";
}
}
onClicked: Qt.quit();
}
}
我通过给 style 属性指定一个 ButtonStyle 对象来定制 Button 的风格。这个就地实现的 ButtonStyle 对象,为 background 属性指定一个 Rectangle 对象来定义按钮的背景。我定义了背景的建议宽度和高度,根据按钮的 pressed 属性(control 是实际按钮的引用)来设置背景矩形的边框粗细,而边框颜色则随着按钮的 hovered 和pressed属性而变化。
多个按钮使用 ButtonStyle
对于 ButtonStyle,如果有多个按钮同时用到,上面的方式就有点烦琐了,此时我们可以使用 Component 在 QML 文档内定义一个组件,设置其 id 属性的值为 btnStyle,然后在 Button 中设定 style 属性时直接使用btnStyle。示例代码如下:
import QtQuick 2.0
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.2
Rectangle {
300
height: 200
Component {
id: btnStyle
ButtonStyle {
background: Rectangle {
implicitWidth: 70;
implicitHeight: 25;
// 按下按键时,边框宽度增大为2
border. control.pressed ? 2 : 1;
// 鼠标覆盖或者按下按键时,边框颜色变为"green"
border.color: (control.hovered || control.pressed)
? "green" : "#888888";
}
}
}
Button {
text: "OK"
style: btnStyle
onClicked: Qt.quit();
}
Button {
text: "Quit"
style: btnStyle
anchors.centerIn: parent
onClicked: Qt.quit();
}
}
2.4 MouseArea(鼠标句柄交互 )
MouseArea 元素的一个很典型的用法是和一个可视的 item 一起用,处理这个 item 的鼠标响应。
在下例中我们将 MouseArea 放到 Rectangle 中,当单击 Rectangle 区域中时,Rectangle 颜色会变成红色。
import QtQuick 2.0
import QtQuick.Controls 1.2
Rectangle {
100; height: 100
color: "green"
MouseArea {
anchors.fill: parent
onClicked: { parent.color = 'red' }
}
}
很多时候,MouseArea 区域会传递一个鼠标事件作为参数,这个参数中包含了很多鼠标事件信息,例如,
单击的位置,具体按下的一个鼠标左键还是右键,以及一些键盘按键信息。在下面的例子中,当 Rectangle
区域被右键单击时会触发改变颜色。
Rectangle {
100; height: 100
color: "green"
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if (mouse.button == Qt.RightButton)
parent.color = 'blue';
else
parent.color = 'red';
}
}
}
对于其他键盘按键的处理,请参考Keys元素的介绍。MouseArea是一个可见的item,但它本身并不显示什么。
三、其它
3.1 FileDialog
FileDialog 是 Qt Quick 中的文件对话框,它可以用来选择已有的文件、文件夹,支持单 选、多选,也可以用来在保存文件或创建文件夹时让用户提供一个名字。
FileDialog 的 visible 属性的默认值为 false,如果要显示对话框,则需要调用 open() 方法或者设置此属性为 true。
selectExisting 属性的默认值为 true,表示选择已有文件或文件夹;当其为false时,用于供用户创建文件或文件夹名字。
selectFolder 属性的默认值为 false,表示选择文件;设置其为 true,则表示选择文件夹。
selectMultiple 属性的默认值为 false,表示单选;设置其为 true,则表示多选。当 selectExisting 为 false 时,selectMultiple 应该为 false。
FileDialog 还支持名字过滤功能,nameFilters 用于设定一个过滤器列表。而 selectedNameFilter 则保存用户选择的过滤器,或者用来设置初始的过滤器。
当用户选择了一个文件时,fileUrl 属性保存该文件的路径。如果用户选择了多个文件, 该属性为空。fileUrls 属性是一个列表,保存用户选择的所有文件的路径。
folder 属性存放的是用户选择的(文件所在的)文件夹的位置。
上面图片浏览器实例中选择图片文件的对话框也可以修改成这样:
FileDialog {
id: fileDialog
title: "Please choose a ImageFile"
nameFilters: ["Image Files (*.jpg *.png *.gif)"]
selectMultiple: true
onAccepted: {
imageViewer.source = fileDialog.fileUrls[0]
var imageFile = new String(fileDialog.fileUrls[0])
imageText.text = imageFile.slice(8)
}
}
做了上述修改后,可以一次选择多个图片文件,也可以切换名字过滤器。
四、实例 - 图片浏览器V1.0
import QtQuick 2.11
import QtQuick.Window 2.11
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.2
import QtQuick.Dialogs 1.2
Window {
id: window
visible: true
640
height: 480
minimumWidth: 480
minimumHeight: 380
title: qsTr("ImageViewer")
BusyIndicator {
id: busy
running: false
anchors.centerIn: parent
z: 2
}
Text {
id: stateLabel
visible: false
anchors.centerIn: parent
z: 3
}
Image {
id: imageViewer
asynchronous: true
cache: false
anchors.fill: parent
fillMode: Image.PreserveAspectFit
onStatusChanged: {
if (imageViewer.status === Image.Loading) {
busy.running = true
stateLabel.visible = false
}
else if(imageViewer.status === Image.Ready)
busy.running = false
else if(imageViewer.status === Image.Error) {
busy.running = false
stateLabel.visible = true
stateLabel.text = "Error"
}
}
}
Button {
id: openFile
text: "open"
anchors.left: parent.left
anchors.leftMargin: 8
anchors.bottom: parent.bottom
anchors.bottomMargin: 8
style: ButtonStyle {
background: Rectangle {
implicitWidth: 70;
implicitHeight: 25;
border. control.pressed ? 2 : 1;
border.color: (control.hovered || control.pressed)
? "green" : "#888888";
}
}
//按下按钮,打开文件对话框
onClicked: fileDialog.open()
z: 4
}
Text {
id: imageText
anchors.left: openFile.right
anchors.leftMargin: 8
anchors.verticalCenter: openFile.verticalCenter
font.pixelSize: 20
}
FileDialog {
id: fileDialog
title: "Please choose a ImageFile"
nameFilters: ["Image Files (*.jpg *.png *.gif)"]
onAccepted: {
imageViewer.source = fileDialog.fileUrl
var imageFile = new String(fileDialog.fileUrl)
imageText.text = imageFile.slice(8)
}
}
}
在 Open 按钮的 onClicked 信号处理器中,调用 FileDialog 对象的 open() 方法让用户选择文件。当用户选择文件后会触发 FileDialog 的 accepted 信号,我为它创建了 onAccepted 信号处理器,在信号处理器内设置 imageViewer 的 source 属性来显示图片,同时设置 imagePath的text 属性来展示图片文件的路径。程序效果如下图所示: