Qt 6中的隐式导入与QML模块

Implicit Imports vs. QML Modules in Qt 6

Qt 6中的隐式导入与QML模块

February 28, 2024 by Ulf Hermann | Comments

​2024年2月28日:Ulf Hermann |评论

Several versions of Qt have been released since my last treatise on QML modules. Most of it is in fact still very valid advice, but I feel I have to stress a few things that people often misunderstand.

​自从我上一篇关于QML模块的论文以来,Qt已经发布了几个版本。事实上,其中大部分仍然是非常有效的建议,但我觉得我必须强调一些人们经常误解的事情。

Turning Pickles into Chocolate with QML

用QML把Pickles变成Chocolate

As you may know, each QML document has an implicit import. Other QML documents in the same directory can be used without importing anything. This is a very useful feature that greatly reduces the amount of boiler plate you need to write. You generally don't have to import the module a QML file belongs to itself.

​如您所知,每个QML文档都有一个隐式导入。可以在不导入任何内容的情况下使用同一目录中的其他QML文档。这是一个非常有用的功能,可以大大减少需要编写的锅炉板的数量。通常不必导入QML文件本身所属的模块。

This feature is distinctively less useful if the implicit import does not give you the module your file belongs to. Now you will ask me "How can that happen?" and I would like to reply "It doesn't!" However, since I can't go back in time to restrict our CMake API, I have to admit it can happen. Clearly, when writing about QML modules, I did not think of this case. None of the examples exercise it.

​如果隐式导入没有提供文件所属的模块,则此功能的用处明显较小。现在您会问我“这怎么会发生?”我想回答“不会!”然而,由于我无法及时返回限制我们的CMake API,我不得不承认这是可能发生的。很明显,在写QML模块时,我没有想到这个案例。没有一个例子能证明这一点。

Yet, people more imaginative than me knew how to use the CMake API for QML modules in ways it was not intended for. For educational purposes, let's explore what happens then. You mustn't do this without your parents.

然而,比我更有想象力的人知道如何将CMake API用于QML模块,而不是用于它。出于教育目的,让我们探究一下会发生什么。没有父母你不能这样做。

So, here is how you make your QML module different from the implicit import of its files:

因此,以下是如何使QML模块不同于其文件的隐式导入:

myProject
    | - CMakeLists.txt
    | - main.cpp
    | - some_qml_type.h
    | - some_qml_type.cpp
    | - qml
        | - main.qml
        | - Pickles.qml

With the above project structure, I assume you have a call to qt_add_qml_module in the CMakeLists.txt, and the QML module defined this way contains main.qml and Pickles.qml. The implicit import of main.qml and Pickles.qml is the "qml" directory. Their module, however, is defined in the "myProject" directory. Now, if you want main.qml or Pickles.qml to use anything declared in some_qml_type.h, you have to explicitly import myProject. Congratulations!

​使用上述项目结构,我假设在CMakeLists.txt中有一个对qt_add_qml_module的调用,并且以这种方式定义的qml模块包含main.qml和Pickles.qml。main.qml和Pickles.qml的隐式导入是“qml”目录。然而,它们的模块是在“myProject”目录中定义的。现在,如果希望main.qml或Pickles.qml使用some_qml_type.h中声明的任何内容,则必须显式导入myProject。祝贺

Why is that? Quite simple: main.qml and Pickles.qml will not see a qmldir file in their directory because the qmldir file is one directory up, in myProject. If there is no qmldir file in the same directory, the implicit import is constructed using only the file names of the QML files found there. There is no space for C++-defined types in this case.

为什么?很简单:main.qml和Pickles.qml在它们的目录中不会看到qmldir文件,因为qmldir是myProject中的一个目录。如果同一目录中没有qmldir文件,则隐式导入仅使用在其中找到的QML文件的文件名构建。在这种情况下,没有空间容纳C++定义的类型。

And you can have even more fun by adding the following to your CMakeLists.txt:

通过将以下内容添加到CMakeLists.txt中,可以获得更多乐趣:

set_source_files_properties(qml/Pickles.qml PROPERTIES
    QT_QML_SOURCE_TYPENAME Chocolate
)

Now, in main.qml you can use a component called "Pickles" as defined by Pickles.qml next to it, just like before. If you import myProject, though, you do not get a component called "Pickles". Instead you get a component called "Chocolate" with the same contents. main.qml, which doesn't import myProject, does not get to have your Chocolate. Isn't it beautiful?

现在,在main.qml中,可以使用一个名为“Pickles”的组件,该组件由旁边的Pickles.qml定义,就像以前一样。但是,如果导入myProject,则不会得到名为“Pickles”的组件。相反,会得到一种叫做“Chocolate”的内容,其意义相同。main.qml,它不导入myProject,不能拥有Chocolate。它不是很美吗?

But we're still not done. Did you know you can have a type name that refers to a singleton when imported via a named module, but to a regular type when accessed via the implicit import inside the same module? I will leave the exact way to do this as an exercise to the reader (under parental observation). Just a hint: It's not as trivial as you think. I will send some pickles to the first one who can post a correct solution in the comments.

但我们仍然没有完成。你知道吗?当通过命名模块导入时,可以有一个类型名称引用单例,但当通过同一模块内的隐式导入访问时,它可以引用常规类型?我将把做这件事的确切方法留给读者(在家长的观察下)。只是一个提示:这并不像你想象的那么微不足道。我会给第一个能在评论中发布正确解决方案的人发一些泡菜。

All of these effects are features, of course. Enjoy. And don't you dare pestering me with bug reports. So much for the fun. Let's get back to the serious parts.

当然,所有这些效果都是特征。享受你也不敢拿窃听报告纠缠我。太有趣了。让我们回到严肃的部分。

How did we get here?

我们是怎么到这里的?

The sad truth is that many of the official examples show exactly the structure presented above, with an extra directory called "qml" between the application root and the QML files. Back in Qt5, when it was hard to create proper named QML modules, this was a good way to structure your code and separate your QML files from your C++ code. When porting to CMake, the problem with this structure went undetected for a while. Now, having crept into everybody's projects, it is enshrined in legacy.

可悲的事实是,许多官方示例恰恰显示了上述结构,在应用程序根和qml文件之间有一个名为“qml”的额外目录。回到Qt5,当很难创建正确命名的QML模块时,这是一种很好的方法来构建代码并将QML文件与C++代码分离。当移植到CMake时,这个结构的问题在一段时间内没有被发现。现在,它已经渗透到每个人的项目中,被载入了遗产。

People also create such subdirectories on purpose, though. Having an internal structure to your QML module also helps break a large module into smaller parts, improving readability. Those parts should, by definition, be separate modules. However, adding a subdirectory has a lower up front cost than splitting a module. You don't have to write an extra CMakeLists.txt after all, and you don't have to think about the URIs, whether the modules should be dynamic or static, how they should be linked etc. If you never run into the situations described above, splitting a large module might never pay off.

不过,人们也会故意创建这样的子目录。QML模块的内部结构也有助于将大模块分解为更小的部分,从而提高可读性。根据定义,这些部分应该是独立的模块。但是,添加子目录比拆分模块的前期成本更低。毕竟,不必编写额外的CMakeLists.txt,也不必考虑URI,模块应该是动态的还是静态的,它们应该如何链接等等。如果从未遇到过上述情况,拆分一个大模块可能永远不会有回报。

How to fix it?

如何修复?

There are multiple ways to change your project structure so that your QML files' implicit import becomes the same as the module they belong to:

有多种方法可以更改项目结构,使QML文件的隐式导入与它们所属的模块相同:

1.Move the QML files one level up and dissolve the "qml" directory. This is the simplest way to deal with it. However, it also dissolves the internal separation of QML from C++ code that you have probably created for a reason.

1.将QML文件向上移动一级,并解散“qml”目录。这是最简单的处理方法。然而,它也消除了QML与C++代码的内部分离,而这些代码可能是出于某种原因创建的。

2.Define more QML modules. If the top level directory defines a QML module called "myProject", the "qml" subdirectory can define a QML module called "myProject.qml". It's a matter of adding another CMakeLists.txt in the "qml" directory and an "add_subdirectory(qml)" in the "myProject" directory. If you want to use types from one of those modules in the other one, you have to explicitly import. Arguably, the explicit imports are better than the ambiguities described above (unless you've actually enjoyed, that is). You can have multiple QML modules in the same binary.

​2.定义更多QML模块。如果顶级目录定义了一个名为“myProject”的QML模块,则“QML”子目录可以定义一个名“myProject.qml”的QML模块。这需要在“qml”目录中添加另一个CMakeLists.txt,在“myProject”目录中增加一个“add_subdirectory(qml)”。如果要在另一个模块中使用其中一个模块的类型,则必须显式导入。可以说,明确的导入比上面描述的模棱两可要好(除非你真的很喜欢)。在同一二进制文件中可以有多个QML模块。

3.Always explicitly import your own module in each file. Mind that this requires that the module can be found in the import path. You should definitely not use NO_RESOURCE_TARGET_PATH in this (or any other) case. And you have to make sure the module is either available from a well-known import path (e.g. ":/qt/qml" or your application directory), or an explicitly defined import path.

3.始终在每个文件中显式导入自己的模块。请注意,这需要在导入路径中找到模块。在这种(或任何其他)情况下,绝对不应该使用NO_RESOURCE_TARGET_PATH。必须确保模块可从众所周知的导入路径(例如“:/qt/qml”或应用程序目录)或明确定义的导入路径获得。

4.Wait for Ulf to come up with a solution. Since these "qml" subdirectories are so widespread, it is time to fix this in a central way, probably via some extra argument to qt_add_qml_module() or via a policy you can enable using qt_standard_project_setup. There are a few ideas floating around, but there is no definite solution, yet. See QTBUG-111763 for the progress on this.

​4.等待Ulf拿出解决方案。由于这些“qml”子目录非常普遍,现在是时候以一种集中的方式解决这个问题了,可能是通过qt_add_qml_module()的一些额外参数,或者通过可以使用qt_standard_project_setup启用的策略。有一些想法在流传,但还没有确切的解决方案。这方面的进展情况见QTBUG-111763。

Epilogue

后记

While writing this post I realized that if:

在写这篇文章的时候,我意识到如果:

  • your module doesn't have a plugin
  • 你的模块没有插件
  • you are using one of its components as entry point with the URL-based QQmlComponent constructor
  • 正在使用它的一个组件作为基于URL的QQmlComponent构造函数的入口点
  • the QML file you are loading this way is in the module's base directory
  • 以这种方式加载的QML文件位于模块的基本目录中

then the module's C++-defined types will still not be loaded from the implicit import. Clearly, this is a bug. https://codereview.qt-project.org/c/qt/qtdeclarative/+/541951 fixes it.

​则模块的C++定义的类型仍然不会从隐式导入加载。很明显,这是一个bug。 https://codereview.qt-project.org/c/qt/qtdeclarative/+/541951修复了它。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值