QML学习笔记(一)基本了解和工程配置
前言:
已经从事QT开发几年了,但对于QML这个东西始终是没有彻底掌握,一方面实际工作中没有用到过,其次它的语法对我来说是全新的东西,不像QWidget那一套可以直接在C++中去写。这就是为什么网上都说qml更简单,我却屡屡学不会的原因。
让我下定决心一定要开始学QML的契机,就是因为最近在找工作,面试官让我在纸上直接写一个按钮,并设置一段文字,结果我犹豫半天,居然没能动笔。我大概知道是写button,然后用花括号括起来,但button是否需要大写,花括号后面有没有分号,里面的属性又是什么来着……我一下子陷进了沮丧。
这其实就是没有真正使用qml写过代码的必然结果,即便对此有基本的认知,但别人考到你的时候,实际上是写不出来的。所以接下来的学习当中,我必须要做好详细的记录和笔记,这也是对自己的要求。(偷偷抱怨一句,在这个时代只会qt,是真的难找到工作啊)
话不多说,
一、QML基本了解
QML是Qt框架的声明式UI开发语言,与传统的Qt Widgets(基于C++的界面库)在语法特性、适用场景和视觉效果上存在显著差异。
1.语法特性: QML采用JSON-like声明式语法,支持数据绑定和热重载。 Qt
Widgets使用C++编写,采用命令式编码模式。
2.视觉效果: QML支持矢量渲染与复杂动画,可自定义渐变色、圆角等效果。 Qt Widgets基于原生控件,动画效果有限。
3.开发场景: QML适合移动端/嵌入式设备的动态UI(占比65%应用场景)。 Qt Widgets适合传统桌面软件(如Office类应用)。
以上是网上找到的内容,结合我自身的认知,再浅浅谈一下。
1.QML常常伴随Qt Quick这个词语,看上去好像是同一个东西,实则不然。QML是Quick Markup Language,它是一门语言。而Qt Quick是一个模块,可以理解为Qt的一个扩展库,你必须要先引用Qt Quick这个模块,才能用QML语言来写代码。
2.为什么会出现QML这个东西,我猜是为了将界面描述和逻辑代码彻底分开。如果你深入用QWidget来写过代码,你会有个感悟,界面层的代码和业务功能代码是深度耦合在一起的,特别是还有信号槽这种东西。而如果我们将界面相关的东西,全部用单独的文件编写起来,业务代码继续用C++写,是不是就更清晰明了,分工明确呢。所以说,QML其实是专门做界面+加动画+小逻辑的语言,为的是不抢C++业务。
3.说到QML的优势,大家说的基本上可以使用GPU硬件加速和支持动画效果,但这方面我目前还没体会到,后面学习再加上。还有一点是,QML更方便应用在嵌入式小设备和移动端。至于“学习成本低”、“语法更简单”,就见仁见智了。
二、QML工程创建
先说一下,我的QT版本是5.14.2,默认就可以创建Qt Quick工程,如果不行可能是QT安装的时候没有安装对应的模块,这个就得重新装下QT了。
咱们先创建新的工程,选Qt Quick的应用工程,这里有好几个选项,直接选空的即可。后面的步骤和QWidget是一样的,就不啰嗦了。
创建完工程后,直接编译运行,成功显示出空白界面。(还很贴心给了个Hello World的提示)
三、工程结构
在看具体的qml代码之前,我们先看一下基本的工程目录结构。
如果是传统的QWidget工程,代码一般放在Headers(头文件)和Sources(源文件)两个目录下标里面,但存放qml代码的qml文件显然不是。你可以直观看到,qml文件默认是放在Resources底下的qrc里面的。而qrc一般存放的是资源文件,最常见的就是各种ui图片。
也就是说,qml作为一种界面描述文件,本质上是当做一种资源来管理的(不知道说得对不对),他区别于其他正式的C++代码文件,存放在不同的目录底下。
当然,这里所说的目录不是文件实际存放的目录,说得上qt IDE里面显示的分类目录哈。
咱们打开工程的pro文件,发现它加载的是quick模块。这里对比一下传统QWidget应用,这里夹在的事core和gui模块。
当然,这并不是二选一的问题,事实上qml是可以和widget混合使用的,比如从一个qwidget界面跳转到qt quick界面,是完全可以的。我想表达的是,如果我们在qwidget工程中,想要新增一个qml工程,需要自己手动在pro中添加quick模块,这样qml才能正常使用哈。
四、C++中打开并显示QML界面
我们直接把main中的代码放上来。咋一看很复杂,没事我们慢慢来。
#include <QGuiApplication>
#include <QQmlApplicationEngine>int main(int argc, char *argv[])
{QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);QGuiApplication app(argc, argv);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);engine.load(url);return app.exec();
}
首先是头文件,这里引用的QGuiApplication,它实际上对应的是QApplication,QGuiApplication比QApplication会更加轻量级,更适合纯QML的工程。这里换成QApplication其实也是可以的,但pro要注意把core和gui模块加上。
第二个头文件叫QQmlApplicationEngine
QML 引擎,负责解析+加载+实例化你的 main.qml 文件。
我们看main的代码,第一行是QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);这是开启高分辨率自适应,这和qwidget是一样的,它默认给你添加了。
下一行是QGuiApplication app(argc, argv);这是创建了一个应用对象。
重点是下面的代码。
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
engine = QML 解析器 + 加载器 + 对象工厂。
url = 要加载的 QML 文件(qrc 资源系统路径)。
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,&app, [url](QObject *obj, const QUrl &objUrl) {if (!obj && url == objUrl)QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
这句信号槽实际上是engine引擎加载url后的结果回调,返回参数是obj和obj的url,这里做了个保险丝判断,如果 main.qml 加载失败(文件不存在/语法错误),立即退出程序,防止黑屏。
engine.load(url);
return app.exec();
load = 解析 + 实例化整个 QML 对象树。
exec() = 进入事件循环,直到 QCoreApplication::quit() 或窗口关闭。
最后再说一下我自己的理解。这种写法更多是初始化一个qml引擎,然后加载url上的文件,把qml描述的界面显示出来。实际上我更喜欢另一种方法,用到了qt中的QQuickWidget,它是一种组件,可以直接嵌QWidget中。直接调用QQuickWidget中的setSource似乎是一种更方便的做法,当然以我目前的水平,还说不好谁更好。这种方法我在下一篇中会详细再说。
至此,一个qml界面便显示出来咯。
五、QML最小可运行实例
我们双击main.qml,发现里面有这段代码。
import QtQuick 2.14
import QtQuick.Window 2.14Window {visible: truewidth: 640height: 480title: qsTr("Hello World")
}
类似于C++代码,其实还蛮好理解的。
-
import QtQuick 2.14
引入 QtQuick 核心库(对象、动画、绘制等基础类)。 -
import QtQuick.Window 2.14
引入 Window 组件(ApplicationWindow 的简化版),提供顶层窗口。 -
Window { … }
顶层窗口对象,等价于 QMainWindow 的 QML 版。 -
visible: true
立即显示窗口(false 则隐藏)。 -
width: 640 / height: 480
窗口初始大小(像素)。 -
title: qsTr("Hello World")
窗口标题,qsTr() 是 Qt 的国际化函数,可被翻译系统替换。
这 7 行是 QML 的最小可运行模板: 导入 QtQuick 和 Window,创建 640×480 的顶层窗口,标题‘Hello World’,立即显示。
六、顶层窗口描述
我再补充一点,这里用到的Window事实上并不是固定的,我有见过b站up主用的Rectangle,于是用kimi问了一下:
- Window = 顶层窗口(有标题栏、系统边框、任务栏图标)。
- Rectangle = 普通矩形对象(无边框,必须被别的窗口或组件装载)。
1. 顶层 vs 被装载
类 | 作用 | 是否能独立显示 |
---|---|---|
Window | 顶层窗口(系统边框、标题栏) | ✅ 直接显示 |
Rectangle | 矩形+颜色+子控件 | ❌ 必须被 Window/ApplicationWindow 装载 |
2. 代码对比
顶层窗口(独立显示):
import QtQuick 2.14
import QtQuick.Window 2.14Window { ← 顶层窗口visible: truewidth: 640; height: 480Rectangle { ← 被装载的矩形width: 200; height: 200color: "red"}
}
仅 Rectangle(无边框,需被装载):
import QtQuick 2.14
Rectangle { ← 无边框矩形width: 200; height: 200color: "red"
}
后者无法独立显示——必须被 Window/ApplicationWindow 或别的组件装载。
3. 常用顶层类对照
QML 类 | 作用 | 备注 |
---|---|---|
Window | 最简顶层窗口 | 无边框菜单 |
ApplicationWindow | 带菜单栏/工具栏的主窗口 | QtQuick.Controls 2 推荐 |
Rectangle | 普通矩形+子控件 | 被装载对象 |
这就能解释得通了,因为Rectangle的方式是加载在QQuickWidget控件里面的,不像main.qml中的Window必须是顶层窗口。(我尝试直接将Window改成Rectangle,结果QQmlApplicationEngine加载返回的信号槽没有出错,但界面没有弹出来,不符合我们的预期。)
这一点跟QMainWindow和QWidget的设计是类似的。只是QWidget不仅可以作为子控件,其实还可以用作顶层窗口,如果做了相关设置,甚至可以变成dialog弹窗,不过这个就不细说了。
七、总结
写到这里,我已经尽可能说得很详细,也努力让自己印象深刻一点,毕竟这其实是自学笔记嘛。如果有人真的读到这里,有什么问题也可以在评论区提出。
如果可以的话,接下来我会按照自己的节奏,按照自己的理解,一点点更新这个学习笔记,要是能成为一个教程就更棒了。