From 7b32475d7bc4078e01fa513f01338bb7de54db8d Mon Sep 17 00:00:00 2001 From: "Rick.Chan" Date: Tue, 13 Dec 2022 16:41:41 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=20QML=20=E5=BA=94=E7=94=A8?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=BC=80=E5=8F=91=E6=8A=80=E6=9C=AF=E6=80=BB?= =?UTF-8?q?=E7=BB=93.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rick.Chan --- .../Qt/QML/QML_应用程序开发技术总结.md | 532 ++++++++++++++++-- 1 file changed, 489 insertions(+), 43 deletions(-) diff --git a/Software/Development/Environment/Qt/QML/QML_应用程序开发技术总结.md b/Software/Development/Environment/Qt/QML/QML_应用程序开发技术总结.md index 29d2a17..da32226 100644 --- a/Software/Development/Environment/Qt/QML/QML_应用程序开发技术总结.md +++ b/Software/Development/Environment/Qt/QML/QML_应用程序开发技术总结.md @@ -1,12 +1,42 @@ # QML 应用程序开发技术总结 +- [QML 应用程序开发技术总结](#qml-应用程序开发技术总结) + - [1. 方法/属性名称大小写](#1-方法属性名称大小写) + - [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) + - [12. QML 与 C++ 交互](#12-qml-与-c-交互) + - [12.1. QML 访问 C++ 中声明的类型](#121-qml-访问-c-中声明的类型) + - [12.2. C++ 访问 QML 对象](#122-c-访问-qml-对象) + - [12.3. 通过信号槽传递自建类型](#123-通过信号槽传递自建类型) + - [12.4. QML 与 C++ 交互综合示例](#124-qml-与-c-交互综合示例) + - [13. Windows 下 QML 程序的打包发布](#13-windows-下-qml-程序的打包发布) + - [14. 外部参考资料](#14-外部参考资料) + ## 1. 方法/属性名称大小写 QML 语法与 JS 相同,具体请参考相关教程,此处只对一些需要特殊注意的地方进行说明。 QML 的属性、变量、函数等一般为小写字母开头,大写字母开头通常具有特殊意义,比如使用 QML 连接名为 demo() 的信号时,系统将自动连接 onDemo() 作为其槽函数。以下以连接 Button 的 clicked 信号为例进行演示: -```qml +```js import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 2.12 @@ -26,7 +56,7 @@ Window { ## 2. 全局属性 -```qml +```js import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 2.12 @@ -41,54 +71,128 @@ Window { } ``` -## 3. 连接信号槽 +## 3. 信号与槽 -TODO: 细化 +在 QML 中连接信号槽主要有以下 5 种方法。 -信号与信号处理器 +### 3.1. 信号与信号处理器 -```qml +```js signal() on: {} ``` -属性变化信号与属性变化信号处理器 +这是最常见的方法,比如 onClicked: {} 槽。 -```qml +### 3.2. 属性变化信号与属性变化信号处理器 + +```js property onChanged: {} ``` -附加属性与附加信号处理器 +QML 规范,当属性变化后会发送一个 \Changed 信号。从 C 环境创建属性时需要注册 NOTIFY 信号,其命名格式也需要遵循该规范。 -```qml +### 3.3. 附加属性与附加信号处理器 + +```js Attached Signal(附加属性) XX元素.on<附加属性>: {} ``` -Connections 建立信号与槽的连接 +最常见的是 Component.onCompleted:{} 槽。 -```qml +### 3.4. Connections 建立信号与槽的连接 + +```js Connections { target: 发送者 发送者信号处理器:{} } ``` -connect()方法 信号连接信号 +前面的信号连接都嵌入在某个对象中,当信号与槽的连接不依赖具体对象时,需要将其嵌入到 Connections 对象中。Connections 的 target 指向信号的发送者,一个 Connections 可以绑定同一个 target 的多个信号处理机。 -```qml -元素对象.信号.connect(信号) +```js +Connections { + target: whomSentTheSignal + + function onSignal0(arg) { + console.log(arg) + } + + function onSignal1(arg) { + console.log(arg) + } + // ... +} +``` + +### 3.5. connect()方法 + +使用 connect() 方法,可以为信号指定非 on\ 格式命名的响应方法,也可以指令信号触发下一个信号形成信号的连锁反应。 + +```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 [([ [, ...]])] +``` + +示例如下: + +```js +Rectangle { + id: root + signal mysignal(int x, int y) + + MouseArea { + anchors.fill: parent + onPressed: root.mysignal(mouse.x, mouse.y) + } +} ``` ## 4. 界面加载完成信号 许多时候需要在 QML 程序界面完成加载后做一些事情,比如设置一些属性的初始值。这时候就需要用到 Component 的 completed() 信号: -```qml +```js import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 2.12 @@ -113,7 +217,7 @@ Window { 编写 QML 应用时需要像 include 那样引用一些软件包,有的时候软件包中的对象会有重名的情况,比如 QtQuick.Dialogs 1.3 和 Qt.labs.qmlmodels 1.0 中均包含了 FileDialog 对象,但他们的功能并不相同。此时可以使用 import as 来实现类似别名或者命名空间的功能。 -```qml +```js import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Dialogs 1.3 @@ -147,19 +251,19 @@ magick.exe convert icon-16.png icon-32.png icon-256.png myappico.ico 在 Qt .pro 文件中添加以下内容,以便将图标编译到 Qt 程序中: -```qt +```js 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 +```js IDI_ICON1 ICON "myappico.ico" ``` Then, add this line to your myapp.pro file: -```qt +```js RC_FILE = myapp.rc ``` @@ -196,7 +300,7 @@ Dialog 对象默认不显示,当调用 Dialog 的 open() 方法后弹出窗口 FileDialog 为标准文件对话框。 -```qml +```js import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 2.12 @@ -235,7 +339,7 @@ Window { MessageDialog 为标准消息对话框。 -```qml +```js import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 2.12 @@ -275,7 +379,7 @@ Window { ComboBox 为标准组合框 -```qml +```js import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 2.12 @@ -307,7 +411,7 @@ ScrollView 会为其所容纳的对象创建滚动条。 GridView 可以以网格的形式显示模型内容。可以使用 ListModel 或 XmlListModel 作为模型。 -```qml +```js import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 2.12 @@ -431,9 +535,13 @@ QML 使用 C++ 中声明的类型可以为类、结构体或枚举等。若需 qmlRegisterType("package.Type", , ,"Type"); ``` -之后在 QML 中使用 C++ 类创建对象即可。 +之后在 QML 中使用 C++ 类创建对象即可。若需要在 QML 中使用类中定义的枚举,格式如下: -若 QML 需要使用 C++ 中声明的枚举类型,则该枚举需要使用 Q_ENUM() 进行修饰;若访问属性成员则需要使用 Q_PROPERTY() 进行修饰;若访问方法则该方法需要使用 Q_INVOKABLE() 来修饰。C++ 中的信号不需要特殊处理,QML 可直接访问。 +```js +<类名>.<枚举值> +``` + +若 QML 需要使用 C++ 中声明的枚举类型,则该枚举需要使用 Q_ENUM() 进行修饰(必须放在声明之后);若访问属性成员则需要使用 Q_PROPERTY() 进行修饰;若访问方法则该方法需要使用 Q_INVOKABLE() 来修饰。C++ 中的信号不需要特殊处理,QML 可直接访问。 若对象在 C++ 上下文中创建,并需要在 QML 中使用该对象,可以使用: @@ -457,9 +565,9 @@ auto qmlObj = root.first()->findChild("object name"); 大部分情况下在 QML 中访问 C++ 即可实现较完善的功能,QML 传递信息给 C++ 完全可以通过信号槽机制实现。除非需要在 C++ 中动态创建对象并连接到 QML 中的信号槽,否则没必要这样设计。 -## 12.3. qRegisterMetaType +### 12.3. 通过信号槽传递自建类型 -当使用信号槽机制是,需要注意一点:如果需要通过信号槽传递自建类型数据,则需要使用 qRegisterMetaType() 方法进行注册。 +当使用信号槽机制时,需要注意一点:如果需要通过信号槽传递自建类型数据,需要使用 qRegisterMetaType() 方法进行注册。 ```cpp qRegisterMetaType("Myclass"); @@ -467,16 +575,353 @@ qRegisterMetaType("Myclass"); ### 12.4. QML 与 C++ 交互综合示例 -```cpp -TODO: 添加示例 +该示例包含以下文件: + +- 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 ``` -```cpp -TODO: 添加示例 +qml.qrc 内容如下: + +```xml + + + main.qml + + ``` -```qml -TODO: 添加示例 +DemoDirect.h 内容如下 + +```cpp +#ifndef DEMODIRECT_H +#define DEMODIRECT_H + +#include + +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 +``` + +DemoDirect.cpp 内容如下 + +```cpp +#include "DemoDirect.h" +#include + +DemoDirect::DemoDirect(QObject *parent) + : QObject{parent} +{ + cppStr = "Hello"; +} + +void DemoDirect::writeCppStr(QString str) +{ + cppStr = str; + qDebug()<<"[CPP:DemoDirect:writeCppStr]srt="< +#include + +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 +#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 + +DemoIndirect::DemoIndirect(class DemoJoint* j, QObject *parent) + : QObject{parent} +{ + joint = j; +} + +void DemoIndirect::onQml2Cpp(DemoJoint::DemoEnum op) +{ + qDebug()<<"[CPP:DemoIndirect:onQml2Cpp]op="<cpp2Qml(DemoJoint::DE_OPEN_DONE); + break; + default: + emit joint->cpp2Qml(DemoJoint::DE_CLOSE_DONE); + break; + } +} +``` + +main.cpp 内容如下: + +```cpp +#include +#include +#include +#include "DemoJoint.h" +#include "DemoDirect.h" + +int main(int argc, char *argv[]) +{ + // 注册 DemoDirect 类型, 否则无法在 QML 中使用 DemoDirect 创建对象. + qmlRegisterType("project.DemoDirect", 1, 0, "DemoDirect"); + // 注册 DemoJoint 类型, 否则无法在 QML 中使用 DemoJoint.DemoEnum 类型. + qmlRegisterType("project.DemoJoint", 1, 0, "DemoJoint"); + // 注册用于在信号槽中使用的类型. + qRegisterMetaType("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 方法. + demoDirect.writeCppStr("Hello") + } + } + + // 使用 Connections 与 C 信号建立连接, 并处理 cpp2Qml() 信号. + Connections { + target: demoJoint + function onCpp2Qml(op) { + console.log("[QML:onCpp2Qml]op=", op) + } + } +} ``` ## 13. Windows 下 QML 程序的打包发布 @@ -494,12 +939,13 @@ Qt 自带的打包程序会添加额外的库,如果想进一步减小体积 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) -11. [Qml 与 C++ 交互3:Qml 的信号与 C++ 的槽函数连接](https://blog.csdn.net/tanxuan231/article/details/124990296) +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)