多读书多实践,勤思考善领悟

Qt项目升级到Qt6移植总结

一、前言

Qt 6.2 也是 Qt 6 的第一个版本,Qt 公司将为 Qt 商业客户提供长期支持。

在 Qt 6.2 中,包含了 Qt 5.15 中的所有常用功能以及为 Qt 6 添加的新功能。随着 Qt 6.2 的发布,几乎所有的用户都应该能够将他们的代码从 Qt 5 迁移到 Qt 6。

二、Qt 6 的架构变化

我们在 Qt 6 中进行了一些更广泛的架构更改,我们现在正在构建 Qt 6.2 和任何未来版本。

  • 利用 C++17 在处理 Qt 6 时,我们希望建立在现代 C++ 标准之上。C++17 是最新发布的版本,而 Qt 6 现在依赖于 C++17 兼容编译器。这使得我们可以清理和改进我们的代码库,并为我们的用户提供更现代的 API。
  • 在处理大型数据集和性能方面改进了我们的低级容器类。
  • 下一代 QML 我们已经开始更加努力地更新 QML 语言,使其在未来更安全、更易于使用。Qt 6.0 – 6.2 在这里奠定了基础,但这种持续的努力将在整个 Qt 6 系列中继续进行。
  • 将属性绑定引入 C++ 属性绑定是使 QML 如此成功的概念之一。在 Qt 6 中,我们一直在 C++ 中提供该概念 。
  • 新的图形架构 Qt 6 在如何处理与底层操作系统 API 的集成方面采用了 全新的架构。得益于新的渲染硬件接口 (RHI),Qt 6 现在默认使用每个系统上可用的最佳图形 API,显着提高了兼容性——尤其是在桌面和移动操作系统(如 Windows 和 macOS 以及 iOS)上。
  • Qt Quick 的统一 2D 和 3D Qt Quick 一直是构建动画和流畅的 2D 用户界面的框架。使用 Qt 6,我们也简化了将 3D 内容集成到基于 QML 的应用程序的过程。深度集成使得在任何级别混合 2D 和 3D 内容变得微不足道,同时从系统中获得最大性能。
  • CMake 构建系统 在 Qt 6 中,我们将构建系统从 qmake切换到 cmake,这是当今基于 C++ 的应用程序的标准构建系统。虽然在 Qt 6 的整个生命周期内仍支持 qmake,但初步用户报告表明切换到 cmake 后有显着改进。

三、添加模块

开发 Qt 6.2 的主要工作之一是重新添加我们在 Qt 6.0 中遗漏的所有模块和功能。除了极少数例外,Qt 5.15 支持的所有模块现在也支持 Qt 6.2。

在 Qt 6.2 中,我们添加了对以下附加模块的支持(在 Qt 6.1 中已有的模块之上):

  • Qt Bluetooth
  • Qt Multimedia
  • Qt NFC
  • Qt Positioning
  • Qt Quick Dialogs
  • Qt RemoteObjects
  • Qt Sensors
  • Qt SerialBus
  • Qt SerialPort
  • Qt WebChannel
  • Qt WebEngine
  • Qt WebSockets
  • Qt WebView

这些模块的 API 主要向后兼容 Qt 5,并且在移植到 Qt 6 时只需要对用户代码进行少量调整。

Qt 6.2 支持的 完整模块列表 可以在我们的文档中找到 。

四、Qt 6.2 中的新功能

除了我们从 Qt 5 带来的许多模块之外,我们 在 6.2 中还有大量的 新特性和功能。我们来看一下。

1、Qt 快速 3D

Qt Quick 3D 获得了一些很酷的新功能,现在支持 实例化渲染,允许您使用不同的变换渲染大量相同的对象。我们还添加了一个新的 API,用于向 场景添加 3D 粒子效果。

输入处理已得到改进,我们现在可以为嵌入在 3D 场景中的 2D 项目正确创建 Qt Quick 输入事件。我们还添加了一个新的 API,用于从场景中的任意点进行基于光线的拾取。

2、QML工具

Qt 6.2 对 QML 工具进行了较大改进。我们现在有一个公共的 CMake API ,它极大地简化了创建您自己的 QML 模块的过程。

QML linter (qmlint) 是一种工具,用于检查 QML 源代码的最佳实践、潜在的编码和性能问题,并帮助编写更易于维护的 QML。该工具经历了很大的变化,现在可以完全配置,无论是在命令行级别,还是通过配置文件,甚至是 QML 文件本身中的各个块。此外,它现在可以生成 JSON 输出以简化与其他工具或自动化系统的集成。

QML 格式化程序 (qmlformat) 现在使用 QML dom 库,大大改进了生成的输出。

3、Qt多媒体

Qt 多媒体在 Qt 6 中发生了一些相当大的变化。它是我们在 Qt 5 的生命周期中不满意的 API 之一。因此,我们退后一步,对 Qt 6 进行了一些更广泛的 API 和架构更改。没有过多考虑向后兼容性的模块。

尽管如此,从 Qt 5 中的 Qt 多媒体移植到 Qt 6 应该相对简单。

Qt 6 中的 Qt 多媒体确实支持一些我们在 Qt 5 中从未设法正确支持的高度要求的功能。示例包括播放的字幕和语言选择支持以及媒体捕获的可配置设置。

内部架构已经过清理,不再像 Qt 5 那样通过公共 API 公开。这将使我们能够更快地修复错误,并使将来添加新功能变得更加容易。您可以在有关 Qt 6 中的 Qt 多媒体的单独博客文章中找到更多详细信息。

然而,由于这些巨大的变化,该模块仍然存在粗糙的边缘,并且可能在实现中存在相当多的错误。但是,我们相信多媒体是一项必不可少的功能,我们将在 Qt 6.2 中完全支持该模块。

因此,我们将在补丁级别版本的常规提交策略上有所偏离,如果需要修复较大的问题,可能会添加一些较小的 API。

此外,我们将努力在即将发布的补丁级别版本中尽快修复任何报告的错误。

4、整个过程中的小改进

几乎所有其他模块都看到了许多较小的 API 添加和改进。

我们已经移植了许多 API 以利用新的属性系统,以便您可以使用 C++ 中的属性绑定。这项工作尚未完成,我们将在未来的版本中继续。

我们还在各个地方修复了许多 API 缺点和缺失的功能。仅举几个例子:

  • Qt Charts 获得了一些新的 API,以提高便利性并使事情更加可定制。
  • 我们为 QImage 添加了浮点图像格式。
  • QByteArray::number() 现在可以正确处理 10 以外的基数的负值。
  • QLockFile 现在具有采用 std::chrono 的重载
  • Qt Network 支持多个可以在运行时共存的 SSL 后端。

5、Qt Creator 和 Qt Design Studio

Qt Creator 和 Qt Design Studio 也做了大量工作,以确保它们为 Qt 6.2 提供一流的支持。Qt Creator 5 包含您为 Qt 6.2 开发所需的一切。

我们今天还发布了全新版本的 Qt Design Studio。Qt Design Studio 2.2 基于 Qt 6.2,极大地支持在一个图形工具中创建基于 Qt Quick 和 Qt Quick 的 3D 用户界面。您可以轻松地在目标硬件上测试这些,无论是台式机、移动设备还是嵌入式设备。有关 更多详细信息,请查看有关Qt Design Studio 2.2的单独博客文章。

五、新平台

对于 Qt 6.2,我们做了很多工作来改进我们对当前支持平台的支持,包括桌面和移动端,例如,通过改进我们对 HighDPI 渲染的支持和在 iOS 上添加 NFC 后端。

最重要的是,Qt 6.2 大大扩展了支持平台的范围:

Qt 6.2 完全支持 Apple Silicon 上的 macOS。Qt 现在可以轻松创建通用二进制文件并在 Intel 和 Apple Silicon 上为 macOS 进行开发。当然,该版本也在我们的 CI 系统中进行了全面测试。一直可以通过 Rosetta 层在 Apple 芯片上运行 Qt 应用程序,但 Qt 6.2 现在提供了在 Apple 芯片上本地运行的完整支持。

Qt 6.2 还恢复了对 INTEGRITY 和 QNX 实时操作系统的支持。支持需要 C++17 工具链和最新版本的操作系统。QNX 的最低要求是 7.1 版,在 INTEGRITY 上,我们支持 19.0.13 版。

针对 Qt 6.2 的 webOS 验证也已完成,以进一步加强 Qt 对 webOS 的承诺。

有很多工作正在进行以支持 Windows 11,我们希望能够在 6.2 补丁级别版本中为其提供全面支持。Windows on ARM HW 也可作为 Qt 6.2 的技术预览版提供。

最后,我们做了进一步的工作来改进我们对 WebAssembly 的支持,它在 Qt 6.2 中作为技术预览提供支持。

Qt for Python 今天也发布了,大家可以试一试。未来几天将发布一篇单独的博客文章,重点介绍最新 Qt 6.2 更改中采用的所有功能。敬请关注!

六、从 Qt 5 移植

在开发 Qt 6 时,与 Qt 5 的源代码兼容性一直是我们工作的关键部分。有一些地方我们不得不在某种程度上打破这种兼容性,以进行一些必需的架构更改或为我们带来一些巨大的性能优势。

在大多数情况下,从 Qt 5 移植到 Qt 6 应该很简单。在Qt的6移植指南 列出了所需要的步骤,并具有更多的信息。您还可以从我们的合作伙伴之一或我们的顾问那里获得移植帮助。

移植到 Qt 6 的典型步骤是:

  • 检查您是否使用了受支持的编译器和平台版本
  • 首先在 Qt 6 模式下使用 Qt 5.15 编译(使用 QT_DISABLE_DEPRECATED_BEFORE 宏)
  • 然后用 Qt 6.x 编译 - 如果需要,在移植阶段利用兼容性模块

有了这些,您就可以在 Qt 6 上运行应用程序,并可以开始使用它提供的所有新特性和功能。例如,如果您的应用程序使用 QML,请运行 qmlint 工具并修复它给出的警告。

1、变化总括

1). 增加了很多轮子,同时原有模块拆分的也更细致,估计为了方便拓展个管理。

2). 把一些过度封装的东西移除了(比如同样的功能有多个函数),保证了只有一个函数执行该功能。
3). 把一些Qt5中兼容Qt4的方法废弃了,必须用Qt5中对应的新的函数。
4). 跟随时代脚步,增加了不少新特性以满足日益增长的客户需求。
5). 对某些模块和类型及处理进行了革命性的重写,运行效率提高不少。
6). 有参数类型的变化,比如 long 到 qintptr 等,更加适应后续的拓展以及同时对32 64位不同系统的兼容。
7). 源码中的double数据类型全部换成了qreal,和Qt内部数据类型高度一致和统一。
8). 我测试的都是QWidget部分,quick部分没有测试,估计quick部分更新可能会更多。
9). 强烈建议暂时不要用Qt6.0到Qt6.2之间的版本,一些模块还缺失,相对来说BUG也比较多,推荐6.2版本开始正式迁移。

2、移植总结

1). 万能方法:安装5.15版本,定位到报错的函数,切换到源码头文件,可以看到对应提示字样 QT_DEPRECATED_X(“Use sizeInBytes”) 和新函数。按照这个提示类修改就没错,一些函数是从Qt5.7 5.9 5.10等版本新增加的,可能你的项目还用的Qt4的方法,但是Qt6以前都兼容这些旧方法,到了Qt6就彻底需要用新方法了。
2). Qt6对core这个核心类进行了拆分,多出来core5compat,因此你需要在pro增加对应的模块已经代码中引入对应的头文件。

1
2
3
4
5
6
7
8
9
10
11
//pro文件引入模块
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
greaterThan(QT_MAJOR_VERSION, 5): QT += core5compat

//代码中引入头文件
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
#include <QtWidgets>
#endif
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
#include <QtCore5Compat>
#endif

(1). 默认Qt6开启了高分屏支持,界面会变得很大,甚至字体发虚,很多人会不习惯,因为这种模式如果程序很多坐标计算没有采用devicePixelRatio进行运算的话,100%会出现奇奇怪怪的问题,因为坐标不准确了。要取消这种效果可以设置高分屏缩放因子。

1
2
3
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::Floor);
#endif

(1). 原有的随机数函数提示用QRandomGenerator替代,为了兼容所有qt版本,改动最小的办法是直接用c++中的随机数,比如qsrand函数换成srand,qrand函数换成rand,查看过源代码,其实封装的就是c++中的随机数,很多类似的封装比如qSin封装的sin。
(2). QColor的 light 改成 lighter ,dark 改成 darker,其实 lighter、darker 这两个方法以前一直有。
(3). QFontMetricsF 中的 fm.width 换成 fm.horizontalAdvance ,从5.11开始用新函数。
(4). QPalette调色板枚举值,Foreground = WindowText, Background = Window,其中 Foreground 和 Background 没有了,要用 WindowText 和 Window 替代,以前就有。类似的还有 setTextColor 改成了 setForeground 。
(5). QWheelEvent的 delta() 改成 angleDelta().y(),pos() 改成 position() 。
(6). svg模块拆分出来了svgwidgets,如果用到了该模块则需要在pro增加 QT += svgwidgets 。
(7). qlayout中的 margin() 函数换成 contentsMargins().left(),查看源码得知以前的 margin() 返回的就是 contentsMargins().left(),在四个数值一样的时候,默认四个数值就是一样。类似的还有setMargin移除了,统统用setContentsMargins。
(8). 之前 QChar c = 0xf105 全部要改成强制转换 QChar c = (QChar)0xf105,不再有隐式转换,不然编译报错提示error: conversion from ‘int’ to ‘QChar’ is ambiguous 。
(9). qSort等一些函数用回c++的 std::sort 。

1
2
3
4
5
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
std::sort(ipv4s.begin(), ipv4s.end());
#else
qSort(ipv4s);
#endif

(1). Qt::WA_NoBackground 改成 Qt::WA_OpaquePaintEvent 。
(2). QMatrix 类废弃了没有了,换成 QTransform ,函数功能基本一致,QTransform 类在Qt4就一直有。
(3). QTime 计时去掉了,需要改成 QElapsedTimer ,QElapsedTimer 类在Qt4就一直有。
(4). QApplication::desktop()废弃了, 换成了 QApplication::primaryScreen()。

1
2
3
4
5
6
7
8
9
#if (QT_VERSION > QT_VERSION_CHECK(5,0,0))
#include "qscreen.h"
#define deskGeometry qApp->primaryScreen()->geometry()
#define deskGeometry2 qApp->primaryScreen()->availableGeometry()
#else
#include "qdesktopwidget.h"
#define deskGeometry qApp->desktop()->geometry()
#define deskGeometry2 qApp->desktop()->availableGeometry()
#endif

(1). 获取当前屏幕索引以及尺寸需要分别处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//获取当前屏幕索引
int QUIHelper::getScreenIndex()
{
//需要对多个屏幕进行处理
int screenIndex = 0;
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
int screenCount = qApp->screens().count();
#else
int screenCount = qApp->desktop()->screenCount();
#endif

if (screenCount > 1) {
//找到当前鼠标所在屏幕
QPoint pos = QCursor::pos();
for (int i = 0; i < screenCount; ++i) {
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
if (qApp->screens().at(i)->geometry().contains(pos)) {
#else
if (qApp->desktop()->screenGeometry(i).contains(pos)) {
#endif
screenIndex = i;
break;
}
}
}
return screenIndex;
}

//获取当前屏幕尺寸区域
QRect QUIHelper::getScreenRect(bool available)
{
QRect rect;
int screenIndex = QUIHelper::getScreenIndex();
if (available) {
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
rect = qApp->screens().at(screenIndex)->availableGeometry();
#else
rect = qApp->desktop()->availableGeometry(screenIndex);
#endif
} else {
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
rect = qApp->screens().at(screenIndex)->geometry();
#else
rect = qApp->desktop()->screenGeometry(screenIndex);
#endif
}
return rect;
}

(1). QRegExp类移到了core5compat模块,需要主动引入头文件 #include 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    //设置限制只能输入数字+小数位
QString pattern = "^-?[0-9]+([.]{1}[0-9]+){0,1}$";
//设置IP地址校验过滤
QString pattern = "(2[0-5]{2}|2[0-4][0-9]|1?[0-9]{1,2})";

//确切的说 QRegularExpression QRegularExpressionValidator 从5.0 5.1开始就有
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
QRegularExpression regExp(pattern);
QRegularExpressionValidator *validator = new QRegularExpressionValidator(regExp, this);
#else
QRegExp regExp(pattern);
QRegExpValidator *validator = new QRegExpValidator(regExp, this);
#endif
lineEdit->setValidator(validator);

(1). QWheelEvent构造参数和对应的计算方位函数变了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//模拟鼠标滚轮
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
QWheelEvent wheelEvent(QPoint(0, 0), -scal, Qt::LeftButton, Qt::NoModifier);
#else
QWheelEvent wheelEvent(QPointF(0, 0), QPointF(0, 0), QPoint(0, 0), QPoint(0, -scal), Qt::LeftButton, Qt::NoModifier, Qt::ScrollBegin, false);
#endif
QApplication::sendEvent(widget, &wheelEvent);

//鼠标滚轮直接修改值
QWheelEvent *whellEvent = (QWheelEvent *)event;
//滚动的角度,*8就是鼠标滚动的距离
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
int degrees = whellEvent->delta() / 8;
#else
int degrees = whellEvent->angleDelta().x() / 8;
#endif
//滚动的步数,*15就是鼠标滚动的角度
int steps = degrees / 15;

(1). qVariantValue 改成 qvariant_cast ,qVariantSetValue(v, value) 改成了 v.setValue(val)。相当于退回到最原始的方法,查看qVariantValue源码封装的就是qvariant_cast。
(2). QStyleOption的init改成了initFrom。
(3). QVariant::Type 换成了 QMetaType::Type ,本身以前的 QVariant::Type 封装的就是 QMetaType::Type 。
(4). QStyleOptionViewItemV2 V3 V4 之类的全部没有了,暂时可以用 QStyleOptionViewItem 替代。
(5). QFont的 resolve 的一个重载函数换成了 resolveMask。
(6). QSettings的 setIniCodec 方法移除了,默认就是utf8,不需要设置。
(7). qcombobox 的 activated(QString) 和 currentIndexChanged(QString) 信号删除了,用int索引参数的那个,然后自己通过索引获取值。个人觉得这个没必要删除。
(8). qtscript模块彻底没有了,尽管从Qt5时代的后期版本就提示为废弃模块,一致坚持到Qt6才正式废弃,各种json数据解析全部换成qjson类解析。
(9). QByteArray 的 append indexOf lastIndexOf 等众多方法的QString参数重载函数废弃了,要直接传 QByteArray,就在原来参数基础上加上 .toUtf8() 。查看源码也看得到以前的QString参数也是转成.toUtf8()再去比较。
(10). QDateTime的时间转换函数 toTime_t + setTime_t 名字改了,对应改成了 toSecsSinceEpoch + setSecsSinceEpoch ,这两个方法在Qt5.8时候新增加的。
(11). QLabel的 pixmap 函数之前是指针 *pixmap() 现在换成了引用 pixmap()。
(12). QTableWidget的 sortByColumn 方法移除了默认升序的方法,必须要填入第二个参数表示升序还是降序。
(13). qtnetwork中的错误信号error换成了errorOccurred。

1
2
3
4
5
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
connect(tcpSocket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), this, SLOT(error()));
#else
connect(tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(error()));
#endif

(1). XmlPatterns模块木有了,全部用xml模块重新解析。
(2). nativeEvent的参数类型变了。

1
2
3
4
5
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
bool nativeEvent(const QByteArray &eventType, void *message, qintptr *result);
#else
bool nativeEvent(const QByteArray &eventType, void *message, long *result);
#endif

(1). QButtonGroup的buttonClicked信号中int参数的函数全部改名字叫idClicked。

1
2
3
4
5
6
    QButtonGroup *btnGroup = new QButtonGroup(this);
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
connect(btnGroup, SIGNAL(idClicked(int)), ui->xstackWidget, SLOT(setCurrentIndex(int)));
#else
connect(btnGroup, SIGNAL(buttonClicked(int)), ui->xstackWidget, SLOT(setCurrentIndex(int)));
#endif

(1). QWebEngineSettings之前是QWebEngineSettings::defaultSettings();现在改成了QWebEngineProfile::defaultProfile()->settings();通过查看之前的源码得知QWebEngineSettings::defaultSettings();封装的就是QWebEngineProfile::defaultProfile()->settings();因为Qt6去除了N多过度封装的函数。

1
2
3
4
5
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
QWebEngineSettings *webSetting = QWebEngineProfile::defaultProfile()->settings();
#else
QWebEngineSettings *webSetting = QWebEngineSettings::defaultSettings();
#endif