From 1993bae60b674babac21441a9cb4640e778c78fb Mon Sep 17 00:00:00 2001 From: "rick.chan" Date: Thu, 11 Jul 2024 10:51:46 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E5=85=85=20=E5=AF=B9=E8=B1=A1=20ID=20?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=EF=BC=8C=E5=B9=B6=E8=B0=83=E6=95=B4=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: rick.chan --- .../Qt/QML/QML_应用程序开发技术总结.md | 300 ++++++++++++------ 1 file changed, 202 insertions(+), 98 deletions(-) diff --git a/Software/Development/Environment/Qt/QML/QML_应用程序开发技术总结.md b/Software/Development/Environment/Qt/QML/QML_应用程序开发技术总结.md index 93724cf..4946b54 100644 --- a/Software/Development/Environment/Qt/QML/QML_应用程序开发技术总结.md +++ b/Software/Development/Environment/Qt/QML/QML_应用程序开发技术总结.md @@ -14,6 +14,9 @@ published: true - [1. 基础部分](#1-基础部分) - [1.1. 方法/属性名称的大小写](#11-方法属性名称的大小写) - [1.2. 显示顺序](#12-显示顺序) + - [1.3. 使用自定义元件](#13-使用自定义元件) + - [1.4. 包别名](#14-包别名) + - [1.5. 对象 ID](#15-对象-id) - [2. 全局属性](#2-全局属性) - [3. 信号与槽](#3-信号与槽) - [3.1. 信号与信号处理器](#31-信号与信号处理器) @@ -23,41 +26,40 @@ published: true - [3.5. connect()方法](#35-connect方法) - [3.6. 自定义信号](#36-自定义信号) - [4. 界面加载完成信号](#4-界面加载完成信号) -- [5. 包别名](#5-包别名) -- [6. 添加图标](#6-添加图标) - - [6.1. 制作图标](#61-制作图标) - - [6.2. 添加图标到应用](#62-添加图标到应用) -- [7. 绘制圆形](#7-绘制圆形) -- [8. 常用组件](#8-常用组件) - - [8.1. QtQuick.Loader](#81-qtquickloader) - - [8.2. QtQuick.Controls.Button](#82-qtquickcontrolsbutton) - - [8.3. Dialog 对象](#83-dialog-对象) - - [8.3.1. QtQuick.Controls 中的 Dialog](#831-qtquickcontrols-中的-dialog) - - [8.3.2. QtQuick.Dialogs](#832-qtquickdialogs) - - [8.3.2.1. FileDialog](#8321-filedialog) - - [8.3.2.2. MessageDialog](#8322-messagedialog) - - [8.4. ComboBox](#84-combobox) - - [8.5. Grid](#85-grid) - - [8.6. ScrollView](#86-scrollview) - - [8.7. QtQuick.ListView](#87-qtquicklistview) - - [8.7. GridView](#87-gridview) - - [8.8. BusyIndicator](#88-busyindicator) - - [8.9. VirtualKeyboard](#89-virtualkeyboard) - - [8.10. Multimedia](#810-multimedia) - - [8.10.1. VideoOutput 和 MediaPlayer](#8101-videooutput-和-mediaplayer) -- [9. 多文档开发](#9-多文档开发) - - [9.1. 多 QML 文件的管理](#91-多-qml-文件的管理) - - [9.2. 如何引用自定义 QML 文件](#92-如何引用自定义-qml-文件) - - [9.3. 使用另一 QML 文件中的元件或属性](#93-使用另一-qml-文件中的元件或属性) - - [9.4. 示例](#94-示例) -- [10. QML 与 C++ 交互](#10-qml-与-c-交互) - - [10.1. QML 访问 C++ 中声明的类型](#101-qml-访问-c-中声明的类型) - - [10.2. C++ 访问 QML 对象](#102-c-访问-qml-对象) - - [10.3. 通过信号槽传递自建类型](#103-通过信号槽传递自建类型) - - [10.4. QML 与 C++ 交互综合示例](#104-qml-与-c-交互综合示例) -- [11. Linux 下为 Qt Quick 应用程序隐藏鼠标指针](#11-linux-下为-qt-quick-应用程序隐藏鼠标指针) -- [12. Windows 下 QML 程序的打包发布](#12-windows-下-qml-程序的打包发布) -- [13. 外部参考资料](#13-外部参考资料) +- [5. 添加图标](#5-添加图标) + - [5.1. 制作图标](#51-制作图标) + - [5.2. 添加图标到应用](#52-添加图标到应用) +- [6. 绘制圆形](#6-绘制圆形) +- [7. 常用组件](#7-常用组件) + - [7.1. QtQuick.Loader](#71-qtquickloader) + - [7.2. QtQuick.Controls.Button](#72-qtquickcontrolsbutton) + - [7.3. Dialog 对象](#73-dialog-对象) + - [7.3.1. QtQuick.Controls 中的 Dialog](#731-qtquickcontrols-中的-dialog) + - [7.3.2. QtQuick.Dialogs](#732-qtquickdialogs) + - [7.3.2.1. FileDialog](#7321-filedialog) + - [7.3.2.2. MessageDialog](#7322-messagedialog) + - [7.4. ComboBox](#74-combobox) + - [7.5. Grid](#75-grid) + - [7.6. ScrollView](#76-scrollview) + - [7.7. QtQuick.ListView](#77-qtquicklistview) + - [7.8. GridView](#78-gridview) + - [7.9. BusyIndicator](#79-busyindicator) + - [7.10. VirtualKeyboard](#710-virtualkeyboard) + - [7.11. Multimedia](#711-multimedia) + - [7.11.1. VideoOutput 和 MediaPlayer](#7111-videooutput-和-mediaplayer) +- [8. 多文档开发](#8-多文档开发) + - [8.1. 多 QML 文件的管理](#81-多-qml-文件的管理) + - [8.2. 如何引用自定义 QML 文件](#82-如何引用自定义-qml-文件) + - [8.3. 使用另一 QML 文件中的元件或属性](#83-使用另一-qml-文件中的元件或属性) + - [8.4. 示例](#84-示例) +- [9. QML 与 C++ 交互](#9-qml-与-c-交互) + - [9.1. QML 访问 C++ 中声明的类型](#91-qml-访问-c-中声明的类型) + - [9.2. C++ 访问 QML 对象](#92-c-访问-qml-对象) + - [9.3. 通过信号槽传递自建类型](#93-通过信号槽传递自建类型) + - [9.4. QML 与 C++ 交互综合示例](#94-qml-与-c-交互综合示例) +- [10. Linux 下为 Qt Quick 应用程序隐藏鼠标指针](#10-linux-下为-qt-quick-应用程序隐藏鼠标指针) +- [11. Windows 下 QML 程序的打包发布](#11-windows-下-qml-程序的打包发布) +- [12. 外部参考资料](#12-外部参考资料) ## 1. 基础部分 @@ -89,6 +91,136 @@ Window { QML 可以包含多个可显示对象,默认情况下 QML 文件中先写的对象显示在下层,后写的对象显示在上层。在显示时,上层的对象覆盖下层对象。 +### 1.3. 使用自定义元件 + +QML 中原件被定义在包中,通过 import 引用对应的包来使用其中的元件,一个文件夹可以构成一个包。 + +也可以自定义元件,每个 QML 文件都可以是一个自定义元件,QML 文件的名称就是元件的名称。例如定义一个 SubRec 原件,首先创建 SubRec.qml 文件,内容如下: + +```js +import QtQuick 2.12 + +Rectangle { + color: "#bad6bf" +} +``` + +当 QML 文件作为元件使用时,文件名必须以大写字母开头,以表明这是一个 QML 类型。 + +如果调用 SubRec 的地方与 SubRec.qml 处于同一文件夹中,则认为他们是同一个包中的元件,不需要 import 就可以直接使用。 + +如果调用 SubRec 的地方与 SubRec.qml 处于不同文件夹中(子文件夹也算不同文件夹),则认为不是同一个包中的原件,需要 import 才可以使用,例如,上面的 SubRec.qml 处于子文件夹 SubComp 中时,使用相对路径方式进行引用,如下: + +```js +import QtQuick 2.12 +import QtQuick.Window 2.12 +import "./SubComp" + +Window { + visible: true + width: 640 + height: 480 + title: qsTr("Hello World") + + Rectangle { + color: "#bfd1da" + anchors.fill: parent + + SubRec { + anchors.rightMargin: 64 + anchors.leftMargin: 64 + anchors.bottomMargin: 64 + anchors.topMargin: 64 + anchors.fill: parent + } + } +} +``` + +### 1.4. 包别名 + +编写 QML 应用时需要像 include 那样引用一些软件包,有的时候软件包中的对象会有重名的情况,比如 QtQuick.Dialogs 1.3 和 Qt.labs.qmlmodels 1.0 中均包含了 FileDialog 对象,但他们的功能并不相同。此时可以使用 import as 来实现类似别名或者命名空间的功能。 + +```js +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) + } +} +``` + +### 1.5. 对象 ID + +QML 中可以使用各种元素来构建 UI,比如 Window、Rectangle、Item 等。被使用的元素形成具体的对象,在运行时创建对应实例。对象可以被分配 ID,对象之间可以使用 ID 相互引用,这种引用是“全局”的。例如,在不同 QML 文件中,分别创建主对象和子对象,在子对象中可以直接引用主对象的 ID。 + +主对象 mainRec 在 main.qml 文件中,内容如下: + +```js +import QtQuick 2.12 +import QtQuick.Window 2.12 + +Window { + id: root + visible: true + width: 640 + height: 480 + title: qsTr("Hello World") + + Rectangle { + id: mainRec + color: "#bfd1da" + anchors.fill: parent + + SubRec { + anchors.rightMargin: 64 + anchors.leftMargin: 64 + anchors.bottomMargin: 64 + anchors.topMargin: 64 + anchors.fill: parent + } + } +} +``` + +子对象在 SubRec.qml 中,内容如下: + +```js +import QtQuick 2.12 +import QtQuick.Controls 2.12 + +Rectangle { + id: root + color: "#bad6bf" + + Timer { + id: timeout + interval: 3000 // 10s + running: true + repeat: false + onTriggered: { + mainRec.color = "#bad6bf" + root.color = "#bfd1da" + } + } +} +``` + +虽然并没有在 SubRec.qml 文件中对 mainRec 进行任何声明或者引用,但仍然可以直接使用,QML 引擎能够在运行时找到 mainRec。 + +另外我们注意到,SubRec.qml 和 main.qml 都使用了 root 分别作为 Rectangle 和 Window 对象的 ID,此时在 SubRec.qml 中优先使用“局部 ID”,也就是 Rectangle 类型的对象。 + +使用 QML 创建 UI 时,各对象逐级构建,形成树形结构。不难发现,QML 引擎能够动态推演所使用的 ID 指的是具体哪个对象(因此跨文件使用对象时不需要声明),其规则为从当前的使用位置开始,逐级向树的根部搜索,直到找打一个匹配的对象为止。 + ## 2. 全局属性 使用 property 关键字可以定义全局属性,该语法格式如下: @@ -259,31 +391,9 @@ Window { } ``` -## 5. 包别名 +## 5. 添加图标 -编写 QML 应用时需要像 include 那样引用一些软件包,有的时候软件包中的对象会有重名的情况,比如 QtQuick.Dialogs 1.3 和 Qt.labs.qmlmodels 1.0 中均包含了 FileDialog 对象,但他们的功能并不相同。此时可以使用 import as 来实现类似别名或者命名空间的功能。 - -```js -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. 制作图标 +### 5.1. 制作图标 先安装 ImageMagic 工具,并将其添加到系统 PATH 下方便使用。 @@ -293,7 +403,7 @@ Window { magick.exe convert icon-16.png icon-32.png icon-256.png myappico.ico ``` -### 6.2. 添加图标到应用 +### 5.2. 添加图标到应用 在 Qt .pro 文件中添加以下内容,以便将图标编译到 Qt 程序中: @@ -317,18 +427,18 @@ If you do not use qmake, the necessary steps are: first, create an .rc file and 更详细内容见《[Setting the Application Icon](https://doc.qt.io/qt-5/appicon.html)》。 -## 7. 绘制圆形 +## 6. 绘制圆形 设置 Rectangle 的 radius 为边长的一半即可。 -## 8. 常用组件 +## 7. 常用组件 -### 8.1. QtQuick.Loader +### 7.1. QtQuick.Loader TODO: TODO: 参考 XiaYu 项目中的 SubMenuGrp 和 SubMenuBtn 对 default 属性和子组件引用父组件方法、属性进行说明。 -### 8.2. QtQuick.Controls.Button +### 7.2. QtQuick.Controls.Button ```js Button { @@ -344,11 +454,11 @@ Button { } ``` -### 8.3. Dialog 对象 +### 7.3. Dialog 对象 QML 中有三大类 Dialog 对象,这里主要介绍 QtQuick.Controls 中的 Dialog 和 QtQuick.Dialogs。 -#### 8.3.1. QtQuick.Controls 中的 Dialog +#### 7.3.1. QtQuick.Controls 中的 Dialog QtQuick.Controls 中的 Dialog 比较原始,属性需要自行定义和实现,所以它的自由度也比较高。该 Dialog 包含了页眉(Header)、页脚(Footer)和内容(Content)三部分,每个部分都可以单独设定。 @@ -390,7 +500,7 @@ Dialog { 除 Ok Button 和 Cancel Button 外,还有许多系统预定义的 Standard Button 可自行翻阅 QML 帮助获得相关帮助信息。 -#### 8.3.2. QtQuick.Dialogs +#### 7.3.2. QtQuick.Dialogs 在使用 QtQuick.Dialogs 的 Dialog 对象时,如果使用 QGuiApplication 来执行则会导致无法加载主题风格,并且对话框无法正确显示图标。如果最初使用 QGuiApplication 创建了 app,则需要进行如下修改: @@ -411,7 +521,7 @@ QApplication app(argc, argv); Dialog 对象默认不显示,当调用 Dialog 的 open() 方法后弹出窗口并阻塞父窗体的执行。 -##### 8.3.2.1. FileDialog +##### 7.3.2.1. FileDialog FileDialog 为标准文件对话框。 @@ -450,7 +560,7 @@ Window { } ``` -##### 8.3.2.2. MessageDialog +##### 7.3.2.2. MessageDialog MessageDialog 为标准消息对话框。 @@ -490,7 +600,7 @@ Window { } ``` -### 8.4. ComboBox +### 7.4. ComboBox ComboBox 为标准组合框 @@ -518,7 +628,7 @@ Window { } ``` -### 8.5. Grid +### 7.5. Grid 布局组件,可以布局多个行和列,每个子成员必须是相同类型,一般使用 Item 来组织多个不同类型。每个子成员必须设置高度和宽度,否则不被显示。 @@ -555,17 +665,17 @@ Grid { } ``` -### 8.6. ScrollView +### 7.6. ScrollView ScrollView 会为其所容纳的对象创建滚动条。 -### 8.7. QtQuick.ListView +### 7.7. QtQuick.ListView TODO: header, headerPositioning TODO: 如果通过设置 header 和 headerPositioning 参数来实现固定 header,且同时伴有 ScrollBar 的情况下,滚动条依然会覆盖到 header 上,效果不理想。此时最好在 ListView 之外使用 Row 嵌套 Label 来实现类似效果。 -### 8.7. GridView +### 7.8. GridView GridView 可以以网格的形式显示模型内容。可以使用 ListModel 或 XmlListModel 作为模型。 @@ -673,7 +783,7 @@ Window { } ``` -### 8.8. BusyIndicator +### 7.9. BusyIndicator 用于指示工作状态,设置 BusyIndicator 的 running 属性为 true 将默认显示一个旋转的圆圈;设置 running 属性为 false 则 BusyIndicator 将不显示。 @@ -698,7 +808,7 @@ Window { 可以对 BusyIndicator 进行自定义。 -### 8.9. VirtualKeyboard +### 7.10. VirtualKeyboard 一些涉及触屏的应用会涉及到虚拟键盘/软键盘的应用,不同系统平台上往往会提供不同的软键盘工具,但相比之下,Qt 内嵌的 VirtualKeyboard 更加易用,并具有很好的跨平台能力,中文(拼音)、英文以及其他主要语言的支持能力也比较好。 @@ -747,7 +857,7 @@ CONFIG+="lang-en_GB lang-zh_CN" 更多关于 VirtualKeyboard 的应用可以参考官方自带示例。 -### 8.10. Multimedia +### 7.11. Multimedia 需要引入: @@ -757,7 +867,7 @@ import QtMultimedia x.xx 主要包含音视频的输入、处理和输出,如:Video、Audio、Camera、VideoOutput、MediaPlayer、SoundEffect 等。 -#### 8.10.1. VideoOutput 和 MediaPlayer +#### 7.11.1. VideoOutput 和 MediaPlayer Qt 只是提供了便捷易用的接口,实际的编解码功能由后端实现。 Qt5 Linux 下视频后端为 GStreamer,Win 下为 DirectShow,建议使用 [LAVFilters](https://github.com/Nevcairiel/LAVFilters)(ffmpeg based DirectShow Splitter and Decoders)。Qt6 在 Linux 下好像增加了对 ffmpeg 的支持。 @@ -783,9 +893,9 @@ VideoOutput { 目前不清楚能否使用网络 URL 格式路径。 -## 9. 多文档开发 +## 8. 多文档开发 -### 9.1. 多 QML 文件的管理 +### 8.1. 多 QML 文件的管理 在创建基于 Qt6 的 QML 项目时,不会包含 qml.qrc 文件,系统编译时自动创建该文件,创建的依据是 .pro 文件中的 resources.xxx 字段: @@ -810,17 +920,11 @@ failed to load component ... is not a type 错误,此时需要手动添加 Demo.qml 到 resources.files 字段中。 -### 9.2. 如何引用自定义 QML 文件 +### 8.2. 如何引用自定义 QML 文件 -引用同级目录下的 QML 文件时不需要 import,在引用其他文件夹下的 QML 文件时需要: +参考 [1.3. 使用自定义元件](#13-使用自定义元件) -```js -import "./DemoQml" -``` - -QML 文件必须以大写字母开头,以表明这是一个 QML 类型。 - -### 9.3. 使用另一 QML 文件中的元件或属性 +### 8.3. 使用另一 QML 文件中的元件或属性 可以使用: @@ -830,7 +934,7 @@ property alias <别名>:<属性名称/ID> 将一个 QML 文件中的某属性,某子组件的属性或某个子组件变为外部可见。 -### 9.4. 示例 +### 8.4. 示例 假设存在 DemoQml/Demo.qml 文件,该文件内容如下: @@ -892,7 +996,7 @@ Window { } ``` -## 10. QML 与 C++ 交互 +## 9. QML 与 C++ 交互 QML 与 C++ 交互的主要实现方式是: @@ -904,7 +1008,7 @@ QML 与 C++ 交互的主要实现方式是: QML 与 C++ 之间主要通过信号槽机制来传递消息。 -### 10.1. QML 访问 C++ 中声明的类型 +### 9.1. QML 访问 C++ 中声明的类型 QML 使用 C++ 中声明的类型可以为类、结构体或枚举等。若需要将 C++ 类导出给 QML,则需要使用 qmlRegisterType() 方法进行注册: @@ -929,7 +1033,7 @@ engine.rootContext()->setContextProperty("qmlObj", cObj); 将 C++ 对象注册到 QML 上下文环境中。 -### 10.2. C++ 访问 QML 对象 +### 9.2. C++ 访问 QML 对象 在 QML 中为对象添加 objectName 属性后,在 C++ 中可使用: @@ -942,7 +1046,7 @@ auto qmlObj = root.first()->findChild("object name"); 大部分情况下在 QML 中访问 C++ 即可实现较完善的功能,QML 传递信息给 C++ 完全可以通过信号槽机制实现。除非需要在 C++ 中动态创建对象并连接到 QML 中的信号槽,否则没必要这样设计。 -### 10.3. 通过信号槽传递自建类型 +### 9.3. 通过信号槽传递自建类型 当使用信号槽机制时,需要注意一点:如果需要通过信号槽传递自建类型数据,需要使用 qRegisterMetaType() 方法进行注册。 @@ -950,7 +1054,7 @@ auto qmlObj = root.first()->findChild("object name"); qRegisterMetaType("Myclass"); ``` -### 10.4. QML 与 C++ 交互综合示例 +### 9.4. QML 与 C++ 交互综合示例 该示例包含以下文件: @@ -1301,7 +1405,7 @@ Window { } ``` -## 11. Linux 下为 Qt Quick 应用程序隐藏鼠标指针 +## 10. Linux 下为 Qt Quick 应用程序隐藏鼠标指针 QML 中可以使用 MouseArea 来隐藏鼠标指针,但是在程序刚启动时鼠标指针依然可见,只有鼠标动过或者点击过才会消失。因此需要 unclutter 程序来辅助,unclutter 可以在系统空闲时自动隐藏鼠标指针。首先安装 unclutter: @@ -1337,7 +1441,7 @@ MouseArea { } ``` -## 12. Windows 下 QML 程序的打包发布 +## 11. Windows 下 QML 程序的打包发布 Qt 提供了导出 Qt 环境变量的命令行脚本,比如“Qt 5.15.2 (MinGW 8.1.0 64-bit)”,运行该脚本可进入带有 Qt 环境变量的命令行界面,之后可通过如下命令打包程序(编译生成的可执行程序需要拷贝到\): @@ -1348,7 +1452,7 @@ windeployqt [--qmldir ] Qt 自带的打包程序会添加额外的库,如果想进一步减小体积,可手动筛减。 -## 13. 外部参考资料 +## 12. 外部参考资料 1. [深入了解JS中的整数](https://www.jianshu.com/p/1ba45c3894ab) 2. [QML 中的信号与槽](https://blog.csdn.net/Love_XiaoQinEr/article/details/123746983)