跳至主要內容

Qt

LincZero大约 11 分钟

Qt

目录

QML 编程简概

参考:

简概 - 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),创建.qmlQML文件 (需要注意的是,一般会将默认创建的qml放入qrc资源文件,然后可以将.pro 中 DISTFILES 列表中的qml文件删掉)
    • QtQuick UI File,创建两个文件 xxx.qml xxxFrom.ui.qml(需要注意的是,一般会将默认创建的qml放入qrc资源文件,然后可以将.pro 中 DISTFILES 列表中的qml文件删掉) (新版Qt Creator把这个选项给删了)

.qml 与 .ui.qml 区别(现在.ui.qml选项已经被删了)

简单来说就是后者有助于UI和业务分离,有点类似于这个比喻:(不确定我理解得对不对)

.qml 类似于 .html,你可以在里面写标签和Script,也可以只写标签、另建一个文件写Script .ui.qml 类似于声明了一种新的 .html,你不能在里面写Script

当然我个人的想法是更类似于Vue那种设计的,组件分离 First,UI业务分离 Second,两者都需要分离。

https://www.zhihu.com/question/55353497

.qml文件,就叫QML文件。.ui.qml文件,叫QtQuick UI文件

对于.ui.qml文件来说,不支持以下特性:

(1)JavaScript块代码。 (2)纯表达式之外的其他绑定。 (3)信号处理。 (4)在根组件open in new window之外的其他组件中的状态。 (5)不是从QQuickItem或Item派生的根组件。 (6)引用根组件的父组件open in new window

.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与应用程序open in new window逻辑分离是一种较好的开发方式。一般来说,设计人员应该使用UI文件(.ui.qml),而开发人员应该使用相应的实现文件(.qml)来定义编程行为或编写JavaScript代码。通过这种方式,可以使得设计端open in new window和开发端都可以进行迭代,而不会出现覆盖彼此工作的问题。

https://stackoverflow.com/questions/30652537/what-is-the-use-of-the-ui-qml-files-in-qt5-qml

.ui.qml文件的存在是为了帮助 Qt Quick Designer。 例如,普通 QML 文件可以包含 JavaScript 表达式,但 Qt Quick Designer 很难使用这些表达式。 另一方面,普通 QML 并不那么困难,并且更接近于等价于的小部件 .ui文件 - 详细说明用户界面中的一组项目的文档,而不是它们背后的逻辑。

该功能 是几年前在博客上提出的

经典的 Widget Designer 是围绕声明式和命令式逻辑之间的区别构建的。 声明形式是可设计的并存储在 .ui 文件中。

在 Qml 中,很容易混合声明式代码和命令式代码。 如果您向 Qml 文件添加命令式指令(影响视觉方面),它们不再是纯粹的声明式,并且可视化编辑器中的可视化表示将会中断。 可视化编辑器需要一种方法将可视化描述转换回文本描述。 对于命令式代码,这通常是不可能的,Qt Quick Designer 甚至不会尝试。

QML 文档 open in new window文档指出

从 Qt 5.4 开始,文档也可以有文件扩展名“.ui.qml”。 QML 引擎像处理标准 .qml 文件一样处理这些文件,并忽略扩展名的 .ui 部分。 Qt Creator 将这些文件作为 Qt Quick Designer 的 UI 表单处理。 这些文件只能包含 Qt Creator 定义的 QML 语言的一个子集。

Qt Quick UI 表单 open in new window

您可以使用 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 Assistant:qthelp://org.qt-project.qtqml.5128/qtqml/qtqml-index.html

从Qt 5.4开始,文档也可以有扩展名为" .ui.qml "的文件。QML引擎像处理标准的. QML文件一样处理这些文件,而忽略扩展名的.ui部分。Qt Creator将这些文件作为Qt Quick Designer的UI表单处理。这些文件只能包含Qt Creator定义的QML语言的一个子集。

QML底层原理

参考

基本原理

QML文件加载步骤

探寻QML引擎从解析QML文件开始,到形成一棵完整的C++对象树的整个过程

当加载QML文件时,会执行三个不同的步骤,接下来我们将深入研究这些步骤:

  1. 解析
  2. 编译
  3. 创建

解析

首先,QML文件是由QQmlScript::Parser这个解析器来解析的。该解析器内部的绝大多数内容都是由��语法文件open in new window自动生成的。我们这个例子的抽象语法树(AST)看起来是这样的:

![img](01.%20QML 编程简概.assets/438086-608a25f403cf24b7.png)

这个AST是比较底层的东西,紧接着,它将被转换成更高层级结构的对象open in new window属性open in new windowopen in new window。这是通过使用一个访问器遍历AST来完成的。这一步的对象就和QML中的元素一一对应上了,且对象的属性/值和QML元素的属性/值也一一对应上。我们的例子中Rectangle元素的属性“color”,其对应的值是“lightsteelblue”,它们就是属性/值的关系。即使像onClicked这样的信号处理程序也被看作只是属性/值的关系,属性是onClicked,值就是JavaScript函数体。

编译

在理论上,对象,属性和值已经足够用于创建对应的C++对象,并给属性赋上对应的值。但这些对象,属性和值依然过于原始,在创建C++对象之前,还需要进行一些后置处理。这些后置处理是由QQmlCompileropen in new window来完成的,这对应于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的其余部分仅是辅助数据。

在创建阶段,字节码是由QQmlVMEopen in new window类解析的。阅读QQmlVME::run()这个函数的代码,里面有一个循环用于遍历字节码包含的所有指令,在循环体内部,有一个很大的判定不同指令类型的switch语句。运行带有QML_COMPILER_DUMP=1的例子程序,我们可以看到字节码所包含的每个指令:

(略)

结论

在这篇博文的最后,我们已经揭示了一个QML文件是如何进行解析、处理、编译的,以及VME是如何创建对象的。我希望你已经更加深入地理解了QML引擎。

下一篇的博文(绑定(Bindings)open in new window)将进一步探讨属性绑定是如何进行的,敬请关注!