Qt
Qt
目录
QML 编程简概
参考:
- 【C语言中文网】学习QML还是C++?
- QML文档:
- 个人博客:https://robinsea.github.io/2020/09/16/QmlBook/quick_starter/README/
简概 - Qt Quick项目
QML与QtQuick
QML
概念
- QML(Qt Meta Language,Qt元语言)是一种用来描述应用程序界面的声明式脚本语言(这是标记语言吧)。自Qt4.7引用
优点
- 良好的易读性。以可视化组件及其交互和相互关联的方式来描述界面,使组件能在动态行为中互相连接
原理
通过Qt QML引擎在程序运行时解析并运行的
(旧) 编译器通道
(新) 编译器通道
Qt Quick
- 概念
- 是Qt为QML提供的一套类库,由QML标准类型和功能组成
- 包括可视化类型、交互类型、动画类型、模型和视图、粒子系统和渲染效果等
- 优点
- 易于使用,在编程时只需要一条import语句就能访问这些功能
- 易于开发UI界面
- 概念
比较 QML C++
- 并列称为Qt的首选编程语言
创建Qt Quick工程
详见 简概 > 开发流程 - 创建
创建 - 选项配置
- 其他和普通项目选项差不多,主要注意一个新选项
- Define Project Details:选择最低适应的Qt版本
项目基本结构
- QtQuickTest.pro
- main.cpp
- qml.qrc
- main.qml(该文件视为qrc资源文件)
pro
QT += quick
CONFIG += c++11
# 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 += \
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
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine; // QML引擎
const QUrl url(QStringLiteral("qrc:/main.qml")); // 需要加载的qml
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, // 将引擎结果关联到lambda上
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1); // 无法加载则退出程序
}, Qt::QueuedConnection);
engine.load(url); // 用QML引擎加载qml文档
return app.exec(); // 消息循环
}
qml
/* import部分 */
import QtQuick 2.12 // 导入Qt Quick 2.7库
import QtQuick.Window 2.12 // 导入Qt Quick 窗体库
/* 对象声明部分 */
Window { // 对象、根对象
width: 640 // 宽度属性
height: 480 // 高度属性
visible: true // 可见性属性
title: qsTr("Hello World") // 标题属性
MainForm { // 子对象
anchor.fill: parent
mouseArea.onClicked: { // 点击的回调函数
console.log(qsTr('Clicked on background. Text: "' + textEdit.text +'"'))
}
}
}
项目分析
要点如下:
- cpp:
#include <QtQml>
,包含模块类的定义 - .qml:
import QtQml 2.15
,能使用QML类型 - .pro:
QT += qml
,链接模块
简概 - Qt Widgets项目
创建 QML 文件
项目右键 > Add new > Qt >
- Qt
- QML File (Qt Quick 2),创建
.qml
QML文件 (需要注意的是,一般会将默认创建的qml放入qrc资源文件,然后可以将.pro 中 DISTFILES 列表中的qml文件删掉) QtQuick UI File,创建两个文件xxx.qml
xxxFrom.ui.qml
(需要注意的是,一般会将默认创建的qml放入qrc资源文件,然后可以将.pro 中 DISTFILES 列表中的qml文件删掉)(新版Qt Creator把这个选项给删了)
- QML File (Qt Quick 2),创建
.qml 与 .ui.qml 区别(现在.ui.qml选项已经被删了)
简单来说就是后者有助于UI和业务分离,有点类似于这个比喻:(不确定我理解得对不对)
.qml 类似于 .html,你可以在里面写标签和Script,也可以只写标签、另建一个文件写Script .ui.qml 类似于声明了一种新的 .html,你不能在里面写Script
当然我个人的想法是更类似于Vue那种设计的,组件分离 First,UI业务分离 Second,两者都需要分离。
.qml文件,就叫QML文件。.ui.qml文件,叫QtQuick UI文件
对于.ui.qml
文件来说,不支持以下特性:
(1)JavaScript块代码。 (2)纯表达式之外的其他绑定。 (3)信号处理。 (4)在根组件之外的其他组件中的状态。 (5)不是从QQuickItem或Item派生的根组件。 (6)引用根组件的父组件。
在.ui.qml
文件中,不支持下列组件类型: (1)Behavior (2)Binding (3)Canvas (4)Shader Effect (5)Timer (6)Transform
在.ui.qml
文件中,支持的方法如下『JavaScript 函数』:
这 .ui.qml
文件的存在是为了帮助 Qt Quick Designer。 例如,普通 QML 文件可以包含 JavaScript 表达式,但 Qt Quick Designer 很难使用这些表达式。 另一方面,普通 QML 并不那么困难,并且更接近于等价于的小部件 .ui
文件 - 详细说明用户界面中的一组项目的文档,而不是它们背后的逻辑。
用处:
将UI与应用程序逻辑分离是一种较好的开发方式。一般来说,设计人员应该使用UI文件(.ui.qml
),而开发人员应该使用相应的实现文件(.qml
)来定义编程行为或编写JavaScript代码。通过这种方式,可以使得设计端和开发端都可以进行迭代,而不会出现覆盖彼此工作的问题。
这 .ui.qml
文件的存在是为了帮助 Qt Quick Designer。 例如,普通 QML 文件可以包含 JavaScript 表达式,但 Qt Quick Designer 很难使用这些表达式。 另一方面,普通 QML 并不那么困难,并且更接近于等价于的小部件 .ui
文件 - 详细说明用户界面中的一组项目的文档,而不是它们背后的逻辑。
该功能 是几年前在博客上提出的
经典的 Widget Designer 是围绕声明式和命令式逻辑之间的区别构建的。 声明形式是可设计的并存储在 .ui 文件中。
在 Qml 中,很容易混合声明式代码和命令式代码。 如果您向 Qml 文件添加命令式指令(影响视觉方面),它们不再是纯粹的声明式,并且可视化编辑器中的可视化表示将会中断。 可视化编辑器需要一种方法将可视化描述转换回文本描述。 对于命令式代码,这通常是不可能的,Qt Quick Designer 甚至不会尝试。
QML 文档 文档指出
从 Qt 5.4 开始,文档也可以有文件扩展名“.ui.qml”。 QML 引擎像处理标准 .qml 文件一样处理这些文件,并忽略扩展名的 .ui 部分。 Qt Creator 将这些文件作为 Qt Quick Designer 的 UI 表单处理。 这些文件只能包含 Qt Creator 定义的 QML 语言的一个子集。
您可以使用 Qt Creator 向导创建文件扩展名为 .ui.qml 的 UI 表单。 UI 表单包含 QML 语言的纯声明子集。 建议您在设计模式下编辑表单。 但是,将项目导出为别名属性是一项仅供商业使用的功能,因此如果您使用 Qt Creator 的开源版本,则必须使用编辑模式来执行此操作。 Qt Creator 通过显示错误消息强制使用支持的 QML 功能。
不支持以下功能:
- JavaScript 块
- 函数定义
- 函数调用(qsTr 除外)
- 纯表达式以外的其他绑定
- 信号处理器
- 根项以外的其他项中的状态
- 不是从 QQuickItem 或 Item 派生的根项
不支持以下类型:
- Behavior,行为
- Binding,捆绑
- Canvas,帆布
- Component,零件
- Shader Effect,着色效果
- Timer,计时器
- Transform,转换
- Transition,过渡
从Qt 5.4开始,文档也可以有扩展名为" .ui.qml "的文件。QML引擎像处理标准的. QML文件一样处理这些文件,而忽略扩展名的.ui部分。Qt Creator将这些文件作为Qt Quick Designer的UI表单处理。这些文件只能包含Qt Creator定义的QML语言的一个子集。
QML底层原理
参考
- 【Qt官网文档】Qt QML
- 【简书】[翻译] 深入解析QML引擎, 第1部分:QML文件加载 (原文 QML Engine Internals, Part 1: QML File Loading,这个解析QML引擎的文章共4篇)
- 【CSDN】Qml运行机制的理解(原创)
- qml 引擎(V8)原理总结
基本原理
- QML生成C++类
- QML文件中每个元素都对应于一个C++类。QML引擎在加载QML文件时,会为文件中的所有元素以某种方式创建相应的C++对象
- 绘制C++对象树
- 这些元素将被绘制在一个OpenGL scenegraph中,绘制及事件处理都是由QQuickView控制的
- 我们可以利用KDAB的Qt自检工具GammaRay来验证QML文件对应的C++对象树
- ![img](01.%20QML 编程简概.assets/438086-2eb1771ce04b1370.png)
- 用QML分析器(QML profiler)来运行并分析
QML文件加载步骤
探寻QML引擎从解析QML文件开始,到形成一棵完整的C++对象树的整个过程
当加载QML文件时,会执行三个不同的步骤,接下来我们将深入研究这些步骤:
- 解析
- 编译
- 创建
解析
首先,QML文件是由QQmlScript::Parser这个解析器来解析的。该解析器内部的绝大多数内容都是由��语法文件自动生成的。我们这个例子的抽象语法树(AST)看起来是这样的:
![img](01.%20QML 编程简概.assets/438086-608a25f403cf24b7.png)
这个AST是比较底层的东西,紧接着,它将被转换成更高层级结构的对象,属性和值。这是通过使用一个访问器遍历AST来完成的。这一步的对象就和QML中的元素一一对应上了,且对象的属性/值和QML元素的属性/值也一一对应上。我们的例子中Rectangle元素的属性“color”,其对应的值是“lightsteelblue”,它们就是属性/值的关系。即使像onClicked这样的信号处理程序也被看作只是属性/值的关系,属性是onClicked,值就是JavaScript函数体。
编译
在理论上,对象,属性和值已经足够用于创建对应的C++对象,并给属性赋上对应的值。但这些对象,属性和值依然过于原始,在创建C++对象之前,还需要进行一些后置处理。这些后置处理是由QQmlCompiler来完成的,这对应于QML分析器(QML profiler)输出中看到的编译阶段。该编译器会为QML文件创建了一个QQmlCompiledData对象。 用QQmlCompiledData创建C++对象比直接使用对象、属性和值来创建C++对象快了很多。当多次使用同一个QML文件,该文件也只会编译一次。比如在一个工程中,其他所有的QML文件都会用到的Button.qml,编译时Button.qml只会被编译一次。Button.qml的QQmlCompiledData会一直保存,每次使用该按钮组件时,都会根据这个Button.qml的QQmlCompiledData来创建C++对象。在编译之后,就是创建阶段,这在QML分析器(QML profiler)的输出中可以看到。
综上所述:解析和编译QML文件都只会做一次,在此之后,都是直接使用QQmlCompiledData对象来快速创建C++对象。
创建
我不会深入研究QQmlCompiledData的细节,但有一个东西可能会引起你的注意:“QByteArray bytecode”成员变量。实际上,创建C++对象并给它的属性赋值的指令会被编译为了字节码,之后由字节码解析器解析!字节码包含了一堆指令,当这些指令执行时,QQmlCompiledData的其余部分仅是辅助数据。
在创建阶段,字节码是由QQmlVME类解析的。阅读QQmlVME::run()这个函数的代码,里面有一个循环用于遍历字节码包含的所有指令,在循环体内部,有一个很大的判定不同指令类型的switch语句。运行带有QML_COMPILER_DUMP=1的例子程序,我们可以看到字节码所包含的每个指令:
(略)
结论
在这篇博文的最后,我们已经揭示了一个QML文件是如何进行解析、处理、编译的,以及VME是如何创建对象的。我希望你已经更加深入地理解了QML引擎。
下一篇的博文(绑定(Bindings))将进一步探讨属性绑定是如何进行的,敬请关注!