6.5中QML模块的新增功能

What’s new for QML Modules in 6.5

6.5中QML模块的新增功能

March 03, 2023 by Fabian Kosmale | Comments

2023年3月3日,Fabian Kosmale |评论

While QML modules have existed for a long time, their use had been rather sparse before Qt 6. With the introduction of qt_add_qml_module in Qt 6, they have however become much more prevalent. And with good reason: Only by placing all related QML in a module can tooling like qmllint or the Qt Quick Compilers work correctly.

虽然QML模块已经存在了很长时间,但在Qt 6之前,它们的使用已经相当稀少。随着qt 6中qt_add_qml_module的引入,它们变得更加流行。而且有充分的理由:只有将所有相关的QML放在一个模块中,像qmllint或Qt Quick Compilers这样的工具才能正常工作。

However, some parts of Qt’s own API were not aware of modules so far. When interacting with QML types, e.g. via QQmlComponent, you would need to use explicit file paths so far. Starting from 6.5, there’s now an alternative solution leveraging modules, which we’ll introduce in this blog post. Moreover, we now provide a solution which obsoletes fiddling with import paths in most common cases.

然而,到目前为止,Qt自己的API的一些部分还不知道模块。当与QML类型交互时,例如通过QQmlComponent,到目前为止,您需要使用显式文件路径。从6.5开始,现在有一个利用模块的替代解决方案,我们将在本博客文章中介绍。此外,我们现在提供了一种解决方案,在大多数常见情况下,该解决方案不再需要修改导入路径。

New default import path
新建默认导入路径

Let’s start with the new default import path that has been added: You can now put your modules under qrc:/qt/qml, and they will be automatically found there. So far, if you wanted to place modules the resource system, you would either had to call QQmlEngine::addImportPath with a path of your choosing or place it under qrc:/qt-project.org/imports. The latter is strongly discouraged, as it is only meant for Qt’s own modules and libraries that are expected to be used system wide. The former cannot be easily done by a library, and forcing every user of a library to first call addImportPath isn’t much of a solution either. To remedy this, we now provide qrc:/qt/qml as a default import path, and recommend applications and libraries to use it.

让我们从添加的新默认导入路径开始:现在可以将模块放在qrc:/qt/qml下,它们将在那里自动找到。到目前为止,如果您想将模块放置在资源系统中,您必须使用您选择的路径调用QQmlEngine::addImportPath,或者将其放置在qrc:/qt-project.org/imports下。强烈不建议使用后者,因为它只适用于qt自己的模块和库,这些模块和库预计将在系统范围内使用。前者不容易由库完成,强制库的每个用户首先调用addImportPath也不是很好的解决方案。为了解决这个问题,我们现在提供qrc:/qt/qml作为默认导入路径,并建议应用程序和库使用它。


NOTE:

Applications could avoid the issue with the import path by simply loading their main entry point from a path in the resource system, and relying on the implicit import to find the module’s qmldir, assuming the main file and the qmldir are all part of the module and placed in the same folder. We’ll see below why this solution has its own shortcomings, and for libraries it was never feasible in any case.
应用程序可以避免导入路径的问题,只需从资源系统中的路径加载它们的主入口点,并依靠隐式导入来查找模块的qmldir,假设主文件和qmldir都是模块的一部分并放在同一文件夹中。我们将在下面看到为什么这个解决方案有自己的缺点,并且对于库来说,它在任何情况下都是不可行的。

CMake integration
CMake集成

Unfortunately, qt_add_qml_module has been using / as the default for the resource prefix so far, and we cannot silently change it. Fortunately, Qt 6.5 also brings support Qt CMake policies, which allow for a controlled way of evolving the defaults in our CMake API. Enabling QTP0001 sets /qt/qml as the default when no explicit value for RESOURCE_PREFIX has been provided. The easiest way to enable this policy is to call

不幸的是,到目前为止,qt_add_qml_module一直使用/作为资源前缀的默认值,我们无法默默地更改它。幸运的是,qt 6.5还提供了对qt CMake策略的支持,它允许在CMake API中以可控的方式演变默认值。当未提供RESOURCE_PREFIX的显式值时,启用QTP0001会将/qt/qml设置为默认值。启用此策略的最简单方法是调用

qt_standard_project_setup(REQUIRES 6.5)

Passing REQUIRES 6.5 to qt_standard_project_setup will globally enable all new policies introduce up to Qt 6.5. That happens to only be the one about the resource prefix. For more details on controlling policies, please consult their documentation and the documentation of the qt_policy command.

将REQUIRES 6.5传递到qt_standard_project_setup将全局启用所有新策略,直到qt 6.5。这恰好是关于资源前缀的。有关控制策略的更多详细信息,请参阅其文档和qt_policy命令的文档。

But I’m using qmake!
但我在用qmake!

In case you’re on a project using qmake, you can still benefit from the new default import path, by setting the prefix on the RESOURCES variable.

如果您正在使用qmake进行项目,那么通过在RESOURCES变量上设置前缀,您仍然可以从新的默认导入路径中受益。

QML_IMPORT_NAME = MyModule
QML_IMPORT_MAJOR_VERSION = 1

SOURCES += \
    main.cpp

HEADERS += \
    filesystemmodel.h

qml_resources.files = \
    qmldir \
    Main.qml \
    MyType.qml

qml_resources.prefix = /qt/qml/MyModule

RESOURCES += qml_resources

With this setup, the MyModule module can be found by the engine without any further setup at runtime.

使用此设置,引擎可以找到MyModule模块,而无需在运行时进行任何进一步设置。

Module aware API
模块感知API

The second substantial change related to QML module is a new set of APIs to interact with QML types, both from C++ and from QML.

与QML模块相关的第二个实质性变化是一组新的API,用于与来自C++和QML的QML类型交互。

Loading Components: The state so far
加载组件:目前的状态

Before we dive into the new API, let’s first look at how a simple QML application project would look before. We would have the following directory structure:

在深入研究新的API之前,让我们先看看一个简单的QML应用程序项目以前的样子。我们将具有以下目录结构:

 helloqml
├── CMakeLists.txt
├── main.cpp
└── main.qml

The CMakeLists.txt would look like:

CMakeLists.txt如下所示:

cmake_minimum_required(VERSION 3.21)

project(helloqml VERSION 0.1 LANGUAGES CXX)

find_package(Qt6 6.5 COMPONENTS Quick REQUIRED)

qt_standard_project_setup(REQUIRES 6.5)

qt_add_executable(helloqmlapp
    main.cpp
)

qt_add_qml_module(helloqmlapp
    URI helloqml
    QML_FILES main.qml 
)

target_link_libraries(helloqmlapp
    PRIVATE Qt6::Quick)

Note that we’re already using the new default import path mentioned above. main.cpp contains

注意,我们已经使用了上面提到的新的默认导入路径。main.cpp内容如下

#include <QGuiApplication>
#include <QQmlApplicationEngine>

using namespace Qt::Literals::StringLiterals;

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    const QUrl url(u"qrc:/qt/qml/helloqml/main.qml"_s);
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed,
                     &app, []() { QCoreApplication::exit(-1); },
                     Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}

and finally, main.qml contains

最后,main.qml内容如下

import QtQuick

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")
}

The line of interest is const QUrl url(u"qrc:/qt/qml/helloqml/main.qml"_s);. There are multiple gotchas in here. The first is that because we are dealing with a QML module, main.qml has now been placed under a helloqml folder (remember, a QML module must be in one of the import paths within a folder corresponding to the module’s URI). The second gotcha is that while qt_add_qml_module helpfully places the QML files into the resource system, we still have to remember to load from there and not directly from the file system if we want to avoid potentially slow file system operations (and ensure that AOT compiled bindings are executed instead of being interpreted at runtime). On the other hand, if we do want to load from the normal file system during development (to iterate faster on our QML files without having to recompile), we would be out of luck as we have now hard-coded the qrc path. With Qt 6.5 we have a new option which avoids all the issues.

感兴趣的行是const QUrl url(u"qrc:/qt/qml/helloqml/main.qml"_s);。这里有多个陷阱。第一个原因是,因为我们处理的是QML模块,main.qml现在被放置在helloqml文件夹下(请记住,QML模块必须位于与模块URI对应的文件夹中的一个导入路径中)。第二个问题是,尽管qt_add_qml_module有助于将qml文件放置到资源系统中,但如果我们想避免可能缓慢的文件系统操作(并确保执行AOT编译的绑定而不是在运行时解释),我们仍然必须记住从那里加载,而不是直接从文件系统加载。另一方面,如果我们确实想在开发期间从正常的文件系统加载(以便在QML文件上更快地迭代,而不必重新编译),那么我们就不走运了,因为我们现在已经硬编码了qrc路径。有了Qt 6.5,我们有了一个新选项,可以避免所有问题。

Loading Components: The new approach
加载组件:新方法

With QML modules, we know that a given QML element can be identified via the name of its module and its type-name. So let’s use those: We rename main.qml to Main.qml (recally that only upper case files lead to exported QML types by default), and change main.cpp to use engine.loadFromModule("helloqml", "Main"); instead of engine.load(url). And we’re done. We no longer have to care where exactly the file is located, and the loading from the resource file system is handled behind the scenes. But loadFromModule can do more than just replacing existing load(QUrl) calls. It also enables a few things that were not possible before. loadFromModule does not require that the QML element we’re loading is a file. Instead it supports any element that can be created in QML. That includes

对于QML模块,我们知道给定的QML元素可以通过其模块名称和类型名称来识别。所以让我们使用它们:我们将main.qml重命名为main.qml(默认情况下,只有大写文件才能导出qml类型),并将main.cpp更改为使用engine.loadFromModule("helloqml", "Main");而不是engine.load(url)。我们完成了。我们不再需要关心文件的确切位置,而资源文件系统的加载是在幕后处理的。但是loadFromModule可以做的不仅仅是替换现有的load(QUrl)调用。它还实现了一些以前不可能实现的事情。loadFromModule不要求我们加载的QML元素是一个文件。相反,它支持可以在QML中创建的任何元素。这包括

  • elements backed by files (as we’ve already seen),

  • 文件支持的元素(正如我们已经看到的),

  • inline components: Given

  • 内联组件:给定

// Outer.qml, in module MyModule
Item {
    component Inner : Rectangle { color: "red" }
}

we can load Inner via engine.loadFromModule("MyModule", "Outer.Inner").

我们可以通过engine.loadFromModule("MyModule", "Outer.Inner")加载Inner。

  • and types defined in C++: Given

  • 和C++中定义的类型:Given

// part of MyModule
struct MyFancyItem : public QQuickItem
{
  QML_ELEMENT
  // ...
}

we can create an instance of MyFancyItem via engine.loadFromModule("MyModule", "MyFancyItem").

我们可以通过engine.loadFromModule("MyModule", "MyFancyItem")创建MyFancyItem的实例。

Before, the last two cases would have required creating a wrapper QML file instantiating the desired element. Additionally, the above is not limited to QQmlApplicationEngine. An equivalent API exists also in QQmlComponent in the form of QQmlComponent::loadFromModule. Lastly, there’s also a new overload of Qt.createComponent, exposing the functionality in QML:

之前,最后两种情况需要创建一个包装器QML文件来实例化所需的元素。此外,上述内容不限于QQmlApplicationEngine。QQmlComponent中也存在QQmlComponent::loadFromModule形式的等效API。最后,还有一个新的Qt.createComponent重载,暴露了QML中的功能:

import QtQuick
ListView {
    model: 10
    delegate: Qt.createComponent("MyModule", "MyFancyItem")
}
Singletons
单例

The API so far was used to create new objects, and thus it’s unsuitable for QML singletons. For consistency’s sake, there is however also a new overload of QQmlEngine::singletonInstance working with singletons. That can be useful when setting up some application global data, e.g. to expose a QAbstractItemModel, or some application configuration data fetched from the network: Given a singleton defined in C++ 1

到目前为止,API用于创建新对象,因此它不适用于QML单体。然而,出于一致性的考虑,QQmlEngine::singletonInstance也有一个新的重载,用于处理singleton。这在设置一些应用程序全局数据时非常有用,例如,暴露QAbstractItemModel或从网络获取的一些应用程序配置数据:在C++中定义的单例

class Globals : public QObject
{
   Q_OBJECT
   QML_ELEMENT
   QML_SINGLETON
   
   Q_PROPERTY(QAbstractItemModel *model READ model WRITE setModel NOTIFY modelChanged)
   //...
}

we can then write

我们可以写成

QQmlApplicationEngine engine;
// access the singleton
auto globals = engine.singletonInstance<Globals>("MyModule", "Globals");
// set up global state
globals.setModel(fancyModel);
// start the application after the initial setup
engine.loadFromModule("MyModule", "Main")

Note that it was possible to do this even in prior Qt versions by using qmlTypeId in combination with the the id based overload of QQmlEngine::singletonInstance. The new function has two benefits: It's slighly less to write, and it is a tiny bit faster if only one call to singletonInstance2 is needed.

请注意,即使在以前的Qt版本中,也可以通过结合使用qmlTypeId和QQmlEngine::singletonInstance的基于id的重载来实现这一点。新函数有两个好处:编写起来稍微少了一点,如果只需要调用singletonInstance,速度会稍微快一点。

Outlook
展望

We hope that the new module related functionality will make it easier to work with them. The ability to load QML elements also brings us a step closer to a more seamless integration with the QML type compiler. Currently, usage of the type compiler requires modifying the entry point of your application. In the future, we plan to support qmltc-compiled types transparently in the various loadFromModule functions. Stay tuned for futher updates!

我们希望新的与模块相关的功能将使其更易于使用。加载QML元素的能力也使我们更接近于与QML类型编译器的无缝集成。目前,使用类型编译器需要修改应用程序的入口点。未来,我们计划在各种loadFromModule函数中透明地支持qmltc编译类型。敬请关注更多更新!



  1. The same function works with QML singletons, too.↩︎

1.同样的功能也适用于QML单体。↩︎

  1. If you cannot cache the singleton itself, but can cache the singleton id and you need to fetch the singleton repeatedly.↩︎

2.如果不能缓存单例本身,但可以缓存单例id,则需要重复获取单例。↩︎

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Qt 6.5 QML 的性能进行了一系列的改进和提升,主要体现在以下几个方面: 首先,Qt 6.5 引入了的 JIT(即时编译)引擎。与过去的 AOT(提前编译)相比,JIT 引擎能够在运行时动态地将 QML 代码编译成机器码执行,从而减少了解析和动态编译的开销,提高了性能。这使得 QML 应用的启动速度更快,界面响应更即时。 其次,Qt 6.5 在渲染器方面进行了优化。引入了的基于 Vulkan 的渲染器,利用 GPU 进行加速渲染,提升了 QML 界面的渲染性能。同时,针对复杂场景和高分辨率屏幕,Qt 6.5 进一步优化了渲染流程,减少了不必要的重复渲染,降低了 CPU 和 GPU 的开销,提高了性能。 另外,Qt 6.5 支持基于 Vulkan 的 QSG(Qt Scene Graph)后端。通过使用 Vulkan API,能够更好地利用现代 GPU 的硬件特性,提升渲染性能。这对于涉及大量图形处理的 QML 应用尤为重要,例如游戏或图形编辑器等。 此外,Qt 6.5 在 QML 引擎的内部实现上也进行了一些改进。为了提高 QML 的执行效率,对于频繁使用的表达式或函数调用,Qt 6.5 会采用一种的编译优化技术,缓存计算结果,避免重复计算,提升性能。同时,Qt 6.5 还改进了对于 JavaScript Proxy 类型的支持,使得在 QML 使用 JavaScript 时更加高效。 综上所述,Qt 6.5 在 QML 的性能方面进行了多方面的优化和提升,通过引入的 JIT 引擎、提升渲染器性能、支持 Vulkan QSG 后端以及改进内部实现等方式,明显强了 QML 应用的运行效率和性能表现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值