Qml高级功能
# 03.Quick基础
# 目录介绍
- 3.4 事件处理
- 3.4.1 MouseArea事件
- 3.4.2 键盘事件
- 3.4.3 触摸事件
- 3.4.4 手势识别
- 3.4.5 自定义事件
- 3.4.6 事件传播
- 3.4.7 事件过滤器
- 3.4.8 Keys按键事件
- 3.5 动态加载组件
- 3.5.1 Qt.createQmlObject
- 3.5.2 createObject
- 3.5.3 Loader方式
- 3.5.4 Repeater方式
- 3.5.5 JavaScript方式
- 3.8 异步编程技巧
- 3.8.1 WorkerScript
- 3.8.2 Timer定时器
- 3.8.3 Qt.callLater
- 3.8.4 C++和QML结合
- 3.8.5 JavaScript异步
- 3.8.6 动态加载
# 3.1 基础可视项目
# 3.1.1 Item
Item 是一个非常重要的基础类型。它是所有可视化元素的基类,但本身并不直接绘制任何内容。
Item 提供了一个容器和布局的基础,用于组织和管理其他可视化元素。Item 的使用场景:
- 布局容器: 用于组织和管理子元素的位置和大小。
- 事件处理: 用于捕获鼠标或触摸事件。
- 变换和动画: 用于实现复杂的动态效果。
- 不可见的逻辑容器: 用于逻辑分组,而不直接显示内容。
Item 是 Qt Quick 中的一个核心类型,位于 QtQuick 模块中。它是一个不可见的容器,主要用于:
- 定义一个矩形区域。
- 组织和管理子元素。
- 提供基本的属性(如位置、大小、锚点等)。
- 处理输入事件(如鼠标和触摸事件)。
Item 本身不会绘制任何内容,但它可以作为其他可视化元素(如 Rectangle、Image、Text 等)的父级。
Item常用属性,几何属性:
x和y: 定义Item的位置(相对于父级)。width和height: 定义Item的宽度和高度。z: 定义Item的堆叠顺序,值越大,越靠前显示。
Item常用属性,锚点属性:
anchors,用于将Item锚定到父级或其他元素。
Item常用属性,可见性属性
visible: 控制Item是否可见(true或false)。opacity: 控制Item的透明度(范围为 0.0 到 1.0)。
Item常用属性,变换属性
scale: 控制Item的缩放比例。rotation: 控制Item的旋转角度(以度为单位)。transform: 用于应用复杂的变换(如平移、旋转、缩放等)。
Item子元素管理
Item 可以作为其他元素的父级,子元素会继承父级的几何属性(如位置和变换)。
# 3.1.2 Text
Text 是用于显示文本内容的 QML 类型。Text 类型允许你在用户界面中显示静态或动态文本,并提供了许多属性来控制文本的外观、样式和布局。
- 基本用法:Text综合案例,展示各种用法
Text 类型用于显示文本内容。你可以设置文本内容、字体、颜色、大小、对齐方式等属性。 以下是一个简单的 Text 示例:
Text {
text: "Hover over this text to change color"
font.pixelSize: 20
color: "black"
font.bold: true
horizontalAlignment: Text.AlignHCenter
//创建一个鼠标悬停效果,当鼠标悬停在文本上时,改变文本的颜色。
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: {
parent.color = "red"
}
onExited: {
parent.color = "black"
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 文本样式:
- 你可以通过设置
font.family、font.bold、font.italic等属性来定义文本的字体样式。 - 使用
font.pixelSize或font.pointSize属性可以设置文本的字体大小。
- 文本布局:
- 通过设置
horizontalAlignment和verticalAlignment属性,你可以控制文本的水平和垂直对齐方式。 - 使用
wrapMode属性可以指定文本的换行方式,如自动换行或截断。
- 文本格式化:
Text类型支持基本的文本格式化,如加粗、斜体、下划线等。- 你可以使用
style属性来设置文本的格式化样式。
- 动态文本:
- 你可以使用 JavaScript 表达式或绑定来动态设置文本内容,以实现根据数据变化而更新的文本显示。
- 例如,你可以将文本内容绑定到一个变量或属性,使得文本内容随着变量的改变而更新。
- 交互和事件处理:
- 你可以为
Text添加交互行为,如鼠标点击事件、悬停效果等。 - 通过处理鼠标事件或触摸事件,你可以实现与用户的交互。
- 多语言支持:
Text类型支持多语言文本显示,你可以使用qsTr()函数来实现国际化和本地化。
# 3.1.3 TextInput
TextInput 是用于接收用户输入文本的 QML 类型。TextInput 类型允许用户在应用程序中输入文本,并提供了许多属性和信号来管理和响应用户输入。
- 基本用法:
TextInput 类型用于接收用户输入的文本。用户可以在 TextInput 中输入文本,并应用程序可以获取用户输入的内容。
TextInput {
width: 200
placeholderText: "Enter text here"
onTextChanged: {
console.log("User input: " + text)
}
}
2
3
4
5
6
7
- 属性和信号:
TextInput类型提供了许多属性,如text(用户输入的文本内容)、placeholderText(占位文本)、cursorPosition(光标位置)等。- 通过处理信号,如
onTextChanged、onEditingFinished等,你可以响应用户输入的变化和完成编辑操作。
- 键盘输入:
TextInput类型会自动弹出软键盘,以便用户输入文本。你可以通过设置focus属性来控制TextInput的焦点状态。- 你可以使用
inputMethodHints属性来指定输入法提示,以告知输入法如何显示键盘。
- 文本格式化和验证:
- 你可以使用
validator属性来指定文本验证器,以限制用户输入的内容。 - 通过设置
inputMask属性,你可以定义文本输入的格式,如日期、电话号码等。
- 密码输入:
- 如果需要接收密码或其他敏感信息,你可以将
echoMode属性设置为TextInput.Password,以隐藏用户输入的文本。
- 多行输入:
- 除了单行输入,
TextInput类型还支持多行文本输入。你可以设置wrapMode属性来控制文本的换行方式。
- 样式和外观:
- 你可以通过设置
font、color、background等属性来自定义TextInput的外观和样式。 - 使用
readOnly属性可以将TextInput设置为只读模式,禁止用户编辑文本内容。
# 3.1.4 TextEdit
TextEdit 是用于显示和编辑多行文本的 QML 类型。TextEdit 类型允许用户在应用程序中输入、编辑和显示多行文本内容,并提供了许多属性和信号来管理和响应用户的文本操作。
- 基本用法:
TextEdit 类型用于显示和编辑多行文本内容。用户可以在 TextEdit 中输入、编辑和查看多行文本。
TextEdit {
width: 300
height: 200
text: "Initial text content"
onTextChanged: {
console.log("Text content changed: " + text)
}
}
2
3
4
5
6
7
8
- 属性和信号:
TextEdit类型提供了许多属性,如text(文本内容)、font(字体样式)、wrapMode(换行方式)等。- 通过处理信号,如
onTextChanged、onCursorPositionChanged等,你可以响应用户对文本内容的操作和变化。
- 键盘输入和编辑:
TextEdit类型支持多行文本输入和编辑。用户可以在TextEdit中输入、删除、复制、粘贴文本内容。- 你可以设置
readOnly属性来将TextEdit设置为只读模式,禁止用户编辑文本内容。
- 文本格式化和样式:
- 你可以使用
font、color、highlightedText等属性来自定义TextEdit中文本的样式和外观。 - 通过设置
textFormat属性,你可以指定文本的格式化方式,如纯文本、富文本等。
- 滚动和布局:
- 如果文本内容超出
TextEdit的显示区域,TextEdit会自动添加滚动条以便用户查看全部内容。 - 你可以设置
wrapMode属性来控制文本的换行方式,以适应TextEdit的大小。
- 文本选择和操作:
- 用户可以通过鼠标或触摸操作来选择文本内容,并执行复制、剪切、粘贴等操作。
- 通过设置
selectByMouse属性,你可以控制用户是否可以使用鼠标选择文本。
- 撤销和重做:
TextEdit类型支持撤销和重做操作,用户可以通过快捷键或菜单操作来撤销或重做文本编辑操作。
# 3.1.5 Image
Image 写一个包含布局大小,位置,阴影效果,圆角,有交互事件的综合案例
// 图片元素
Image {
id: image
width: parent.width
height: parent.height
source: "https://pics4.baidu.com/feed/728da9773912b31b37cbc23bb3752575dbb4e1fd.jpeg" // 图片 URL
fillMode: Image.PreserveAspectCrop // 保持宽高比并裁剪
clip: true // 裁剪超出部分
radius: 10 // 圆角效果
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: image.width
height: image.height
radius: image.radius
}
}
// 鼠标悬停效果
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: {
image.scale = 1.05 // 放大图片
}
onExited: {
image.scale = 1.0 // 恢复原大小
}
onClicked: {
console.log("Image clicked!");
}
}
// 缩放动画
Behavior on scale {
NumberAnimation { duration: 200 } // 200ms 动画
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 3.2 布局管理
在 Qt Quick 中,布局管理是用于组织和排列界面元素的核心机制。
Qt Quick 提供了多种布局管理器(如 Row、Column、Grid、Flow 等)以及锚点系统(Anchors)来实现灵活的界面布局。
# 3.2.1 容器Rectangle
Rectangle 是一个常用的 QML 类型,用于创建矩形形状的图形元素。
Rectangle 类型允许你定义矩形的外观、位置、大小和其他属性,以便在用户界面中显示矩形元素。
- 基本用法:,
Rectangle类型用于创建矩形形状的图形元素。你可以设置矩形的宽度、高度、颜色、边框等属性。
以下是一个简单的 Rectangle 示例:
Rectangle {
id:root
x:212
y:12
width: 76
height: 96
visible: true
//gradient 属性用于定义渐变效果,这里使用了线性渐变(Gradient)。
gradient: Gradient {
GradientStop { position: 0.0; color: "lightsteelblue" }
GradientStop { position: 1.0; color: "red" }
}
border.color: "green"
border.width: 4
radius: 20
//子元素
Text {
anchors.centerIn: parent
text: "Gradient"
color: "white"
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- 位置和布局:
- 你可以使用
x和y属性来设置矩形的位置,以确定矩形在父元素中的位置。 - 此外,你还可以使用布局属性(如
anchors)来实现更灵活的布局。
- 边框和描边:
- 通过设置
border、border.color和border.width属性,你可以为矩形添加边框。 - 你还可以使用
radius属性来设置矩形的圆角半径,使矩形具有圆角边框。
- 阴影效果:
Rectangle类型还支持阴影效果,你可以使用dropShadow属性来添加阴影效果。- 通过设置阴影的颜色、偏移量和模糊半径,可以实现不同样式的阴影效果。
- 交互和动画:
- 你可以为
Rectangle添加交互行为,如鼠标点击事件、拖动事件等。 - 通过使用动画属性和状态,你可以为矩形添加动画效果,使其在状态变化时产生平滑的过渡效果。
- 嵌套元素:
- 你可以在
Rectangle中嵌套其他 QML 元素,以构建更复杂的用户界面结构。 - 例如,在
Rectangle中嵌套一个Text元素来显示文本内容。
# 3.2.2 基于锚的布局
使用 Anchors 可以实现相对定位,将元素相对于其他元素或父元素进行定位。
通过设置元素的 anchors 属性,你可以定义元素的位置和大小,使得元素能够根据其他元素的位置自动调整。
Anchors基本属性,以下是 anchors 的常用属性:
- 锚定到父组件:
anchors.top:锚定到父组件的顶部。anchors.bottom:锚定到父组件的底部。anchors.left:锚定到父组件的左侧。anchors.right:锚定到父组件的右侧。anchors.horizontalCenter:锚定到父组件的水平中心。anchors.verticalCenter:锚定到父组件的垂直中心。anchors.centerIn:锚定到父组件的中心。
- 锚定到兄弟组件:
anchors.top:锚定到兄弟组件的顶部。anchors.bottom:锚定到兄弟组件的底部。anchors.left:锚定到兄弟组件的左侧。anchors.right:锚定到兄弟组件的右侧。
- 边距:
anchors.margins:设置所有边的边距。anchors.topMargin:设置顶部的边距。anchors.bottomMargin:设置底部的边距。anchors.leftMargin:设置左侧的边距。anchors.rightMargin:设置右侧的边距。
- 填充:
anchors.fill:填充整个父组件。
- 对齐:
anchors.alignWhenCentered:当使用centerIn时,是否对齐到像素网格。
Anchors注意事项
- 避免锚定冲突:不要同时设置
anchors.top和anchors.bottom,或者anchors.left和anchors.right。 - 动态调整布局: 可以使用
Binding或JavaScript动态调整anchors属性。 - 性能优化:对于复杂的布局,
anchors的性能通常优于手动设置x、y、width和height。
# 3.2.3 定位器
Qt Quick 中的 Positioner 类型(如 Column, Row, Grid)可以帮助你自动排列子元素。
布局管理器是用于自动排列子元素的容器。Qt Quick 提供了几种基本的布局类型,如 Column、Row、Grid 等,用于垂直、水平和网格布局。
Row (水平布局)将子元素水平排列。
import QtQuick 2.15
Row {
id: colorRow
spacing: 10 //通过 spacing 属性设置按钮之间的间距。
layoutDirection: Qt.LeftToRight // 布局方向(从左到右或从右到左)
anchors.bottom: paddingRow.top
anchors.horizontalCenter: parent.horizontalCenter
padding: 10 // 内边距
Button {
text: "Light"
onClicked: toolbarBackground.color = "#f0f0f0"
}
Button {
text: "Dark"
onClicked: toolbarBackground.color = "#333333"
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Column (垂直布局)将子元素垂直排列。
import QtQuick 2.15
Column {
spacing: 10 // 子元素之间的间距
Rectangle { width: 50; height: 50; color: "red" }
Rectangle { width: 50; height: 50; color: "green" }
Rectangle { width: 50; height: 50; color: "blue" }
}
2
3
4
5
6
7
8
9
Flow (流式布局)根据可用空间自动换行排列子元素。
import QtQuick 2.15
Flow {
spacing: 10 // 子元素之间的间距
Rectangle { width: 50; height: 50; color: "red" }
Rectangle { width: 50; height: 50; color: "green" }
Rectangle { width: 50; height: 50; color: "blue" }
Rectangle { width: 50; height: 50; color: "yellow" }
}
2
3
4
5
6
7
8
9
10
# 3.3 键盘输入
# 3.3.1 键盘输入概念
Keys附加属性:用于捕获和处理键盘事件。常用事件:
onPressed:按键按下时触发。onReleased:按键释放时触发。onShortcutOverride:快捷键被按下时触发。
KeyEvent对象:表示键盘事件,包含以下常用属性:
key:按键的键值(如Qt.Key_Enter)。text:按键对应的文本(如字母或数字)。modifiers:修饰键(如Shift、Ctrl等)。
- 输入焦点管理:
- 使用
focus属性控制组件是否接收键盘输入。 - 使用
focusScope管理焦点范围。
- 快捷键:
- 使用
Shortcut组件定义快捷键。
# 3.3.2 键盘输入案例
Rectangle {
width: 600
height: 100
color: "#f0f0f0"
focus: true //设置焦点接受键盘事件
Keys.onPressed: {
console.log("Key pressed:", event.key, "Text:", event.text);
if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) {
console.log("Enter key pressed!");
}
}
}
2
3
4
5
6
7
8
9
10
11
12
# 3.3.3 案例说明
捕获键盘事件: 使用 Keys.onPressed 捕获按键事件,并打印按键的键值和文本。
# 3.3.4 键盘注意事项
- 焦点管理:确保组件设置了
focus: true才能接收键盘事件。 使用focusScope管理焦点范围。 - 快捷键冲突:避免快捷键与其他组件的快捷键冲突。
- 平台差异:不同平台的键盘事件可能有所不同,需进行兼容性测试。
- 性能优化:对于复杂的键盘事件处理,避免在事件处理函数中执行耗时操作。
# 3.4 事件处理
事件处理是响应用户交互(如鼠标点击、键盘输入、触摸事件等)的核心机制。
Qt Quick 提供了多种方式来处理事件,包括信号槽机制、事件处理器和手势识别等。
QML 提供了事件处理器(Event Handlers)来直接处理特定事件。事件处理器以 on 开头,后跟事件名称。
# 3.4.1 MouseArea事件
MouseArea 是一个用于处理鼠标(或触摸)事件的组件。它可以附加到任何可视元素(如 Rectangle、Image 等),用于监听鼠标点击、拖动、悬停等操作。
MouseArea 允许你定义鼠标事件的处理函数,如 onClicked、onPressed、onReleased 等。
Rectangle {
width: 200
height: 100
color: "lightblue"
MouseArea {
anchors.fill: parent
onClicked: console.log("Mouse clicked!")
onDoubleClicked: console.log("Mouse double clicked!")
onPressed: console.log("Mouse pressed!")
onReleased: console.log("Mouse released!")
}
}
2
3
4
5
6
7
8
9
10
11
12
13
MouseArea 提供了以下常用属性和信号处理器:
属性:
enabled:是否启用MouseArea(默认为true)。hoverEnabled:是否启用悬停事件(默认为false)。acceptedButtons:指定接收哪些鼠标按钮事件(如Qt.LeftButton、Qt.RightButton)。pressed:鼠标是否按下(true表示按下,false表示释放)。containsMouse:鼠标是否在MouseArea区域内。
信号处理器:
onClicked:鼠标点击时触发。onDoubleClicked:鼠标双击时触发。onPressed:鼠标按下时触发。onReleased:鼠标释放时触发。onEntered:鼠标进入MouseArea区域时触发。onExited:鼠标离开MouseArea区域时触发。onPositionChanged:鼠标在MouseArea区域内移动时触发。
# 3.4.2 键盘事件
通过在元素上设置 Keys.onPressed 或 Keys.onReleased 来处理键盘事件。可以捕获特定按键的按下或释放事件,并执行相应的操作。
使用 Keys 附加属性处理键盘事件:
import QtQuick 2.15
Rectangle {
width: 200
height: 100
color: "lightblue"
focus: true
Keys.onPressed: {
if (event.key === Qt.Key_Space) {
console.log("Space key pressed!")
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 3.4.3 触摸事件
使用 MultiPointTouchArea 处理多点触摸事件,MultiPointTouchArea 允许你处理多点触摸事件,如缩放、旋转等。
import QtQuick 2.15
Rectangle {
width: 200
height: 100
color: "lightblue"
MultiPointTouchArea {
anchors.fill: parent
onTouchUpdated: {
for (var i = 0; i < touchPoints.length; i++) {
console.log("Touch point:", touchPoints[i].x, touchPoints[i].y)
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 3.4.4 手势识别
Qt Quick 提供了手势识别器(Gesture Recognizers)来处理复杂的手势,如滑动、捏合等。
使用 SwipeView 或 SwipeArea 处理滑动手势:
import QtQuick 2.15
import QtQuick.Controls 2.15
SwipeView {
width: 200
height: 100
Rectangle { color: "red" }
Rectangle { color: "green" }
Rectangle { color: "blue" }
}
2
3
4
5
6
7
8
9
10
11
使用 PinchArea 处理捏合手势:
import QtQuick 2.15
Rectangle {
width: 200
height: 100
color: "lightblue"
PinchArea {
anchors.fill: parent
onPinchUpdated: {
console.log("Pinch scale:", pinch.scale)
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 3.4.5 自定义事件
可以通过继承 Item 或 Rectangle 创建自定义事件处理器。
import QtQuick 2.15
Rectangle {
width: 200
height: 100
color: "lightblue"
signal customMouseEvent(int x, int y)
MouseArea {
anchors.fill: parent
onClicked: parent.customMouseEvent(mouse.x, mouse.y)
}
onCustomMouseEvent: {
console.log("Custom mouse event at:", x, y)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 3.4.6 事件传播
QML 中的事件传播遵循父子关系。事件会从子元素向上传播到父元素,直到被处理或到达根元素。
使用 event.accepted = true 阻止事件继续传播。
# 3.4.7 事件过滤器
使用 Item 的 eventFilter 方法过滤事件。允许你在事件到达元素之前拦截和处理事件。
# 3.4.8 Keys按键事件
Keys 附加属性用于处理键盘按键事件。通过 Keys,可以监听和处理用户按下、释放或长按键盘按键的操作。以下是 Keys 的详细使用方法:
Keys 是一个附加属性,通常与 Item 或 FocusScope 一起使用。它提供了以下常用事件处理器:
onPressed:当按键按下时触发。onReleased:当按键释放时触发。onShortcutOverride:当按键事件可能被系统快捷键覆盖时触发。
Rectangle {
width: 200
height: 100
color: "lightblue"
focus: true // 必须设置焦点才能接收按键事件
Keys.onPressed: {
console.log("按键按下:", event.key);
if (event.key === Qt.Key_Return) {
console.log("回车键按下");
}
}
Keys.onReleased: {
console.log("按键释放:", event.key);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Qt 提供了一系列按键常量,用于识别特定的按键。以下是一些常用的按键常量:
Qt.Key_Return:回车键。Qt.Key_Enter:小键盘上的回车键。Qt.Key_Escape:Esc 键。Qt.Key_Space:空格键。Qt.Key_Backspace:退格键。Qt.Key_Delete:删除键。Qt.Key_Left:左箭头键。Qt.Key_Right:右箭头键。Qt.Key_Up:上箭头键。Qt.Key_Down:下箭头键。Qt.Key_A到Qt.Key_Z:字母键。Qt.Key_0到Qt.Key_9:数字键。
阻止事件传播:默认情况下,按键事件会向上传播到父组件。可以通过 event.accepted = true 阻止事件传播。
Rectangle {
width: 200
height: 100
color: "lightblue"
focus: true
Keys.onPressed: {
if (event.key === Qt.Key_Space) {
console.log("空格键按下,事件被阻止");
event.accepted = true; // 阻止事件传播
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# 3.5 动态加载组件
根据具体需求选择合适的方法,可以高效地实现 QML 中的动态创建功能。
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
Qt.createQmlObject | 从字符串动态创建对象 | 灵活 | 性能较差 |
Component | 需要频繁创建对象的场景 | 性能较好 | 需要提前定义模板 |
Loader | 动态加载和切换组件 | 适合动态切换组件 | 只能加载一个组件 |
Repeater | 生成多个相同类型的组件 | 简单易用 | 组件类型必须相同 |
# 3.5.1 Qt.createQmlObject
Qt.createQmlObject 允许从字符串形式的 QML 代码动态创建对象。
object Qt.createQmlObject(string qml, object parent, string filepath)
qml:字符串形式的 QML 代码。适用于需要在运行时动态生成 UI 组件或逻辑的场景。parent:新创建对象的父对象。新创建的对象需要指定一个父对象,用于管理其生命周期。filepath(可选):用于调试的源文件路径,通常设置为创建 QML 代码的文件路径。便于调试时定位问题。
示例:动态创建一个矩形。在窗口加载完成后,动态创建一个红色的矩形,并将其添加到窗口中。this 是父对象,表示新创建的矩形的父对象是当前窗口。
Component.onCompleted: {
// QML 代码字符串
var qmlCode = `
import QtQuick 2.15
Rectangle {
width: 100
height: 100
color: "red"
}
`;
// 动态创建对象并添加到窗口中
var newObject = Qt.createQmlObject(qmlCode, this, "dynamicRect");
console.log("动态创建的对象:", newObject);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 优点:灵活,可以直接从字符串创建对象。
- 缺点:性能较差,适合创建简单对象。
注意事项
- 性能问题:频繁使用
Qt.createQmlObject可能会影响性能,尤其是在创建复杂对象时。 对于需要频繁创建的对象,建议使用Component或Loader。 - 作用域问题:动态创建的对象的作用域受限于其父对象。如果父对象被销毁,动态创建的对象也会被销毁。
- 错误处理:如果 QML 代码字符串有语法错误,
Qt.createQmlObject会抛出异常。建议使用try-catch捕获异常。 - 调试支持:通过
filepath参数指定源文件路径,便于调试时定位问题。
# 3.5.2 createObject
Component + createObject:使用 Component 定义模板,然后通过 createObject 动态创建对象。性能优于 Qt.createQmlObject,适合需要频繁创建对象的场景。
Component 是 QML 中的一种模板机制,可以通过 createObject 方法动态创建对象。示例:动态创建一个按钮
// 定义组件模板
Component {
id: buttonComponent
Button {
text: "点击我"
onClicked: console.log("按钮被点击了!");
}
}
Component.onCompleted: {
// 动态创建对象并添加到窗口中
var newObject = buttonComponent.createObject(this, { x: 150, y: 100 });
console.log("动态创建的对象:", newObject);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
- 优点:性能较好,适合需要频繁创建对象的场景。
- 缺点:需要提前定义
Component。
# 3.5.3 Loader方式
在 Qt Quick (QML) 中,Loader 是一个动态加载和卸载组件的容器。它允许你在运行时根据需要加载不同的 QML 文件或组件,从而优化资源使用和提升性能。Loader 特别适合用于实现动态界面、懒加载或按需加载的场景。
Loader核心属性:
source:指定要加载的 QML 文件或组件的路径。例如:source: "MyComponent.qml"。sourceComponent:指定要加载的组件对象(通常是Component类型)。例如:sourceComponent: myComponent。active: 控制Loader是否处于活动状态。 如果为true,Loader会加载source或sourceComponent指定的内容;如果为false,Loader会卸载内容。item: 获取Loader当前加载的组件实例。 可以通过item访问加载组件的属性和方法。asynchronous:控制是否异步加载组件。如果为true,组件会在后台线程中加载,避免阻塞 UI 线程。status:表示Loader的当前状态,有以下可能值:
Loader.Null:未加载任何内容。Loader.Ready:组件已加载完成。Loader.Loading:组件正在加载。Loader.Error:加载失败。
Loader常用方法
setSource(source, properties):动态设置source并加载组件。properties是一个可选参数,用于传递初始属性值给加载的组件。resetSource():重置source,卸载当前加载的组件。
以下是一个综合案例,展示了如何使用 Loader 动态加载组件、传递属性以及控制加载状态。
// 主布局
Column {
spacing: 20
anchors.centerIn: parent
// 使用 Loader 加载组件
Loader {
id: loader
width: 600
height: 200
onStatusChanged: {
if (status === Loader.Ready) {
console.log("Component loaded successfully!");
} else if (status === Loader.Error) {
console.log("Failed to load component!");
}
}
}
// 1. 动态加载组件
Row {
spacing: 10
Button {
text: "动态加载组件"
onClicked: {
loader.source = "NewPage.qml";
}
}
Button {
text: "卸载组件"
onClicked: {
loader.source = ""; // 卸载组件
}
}
}
// 2. 传递属性给加载的组件
Button {
text: "传递属性给加载的组件"
onClicked: {
loader.setSource("NewPage.qml", { "message": "Hello from Loader!" });
}
}
Component {
id: dynamicComponent
Rectangle {
width: 600
height: 100
color: "lightgreen"
Text {
text: "这个是一个组件"
anchors.centerIn: parent
font.pixelSize: 24
}
}
}
// 3.使用 sourceComponent 加载组件
Button {
text: "Load Dynamic Component"
onClicked: {
loader.sourceComponent = dynamicComponent; //加载组件
}
}
Button {
text: "Unload Dynamic Component"
onClicked: {
loader.sourceComponent = undefined; // 卸载组件
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
监听加载状态:使用 onStatusChanged 监听 Loader 的加载状态,并在加载成功或失败时打印消息。
Loader注意事项:
- 资源管理:及时卸载不再使用的组件,避免内存泄漏。
- 性能优化:对于复杂的组件,可以使用
asynchronous: true异步加载,避免阻塞 UI 线程。 - 组件生命周期:加载的组件的生命周期与
Loader绑定,卸载时组件会被销毁。 - 错误处理:监听
status属性,处理加载失败的情况。
扩展建议
- 可以将
Loader用于实现动态界面切换,如选项卡、向导页面等。 - 可以将
Loader与SwipeView或StackView结合,实现更复杂的导航逻辑。 - 可以将
Loader用于懒加载,仅在需要时加载组件,提升启动性能。
# 3.5.4 Repeater方式
Repeater 是一种用于动态生成多个相同类型组件的机制,通常与模型(model)结合使用。示例:动态生成多个矩形
// 使用 Repeater 动态生成多个矩形
Row {
spacing: 10
Repeater {
model: 5 // 生成 5 个矩形
Rectangle {
width: 50
height: 50
color: index % 2 === 0 ? "red" : "blue"
}
}
}
2
3
4
5
6
7
8
9
10
11
12
优点:适合生成多个相同类型的组件。
缺点:组件类型必须相同。
# 3.5.5 JavaScript方式
# 3.6 导航容器
# 3.6.1 StackView介绍
在 QML 中,StackView 是一个基于堆栈的导航容器,非常适合用于实现页面切换。
它支持页面的推入(push)、弹出(pop)和替换(replace)操作,类似于移动应用中的导航逻辑。
# 3.6.2 核心功能
push(item):将一个新页面推入堆栈,显示在顶部。例如:stackView.push("Page2.qml")。pop(item): 弹出当前页面,返回到上一个页面。例如:stackView.pop()。replace(item):用新页面替换当前页面,堆栈深度不变。例如:stackView.replace("Page3.qml")。clear():清空堆栈中的所有页面。initialItem:设置StackView的初始页面。depth:获取当前堆栈中的页面数量。currentItem:获取当前显示的页面实例。
# 3.6.3 StackView案例
以下是一个完整的案例,展示了如何使用 StackView 实现页面切换。
// 主布局
StackView {
id: stackView
initialItem: page1
anchors.fill: parent
}
// 页面 1
Component {
id: page1
Page {
title: "Page 1"
Column {
spacing: 20
anchors.centerIn: parent
Button {
text: "Go to Page 2"
onClicked: {
stackView.push(page2); // 推入页面 2
}
}
}
}
}
// 页面 2
Component {
id: page2
Page {
title: "Page 2"
Column {
spacing: 20
anchors.centerIn: parent
Button {
text: "Go Back"
onClicked: {
stackView.pop(); // 弹出当前页面
}
}
Button {
text: "Go to Page 3"
onClicked: {
//stackView.push(page3); // 推入页面 3
stackView.push("Container.qml");
}
}
}
}
}
// 页面 3
Component {
id: page3
Page {
title: "Page 3"
Column {
spacing: 20
anchors.centerIn: parent
Button {
text: "Go Back"
onClicked: {
stackView.pop(); // 弹出当前页面
}
}
Button {
text: "Go to Page 1"
onClicked: {
stackView.replace(page1); // 替换为页面 1
}
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# 3.6.4 注意事项
- 页面生命周期:当页面被推入堆栈时,它会创建并显示;当页面被弹出时,它会被销毁。
- 传递参数: 可以通过
push或replace方法的第二个参数传递初始属性值给新页面。 例如:
stackView.push("Page2.qml", { "message": "Hello from Page 1!" });
- 动画效果:
StackView默认支持页面切换的动画效果,可以通过popEnter、popExit、pushEnter和pushExit属性自定义动画。 - 清空堆栈:使用
clear方法可以清空堆栈中的所有页面。
# 3.6.5 扩展功能
1. 传递参数给页面
// 推入页面并传递参数
stackView.push("Page2.qml", { "message": "Hello from Page 1!" });
// 在 Page2.qml 中接收参数
Page {
property string message
Label {
text: message
anchors.centerIn: parent
}
}
2
3
4
5
6
7
8
9
10
11
2. 自定义页面切换动画
StackView {
id: stackView
anchors.fill: parent
pushEnter: Transition {
NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 200 }
}
pushExit: Transition {
NumberAnimation { property: "opacity"; from: 1; to: 0; duration: 200 }
}
popEnter: Transition {
NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 200 }
}
popExit: Transition {
NumberAnimation { property: "opacity"; from: 1; to: 0; duration: 200 }
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
3. 监听页面切换
StackView {
id: stackView
anchors.fill: parent
onCurrentItemChanged: {
console.log("Current page changed to:", currentItem);
}
}
2
3
4
5
6
7
8
# 3.7 页面声明周期
在 QML 中,页面的生命周期由组件的创建、加载、显示、隐藏和销毁等阶段组成。理解 QML 页面的生命周期对于管理资源、处理事件和优化性能非常重要。
# 3.7.1 组件创建和销毁
触发时机:当 QML 文件被加载或实例化时。相关信号和函数:
Component.onCompleted:在组件完全初始化并准备好后触发。Component.onDestruction:在组件即将被销毁时触发。
Rectangle {
width: 100
height: 100
color: "red"
Component.onCompleted: {
console.log("组件创建完成");
}
Component.onDestruction: {
console.log("组件即将销毁");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# 3.7.2 页面加载
触发时机:当页面被加载到视图或窗口时。相关信号和函数:
Page.onStatusChanged:当页面的状态发生变化时触发(如加载、显示、隐藏等)。Loader.onLoaded:当使用Loader动态加载组件时触发。
示例:
Page {
onStatusChanged: {
if (status === Page.Active) {
console.log("页面已激活");
} else if (status === Page.Inactive) {
console.log("页面已隐藏");
}
}
}
2
3
4
5
6
7
8
9
使用场景:
- 页面加载状态:在页面加载完成或状态变化时执行操作。
- 异步操作状态:在异步操作(如网络请求)状态变化时更新 UI。
- 窗口状态:在窗口状态(如最小化、最大化、全屏)变化时执行操作。
# 3.7.3 页面显示
触发时机:当页面变为可见时。相关信号和函数:
Item.visibleChanged:当visible属性发生变化时触发。Window.onVisibleChanged:当窗口的可见性发生变化时触发。
示例:
Item {
visible: true
onVisibleChanged: {
if (visible) {
console.log("页面可见");
} else {
console.log("页面隐藏");
}
}
}
2
3
4
5
6
7
8
9
10
11
使用场景
- 动态显示/隐藏组件:当某个组件的可见性发生变化时,执行特定操作。当 rect.visible 从 false 变为 true 时,会触发 onVisibleChanged。
- 页面切换:在页面显示或隐藏时执行初始化或清理操作。
- 动画控制:在组件显示或隐藏时启动或停止动画。
# 3.7.4 页面激活
触发时机:当页面成为当前活动页面时(例如在 StackView 或 TabView 中切换页面)。
相关信号和函数:Page.onActivated:当页面被激活时触发。Page.onDeactivated:当页面被停用时触发。
示例:
Page {
onActivated: {
console.log("页面已激活");
}
onDeactivated: {
console.log("页面已停用");
}
}
2
3
4
5
6
7
8
9
# 3.7.5 页面销毁
触发时机:当页面被销毁或从内存中移除时。
相关信号和函数:Component.onDestruction:在组件即将销毁时触发。Loader.onItemRemoved:当使用 Loader 动态移除组件时触发。
示例:
Rectangle {
width: 100
height: 100
color: "blue"
Component.onDestruction: {
console.log("组件即将销毁");
}
}
2
3
4
5
6
7
8
9
# 3.7.6 动态加载和卸载
触发时机:当使用 Loader 动态加载或卸载组件时。相关信号和函数:
Loader.onLoaded:当组件加载完成时触发。Loader.onItemRemoved:当组件被移除时触发。
Loader {
id: loader
source: "MyComponent.qml"
onLoaded: {
console.log("组件加载完成");
}
onItemRemoved: {
console.log("组件已移除");
}
}
2
3
4
5
6
7
8
9
10
11
12
# 3.7.7 窗口生命周期
触发时机:当窗口被创建、显示、隐藏或关闭时。相关信号和函数:
Window.onVisibleChanged:当窗口的可见性发生变化时触发。Window.onClosing:当窗口即将关闭时触发。
Window {
visible: true
onVisibleChanged: {
if (visible) {
console.log("窗口可见");
} else {
console.log("窗口隐藏");
}
}
onClosing: {
console.log("窗口即将关闭");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 3.7.8 状态管理
触发时机:当组件的状态发生变化时。相关信号和函数:
State.onCompleted:当状态完全应用时触发。State.onExited:当状态退出时触发。
Rectangle {
width: 100
height: 100
color: "green"
states: [
State {
name: "active"
PropertyChanges { target: parent; color: "red" }
onCompleted: console.log("状态已应用");
onExited: console.log("状态已退出");
}
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 3.7.9 生命周期总结
QML 页面的生命周期包括以下主要阶段:
- 组件创建:
Component.onCompleted和Component.onDestruction。 - 页面加载:
Page.onStatusChanged和Loader.onLoaded。 - 页面显示:
Item.visibleChanged和Window.onVisibleChanged。 - 页面激活:
Page.onActivated和Page.onDeactivated。 - 页面销毁:
Component.onDestruction和Loader.onItemRemoved。 - 动态加载和卸载:
Loader.onLoaded和Loader.onItemRemoved。 - 窗口生命周期:
Window.onVisibleChanged和Window.onClosing。 - 状态管理:
State.onCompleted和State.onExited。
# 3.8 异步编程技巧
在 Qt Quick 和 QML 中,异步编程是处理耗时操作(如网络请求、文件 I/O、复杂计算等)的关键技术,以避免阻塞 UI 线程,保持界面的流畅性。以下是 Qt Quick/QML 中实现异步编程的常见方法:
# 3.8.1 WorkerScript
WorkerScript 是 QML 中用于在后台线程中执行 JavaScript 代码的组件。它通过消息传递机制与主线程通信。示例:
import QtQuick 2.15
import QtQuick.Controls 2.15
ApplicationWindow {
visible: true
width: 400
height: 300
Button {
text: "开始任务"
onClicked: worker.sendMessage({ action: "start" })
}
Text {
id: resultText
anchors.centerIn: parent
text: "等待结果..."
}
WorkerScript {
id: worker
source: "worker.js"
onMessage: {
resultText.text = messageData.result;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
worker.js:
WorkerScript.onMessage = function(message) {
if (message.action === "start") {
// 模拟耗时操作
var result = 0;
for (var i = 0; i < 1000000000; i++) {
result += i;
}
// 将结果发送回主线程
WorkerScript.sendMessage({ result: "计算完成: " + result });
}
};
2
3
4
5
6
7
8
9
10
11
# 3.8.2 Timer定时器
定时器(Timer) 是一种用于执行周期性任务或延迟任务的组件。它可以在指定的时间间隔内触发事件,非常适合用于动画、轮播、数据刷新等场景。
Timer {
id: periodicTimer
interval: 1000 // 定时器的时间间隔,1 秒
repeat: true //是否重复触发定时器
running: true //定时器是否正在运行
onTriggered: {
// 模拟耗时操作
var result = 0;
for (var i = 0; i < 100000000; i++) {
result += i;
}
resultText.text = "计算完成: " + result;
}
}
Button {
text: "Start Delayed Task"
onClicked: {
delayedTimer.start(); //开始启动定时器
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
定时器的核心属性
interval: 定时器的时间间隔,单位为毫秒(ms)。例如,interval: 1000表示每隔 1 秒触发一次。repeat:是否重复触发定时器。如果为true,定时器会按照interval重复触发;如果为false,定时器只会触发一次。running:定时器是否正在运行。如果为true,定时器会立即启动;如果为false,定时器会停止。triggeredOnStart:定时器启动时是否立即触发一次。如果为true,定时器启动时会立即触发一次,然后按照interval重复触发。
定时器的常用方法
start(): 启动定时器。stop(): 停止定时器。restart():重启定时器。
定时器的注意事项
- 性能优化: 对于高频率的定时器(如动画),确保任务执行时间短,避免卡顿。
- 定时器冲突: 避免多个定时器同时运行,导致资源竞争。
- 定时器精度:定时器的触发时间可能受系统负载影响,无法保证完全精确。
- 定时器生命周期:确保定时器在组件销毁时停止,避免内存泄漏。
# 3.8.3 Qt.callLater
Qt.callLater 是 Qt/QML 提供的一个全局函数,用于将指定的函数推迟到当前事件循环结束后执行。
它的主要用途是避免在 UI 更新或事件处理过程中直接执行某些操作,从而防止潜在的竞争条件或 UI 卡顿。
为什么需要 Qt.callLater?
- 避免 UI 卡顿:如果直接调用
passwordDialog.close(),可能会在当前事件循环中立即执行,导致 UI 更新不及时或卡顿。 - 解决竞争条件:在某些情况下,直接执行操作可能会导致未定义的行为(例如,在 UI 更新过程中关闭对话框)。
- 确保操作顺序:
Qt.callLater可以确保某些操作在特定事件(如 UI 更新)完成后执行。
import QtQuick 2.15
import QtQuick.Controls 2.15
ApplicationWindow {
visible: true
width: 400
height: 300
Button {
text: "开始任务"
onClicked: {
// 模拟耗时操作
var result = 0;
for (var i = 0; i < 100000000; i++) {
result += i;
}
// 延迟更新 UI
Qt.callLater(function() {
resultText.text = "计算完成: " + result;
});
}
}
Text {
id: resultText
anchors.centerIn: parent
text: "等待结果..."
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 3.8.4 C++和QML结合
对于更复杂的异步操作(如网络请求、文件 I/O),可以在 C++ 中实现异步逻辑,然后通过 QML 调用。示例:
C++ 部分:
#include <QObject>
#include <QTimer>
#include <QDebug>
class AsyncWorker : public QObject {
Q_OBJECT
public:
AsyncWorker(QObject *parent = nullptr) : QObject(parent) {}
Q_INVOKABLE void startTask() {
QTimer::singleShot(2000, this, [this]() {
qDebug() << "任务完成";
emit taskCompleted("结果数据");
});
}
signals:
void taskCompleted(const QString &result);
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
QML 部分:
import QtQuick 2.15
import QtQuick.Controls 2.15
ApplicationWindow {
visible: true
width: 400
height: 300
Button {
text: "开始任务"
onClicked: worker.startTask()
}
Text {
id: resultText
anchors.centerIn: parent
text: "等待结果..."
}
AsyncWorker {
id: worker
onTaskCompleted: {
resultText.text = result;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 3.8.5 JavaScript异步
虽然 QML 本身不支持 Promise 和 async/await,但可以通过 JavaScript 的异步特性模拟类似行为。示例:
import QtQuick 2.15
import QtQuick.Controls 2.15
ApplicationWindow {
visible: true
width: 400
height: 300
Button {
text: "开始任务"
onClicked: {
asyncTask().then(function(result) {
resultText.text = result;
});
}
}
Text {
id: resultText
anchors.centerIn: parent
text: "等待结果..."
}
function asyncTask() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve("任务完成");
}, 2000);
});
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 3.8.6 异步编程总结
在 Qt Quick/QML 中,实现异步编程的常见方法包括:
WorkerScript:在后台线程中执行 JavaScript 代码。Timer:延迟执行任务或将任务分解为多个步骤。Qt.callLater:将函数推迟到当前事件循环结束后执行。- C++ 和 QML 结合:在 C++ 中实现异步逻辑,通过 QML 调用。
Promise和async/await:模拟 JavaScript 的异步特性。Loader和Component:动态加载和卸载组件。