QML文档的结构

QML文档结构的描述

QML 文档是一个自包含的 QML 源代码片段,由三部分组成:

  • 一个可选的编译指示列表

  • 它的导入语句

  • 单个根对象声明

按照惯例,一个单独的空行将导入与对象层次结构定义分开。

QML 文档始终以 UTF-8 格式编码。

编译指示

Pragmas 是给 QML 引擎本身的指令,可用于指定当前文件中对象的某些特性或修改引擎解释代码的方式。以下 pragmas 将在下面详细解释。

  • Singleton

  • ListPropertyAssignBehavior

  • ComponentBehavior

  • FunctionSignatureBehavior

  • NativeMethodBehavior

  • ValueTypeBehavior

  • Translator

单例模式

pragma Singleton 声明在QML文档中定义的组件为单例。单例在每个QML引擎中只创建一次。为了使用QML声明的单例,你还需要将其注册到其模块中。有关如何使用CMake执行此操作,请参见 qt_target_qml_sources

ListPropertyAssignBehavior

使用这个编译指示,你可以定义在QML文档中定义的组件中,如何处理对列表属性的赋值。默认情况下,对列表属性赋值会追加到列表中。你可以使用值Append明确请求此行为。或者,你可以请求使用Replace始终替换列表属性的内容,或者如果属性不是默认属性,则使用ReplaceIfNotDefault替换。例如:

注意

同样的声明也可以通过将QML_LIST_PROPERTY_ASSIGN_BEHAVIOR_APPENDQML_LIST_PROPERTY_ASSIGN_BEHAVIOR_REPLACEQML_LIST_PROPERTY_ASSIGN_BEHAVIOR_REPLACE_IF_NOT_DEFAULT宏添加到类声明中来为C++定义的类型提供。

组件行为

您可能在同一个QML文件中定义了多个组件。QML文件的根范围是一个组件,您可能还有QQmlComponent类型的元素,这些元素显式或隐式地创建为属性,或者是内联组件。这些组件是嵌套的。每个内部组件都在一个特定的外部组件内。大多数情况下,在外部组件中定义的ID在其所有嵌套的内部组件中都是可访问的。然而,您可以在任何不同的上下文中从组件创建元素,并使用不同的ID。这样做会打破外部ID可用的假设。因此,引擎和QML工具通常无法提前知道这些ID在运行时将解析为什么类型(如果有的话)。

使用ComponentBehavior pragma,您可以限制文件中定义的所有内部组件仅在其原始上下文中创建对象。如果组件绑定到其上下文,您可以安全地在组件内使用同一文件中外部组件的ID。QML工具将假定外部ID及其特定类型可用。

为了将组件绑定到它们的上下文,请指定Bound参数:

这意味着,在名称冲突的情况下,定义在绑定组件外部的ID会覆盖从组件创建的对象的本地属性。否则,使用ID实际上并不安全,因为模块的后续版本可能会向组件添加更多属性。如果组件未绑定,本地属性会覆盖定义在组件外部的ID,但不会覆盖定义在组件内部的ID。

下面的示例打印的是ID为color的ListView对象的r属性,而不是矩形颜色的r属性。

ComponentBehavior 的默认值是 Unbound。你也可以明确指定它。在未来的 Qt 版本中,默认值将更改为 Bound

绑定到其上下文的委托组件在实例化时不会接收自己的私有上下文。这意味着在这种情况下,模型数据只能通过必需的属性传递。通过上下文属性传递模型数据将不起作用。这涉及到例如Instantiator、Repeater、ListView、TableView、GridView、TreeView以及任何内部使用DelegateModel的委托。

例如,以下内容将不会起作用:

ListView的delegate属性是一个组件。因此,这里在Rectangle周围隐式创建了一个Component。该组件绑定到其上下文。它不会接收由ListView提供的上下文属性model。要使其工作,您必须这样编写:

你可以在QML文件中嵌套组件。该编译指示适用于文件中的所有组件,无论嵌套有多深。

函数签名行为

使用此编译指示,您可以更改函数上类型注解的处理方式。自 Qt 6.7 起,调用函数时会强制执行类型注解。在此之前,只有 QML 脚本编译器 强制执行类型注解。解释器和 JIT 编译器会忽略它们。与早期版本相比,始终强制执行类型注解是一个行为变化,因为之前您可以使用不匹配的参数调用函数。

Ignored指定为值会使QML引擎和QML脚本编译器忽略任何类型注释,从而恢复解释器和JIT在6.7版本之前的行为。因此,较少的代码会被提前编译为C++,更多的代码需要被解释或JIT编译。

指定Enforced作为值明确表示默认情况:类型注释始终被强制执行。

本地方法行为

由于历史原因,使用与获取时不同的this对象调用C++方法是不可行的。原始对象被用作this对象。您可以通过设置pragma NativeMethodBehavior: AcceptThisObject来允许使用给定的this对象。指定RejectThisObject则保持历史行为。

可以在C++方法和‘this’对象下找到此示例。

值类型行为

使用此编译指示,您可以更改值类型和序列的处理方式。

通常,在JavaScript代码中,小写名称不能作为类型名称。这是一个问题,因为值类型名称是小写的。你可以指定Addressable作为这个编译指示的值来改变这一点。如果指定了Addressable,JavaScript值可以显式强制转换为特定的、命名的值类型。这是通过使用as操作符完成的,就像你处理对象类型一样。此外,你还可以使用instanceof操作符来检查值类型:

由于上述示例中的rect现在是一个类型名称,它将遮蔽任何名为rect的属性。

显式转换为所需类型有助于工具。它可以让Qt Quick Compiler生成高效的代码,否则它可能无法做到。你可以使用qmllint来找到这些情况。

还有一个Inaddressable值可以用来显式指定默认行为。

另一个属性是ValueTypeBehavior编译指示中的Assertable,这是在Qt 6.8中引入的。由于Qt 6.6和6.7中的一个错误,上面的a as rect不仅检查a是否是rect,而且如果a是兼容类型,还会构造一个rect。这显然不是类型断言应该做的。指定Assertable可以防止这种行为,并将值类型的类型断言限制为仅检查类型。如果你打算将值类型与as一起使用,你应该始终指定它。无论如何,如果值类型的类型断言失败,结果是undefined

instanceof 没有这个问题,因为它只检查继承关系,而不是所有可能的类型强制转换。

注意

使用asintdouble类型是不建议的,因为根据JavaScript的规则,任何计算的结果都是浮点数,即使它恰好与其整数等价物具有相同的值。相反,根据QML的类型映射规则,你在JavaScript中声明的任何整数常量都不是双精度类型。此外,intdouble是保留字。你只能通过类型命名空间来引用这些类型。

值类型和序列通常被视为引用。这意味着,如果您从属性中检索值类型实例到局部值中,然后更改局部值,原始属性也会更改。此外,如果您显式写入原始属性,局部值也会更新。这种行为在许多地方相当不直观,您不应依赖它。CopyReference 值用于 ValueTypeBehavior 编译指示是改变此行为的实验性选项。您不应使用它们。指定 Copy 会导致所有值类型被视为实际副本。指定 Reference 显式声明默认行为。

与其使用Copy,你应该在任何时候它们可能受到副作用影响时显式地重新加载对值类型和序列的引用。副作用可能在你调用函数或命令式设置属性时发生。qmllint 提供了这方面的指导。例如,在以下代码中,变量f在写入width后受到副作用的影响。这是因为在派生类型或Binding元素中可能存在一个绑定,当width改变时更新font

为了解决这个问题,你可以避免在width的写操作期间持有f

这反过来可以简化为:

你可能会认为重新获取font属性是昂贵的,但实际上QML引擎每次读取时都会自动刷新值类型引用。因此,这并不比第一个版本更昂贵,而是表达相同操作的更清晰方式。

翻译器

使用此编译指示,您可以设置文件中翻译的上下文。

有关QML国际化的更多信息,请参见使用qsTr。

导入

文档必须导入必要的模块或类型命名空间,以使引擎能够加载文档中引用的QML对象类型。默认情况下,文档可以访问在同一目录中通过.qml文件定义的任何QML对象类型;如果文档需要引用任何其他对象类型,则必须导入这些类型已注册的类型命名空间。

QML 没有像C或C++那样的预处理器,在呈现给QML 引擎之前修改文档。import语句不会复制并前置文档中的代码,而是指示QML引擎如何解析文档中找到的类型引用。QML文档中存在的任何类型引用——例如RectangleListView——包括在JavaScript块属性绑定中进行的引用,都完全基于import语句进行解析。至少必须存在一个import语句,例如import QtQuick 2.0

请参阅QML语法 - 导入语句文档以获取有关QML导入的深入信息。

根对象声明

QML文档描述了一个可以实例化的对象层次结构。每个对象定义都有一定的结构;它有一个类型,可以有一个id和一个对象名称,它可以有属性,可以有方法,可以有信号,也可以有信号处理程序。

一个QML文件必须只包含一个根对象定义。以下是无效的,并且会生成错误:

// MyQmlFile.qml
import QtQuick 2.0

Rectangle { width: 200; height: 200; color: "red" }
Rectangle { width: 200; height: 200; color: "blue" }    // invalid!

这是因为一个.qml文件自动定义了一个QML类型,它封装了一个单一的QML对象定义。这在文档作为QML对象类型定义中进一步讨论。