505 lines
13 KiB
Markdown
505 lines
13 KiB
Markdown
|
# QML 应用程序开发技术总结
|
|||
|
|
|||
|
## 1. 方法/属性名称大小写
|
|||
|
|
|||
|
QML 语法与 JS 相同,具体请参考相关教程,此处只对一些需要特殊注意的地方进行说明。
|
|||
|
|
|||
|
QML 的属性、变量、函数等一般为小写字母开头,大写字母开头通常具有特殊意义,比如使用 QML 连接名为 demo() 的信号时,系统将自动连接 onDemo() 作为其槽函数。以下以连接 Button 的 clicked 信号为例进行演示:
|
|||
|
|
|||
|
```qml
|
|||
|
import QtQuick 2.12
|
|||
|
import QtQuick.Window 2.12
|
|||
|
import QtQuick.Controls 2.12
|
|||
|
|
|||
|
Window {
|
|||
|
width: 640
|
|||
|
height: 480
|
|||
|
visible: true
|
|||
|
|
|||
|
Button {
|
|||
|
onClicked: {
|
|||
|
// Do something...
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## 2. 全局属性
|
|||
|
|
|||
|
```qml
|
|||
|
import QtQuick 2.12
|
|||
|
import QtQuick.Window 2.12
|
|||
|
import QtQuick.Controls 2.12
|
|||
|
|
|||
|
Window {
|
|||
|
width: 640
|
|||
|
height: 480
|
|||
|
visible: true
|
|||
|
|
|||
|
property int demoInt: 1
|
|||
|
property string demoStr: "Hello"
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## 3. 连接信号槽
|
|||
|
|
|||
|
TODO: 细化
|
|||
|
|
|||
|
信号与信号处理器
|
|||
|
|
|||
|
```qml
|
|||
|
signal()
|
|||
|
|
|||
|
on<Signal>: {}
|
|||
|
```
|
|||
|
|
|||
|
属性变化信号与属性变化信号处理器
|
|||
|
|
|||
|
```qml
|
|||
|
property
|
|||
|
|
|||
|
on<Property>Changed: {}
|
|||
|
```
|
|||
|
|
|||
|
附加属性与附加信号处理器
|
|||
|
|
|||
|
```qml
|
|||
|
Attached Signal(附加属性)
|
|||
|
|
|||
|
XX元素.on<附加属性>: {}
|
|||
|
```
|
|||
|
|
|||
|
Connections 建立信号与槽的连接
|
|||
|
|
|||
|
```qml
|
|||
|
Connections {
|
|||
|
target: 发送者
|
|||
|
发送者信号处理器:{}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
connect()方法 信号连接信号
|
|||
|
|
|||
|
```qml
|
|||
|
元素对象.信号.connect(信号)
|
|||
|
```
|
|||
|
|
|||
|
## 4. 界面加载完成信号
|
|||
|
|
|||
|
许多时候需要在 QML 程序界面完成加载后做一些事情,比如设置一些属性的初始值。这时候就需要用到 Component 的 completed() 信号:
|
|||
|
|
|||
|
```qml
|
|||
|
import QtQuick 2.12
|
|||
|
import QtQuick.Window 2.12
|
|||
|
import QtQuick.Controls 2.12
|
|||
|
|
|||
|
Window {
|
|||
|
width: 640
|
|||
|
height: 480
|
|||
|
visible: true
|
|||
|
|
|||
|
Text {
|
|||
|
id: demoText
|
|||
|
anchors.fill: parent
|
|||
|
}
|
|||
|
|
|||
|
Component.onCompleted: {
|
|||
|
demoText.text = "Hello"
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## 5. 包别名
|
|||
|
|
|||
|
编写 QML 应用时需要像 include 那样引用一些软件包,有的时候软件包中的对象会有重名的情况,比如 QtQuick.Dialogs 1.3 和 Qt.labs.qmlmodels 1.0 中均包含了 FileDialog 对象,但他们的功能并不相同。此时可以使用 import as 来实现类似别名或者命名空间的功能。
|
|||
|
|
|||
|
```qml
|
|||
|
import QtQuick 2.12
|
|||
|
import QtQuick.Window 2.12
|
|||
|
import QtQuick.Dialogs 1.3
|
|||
|
import Qt.labs.platform 1.1 as QLP
|
|||
|
|
|||
|
Window {
|
|||
|
width: 640
|
|||
|
height: 480
|
|||
|
visible: true
|
|||
|
|
|||
|
FileDialog {
|
|||
|
title: qsTr("保存到…")
|
|||
|
folder: QLP.StandardPaths.writableLocation(QLP.StandardPaths.DocumentsLocation)
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## 6. 添加图标
|
|||
|
|
|||
|
### 6.1. 制作图标
|
|||
|
|
|||
|
先安装 ImageMagic 工具,并将其添加到系统 PATH 下方便使用。
|
|||
|
|
|||
|
使用以下命令将多个不同尺寸的图片打包成一个 ICO 文件。
|
|||
|
|
|||
|
```bash
|
|||
|
magick.exe convert icon-16.png icon-32.png icon-256.png myappico.ico
|
|||
|
```
|
|||
|
|
|||
|
### 6.2. 添加图标到应用
|
|||
|
|
|||
|
在 Qt .pro 文件中添加以下内容,以便将图标编译到 Qt 程序中:
|
|||
|
|
|||
|
```qt
|
|||
|
RC_ICONS = myappico.ico
|
|||
|
```
|
|||
|
|
|||
|
However, if you already have an .rc file, for example, with the name myapp.rc, which you want to reuse, the following two steps will be required. First, put a single line of text to the myapp.rc file:
|
|||
|
|
|||
|
```qt
|
|||
|
IDI_ICON1 ICON "myappico.ico"
|
|||
|
```
|
|||
|
|
|||
|
Then, add this line to your myapp.pro file:
|
|||
|
|
|||
|
```qt
|
|||
|
RC_FILE = myapp.rc
|
|||
|
```
|
|||
|
|
|||
|
If you do not use qmake, the necessary steps are: first, create an .rc file and run the rc or windres program on the .rc file, then link your application with the resulting .res file.
|
|||
|
|
|||
|
更详细内容见《[Setting the Application Icon](https://doc.qt.io/qt-5/appicon.html)》。
|
|||
|
|
|||
|
## 7. 绘制圆形
|
|||
|
|
|||
|
设置 Rectangle 的 radius 为边长的一半即可。
|
|||
|
|
|||
|
## 8. Dialog 对象
|
|||
|
|
|||
|
在使用 QML 的 Dialog 对象时,如果使用 QGuiApplication 来执行则会导致无法加载主题风格,并且对话框无法正确显示图标。如果最初使用 QGuiApplication 创建了 app,则需要进行如下修改:
|
|||
|
|
|||
|
```cpp
|
|||
|
/**
|
|||
|
* .pro 文件中增加 widgets
|
|||
|
*/
|
|||
|
QT += widgets
|
|||
|
|
|||
|
/**
|
|||
|
* main.cpp 中替换 QGuiApplication 为 QApplication
|
|||
|
*/
|
|||
|
// #include <QGuiApplication>
|
|||
|
#include <QApplication>
|
|||
|
// QGuiApplication app(argc, argv);
|
|||
|
QApplication app(argc, argv);
|
|||
|
```
|
|||
|
|
|||
|
Dialog 对象默认不显示,当调用 Dialog 的 open() 方法后弹出窗口并阻塞父窗体的执行。
|
|||
|
|
|||
|
### 8.1. FileDialog
|
|||
|
|
|||
|
FileDialog 为标准文件对话框。
|
|||
|
|
|||
|
```qml
|
|||
|
import QtQuick 2.12
|
|||
|
import QtQuick.Window 2.12
|
|||
|
import QtQuick.Controls 2.12
|
|||
|
import QtQuick.Dialogs 1.3
|
|||
|
import Qt.labs.platform 1.1 as QLP
|
|||
|
|
|||
|
Window {
|
|||
|
width: 640
|
|||
|
height: 480
|
|||
|
visible: true
|
|||
|
|
|||
|
Button {
|
|||
|
text: qsTr("保存到")
|
|||
|
|
|||
|
onClicked: {
|
|||
|
savetoDialog.open()
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
FileDialog {
|
|||
|
id: savetoDialog
|
|||
|
title: qsTr("保存到…")
|
|||
|
selectFolder: true
|
|||
|
onAccepted: {
|
|||
|
// ...
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Component.onCompleted: {
|
|||
|
savetoDialog.folder = QLP.StandardPaths.writableLocation(QLP.StandardPaths.DocumentsLocation)
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### 8.2. MessageDialog
|
|||
|
|
|||
|
MessageDialog 为标准消息对话框。
|
|||
|
|
|||
|
```qml
|
|||
|
import QtQuick 2.12
|
|||
|
import QtQuick.Window 2.12
|
|||
|
import QtQuick.Controls 2.12
|
|||
|
import QtQuick.Dialogs 1.3
|
|||
|
import Qt.labs.platform 1.1 as QLP
|
|||
|
|
|||
|
Window {
|
|||
|
width: 640
|
|||
|
height: 480
|
|||
|
visible: true
|
|||
|
|
|||
|
function showInfoDialog(title, info, detail) {
|
|||
|
infoDialog.title = title
|
|||
|
infoDialog.text = info
|
|||
|
infoDialog.detailedText = detail
|
|||
|
infoDialog.open()
|
|||
|
}
|
|||
|
|
|||
|
MessageDialog {
|
|||
|
id: infoDialog
|
|||
|
standardButtons: MessageDialog.Ok
|
|||
|
title: ""
|
|||
|
text: ""
|
|||
|
}
|
|||
|
|
|||
|
Button {
|
|||
|
text: qsTr("显示消息")
|
|||
|
|
|||
|
onClicked: {
|
|||
|
showInfoDialog(qsTr("标题"), qsTr("信息"), qsTr("细节"))
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## 9. ComboBox
|
|||
|
|
|||
|
ComboBox 为标准组合框
|
|||
|
|
|||
|
```qml
|
|||
|
import QtQuick 2.12
|
|||
|
import QtQuick.Window 2.12
|
|||
|
import QtQuick.Controls 2.12
|
|||
|
|
|||
|
Window {
|
|||
|
width: 640
|
|||
|
height: 480
|
|||
|
visible: true
|
|||
|
|
|||
|
ComboBox {
|
|||
|
id: baudrate
|
|||
|
width: 88
|
|||
|
height: 24
|
|||
|
anchors.left: port.right
|
|||
|
anchors.top: parent.top
|
|||
|
currentIndex: 6
|
|||
|
anchors.leftMargin: 8
|
|||
|
anchors.topMargin: 8
|
|||
|
model: ["2400", "4800", "9600", "19200", "38400", "57600", "115200", "230400", "460800"]
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## 10. ScrollView
|
|||
|
|
|||
|
ScrollView 会为其所容纳的对象创建滚动条。
|
|||
|
|
|||
|
## 11. GridView
|
|||
|
|
|||
|
GridView 可以以网格的形式显示模型内容。可以使用 ListModel 或 XmlListModel 作为模型。
|
|||
|
|
|||
|
```qml
|
|||
|
import QtQuick 2.12
|
|||
|
import QtQuick.Window 2.12
|
|||
|
import QtQuick.Controls 2.12
|
|||
|
|
|||
|
Window {
|
|||
|
width: 640
|
|||
|
height: 480
|
|||
|
visible: true
|
|||
|
id: window
|
|||
|
// 测试按钮, 添加项目
|
|||
|
Button {
|
|||
|
id: butAdd
|
|||
|
width: 64
|
|||
|
height: 24
|
|||
|
text: qsTr("添加")
|
|||
|
anchors.left: parent.left
|
|||
|
anchors.top: parent.top
|
|||
|
anchors.leftMargin: 4
|
|||
|
anchors.topMargin: 4
|
|||
|
|
|||
|
onClicked: {
|
|||
|
demoList.append({fname: "fruit", fcost: "price"})
|
|||
|
}
|
|||
|
}
|
|||
|
// 测试按钮, 清空全部项目
|
|||
|
Button {
|
|||
|
id: butClear
|
|||
|
width: 64
|
|||
|
height: 24
|
|||
|
text: qsTr("清空")
|
|||
|
anchors.left: butAdd.right
|
|||
|
anchors.top: parent.top
|
|||
|
anchors.leftMargin: 4
|
|||
|
anchors.topMargin: 4
|
|||
|
|
|||
|
onClicked: {
|
|||
|
demoList.clear()
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 使用 Rectangle 作为背景.
|
|||
|
Rectangle {
|
|||
|
color: "#e0e0e0"
|
|||
|
anchors.left: parent.left
|
|||
|
anchors.right: parent.right
|
|||
|
anchors.top: butAdd.bottom
|
|||
|
anchors.bottom: parent.bottom
|
|||
|
anchors.rightMargin: 4
|
|||
|
anchors.bottomMargin: 4
|
|||
|
anchors.leftMargin: 4
|
|||
|
anchors.topMargin: 4
|
|||
|
|
|||
|
GridView {
|
|||
|
id: demoView
|
|||
|
anchors.fill: parent
|
|||
|
cellWidth: 128
|
|||
|
cellHeight: 32
|
|||
|
contentWidth: cellWidth
|
|||
|
contentHeight: cellHeight
|
|||
|
// clip 属性非常重要, 如不设置该属性, 显示的单元信息有可能溢出 GridView 边界.
|
|||
|
clip: true
|
|||
|
|
|||
|
// 滚动条设置.
|
|||
|
ScrollBar.vertical: ScrollBar {
|
|||
|
policy: ScrollBar.AlwaysOn
|
|||
|
}
|
|||
|
|
|||
|
// 使用 ListModel 作为模型.
|
|||
|
model: ListModel {
|
|||
|
id: demoList
|
|||
|
ListElement {fname: "Apple"; fcost: "2.45"}
|
|||
|
ListElement {fname: "Orange"; fcost: "3.25"}
|
|||
|
}
|
|||
|
|
|||
|
// 委托包含用于显示信息的 Text 对象和用于获取鼠标点击事件的 MouseArea
|
|||
|
delegate: Rectangle {
|
|||
|
id: demoDelegate
|
|||
|
width: demoView.cellWidth
|
|||
|
height: demoView.cellHeight
|
|||
|
color: "#f6f6f6"
|
|||
|
MouseArea {
|
|||
|
anchors.fill: parent
|
|||
|
onClicked: {
|
|||
|
// index 为系统传入参数, 代表当前点击对象的索引值.
|
|||
|
// fcost 和 fcost 为 demoList 中的对象属性, 需要特别注意其用法.
|
|||
|
demoList.setProperty(index, "fcost", "free")
|
|||
|
console.log(demoList.get(index).fcost)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Text {
|
|||
|
anchors.fill: parent
|
|||
|
horizontalAlignment: Text.AlignHCenter
|
|||
|
verticalAlignment: Text.AlignVCenter
|
|||
|
text: fname+": "+fcost
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## 12. QML 与 C++ 交互
|
|||
|
|
|||
|
QML 与 C++ 交互的主要实现方式是:
|
|||
|
|
|||
|
1. QML 访问 C++ 中声明的类型;
|
|||
|
2. QML 访问 C++ 中创建的对象;
|
|||
|
3. QML 调用 C++ 中的方法或发射信号;
|
|||
|
4. QML 响应 C++ 中产生的信号;
|
|||
|
5. C++ 访问 QML 对象。
|
|||
|
|
|||
|
QML 与 C++ 之间主要通过信号槽机制来传递消息。
|
|||
|
|
|||
|
### 12.1. QML 访问 C++ 中声明的类型
|
|||
|
|
|||
|
QML 使用 C++ 中声明的类型可以为类、结构体或枚举等。若需要将 C++ 类导出给 QML,则需要使用 qmlRegisterType() 方法进行注册:
|
|||
|
|
|||
|
```cpp
|
|||
|
qmlRegisterType<Type>("package.Type", <version>, <sub-version>,"Type");
|
|||
|
```
|
|||
|
|
|||
|
之后在 QML 中使用 C++ 类创建对象即可。
|
|||
|
|
|||
|
若 QML 需要使用 C++ 中声明的枚举类型,则该枚举需要使用 Q_ENUM() 进行修饰;若访问属性成员则需要使用 Q_PROPERTY() 进行修饰;若访问方法则该方法需要使用 Q_INVOKABLE() 来修饰。C++ 中的信号不需要特殊处理,QML 可直接访问。
|
|||
|
|
|||
|
若对象在 C++ 上下文中创建,并需要在 QML 中使用该对象,可以使用:
|
|||
|
|
|||
|
```cpp
|
|||
|
QQmlApplicationEngine engine;
|
|||
|
engine.rootContext()->setContextProperty("qmlObj", cObj);
|
|||
|
```
|
|||
|
|
|||
|
将 C++ 对象注册到 QML 上下文环境中。
|
|||
|
|
|||
|
### 12.2. C++ 访问 QML 对象
|
|||
|
|
|||
|
在 QML 中为对象添加 objectName 属性后,在 C++ 中可使用:
|
|||
|
|
|||
|
```cpp
|
|||
|
auto root = engine.rootObjects();
|
|||
|
auto qmlObj = root.first()->findChild<QObject*>("object name");
|
|||
|
```
|
|||
|
|
|||
|
查找该对象,再连接信号槽等。
|
|||
|
|
|||
|
大部分情况下在 QML 中访问 C++ 即可实现较完善的功能,QML 传递信息给 C++ 完全可以通过信号槽机制实现。除非需要在 C++ 中动态创建对象并连接到 QML 中的信号槽,否则没必要这样设计。
|
|||
|
|
|||
|
## 12.3. qRegisterMetaType
|
|||
|
|
|||
|
当使用信号槽机制是,需要注意一点:如果需要通过信号槽传递自建类型数据,则需要使用 qRegisterMetaType() 方法进行注册。
|
|||
|
|
|||
|
```cpp
|
|||
|
qRegisterMetaType<MyClass>("Myclass");
|
|||
|
```
|
|||
|
|
|||
|
### 12.4. QML 与 C++ 交互综合示例
|
|||
|
|
|||
|
```cpp
|
|||
|
TODO: 添加示例
|
|||
|
```
|
|||
|
|
|||
|
```cpp
|
|||
|
TODO: 添加示例
|
|||
|
```
|
|||
|
|
|||
|
```qml
|
|||
|
TODO: 添加示例
|
|||
|
```
|
|||
|
|
|||
|
## 13. Windows 下 QML 程序的打包发布
|
|||
|
|
|||
|
Qt 提供了导出 Qt 环境变量的命令行脚本,比如“Qt 5.15.2 (MinGW 8.1.0 64-bit)”,运行该脚本可进入带有 Qt 环境变量的命令行界面,之后可通过如下命令打包程序(编译生成的可执行程序需要拷贝到\<Package Output Path\>):
|
|||
|
|
|||
|
```bash
|
|||
|
cd <Package Output Path>
|
|||
|
windeployqt <Exe File> [--qmldir <Project QML File Path>]
|
|||
|
```
|
|||
|
|
|||
|
Qt 自带的打包程序会添加额外的库,如果想进一步减小体积,可手动筛减。
|
|||
|
|
|||
|
## 14. 外部参考
|
|||
|
|
|||
|
1. [深入了解JS中的整数](https://www.jianshu.com/p/1ba45c3894ab)
|
|||
|
2. [QML 中的信号与槽](https://blog.csdn.net/Love_XiaoQinEr/article/details/123746983)
|
|||
|
3. [QML 控件类型:ScrollBar、ScrollIndicator](https://blog.csdn.net/kenfan1647/article/details/122522063)
|
|||
|
4. [QML类型:GridView](https://blog.csdn.net/kenfan1647/article/details/120761466)
|
|||
|
5. [Qt Quick 常用元素:ComboBox(下拉列表) 与 ProgressBar(进度条)](https://www.cnblogs.com/linuxAndMcu/p/11949814.html)
|
|||
|
6. [【QML Model-View】ListView-增删改查(二)](https://www.cnblogs.com/linuxAndMcu/p/13597106.html)
|
|||
|
7. [关于 Q_ENUMS 和 Q_ENUM 的区别和用法](https://blog.csdn.net/lsylovezsl/article/details/108766437)
|
|||
|
8. [C++ 共享枚举类型给 QML](https://www.cnblogs.com/linkyip/p/14462288.html)
|
|||
|
9. [QML Connections: Implicitly defined onFoo properties in Connections are deprecated.](https://blog.csdn.net/weixin_43720622/article/details/112346039)
|
|||
|
10. [QML 调用 C++ 方法](https://blog.csdn.net/woshouji1/article/details/121348179)
|