Qt 6.5 中 QML 模块的新功能

写在前面

尽管 QML模块 已经存在很长时间了,它们的使用在 Qt6 之前相当稀少。 qt_add_qml_module 在 Qt6 中,它们变得更加普遍。并且有充分的理由:只有将所有相关的 QML 放在一个模块中,才能使 qmllintQt Quick 编译器 等工具正常工作。

但是,到目前为止,Qt 自己的 API 的某些部分还不知道 module。与 QML 类型相互作用时,例如通过 QQmlComponent ,您需要使用明确的文件路径。

从 Qt 6.5 开始,现在有一个替代解决方案来利用模块,我们将在此博客文章中介绍。

此外,我们现在还提供了一种解决方案,该解决方案在大多数常见情况下都不会篡改导入路径。


新的默认导入路径

让我们从已添加的新 默认导入路径 开始:您现在可以将模块放在下面 qrc:/qt/qml ,它们将在那里被自动找到。

注意:

应用程序可以通过简单地从资源系统中的路径加载其主要入口点,并依靠隐式导入查找模块的QMLDIR,假设主文件和QMLDIR都是模块的一部分,并且放置在同一文件夹中。我们将在下面看到为什么该解决方案有其自己的缺点,对于库来说,无论如何,它从来都不是可行的。


CMake 集成

很遗憾, qt_add_qml_module 一直在使用 / 作为到目前为止资源前缀的默认值,我们无法静默地更改它。

幸运的是,Qt 6.5 还带来了对 Qt CMake 策略的支持,它允许以一种受控的方式来改变我们的 CMake API 中的默认值。

当未提供显式值时,启用 QTP0001 设置为默认值。

启用此策略的最简单方法是调用:

qt_standard_project_setup(REQUIRES 6.5)

传递到 qt_standard_project_setup 将在全局范围内启用所有引入 Qt 6.5 的新策略。这恰好是关于资源前缀的问题。

有关控制策略的更多详细信息,请参阅其文档和 qt_policy 命令的文档。


如果正在使用 QMake

如果您正在使用一个项目 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

通过此设置,引擎可以在运行时找到该模块,而无需进行任何进一步的设置。


模块感知 API

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

加载组件:到目前为止的状态

在我们深入研究新 API 之前,让我们首先看看一个简单的 QML 应用程序项目以前的样子。

我们具有以下目录结构:

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

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)

请注意,我们已经在使用上述新的默认导入路径, 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();
}

最后,main.qml 包含

import QtQuick

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

值得注意的是 const QUrl url(u"qrc:/qt/qml/helloqml/main.qml"_s); ,这里有多个陷阱:

首先是因为我们正在处理QML模块,所以 main.qml 现在已放置在 HelloQML 文件夹下(请记住,QML 模块必须位于与模块 URI 相对应的文件夹中的导入路径之一)。

第二个陷阱是 qt_add_qml_module 有助于将 QML 文件放入资源系统中,如果我们想避免潜在的慢速文件系统操作,我们仍然必须记住从那里加载而不是直接从文件系统加载(并确保执行 AOT 编译的绑定,而不是在运行时解释)。

另一方面,如果我们确实想在开发过程中从普通文件系统加载(以便在不必重新编译的情况下更快地迭代我们的 QML 文件),我们将不可能实现,因为现在我们已经对路径进行了硬编码。


在 Qt 6.5 中,我们有了一个避免所有问题的新方式。

加载组件:新方法

使用QML模块,我们知道给定的 QML 元素可以通过其模块的名称和类型名称来识别。

因此,让我们使用它们:我们将 main.qml 重命名为 Main.qml(默认情况下只有大写文件会导致导出的 QML 类型),并将 main.cpp 更改为使用。

我们不再需要关心文件的确切位置,并且从资源文件系统的加载在后台处理,但可以做的不仅仅是替换现有调用,它还实现了一些以前不可能的事情:不要求我们加载的 QML 元素是一个文件。

相反,它支持可以在 QML 中创建的任何元素,这包括:

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

  • 内联部分:

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

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

  • 和 C++ 定义的类型:

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

    我们可以通过 engine.loadFromModule("MyModule", "MyFancyItem")

之前,最后两种情况需要创建一个 QML 包装器文件来实例化所需元素。

此外,以上不限于 QQmlApplicationEngine ,最后,还有一个新的重载,暴露了 QML 中的功能:

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

单例

到目前为止,该API用于创建新对象,因此它不适合 QML 单例。然而,为了保持一致性,使用单例也存在新的重载。这在设置某些应用程序全局数据(例如公开 QAbstractItemModel 或从网络获取的某些应用程序配置数据)时非常有用:给定 C++ 中定义的单例:QQmlEngine::singletonInstance

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

然后我们可以写出:

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" )

请注意,即使在以前的 Qt 版本中,也可以通过将 qmlTypeId 与基于 id 的重载结合使用来做到这一点。

新函数有两个好处:写入速度略低,如果只需要调用一次,速度会快一点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦起丶

您的鼓励和支持是我创作最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值