最近更新日期:2024/12/18
目录
一、Qt Quick简介
写在前面:
本篇文章虽然只是作为我的学习笔记,但也作为我日后复习之用,所以会认真并详细记录,但会分重点。
1.3 新建Qt Quick Application工程
这节主要讲2个知识点,1个是【导入Qt资源文件】,1个是【设置应用图标】。
1.3.1 导入Qt资源文件
首先在工程目录中新建一个名为 images 的文件夹,把程序需要用到的图片放进去,png、ico格式的都一起。(ico格式的是用来作为应用图标的)
这里不用分格式、用途,全部一股脑放这个文件夹里就行。
图片放好之后,我们回到Qt Creator中,在工程目录中新建一个资源文件(Qt Resource File),文件名随意,我的就叫做images和图片文件夹名称一致。
添加好之后,你的工程目录中会多出一个后缀名为 .qrc 的文件,右键添加现有文件,把文件夹里的图片全部选中点击确定即可。然后你逐一点开这个qrc文件,就可以在这个文件里看到你上传的所有图片了。
到这里还没完,最后一步是点开工程目录中的 CMakeLists.txt 文件,然后在里面加一句代码(否则我们无法使用资源文件 ):
set(CMAKE_AUTORCC ON)
这样就导入完成了,虽然有一丢丢麻烦,但是你总不会一直导的,对吧!?
1.3.2 设置应用图标(Windows系统)
应用图标的格式要求是 .ico 格式,png、jpg其他的可不行哦!上面我们已经导入了1张 ico 格式的图片,接下来我们看一下怎么设置成应用图标吧!
(这里推荐一个图片转ico格式的网站,免费且广告很少:锤子在线工具。)
首先我们在工程目录中新建一个文本文档,把文件名改为 ico.rc,然后双击打开这个文件,还是记事本打开哈,在里面写入下面这句代码:
IDI_ICON1 ICON DISCARDABLE "images/青蛙大头贴_256x256.ico"
注意,双引号里面的是你自己的路径,写好之后保存该文件。
最后一步, 点开工程目录中的 CMakeLists.txt 文件,在 qt_add_executable 里面加上:
ico.rc
我们运行一下看看,果然,报错了!
原来我们导入的资源文件是带中文的,真的是太太粗心了!但是为什么直接在 Image 模块中用带中文的路径能正常显示,而应用图标带中文就不行了呢?
这个我也不太清楚,建议大家还是都用英文和下划线吧!
把文件名改成英文,然后重新弄了一遍之后,运行成功了!包括任务栏图标也是这个可可爱爱的青蛙了~
二、QML
2.2 import
2.2.1 import模块
import是QML中的一个导入关键字,一般用于导入下面代码需要用到的模块。
这里讲一个特殊情况,比如在同一个代码文件中,我们需要导入多个名称相同或者非常相似的模块,容易搞混弄错,那么我们可以使用 as 关键字来给其中一些模块单独命名。
那相应的,我们使用这个模块的方式就要改变了,否则会报错。
2.2.2 import代码文件
代码文件比如说我们在其他qml文件中写了一些代码,想要在另外一个代码文件中使用,那么也需要import关键字去导入。
这种一般都是借用别人的代码,如果是我们自己写的话,大概率会直接在Qt Creator中新建相应的文件。
假设我们现在工程目录中粘贴了一个 MyButton的文件,现在想要在 Main.qml 中使用,那么就在顶部写上:
import "MyButton"
如果是文件中的代码文件该怎么导入呢?也很简单!直接把这个文件夹导入就行了,这样里面的所有文件都能使用了。
import "./FolderName"
格式就是 ./ + 文件夹名称 。另外,这个也可以用 as关键字来简写,像下面这样:
import "./FolderName" as Fn
Fn.MyButton1 {
}
Fn.MyButton2 {
}
import用好了,做大一点的项目也会方便、清晰很多哦!
2.3 属性:property
只读:修饰符 readonly
只读属性必须给初始值,之后不允许修改。
附加属性
附加属性和附加信号处理器是一种允许对象使用额外的属性或信号处理器的机制,允许对象访问一些与个别对象相关的属性或者信号。
有点抽象对吧?没关系,看下面例子就行。
import QtQuick
import QtQuick.Controls
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
ListView {
width: 150; height: 400
model: ListModel {
id: listModel
Component.onCompleted: {
for ( var i = 0; i < 50; i++ )
{
listModel.append( { "name" : "Item " + i } )
}
}
}
delegate: Text {
text: name
font.pixelSize: 32
}
}
}
这里的 onCompleted 就是 Component 的附加属性。我们不需要了解过深,只需要会用,知道哪些属性有附加属性可供使用就行了。
2.4 方法
方法就是函数,可以执行某些处理或者触发其他事件。可以将方法关联到信号上,这样在发射该信号时就会自动调用该方法。
import QtQuick
import QtQuick.Controls
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
function add(parameter) {
testLabel.testResult -= parameter
}
function add(parameter) {
testLabel.testResult += parameter
}
function getSum(para1, para2)
{
var result
result = para1 + para2
return result
}
}
在qml中的函数其实不用写的太复杂,如果你真的有很复杂的函数需要实现,可以在C++中。
2.5 自定义信号
信号和信号接收器是一对,我们不仅可以在同一文件中去实现他们,甚至可以把信号写在另一个文件,在当前文件中用信号接收器去接收。
属性值改变信号
QML类型提供内建属性值改变信号,属性值改变就会自动发出信号。
Connections
用于连接外部对象的信号。可以接收指定目标的任意信号。
connect() 函数
将信号与动态创建的对象关联(也可以和函数关联)。
2.6 枚举
枚举(Enumeration)提供了一组固定的命名选项。
自定义的qml文件必须首字母大写。
枚举名字必须首字母大写。
2.7 基本类型
QML支持常见的数据类型,包括:整型、浮点型、字符串和布尔类型。在QML中,将这种仅指向简单数据的类型称为基本类型。
类型 | 描述 |
---|---|
int | 整型 |
bool | 布尔型 |
real | 单精度浮点型 |
double | 双精度浮点型 |
string | 字符串 |
list | QML对象列表 |
url | 资源定位符 |
var | 通用属性类型 |
enumeration | 枚举 |
类型 | 描述 |
---|---|
color | ARGB颜色值 |
font | 字体类型 |
matrix4x4 | 4x4矩阵 |
quaternion | 四元数,包含一个标量以及x、y和z的属性 |
vector2d | 二维向量,包含x、y属性 |
vector3d | 三维向量,包含x、y、z属性 |
date | 日期 |
point | 点,包含x、y属性 |
size | 大小值,包含width、height属性 |
rect | 矩形值,包含x、y、width、height属性 |
随便举几个例子:
2.8 其它类型
2.8.1 对象类型
QML对象类型用于对象实例化,与基本类型的区别是它可以声明一个对象。例如:Rectangle、Button等对象类型。
2.8.2 JavaScript类型
Qt Quick 支持JavaScript对象和数组。可以创建任何标准的JavaScript类型,例如:Date、Array等。
2.9 JavaScript
1. JavaScript在QML中的定位
1)辅助实现复杂的界面逻辑,扩展QML功能
2)主流是QML搭配C++实现完整的程序
3)执行效率远低于C++
4)过度使用JavaScript代码会导致复杂度增加、可维护性降低
2. 属性绑定
1)属性绑定可以是表达式、对象属性、JavaScript函数
2)语法 “属性 :值”
3)属性绑定一旦被重新赋值,就会解除绑定关系
4)如果需要重新绑定,使用 Qt.binding() 函数
3. 调用外部JavaScript文件
import "script.js" as MyScript
2.10 自定义QML组件
1. 定义对象类型
1)必须以大写字母开头,不能包含除字母、数字、下划线以外的字符。这个后缀名为 .qml 的文件会被引擎识别为1个QML类型的定义
2)同一目录的其它QML文件会被自动设置为可用
2. 自定义类型的可访问特性
property propertyType propertyName : value
将自定义类型的属性暴露出去,在其他文件就可以修改。
2.11 作用域
1. 作用
1)表达式可以访问哪些变量
2)重名时的优先级
2. 绑定的作用域对象
Item {
anchors.left: parent.left
}
3. 组件作用域
1)QML文件每个组件都定义了一个逻辑作用域
2)每个文件都至少有一个根组件
4. 作用域使用技巧
id.property(通过另一个作用域的id来调用)
2.12 代码风格
Qt帮助文档查找:QML Coding Conventions
1. QML对象声明
2. 属性组
3. 列表
4. 信号处理器
5. JavaScript代码
6. 复杂条件判断
涉及到较为复杂的条件判断,可以把属性绑定到函数,在函数中去具体实现,提高代码的可读性。
如果只有1、2层,可以用三目运算符,更多的话还是用函数吧!
三、Qt Quick基础
3.1.1 项目Item
Item 是所有可视化元素的基类,所有其它的可视化元素都继承自 Item。它自身不会有任何绘制操作,但是定义了所有可视化元素共有的属性。
Qt帮助文档搜索 Item 获取更多详细信息。
Group(分组) | Properties(属性) |
---|---|
Geometry(几何属性) | x、y(坐标)定义了元素左上角的位置,width、height(长和宽)定义元素的显示范围,z(堆叠次序)定义元素之间的重叠顺序。 |
Layout(布局) | anchors(锚定),包括:左(left)、右(right)、上(top)、下(bottom)、水平与垂直居中(vertical center,horizontal center),与margins(间距)一起定义了元素与其它元素之间的位置关系。 |
Key(按键) | 附加属性key(按键)和keyNavigation(按键定位)属性来控制按键操作,处理输入焦点(focus)可用操作。 |
Transformation(转换) | 缩放(scale)和rotate(旋转)转换,通用的x、y、z属性列表转换(transform),旋转基点设置(transformOrigin)。 |
Visual(可视化) | 不透明度(opacity)控制透明度,visible(是否可见)控制元素是否显示,clip(裁剪)用来限制元素边界的绘制,smooth(平滑)用来提高渲染质量。 |
State definition(状态定义) | states(状态列表属性)提供了元素当前所支持的状态列表,当前属性的改变也可以使用transitions(转变)属性列表来定义状态转变动画。 |
说明
1)通常被用来作为其它元素的容器使用
2)属性 opacity 指定透明度,取值范围:0.0 ~ 1.0
3)堆叠次序 z
4)可见性 visible,虽然 Item 本身不可见,但可以用来分组其它可视对象
// 代码示例
import QtQuick
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Item {
id: item
x: 200; y: 200
visible: true // 默认就是可见的
rotation: 45 // 旋转45度
scale: 2 // 放大2倍
Rectangle {
width: 100; height: 100; color: "pink"
}
focus: true
Keys.onReturnPressed: { item.visible = !item.visible } // 大回车按下
}
MouseArea {
anchors.fill: parent
onClicked: { item.visible = !item.visible }
}
}
3.1.2 矩形Rectangle
// 代码示例
import QtQuick
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Rectangle {
width: 100; height: 100; color: "lightgreen"
radius: height / 2 // 圆形
}
Rectangle {
width: 200; height: 80; color: "tomato"; x: 120
radius: height / 4 // 一般作为圆角按钮,统一圆角大小会比较美观
}
Rectangle {
width: 200; height: 200; y: 120
// gradient 优先级大于 color
gradient: Gradient {
GradientStop { position: 0.0; color: "red" }
GradientStop { position: 0.3; color: "orange" }
GradientStop { position: 0.7; color: "yellow" }
GradientStop { position: 1.0; color: "green" }
}
}
}
3.1.3 文本text
Text 可以显示纯文本和富文本。实际开发中常用 Label 替代 Text。
Qt帮助文档搜索 Text 获取更多详细信息。
Text 常用属性
1)颜色 color
2)字体 font
3)裁剪 clip
4)省略 elide
5)对齐 alignment
// 代码示例
import QtQuick
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Text {
text: "乘风破浪会有时 直挂云帆济沧海"
color: "blue"
width: 300; height: 50
elide: Text.ElideRight // 右侧省略(前提是给了Text的宽高)
font {
pixelSize: 36 // 字体大小
bold: true // 是否加粗
family: "隶书"
italic: true // 是否斜体
}
}
}
3.1.4 标签Label
描述
Label 继承自 Text。如果没有明确指定背景大小,它会遵循控件的大小。在大多数情况下不需要为背景指定宽度或高度。
Qt帮助文档搜索 Label 获取详细信息。
文本样式
style:文本样式(如:Text.Normal、Text.Outline、Text.Raised、Text.Sunken)
高级属性
1)font.underline、font.strikeout:下划线和删除线样式
2)elide:省略模式(如:Text.ElideLeft、Text.ElideRight、Text.ElideMiddle)
3)lineHeight 和 lineHeightMode:行高和行高模式
4)renderType:渲染类型(如:Text.QtRendering、Text.NativeRendering)
字体 font
family、pointSize、weight
对齐和布局
1)horizontalAlignment:水平对齐方式(如:Qt.AlignLeft、Qt.AlignRight、Qt.AlignHCenter)
2)verticalAlignment:垂直对齐方式(如:Qt.AlignTop、Qt.AlignBottom、Qt.AlignVCenter)
3)wrapMode:文本换行模式(如:Text.NoWrap、Text.WordWrap、Text.WrapAnywhere、Text.Wrap)
交互
1)selectable:是否可以选择文本(用于复制等操作)
2)truncated:文本是否被截断
// 示例代码:春联
import QtQuick
import QtQuick.Controls
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Label {
id: label_1
x: 80; y: 10
text: "好事临门"
font { family: "华文行楷"; pixelSize: 36 }
topPadding: 5
background: Rectangle { // 可以设置文字背景
color: "red"
}
}
Label {
id: label_2
anchors.top: label_1.bottom
anchors.topMargin: 20
x: 20
width: 50; height: 300
text: "迎\n新\n春\n事\n事\n如\n意"
font { family: "华文行楷"; pixelSize: 36; wordSpacing: 10 }
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
topPadding: 5
background: Rectangle {
color: "red"
}
}
Label {
id: label_3
anchors.top: label_1.bottom
anchors.topMargin: 20
anchors.left: label_2.right
anchors.leftMargin: 170
width: 50; height: 300
text: "接\n鸿\n福\n步\n步\n高\n升"
font { family: "华文行楷"; pixelSize: 36 }
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
topPadding: 5
background: Rectangle {
color: "red"
}
}
}
Label 的功能非常强大,我们甚至可以用它显示图片。
import QtQuick
import QtQuick.Controls
Window {
width: 1024 / 2
height: 1536 / 2
visible: true
title: qsTr("Hello World")
Label {
anchors.fill: parent
background: Image { source: "两只猫.png" }
}
}
填充个 MouseArea,它还可以设计成一个自定义按钮:
// 代码示例:用 Label 来自定义按钮
import QtQuick
import QtQuick.Controls
Window {
width: 500
height: 300
visible: true
title: qsTr("Hello World")
Label {
id: labelButton
x: 20; y: 20
background: Image {
id: bgImage
source: "设置.png"
width: 60; height: 60
}
MouseArea {
anchors.fill: bgImage
onPressed: {
bgImage.opacity = 0.5
}
onReleased: {
bgImage.opacity = 1
}
}
}
}
3.1.5 文本输入框 TextInput
描述
TextInput 显示单行可编辑的纯文本。
我们可以通过使用验证器 validator 或输入掩码 inputMask 来实现输入限制。
实际开发中常用 TextField 或 TextEdit 来替代 TextInput。
验证器和掩码
限制输入范围、类型、格式
回显
TextInput.Normal(默认)
TextInput.Password
TextInput.NoEcho
TextInput.PasswordEchoOnEdit
信号处理器
onAccepted()
onEditingFinished()
onTextEdited()
自定义外观
TextInput的缺点就是需要额外定义一下外观,初始的TextInput在窗口上根本看不见在哪。
// 示例代码
import QtQuick
import QtQuick.Controls
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Rectangle {
id: textInputBackground
width: 180; height: 50
x: 20; y: 20
color: "transparent"
border.width: 1; border.color: "darkgray"
// radius: height / 4 // 圆角
TextInput {
anchors.fill: parent
font.pixelSize: 24
verticalAlignment: Text.AlignVCenter // 垂直居中
// validator: IntValidator { bottom: 0; top: 50 } // 验证器:限制输入范围
leftPadding: 10 // 左边距
rightPadding: 10 // 右边距
maximumLength: 6 // 可输入的最长字符数
echoMode: TextInput.Password // 回显模式:密码模式(圆点)
onEditingFinished: { // 按回车表示输入完成的信号处理器
console.log(text)
}
onTextEdited: {
console.log(text) // 实时变更
if (text === "2024") {
tipLabel.text = "验证码输入正确!"
}
}
}
}
Label {
id: tipLabel
anchors.top: textInputBackground.bottom
anchors.topMargin: 20
anchors.left: textInputBackground.left
text: ""
}
}
3.1.6 文本编辑框TextEdit
描述
TextEdit 显示多行可编辑的纯文本或富文本。
3.2.1 行列布局 Row Column
3.2.2 栅格布局 Grid
3.2.3 流布局 Flow
3.2.4 布局过渡 Transition
Positioners 定位器具有 add、move 和 populate 属性,添加或删除控件时,可以使用 Transition 使这些操作具有动画效果。
1)add:定位器创建完毕后,向定位器中添加控件时
2)move:定位器中删除控件时
3)populate:定位器第一次创建时,只会运行一次
// 代码示例
import QtQuick
import QtQuick.Controls
Window {
width: 1920 / 2
height: 1080 / 2
visible: true
title: qsTr("Hello World")
Column {
spacing: 10; padding: 10
Rectangle { id: redRectangle;width: 100; height: 100; color: "red" }
Rectangle { id: orangeRectangle;width: 100; height: 100; color: "orange" }
Rectangle { id: yellowRectangle;width: 100; height: 100; color: "yellow" }
Rectangle { id: greenRectangle;width: 100; height: 100; color: "green" }
move: Transition {
NumberAnimation {
properties: "x, y"
duration: 700
easing.type: Easing.OutElastic // 弹簧的感觉,很逼真
}
}
focus: true
Keys.onSpacePressed: { // 按下空格后,黄色矩形显示或隐藏
orangeRectangle.visible = !orangeRectangle.visible
}
}
}
3.2.5 重复器 Repeater
描述
用来创建大量相似的控件,包含一个模型 model 属性和一个委托 delegate 属性。委托用来将模型中的每个控件进行可视化显示。
// 示例代码
import QtQuick
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Grid {
x: 10; y: 10
columns: 5
spacing: 10
Repeater {
model: 25
Rectangle {
width: 50; height: 50; color: "black"; radius: height / 2
Text {
text: index
font.pixelSize: 24
font.bold: true
anchors.centerIn: parent
color: "white"
}
}
}
}
}

3.2.6 锚布局 Anchors
3.2.7 布局管理器 Layouts
3.3.1 触摸区域 MouseArea
描述
MouseArea 提供一系列触摸事件,它是一个不可见的项目,通常与可见项目组合实现界面交互。
属性
1)mouseX:real
2)mouseY:real
3)enabled:bool
4)pressed:bool
5)propagateComposedEvents:bool
信号
1)clicked(MouseEvent mouse):单击
2)pressed(MouseEvent mouse):按下
3)released(MouseEvent mouse):松开
4)positionChanged(MouseEvent mouse):鼠标移动(x、y)
5)doubleClicked(MouseEvent mouse):双击
6)pressAndHold(MouseEvent mouse):长按,按下达到一定时间才会激发
7)wheel(WheelEvent wheel):滚轮
// 代码示例 1
import QtQuick
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
MouseArea {
anchors.fill: parent
pressAndHoldInterval: 2000 // 按住间隔设置为2s,单位是ms
// 按下
onPressed: {
console.log("按下:onPressed")
}
// 释放(松开)
onReleased: {
console.log("释放:onReleased")
}
// 点击
onClicked: {
console.log("点击:onClicked")
}
// 长按
onPressAndHold: {
console.log("长按:onPressAndHold")
}
// 双击
onDoubleClicked: {
console.log("双击:onDoubleClicked")
}
// 坐标移动
onPositionChanged: {
console.log(mouseX, mouseY)
}
}
}
再做一个小示例:在鼠标按下后,五角星图片跟着光标实时移动,并且图片中心和光标顶点重叠,鼠标松开后五角星图片停留在此处。
// 代码示例2
import QtQuick
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Image {
id: starImage
width: 50; height: 50
source: "star_02.png"
}
MouseArea {
anchors.fill: parent
onPositionChanged: {
starImage.x = mouseX - starImage.width / 2
starImage.y = mouseY - starImage.height / 2
}
}
}
3.3.2 鼠标事件 MouseEvent
1 - 描述
MouseEvent 可以结合 MouseArea 类型获取鼠标事件,通过信号处理器与鼠标交互。通过 modifiers 获取按下键盘修饰符,包括:左键、右键、中键以及滚轮。
2 - 常用按键
在使用 MouseEvent 时,需要将 modifiers 与以下特殊按键进行按位与来判断按键。
- Qt.NoModifier - 没有修饰键被按下
- Qt.ShiftModifier - Shift键被按下
- Qt.ControlModifier - Ctrl键被按下
- Qt.AltModifier - Alt键被按下
- Qt.MetaModifier - Meta键被按下
- Qt.KeypadModifier - 小键盘按钮被按下
3 - WheelEvent 滚轮事件
WheelEvent 最重要的属性是 angleDelta,用来获取滚动距离,它的x、y坐标分别保存了水平和垂直方向的增量。向上或向右滚动返回正值,向下或向左滚动返回负值。
对于大多数鼠标,每当滚轮旋转一下默认是15°,此时 angleDelta 的值就是 15x8,即整数120。
下面做个小示例,窗口中心有个红色矩形,当鼠标放在矩形上并且点击鼠标左键后,矩形变为粉色,若点击右键则变为番茄红。
// 代码示例
import QtQuick
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Rectangle {
id: rectangle
width: 300; height: 300; color: "red"
anchors.centerIn: parent
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton // 接收鼠标的左键和右键
onClicked: (mouse)=> {
if(mouse.button === Qt.LeftButton)
{
rectangle.color = "pink"
}
else if(mouse.button === Qt.RightButton)
{
rectangle.color = "tomato"
}
}
}
}
}

再做一个示例,当我们按下 Ctrl键,并且鼠标左键双击矩形,让矩形变为橘色。
// 代码示例
import QtQuick
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Rectangle {
id: rectangle
width: 300; height: 300; color: "red"
anchors.centerIn: parent
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton // 接收鼠标的左键
onDoubleClicked: (mouse)=> {
if((mouse.button === Qt.LeftButton) && ( mouse.modifiers & Qt.ControlModifier))
{
rectangle.color = "orange"
}
}
}
}
}
鼠标滚轮示例:我们按住Ctrl键,然后光标放在文本上,接着用滚轮来控制文字的大小。
import QtQuick
import QtQuick.Controls
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Label {
id: label
text: "Qt Quick"
color: "#41cd52"
font { pixelSize: 16; bold: true }
anchors.centerIn: parent
MouseArea {
anchors.fill: parent
// 滚轮事件处理
onWheel: (wheel)=> {
if( wheel.modifiers & Qt.ControlModifier )
{
if( wheel.angleDelta.y > 0 )
{
label.font.pixelSize += 4
}
else
{
label.font.pixelSize -= 4
}
}
}
}
}
}
3.3.3 拖拽事件 DragEvent
1 - 描述
实现项目拖拽
2 - 属性
1)drag.target:拖动的项目id
2)drag.active:是否可以被拖动
3)drag.axis:拖动方向
4)drag.minimumX:水平方向最小拖动距离
5)drag.maximumX:水平方向最大拖动距离
6)drag.minimumY:垂直方向最小拖动距离
7)drag.maximumY:垂直方向最大拖动距离
8)drag.threshold:拖动的阈值
3 - DropArea
它是一个不可见的项目。当其他项目拖动到其上时它可以接收相关事件。通过drag.x 和 drag.y 获取最后一个拖放事件坐标,使用drag.source 获取拖放的源对象,通过keys 获取拖放的键列表。
1)onEntered:拖放进入时
2)onDropped:drop 事件发生时
3)onExited:拖放离开时
4)onPositionChanged:拖放位置改变时
做一个拖拽五角星图片例子:
import QtQuick
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Image {
id: starImage
width: 50; height: 50
source: "star_02.png"
fillMode: Image.PreserveAspectFit
}
MouseArea {
anchors.fill: parent
drag.target: starImage // 拖拽目标
drag.axis: Drag.XAndYAxis // 拖动方向:自由拖拽
drag.minimumX: 0
drag.maximumX: parent.width - starImage.width
drag.minimumY: 0
drag.maximumY: parent.height - starImage.height
drag.threshold: 0 // 防抖动、误操作
}
}
代码示例:添加一些不同颜色的小球,当我们拖动小球进入浅灰色正方体时,正方体变成和当前拖动的小球相同的颜色,当小球从正方体中出去时,正方体又恢复成原来的浅灰色。
import QtQuick
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Rectangle {
width: 200; height: 200; color: "#eeeeee"
anchors.centerIn: parent
DropArea {
anchors.fill: parent
onEntered: {
parent.color = drag.source.color
}
onExited: {
parent.color = "#eeeeee"
}
}
}
MyRectangle { color: "tomato" }
MyRectangle { color: "purple"; x: 50 }
MyRectangle { color: "pink"; x: 100 }
MyRectangle { color: "lightgreen"; x: 150 }
MyRectangle { color: "lightblue"; x: 200 }
}
小球是我们在另外一个qml文件中定义的:
// MyRectangle
import QtQuick
Rectangle {
id: rectangle
width: 30; height: 30; radius: height / 2
// 是否可拖动
Drag.active: dragArea.drag.active
// 相对于项目左上角的拖动起始位置坐标,默认是(0,0)
Drag.hotSpot.x: 10
Drag.hotSpot.y: 10
// 拖动对象
Drag.source: rectangle
MouseArea {
id: dragArea
anchors.fill: parent
drag.target: parent
}
}
3.3.4 键盘事件 KeyEvent
3.3.5 定时器 Timer
3.4 自定义滑动区域 SwipeArea
3.5 自定义按钮 QuickButton
3.6 Loader
描述
Loader 用于动态加载QML组件,可以加载QML文件或组件对象。一般将其放在主QML文件中,用于切换界面。
属性
source
从加载的项目中接收信号
任何从被加载的项目中发射的信号都可以使用 Connections 类型进行接收。
Connections {
target: loader.item
function onMessage(msg)
{
console.log(msg)
}
}
代码示例
用 Loader 和 Connections 实现多界面切换。如下图所示,当我们点击对应的动物头像,就可以去拜访它们的家啦!(跳转页面)然后点击中间“这是XX的家”就可以再跳转回主界面。
这个用Loader + Connections 实现起来很简单,但是我个人认为只适合少量的界面切换,如果你的界面很多,这个还是不太适合的,因为这里切换页面的核心是 Connections 下的一个函数,我们通过多条if语句去判断接收的信号,然后将对应的 source 设置到 Loader中。
在子界面中,我们需要自定义一个信号:
signal message(string msg)
并且要发出信号,让主界面去接收,主界面接收了就可以做对应的事。
// 初始界面代码
import QtQuick
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Text {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 60
text: "你想去谁的家呢?"
font { pixelSize: 24; bold: true }
}
Loader {
id: loader
source: "HomePage.qml" // 初始界面
anchors.centerIn: parent
}
Connections {
target: loader.item // 连接的目标是Loader中的Item
function onMessage(msg)
{
if(msg === "dog")
{
loader.source = "DogPage.qml"
}
else if(msg === "panda")
{
loader.source = "PandaPage.qml"
}
else if(msg === "ghost")
{
loader.source = "GhostPage.qml"
}
else if(msg === "home")
{
loader.source = "HomePage.qml"
}
}
}
}
// 子界面代码 1
import QtQuick
Rectangle {
id: root
anchors.fill: parent
color: "#ffda9f"
signal message(string msg)
Text {
anchors.centerIn: parent
text: "这里是超凶狗狗的家~"
MouseArea {
anchors.fill: parent
onClicked: {
root.message("home")
}
}
}
}
// 子界面代码 2
import QtQuick
Rectangle {
id: root
anchors.fill: parent
color: "#d5d5d5"
// 定义1个信号,给主界面的信号处理函数进行判断,然后切换到相应界面
signal message(string msg)
Text {
anchors.centerIn: parent
text: "这里是可爱幽灵的家~"
MouseArea {
anchors.fill: parent
onClicked: {
root.message("home")
}
}
}
}
// 子界面 3(初始展示)
import QtQuick
Item {
id: root
// 定义1个信号,给主界面的信号处理函数进行判断,然后切换到相应界面
signal message(string msg)
Row {
anchors.centerIn: parent
spacing: 15
Image {
width: 150; height: 150
source: "超凶狗狗.png"
MouseArea {
anchors.fill: parent
onPressed: {
// parent.y = parent.y + 3
parent.opacity = 0.5
}
onReleased: {
// parent.y = parent.y - 3
parent.opacity = 1
}
// 被点击后发送不同的消息,在主界面接收到之后切换到相应的界面
onClicked: {
root.message("dog")
}
}
}
Image {
width: 150; height: 150
source: "可爱熊猫.png"
MouseArea {
anchors.fill: parent
onPressed: {
// parent.y = parent.y + 3
parent.opacity = 0.5
}
onReleased: {
// parent.y = parent.y - 3
parent.opacity = 1
}
onClicked: {
root.message("panda")
}
}
}
Image {
width: 150; height: 150
source: "可爱幽灵.png"
MouseArea {
anchors.fill: parent
onPressed: {
// parent.y = parent.y + 3
parent.opacity = 0.5
}
onReleased: {
// parent.y = parent.y - 3
parent.opacity = 1
}
onClicked: {
root.message("ghost")
}
}
}
}
}
// 子界面 4
import QtQuick
Rectangle {
id: root
anchors.fill: parent
color: "#d9ebf9"
// 定义1个信号,给主界面的信号处理函数进行判断,然后切换到相应界面
signal message(string msg)
Text {
anchors.centerIn: parent
text: "这里是可爱熊猫的家~"
MouseArea {
anchors.fill: parent
onClicked: {
root.message("home")
}
}
}
}
3.7 综合示例 HarmonyUI
不全部做了,就记个日期功能吧!
import QtQuick
import QtQuick.Controls
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Label {
id: timeLabel
anchors.left: parent.left
anchors.leftMargin: 20
anchors.top: parent.top
anchors.topMargin: 10
font { pixelSize: 48; bold: true; family: "JetBrains Mono" }
text: Qt.formatDateTime(new Date(), "hh:mm:ss")
Component.onCompleted: { updateTimer.start() }
}
Timer {
id: updateTimer
interval: 1 // 间隔为1ms,如果设为1s会和系统的时间相差1s
repeat: true // 重复触发:是
onTriggered: {
timeLabel.text = Qt.formatDateTime(new Date(), "hh:mm:ss")
dateLabel.text = Qt.formatDateTime(new Date(), "M月d日 dddd")
}
}
Label {
id: dateLabel
anchors.left: timeLabel.left
anchors.top: timeLabel.bottom
anchors.topMargin: 15
font { pixelSize: 48; bold: true; family: "JetBrains Mono" }
text: Qt.formatDateTime(new Date(), "M月d日 dddd")
}
}
四、Qt Quick控件
4.1 窗口 Window
4.2 应用主窗口 ApplicationWindow
4.3 控件基类 Control
4.4 按钮 Button
autoRepeat、autoRepeatInterval 可以用来实现长按持续调节音量大小。下面是一个简单示例(看注释):
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Material
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Slider {
id: slider
x: 10; y: 10
orientation: Qt.Vertical // 垂直显示
from: 0 // 起始值
to: 100 // 结尾值
value: 0 // 默认值,不是步长
stepSize: 1 // 步长,最好指定一下,不然value会有很多位小数
onValueChanged: {
sliderValue.text = value + "%"
}
}
Label {
x: 100; y: 50
id: sliderValue
text: slider.value + "%"
font { pixelSize: 24; bold: true }
}
Button {
x: 100; y: 100
id: voiceAdd
text: "音量+"
// 自动重复开启
autoRepeat: true
// 重复一次的时间间隔:1000ms(默认值是300ms)
autoRepeatInterval: 1000
onClicked: { slider.value ++ }
}
Button {
x: 200; y: 100
id: voiceReduce
text: "音量-"
autoRepeat: true
autoRepeatInterval: 100 // 100ms 感觉正好
onClicked: { slider.value -- }
}
}
这里有个小技巧,当我们需要百分比显示数据,但是又不想要小数,可以直接用整数,后面的百分比符号用字符串来拼接上去,因为我们直接用小数或者百分比去计算,没准哪个区间会冒出老长一段小数。
如果需要用到比较复杂的逻辑判断,最好是封装到一个函数里,然后将值和函数进行绑定。
4.5 复选框 CheckBox
4.6 单选框 RadioButton
4.7 按钮组 ButtonGroup
4.8 延时按钮 DelayButton
4.9 开关按钮 Switch
4.10 进度条 ProgressBar
4.11 忙碌指示器 BusyIndicator
4.12 组合框 ComboBox
4.13 旋钮 Dial
4.14 滑动条 Slider
4.15 数值输入器 SpinBox
4.16 滚轮 Tumbler
4.17 单行文本编辑器 TextField
4.18 多行文本编辑器 TextArea
4.19 边框容器 Frame
4.20 组容器 GroupBox
4.21 滑动视图 SwipeView
4.22 滚动视图 ScrollView
4.23 分割视图 SplitView
4.24 栈视图 StackView
4.25 选项卡 TabBar
4.26 项目代理 ItemDelegate
4.27 按钮类代理 ButtonDelegate
4.28 滑动代理 SwipeDelegate
4.29 菜单 Menu
4.30 导航控件 Drawer
4.31 弹出控件 Popup
4.32 日期控件 Date
4.33 分隔控件 Separator
4.34 设置控件样式 QuickStyle
4.35 自定义控件 QuickToast
4.36 自定义控件 QuickSliderBar
五、Qt Quick对话框
六、图片
七、动画
八、图形效果
九、粒子效果
十、Qt Quick 3D
十一、模型与视图
十二、图表
十三、数据可视化
十四、多媒体
十五、QML与C++交互
15.1 QML与C++交互基础
1 - 描述
QML语言与C++语言可以直接进行交互。这种机制允许将QML和C++进行混合开发。
QML与C++的交互具有以下优势:
1)UI界面与业务逻辑分离
2)扩展了QML语言的功能
注意:
一般情况下,不推荐C++中操作QML对象,重构QML要比重构C++容易的多。为了减少后期维护成本,建议尽量减少在C++端处理QML内容。
2 - QObject子类暴露给QML的方式
只有QObject派生的类才能将数据或函数提供给QML使用。由QObject派生的所有子类的属性、方法和信号等都可以在QML中访问。
暴露给QML的2种方式:
1)C++类注册为实例化QML类型
2)C++类型注册为一个单例类型
3 - QQmlContext
QQmlContext 提供对象实例化和表示式执行所需的运行时上下文。所有对象要在特定的上下文中实例化,所有表达式要在特定上下文中执行。
QQmlContext 允许数据暴露给QML引擎实例化的QML组件,它包括一系列属性,通过名字将数据显示绑定到上下文。
使用 setContextProperty() 函数来定义、更新上下文中的属性。
使用 QQmlContext 需要在C++文件中包含:
#include <QQmlContext>
4 - 示例
添加新文件
第3步的时候,点击 instance,然后右键菜单中选择 重构(R)。
然后再点击 getText 右键去 cpp文件中重构:
这样,我们就可以在qml文件中调用了:

下面附上源码:
// interface.h
#ifndef INTERFACE_H
#define INTERFACE_H
#include <QObject>
// 2
#define INTERFACE (Interface::instance())
class Interface : public QObject
{
Q_OBJECT
public:
explicit Interface(QObject *parent = nullptr);
// 5
Q_INVOKABLE QString getText();
// 3
static Interface* instance();
signals:
};
#endif // INTERFACE_H
// interface.cpp
#include "interface.h"
// 1
Q_GLOBAL_STATIC(Interface, interface)
Interface::Interface(QObject *parent)
: QObject{parent}
{}
// 4
Interface *Interface::instance()
{
return interface;
}
// 6
QString Interface::getText()
{
return "QML and C++";
}
// main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext> // 1
#include "interface.h" // 2
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
QObject::connect(
&engine,
&QQmlApplicationEngine::objectCreationFailed,
&app,
[]() { QCoreApplication::exit(-1); },
Qt::QueuedConnection);
// 3
engine.rootContext()->setContextProperty("ui", INTERFACE);
engine.loadFromModule("untitled8", "Main");
return app.exec();
}
// main.qml
import QtQuick
import QtQuick.Controls
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Label {
font { pixelSize: 24; bold: true }
text: ui.getText()
}
}
15.2.1 Q_INVOKABLE
1 - 描述
Q_INVOKABLE 是一个宏,它可以让 QML 能直接访问 C++ 类中的成员函数。
通过这个宏,使复杂的业务逻辑可以在 C++中实现,而QML则专注于用户界面。这种分工使代码更加清晰,提高了整个应用的性能和可维护性。
在C++中成员函数默认无法被QML直接访问,为了实现这种跨语言的调用,当在类的成员函数前加上这个宏,Qt的元对象编译器(meta-Object Compiler,moc)会处理这个标记并在编译时生产额外的元数据,这些元数据使得函数能够在运行时被QML引擎识别和调用。
注意!在QML中绑定被 Q_INVOKABLE 修饰的函数它不会自动更新数据!
2 - 代码示例
下面我们做一个如下图所示的小程序,每点击一次左边加号按钮,中间的数字就会加上1,右侧的减号按钮则相反,即每点击一次中间的数字就会减掉1。
写完后我们发现,加减的2个按钮好像没生效,因为中间的数字没有变化。但实际上,按钮是生效了的,只是数据没有实时更新到 QML端,这个就是前面描述中提到的,被 Q_INVOKABLE 修饰的函数它不会自动更新数据。
那怎么才能实现实时更新呢?答案是加个定时器(Timer)…… 通过定时器来不断刷新。
// main.qml
import QtQuick
import QtQuick.Controls
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Row {
spacing: 20
Button {
text: "+"; font { pixelSize: 24; bold: true }
onClicked: {
var count = ui.getCount()
ui.setCount(++count)
}
}
Label {
id: label
font { pixelSize: 24; bold: true }
text: ui.getCount()
Timer {
interval: 100
repeat: true
triggeredOnStart: true
running: true
onTriggered: { parent.text = ui.getCount() }
}
}
Button {
text: "-"; font { pixelSize: 24; bold: true }
onClicked: {
var count = ui.getCount()
ui.setCount(--count)
}
}
}
}
// interface.h
#ifndef INTERFACE_H
#define INTERFACE_H
#include <QObject>
// 2
#define INTERFACE (Interface::instance())
class Interface : public QObject
{
Q_OBJECT
public:
explicit Interface(QObject *parent = nullptr);
int count = 0;
Q_INVOKABLE void setCount(int value); // 5
Q_INVOKABLE int getCount();
// 3
static Interface* instance();
signals:
};
#endif // INTERFACE_H
// interface.cpp
#include "interface.h"
#include <QDebug>
// 1
Q_GLOBAL_STATIC(Interface, interface)
Interface::Interface(QObject *parent)
: QObject{parent}
{}
void Interface::setCount(int value) // 6
{
count = value;
qDebug() << count;
}
int Interface::getCount()
{
return count;
}
// 4
Interface *Interface::instance()
{
return interface;
}
15.2.2 Q_PROPERTY
1 - 描述
Q_PROPERTY 宏用于声明类的属性,这些属性可以在Qt的元对象系统中使用,从而实现属性的自动化处理,如绑定、序列化、动态属性设置等。
Q_PROPERTY 的作用:
1)属性声明:用于声明一个类的属性,并将其与类中的成员变量或成员函数关联起来。这使得属性可以通过Qt元对象系统进行访问和操作。
2)与Qt元对象系统集成:通过QObject的 setProperty 和 property 方法进行设置和获取,并且这些属性可以在Qt的设计器、Qt脚本、QML等中使用。
3)信号和槽的集成:可以与信号和槽机制结合使用,从而在属性值改变时自动发出信号,通知相关部分进行相应的处理。
2 - 语法
Q_PROPERTY (
参数(中括号为可选) | 解释 |
---|---|
type | 属性的数据类型 |
name | 属性的名称 |
READ getFunction | 用于读取属性值的成员函数 |
[WRITE setFunction] | 用于写入属性值的成员函数(可选) |
[NOFITY notifySignal] | 属性值改变时发出的信号(可选) |
[RESET resetFunction] | 用于重置属性值的成员函数(可选) |
[REVISION int] | 属性的版本号(可选) |
[DESIGNABLE bool] | 属性是否在Qt设计器中可见(可选) |
[SCRIPTABLE bool] | 属性是否可以在Qt脚本中使用(可选) |
[STORED bool] | 属性是否应该被存储(可选) |
[USER bool] | 是否是用户属性(可选) |
[CONSTANT] | 属性值是否恒定不变(可选) |
[FINAL] | 属性值是否是最终属性,不能被子类重写(可选) |
)
注意!加粗并标红是必选参数,仅加粗是常用参数,其它为非常用参数。
3 - 自动生成
建议使用工具自动生成 Q_PROPERTY 相关代码!
别看 Q_PROPERTY 有这么多参数,其实一个也不用我们亲自去写,只需要用重构中的快捷功能就能快速创建出变量需要的所有信号、函数等等,这种自动生成的代码大大减少了人工编写的失误。
使用方法也非常简单哦~比如我们在 public 下声明一个变量 level。
然后我们点到 变量level 这个单词的中间,接着我们右键重构,点击 Generate Q_PROPERTY and Missing Members。
2步,仅此2步,你就可以去QML直接调用啦!
之前我们使用 Q_INVOKABLE,但是它不能实时更新,我们自己还要在 QML端专门写一个定时器去刷新。现在就不需要定时器了,因为它可以实时更新哦!
看看修改后的代码:
// main.qml
import QtQuick
import QtQuick.Controls
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Row {
spacing: 20
Button {
text: "+"; font { pixelSize: 24; bold: true }
onClicked: {
ui.level ++
}
}
Label {
id: label
font { pixelSize: 24; bold: true }
text: ui.level
}
Button {
text: "-"; font { pixelSize: 24; bold: true }
onClicked: {
ui.level --
}
}
}
}
现在看起来是不是简洁多了?唯一不足可能就是一开始定义类那一块有点小麻烦,后面使用起来就很简单方便了。
15.2.3 信号
1 - 描述
QML 代码可以使用 QObject 子类的任意 public信号。QML引擎会为每一个来自QObject派生类的信号自动创建一个信号处理器。
这些信号处理器具有相对统一的名字:on<Signal>,其中Signal 即信号的名字,首字母大写。信号传递的参数通过其名字在信号处理器中使用。
注意!
信号的参数必须能够被QML引擎支持。如果使用了QML引擎不支持的参数类型,程序不会报错,只是参数值不能在信号处理函数中被访问到。
C++类可能具有参数列表不同的多个同名信号,但是只有最后一个信号才能被QML访问到。使用相同名称不同参数的信号无法被区分。