2022-12-12 15:33:46 +08:00
# QML 应用程序开发技术总结
2022-12-13 16:41:41 +08:00
- [QML 应用程序开发技术总结 ](#qml-应用程序开发技术总结 )
2022-12-13 16:55:12 +08:00
- [1. 基础部分 ](#1-基础部分 )
- [1.1. 方法/属性名称的大小写 ](#11-方法属性名称的大小写 )
- [1.2. 显示顺序 ](#12-显示顺序 )
2022-12-13 16:41:41 +08:00
- [2. 全局属性 ](#2-全局属性 )
- [3. 信号与槽 ](#3-信号与槽 )
- [3.1. 信号与信号处理器 ](#31-信号与信号处理器 )
- [3.2. 属性变化信号与属性变化信号处理器 ](#32-属性变化信号与属性变化信号处理器 )
- [3.3. 附加属性与附加信号处理器 ](#33-附加属性与附加信号处理器 )
- [3.4. Connections 建立信号与槽的连接 ](#34-connections-建立信号与槽的连接 )
- [3.5. connect()方法 ](#35-connect方法 )
- [3.6. 自定义信号 ](#36-自定义信号 )
- [4. 界面加载完成信号 ](#4-界面加载完成信号 )
- [5. 包别名 ](#5-包别名 )
- [6. 添加图标 ](#6-添加图标 )
- [6.1. 制作图标 ](#61-制作图标 )
- [6.2. 添加图标到应用 ](#62-添加图标到应用 )
- [7. 绘制圆形 ](#7-绘制圆形 )
- [8. Dialog 对象 ](#8-dialog-对象 )
- [8.1. FileDialog ](#81-filedialog )
- [8.2. MessageDialog ](#82-messagedialog )
- [9. ComboBox ](#9-combobox )
- [10. ScrollView ](#10-scrollview )
- [11. GridView ](#11-gridview )
2022-12-13 16:55:12 +08:00
- [12. BusyIndicator ](#12-busyindicator )
- [13. QML 与 C++ 交互 ](#13-qml-与-c-交互 )
- [13.1. QML 访问 C++ 中声明的类型 ](#131-qml-访问-c-中声明的类型 )
- [13.2. C++ 访问 QML 对象 ](#132-c-访问-qml-对象 )
- [13.3. 通过信号槽传递自建类型 ](#133-通过信号槽传递自建类型 )
- [13.4. QML 与 C++ 交互综合示例 ](#134-qml-与-c-交互综合示例 )
- [14. Windows 下 QML 程序的打包发布 ](#14-windows-下-qml-程序的打包发布 )
- [15. 外部参考资料 ](#15-外部参考资料 )
2022-12-13 16:41:41 +08:00
2022-12-13 16:55:12 +08:00
## 1. 基础部分
### 1.1. 方法/属性名称的大小写
2022-12-12 15:33:46 +08:00
QML 语法与 JS 相同,具体请参考相关教程,此处只对一些需要特殊注意的地方进行说明。
QML 的属性、变量、函数等一般为小写字母开头,大写字母开头通常具有特殊意义,比如使用 QML 连接名为 demo() 的信号时,系统将自动连接 onDemo() 作为其槽函数。以下以连接 Button 的 clicked 信号为例进行演示:
2022-12-13 16:41:41 +08:00
```js
2022-12-12 15:33:46 +08:00
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...
}
}
}
```
2022-12-13 16:55:12 +08:00
### 1.2. 显示顺序
QML 可以包含多个可显示对象,默认情况下 QML 文件中先写的对象显示在下层,后写的对象显示在上层。在显示时,上层的对象覆盖下层对象。
2022-12-12 15:33:46 +08:00
## 2. 全局属性
2022-12-13 16:41:41 +08:00
```js
2022-12-12 15:33:46 +08:00
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"
}
```
2022-12-13 16:41:41 +08:00
## 3. 信号与槽
2022-12-12 15:33:46 +08:00
2022-12-13 16:41:41 +08:00
在 QML 中连接信号槽主要有以下 5 种方法。
2022-12-12 15:33:46 +08:00
2022-12-13 16:41:41 +08:00
### 3.1. 信号与信号处理器
2022-12-12 15:33:46 +08:00
2022-12-13 16:41:41 +08:00
```js
2022-12-12 15:33:46 +08:00
signal()
on< Signal > : {}
```
2022-12-13 16:41:41 +08:00
这是最常见的方法,比如 onClicked: {} 槽。
### 3.2. 属性变化信号与属性变化信号处理器
2022-12-12 15:33:46 +08:00
2022-12-13 16:41:41 +08:00
```js
2022-12-12 15:33:46 +08:00
property
on< Property > Changed: {}
```
2022-12-13 16:41:41 +08:00
QML 规范,当属性变化后会发送一个 \<Property\>Changed 信号。从 C 环境创建属性时需要注册 NOTIFY 信号,其命名格式也需要遵循该规范。
### 3.3. 附加属性与附加信号处理器
2022-12-12 15:33:46 +08:00
2022-12-13 16:41:41 +08:00
```js
2022-12-12 15:33:46 +08:00
Attached Signal(附加属性)
XX元素.on< 附加属性 > : {}
```
2022-12-13 16:41:41 +08:00
最常见的是 Component.onCompleted: {} 槽。
2022-12-12 15:33:46 +08:00
2022-12-13 16:41:41 +08:00
### 3.4. Connections 建立信号与槽的连接
```js
2022-12-12 15:33:46 +08:00
Connections {
target: 发送者
发送者信号处理器:{}
}
```
2022-12-13 16:41:41 +08:00
前面的信号连接都嵌入在某个对象中,当信号与槽的连接不依赖具体对象时,需要将其嵌入到 Connections 对象中。Connections 的 target 指向信号的发送者,一个 Connections 可以绑定同一个 target 的多个信号处理机。
```js
Connections {
target: whomSentTheSignal
function onSignal0(arg) {
console.log(arg)
}
2022-12-12 15:33:46 +08:00
2022-12-13 16:41:41 +08:00
function onSignal1(arg) {
console.log(arg)
}
// ...
}
```
### 3.5. connect()方法
使用 connect() 方法,可以为信号指定非 on\<Signal\> 格式命名的响应方法,也可以指令信号触发下一个信号形成信号的连锁反应。
```js
[元素对象.]信号.connect(信号/方法)
```
示例如下:
```js
Rectangle {
id: root
signal mySignal()
// 信号响应处理
onMySignal: console.log("clicked connect mySignal")
// 普通方法
function slt_clicked() {
console.log("clicked connect slt_clicked");
}
Component.onCompleted: {
mousearea.clicked.connect(slt_clicked);
mousearea.clicked.connect(mySignal);
}
MouseArea {
id: mousearea
anchors.fill: parent
}
}
```
### 3.6. 自定义信号
用以下形式声明自定义的信号:
```js
signal < name > [([< type > < parameter name > [, ...]])]
```
示例如下:
```js
Rectangle {
id: root
signal mysignal(int x, int y)
MouseArea {
anchors.fill: parent
onPressed: root.mysignal(mouse.x, mouse.y)
}
}
2022-12-12 15:33:46 +08:00
```
## 4. 界面加载完成信号
许多时候需要在 QML 程序界面完成加载后做一些事情,比如设置一些属性的初始值。这时候就需要用到 Component 的 completed() 信号:
2022-12-13 16:41:41 +08:00
```js
2022-12-12 15:33:46 +08:00
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 来实现类似别名或者命名空间的功能。
2022-12-13 16:41:41 +08:00
```js
2022-12-12 15:33:46 +08:00
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 程序中:
2022-12-13 16:41:41 +08:00
```js
2022-12-12 15:33:46 +08:00
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:
2022-12-13 16:41:41 +08:00
```js
2022-12-12 15:33:46 +08:00
IDI_ICON1 ICON "myappico.ico"
```
Then, add this line to your myapp.pro file:
2022-12-13 16:41:41 +08:00
```js
2022-12-12 15:33:46 +08:00
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 为标准文件对话框。
2022-12-13 16:41:41 +08:00
```js
2022-12-12 15:33:46 +08:00
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 为标准消息对话框。
2022-12-13 16:41:41 +08:00
```js
2022-12-12 15:33:46 +08:00
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 为标准组合框
2022-12-13 16:41:41 +08:00
```js
2022-12-12 15:33:46 +08:00
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 作为模型。
2022-12-13 16:41:41 +08:00
```js
2022-12-12 15:33:46 +08:00
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
}
}
}
}
}
```
2022-12-13 16:55:12 +08:00
## 12. BusyIndicator
用于指示工作状态,设置 BusyIndicator 的 running 属性为 true 将默认显示一个旋转的圆圈;设置 running 属性为 false 则 BusyIndicator 将不显示。
```js
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
Window {
width: 640
height: 480
visible: true
BusyIndicator {
id: busyInd
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
running: true
}
}
```
可以对 BusyIndicator 进行自定义。
## 13. QML 与 C++ 交互
2022-12-12 15:33:46 +08:00
QML 与 C++ 交互的主要实现方式是:
1. QML 访问 C++ 中声明的类型;
2. QML 访问 C++ 中创建的对象;
3. QML 调用 C++ 中的方法或发射信号;
4. QML 响应 C++ 中产生的信号;
5. C++ 访问 QML 对象。
QML 与 C++ 之间主要通过信号槽机制来传递消息。
2022-12-13 16:55:12 +08:00
### 13.1. QML 访问 C++ 中声明的类型
2022-12-12 15:33:46 +08:00
QML 使用 C++ 中声明的类型可以为类、结构体或枚举等。若需要将 C++ 类导出给 QML, 则需要使用 qmlRegisterType() 方法进行注册:
```cpp
qmlRegisterType< Type > ("package.Type", < version > , < sub-version > ,"Type");
```
2022-12-13 16:41:41 +08:00
之后在 QML 中使用 C++ 类创建对象即可。若需要在 QML 中使用类中定义的枚举,格式如下:
```js
< 类名 > .< 枚举值 >
```
2022-12-12 15:33:46 +08:00
2022-12-13 16:41:41 +08:00
若 QML 需要使用 C++ 中声明的枚举类型,则该枚举需要使用 Q_ENUM() 进行修饰(必须放在声明之后);若访问属性成员则需要使用 Q_PROPERTY() 进行修饰;若访问方法则该方法需要使用 Q_INVOKABLE() 来修饰。C++ 中的信号不需要特殊处理, QML 可直接访问。
2022-12-12 15:33:46 +08:00
若对象在 C++ 上下文中创建,并需要在 QML 中使用该对象,可以使用:
```cpp
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("qmlObj", cObj);
```
将 C++ 对象注册到 QML 上下文环境中。
2022-12-13 16:55:12 +08:00
### 13.2. C++ 访问 QML 对象
2022-12-12 15:33:46 +08:00
在 QML 中为对象添加 objectName 属性后,在 C++ 中可使用:
```cpp
auto root = engine.rootObjects();
auto qmlObj = root.first()->findChild< QObject * > ("object name");
```
查找该对象,再连接信号槽等。
大部分情况下在 QML 中访问 C++ 即可实现较完善的功能, QML 传递信息给 C++ 完全可以通过信号槽机制实现。除非需要在 C++ 中动态创建对象并连接到 QML 中的信号槽,否则没必要这样设计。
2022-12-13 16:55:12 +08:00
### 13.3. 通过信号槽传递自建类型
2022-12-12 15:33:46 +08:00
2022-12-13 16:41:41 +08:00
当使用信号槽机制时,需要注意一点:如果需要通过信号槽传递自建类型数据,需要使用 qRegisterMetaType() 方法进行注册。
2022-12-12 15:33:46 +08:00
```cpp
qRegisterMetaType< MyClass > ("Myclass");
```
2022-12-13 16:55:12 +08:00
### 13.4. QML 与 C++ 交互综合示例
2022-12-12 15:33:46 +08:00
2022-12-13 16:41:41 +08:00
该示例包含以下文件:
- QmlDemo.por
- qml.qrc
- DemoDirect.h
- DemoDirect.cpp
- DemoJoint.h
- DemoJoint.cpp
- DemoIndirect.h
- DemoIndirect.cpp
- main.qml
- main.cpp
QmlDemo.por 内容如下:
```js
QT += quick widgets
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
DemoDirect.cpp \
DemoIndirect.cpp \
DemoJoint.cpp \
main.cpp
RESOURCES += qml.qrc
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =
# Additional import path used to resolve QML modules just for Qt Quick Designer
QML_DESIGNER_IMPORT_PATH =
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
HEADERS += \
DemoDirect.h \
DemoIndirect.h \
DemoJoint.h
```
qml.qrc 内容如下:
```xml
< RCC >
< qresource prefix = "/" >
< file > main.qml< / file >
< / qresource >
< / RCC >
```
DemoDirect.h 内容如下
2022-12-12 15:33:46 +08:00
```cpp
2022-12-13 16:41:41 +08:00
#ifndef DEMODIRECT_H
#define DEMODIRECT_H
#include <QObject>
class DemoDirect : public QObject
{
Q_OBJECT
// 声明 cppStr 为只读属性. NOTIFY 信号是必须的.
Q_PROPERTY(QString cppStr READ readCppStr NOTIFY cppStrChanged)
public:
explicit DemoDirect(QObject *parent = nullptr);
// 使用 Q_INVOKABLE 向 QML 声明函数方法.
Q_INVOKABLE void writeCppStr(QString str);
Q_INVOKABLE QString readCppStr();
signals:
void cppStrChanged(QString str);
protected:
QString cppStr;
};
#endif // DEMODIRECT_H
2022-12-12 15:33:46 +08:00
```
2022-12-13 16:41:41 +08:00
DemoDirect.cpp 内容如下
2022-12-12 15:33:46 +08:00
```cpp
2022-12-13 16:41:41 +08:00
#include "DemoDirect.h"
#include <QDebug>
DemoDirect::DemoDirect(QObject *parent)
: QObject{parent}
{
cppStr = "Hello";
}
void DemoDirect::writeCppStr(QString str)
{
cppStr = str;
qDebug()< < "[CPP:DemoDirect:writeCppStr]srt="< < cppStr ;
emit cppStrChanged(cppStr);
}
QString DemoDirect::readCppStr()
{
return cppStr;
}
2022-12-12 15:33:46 +08:00
```
2022-12-13 16:41:41 +08:00
DemoJoint.h 内容如下
```cpp
#ifndef DEMOJOINT_H
#define DEMOJOINT_H
#include <QThread>
#include <QSemaphore>
class DemoJoint : public QThread
{
Q_OBJECT
public:
enum DemoEnum {
DE_OPEN,
DE_READ,
DE_WRITE,
DE_CLOSE,
DE_OPEN_DONE,
DE_READ_DONE,
DE_WRITE_DONE,
DE_CLOSE_DONE
};
// 向 QML 声明 DemoEnum 类型.
Q_ENUM(DemoEnum);
explicit DemoJoint(QObject *parent = nullptr);
~DemoJoint();
signals:
// 从 QML 调用的信号, 信号不必使用 Q_INVOKABLE 进行显式声明.
void qml2Cpp(DemoJoint::DemoEnum op);
// 从 DemoIndirect 中发射该信号, QML 中响应该信号.
void cpp2Qml(DemoJoint::DemoEnum op);
protected:
class DemoIndirect* demoIndirect;
void run() override;
private:
QSemaphore initLock;
};
#endif // DEMOJOINT_H
```
DemoJoint.cpp 内容如下
```cpp
#include "DemoJoint.h"
#include "DemoIndirect.h"
DemoJoint::DemoJoint(QObject *parent)
: QThread{parent}
{
this->start();
initLock.acquire(1);
}
DemoJoint::~DemoJoint()
{
this->wait();
}
void DemoJoint::run()
{
this->setPriority(QThread::NormalPriority);
demoIndirect = new DemoIndirect(this, nullptr);
connect(this, & DemoJoint::qml2Cpp, demoIndirect, & DemoIndirect::onQml2Cpp, Qt::QueuedConnection);
initLock.release(1);
this->exec();
demoIndirect->deleteLater();
}
```
DemoIndirect.h 内容如下
```cpp
#ifndef DEMOINDIRECT_H
#define DEMOINDIRECT_H
#include <QObject>
#include "DemoJoint.h"
class DemoIndirect : public QObject
{
Q_OBJECT
public:
explicit DemoIndirect(DemoJoint* j, QObject *parent = nullptr);
public slots:
// 在 DemoJoint run() 方法中连接了该槽.
void onQml2Cpp(DemoJoint::DemoEnum op);
signals:
private:
DemoJoint* joint;
};
#endif // DEMOINDIRECT_H
```
DemoIndirect.cpp 内容如下
```cpp
#include "DemoIndirect.h"
#include <QDebug>
DemoIndirect::DemoIndirect(class DemoJoint* j, QObject *parent)
: QObject{parent}
{
joint = j;
}
void DemoIndirect::onQml2Cpp(DemoJoint::DemoEnum op)
{
qDebug()< < "[CPP:DemoIndirect:onQml2Cpp]op="< < op ;
switch (op) {
case DemoJoint::DE_OPEN:
emit joint->cpp2Qml(DemoJoint::DE_OPEN_DONE);
break;
default:
emit joint->cpp2Qml(DemoJoint::DE_CLOSE_DONE);
break;
}
}
```
main.cpp 内容如下:
```cpp
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "DemoJoint.h"
#include "DemoDirect.h"
int main(int argc, char *argv[])
{
// 注册 DemoDirect 类型, 否则无法在 QML 中使用 DemoDirect 创建对象.
qmlRegisterType< DemoDirect > ("project.DemoDirect", 1, 0, "DemoDirect");
// 注册 DemoJoint 类型, 否则无法在 QML 中使用 DemoJoint.DemoEnum 类型.
qmlRegisterType< DemoDirect > ("project.DemoJoint", 1, 0, "DemoJoint");
// 注册用于在信号槽中使用的类型.
qRegisterMetaType< DemoJoint::DemoEnum > ("DemoJoint::DemoEnum");
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QApplication app(argc, argv);
// 在 C 上下文创建 demoJoint 对象.
DemoJoint* demoJoint = new DemoJoint();
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(& engine, & QQmlApplicationEngine::objectCreated,
& app, [url ](QObject *obj, const QUrl &objUrl ) {
if (!obj & & url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
// 将 C 环境下的 demoJoint 注册到 QML 上下文中.
engine.rootContext()->setContextProperty("demoJoint", demoJoint);
engine.load(url);
return app.exec();
}
```
main.qml 内容如下:
```js
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
// 导入自定义包, 包名与版本号要与 C 环境下注册的一致.
import project.DemoDirect 1.0
import project.DemoJoint 1.0
Window {
width: 640
height: 480
visible: true
id: window
// 直接从 QML 环境下实例化 C 对象.
DemoDirect {
id: demoDirect
}
Text {
id: demoText
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: 8
anchors.leftMargin: 8
anchors.topMargin: 8
// 显示 demoDirect 的 cppStr 属性值, 注意这是一个只读属性.
text: demoDirect.cppStr
}
Button {
id: demoBut
width: 64
height: 24
text: qsTr("信号测试")
anchors.left: parent.left
anchors.top: demoText.bottom
anchors.leftMargin: 8
anchors.topMargin: 8
onClicked: {
// 从 QML 环境中向 C 环境发送信号.
demoJoint.qml2Cpp(DemoJoint.DE_OPEN)
// 直接调用 C 方法.
2022-12-13 16:45:15 +08:00
demoDirect.writeCppStr("Demo")
2022-12-13 16:41:41 +08:00
}
}
// 使用 Connections 与 C 信号建立连接, 并处理 cpp2Qml() 信号.
Connections {
target: demoJoint
function onCpp2Qml(op) {
console.log("[QML:onCpp2Qml]op=", op)
}
}
}
2022-12-12 15:33:46 +08:00
```
2022-12-13 16:55:12 +08:00
## 14. Windows 下 QML 程序的打包发布
2022-12-12 15:33:46 +08:00
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 自带的打包程序会添加额外的库,如果想进一步减小体积,可手动筛减。
2022-12-13 16:55:12 +08:00
## 15. 外部参考资料
2022-12-12 15:33:46 +08:00
1. [深入了解JS中的整数 ](https://www.jianshu.com/p/1ba45c3894ab )
2. [QML 中的信号与槽 ](https://blog.csdn.net/Love_XiaoQinEr/article/details/123746983 )
2022-12-13 16:41:41 +08:00
3. [QML 信号与响应方法的总结 ](https://zhuanlan.zhihu.com/p/576316521 )
4. [QML 控件类型: ScrollBar、ScrollIndicator ](https://blog.csdn.net/kenfan1647/article/details/122522063 )
5. [QML 类型: GridView ](https://blog.csdn.net/kenfan1647/article/details/120761466 )
6. [Qt Quick 常用元素: ComboBox(下拉列表) 与 ProgressBar(进度条) ](https://www.cnblogs.com/linuxAndMcu/p/11949814.html )
7. [【QML Model-View】ListView-增删改查(二) ](https://www.cnblogs.com/linuxAndMcu/p/13597106.html )
8. [关于 Q_ENUMS 和 Q_ENUM 的区别和用法 ](https://blog.csdn.net/lsylovezsl/article/details/108766437 )
9. [C++ 共享枚举类型给 QML ](https://www.cnblogs.com/linkyip/p/14462288.html )
10. [QML Connections: Implicitly defined onFoo properties in Connections are deprecated. ](https://blog.csdn.net/weixin_43720622/article/details/112346039 )
11. [QML 调用 C++ 方法 ](https://blog.csdn.net/woshouji1/article/details/121348179 )
12. [Qml 与 C++ 交互3: Qml 的信号与 C++ 的槽函数连接 ](https://blog.csdn.net/tanxuan231/article/details/124990296 )