QML 文档的结构

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

  • 可选的实用程序列表
  • 导入语句
  • 单个根对象声明

按照惯例,导入与对象层次结构定义之间只隔一行空行。

QML 文档总是以 UTF-8 格式编码。

语法

语法是 QML 引擎本身的指令,可用来指定当前文件中对象的某些特性,或修改引擎解释代码的方式。以下是对这些语法的详细说明。

  • Singleton
  • ListPropertyAssignBehavior
  • ComponentBehavior
  • FunctionSignatureBehavior
  • NativeMethodBehavior
  • ValueTypeBehavior
  • Translator

单件

pragma Singleton 将 QML 文档中定义的组件声明为单例。每个 QML 引擎只能创建一次单例。为了使用 QML 声明的单例,你还必须在其模块中注册它。请参阅qt_target_qml_sources,了解如何通过 CMake 实现这一点。

ListPropertyAssignBehavior 属性分配行为

通过这个 pragma,你可以定义如何在 QML 文档中定义的组件中处理对 list 属性的赋值。默认情况下,对列表属性赋值会追加到列表中。您可以使用Append 明确要求这种行为。或者,你也可以使用Replace 来要求列表属性的内容总是被替换,或者使用ReplaceIfNotDefault 来要求在属性不是默认属性时被替换。例如

pragma ListPropertyAssignBehavior: 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(组件行为)语法,你可以限制文件中定义的所有内部组件只能在其原始上下文中创建对象。如果一个组件被绑定到它的上下文中,你就可以在组件中安全地使用同一文件中外部组件的 ID。QML 工具将假定外部 ID 及其特定类型是可用的。

为了将组件与上下文绑定,请指定Bound 参数:

pragma ComponentBehavior: Bound

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

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

pragma ComponentBehavior: Bound
import QtQuick

ListView {
 id: color
 property int r: 12
 model: 1

 delegate: Rectangle {
  Component.onCompleted: console.log(color.r)
 }
}

ComponentBehavior 的默认值是Unbound 。您也可以明确指定该值。在未来的 Qt XML 版本中,默认值将变为Bound

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

例如,以下情况将不起作用

pragma ComponentBehavior: Bound
import QtQuick

ListView {
 delegate: Rectangle {
     color: model.myColor
 }
}

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

pragma ComponentBehavior: Bound
import QtQuick

ListView {
 delegate: Rectangle {
     required property color myColor
     color: myColor
 }
}

你可以在 QML 文件中嵌套组件。无论嵌套多深,该语法都适用于文件中的所有组件。

函数签名行为

通过该 pragma,您可以改变函数上类型注解的处理方式。自 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 作为此 pragma 的值来改变这种情况。如果指定了Addressable ,就可以显式地将 JavaScript 值强制为特定的、已命名的值类型。这可以使用as 操作符来完成,就像处理对象类型一样。此外,还可以使用instanceof 操作符检查值类型:

pragma ValueTypeBehavior: Addressable
import QtQml

QtObject {
 property var a
 property real b: (a as rect).x
 property bool c: a instanceof rect

 property var rect // inaccessible. "rect" is a type name.
}

由于上例中的rect 现在是一个类型名称,它将对任何名为rect 的属性产生阴影。

显式转换为所需类型有助于工具的使用。它可以让 Qt Quick Compiler生成高效代码。您可以使用qmllint查找此类情况。

您还可以使用Inaddressable 值来明确指定默认行为。

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

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

注意: asintdouble 类型一起使用并不可取,因为根据 JavaScript 的规则,任何计算的结果都是浮点数,即使它的值恰好与整数等价。相反,根据 QML 的类型映射规则,您在 JavaScript 中声明的任何整数常量都不是 double。此外,intdouble 是保留字。您只能通过类型命名空间来处理这些类型。

值类型和序列通常被视为引用。这意味着,如果你从一个属性检索一个值类型实例到一个本地值,然后改变本地值,原始属性也会改变。此外,如果显式写入原始属性,本地值也会更新。这种行为在很多地方都很不直观,不应依赖它。ValueTypeBehavior pragma 的CopyReference 值是改变这种行为的试验性选项。不应使用它们。指定Copy 会导致所有值类型都被视为实际副本。指定Reference 则明确说明了默认行为。

在值类型和序列受到副作用影响时,不应使用Copy ,而应显式地重新加载对它们的引用。只要调用函数或必须设置属性,就会产生副作用。qmllint提供了这方面的指导。例如,在下面的代码中,变量f 在写入width 后会受到副作用的影响。这是因为派生类型或Binding 元素中可能存在绑定,当width 发生变化时,绑定会更新font

import QtQuick
Text {
 function a() : real {
     var f = font;
     width = f.pixelSize;
     return f.pointSize;
 }
}

为了解决这个问题,可以避免在对width 进行写操作时保留f

import QtQuick
Text {
 function a() : real {
     var f = font;
     width = f.pixelSize;
     f = font;
     return f.pointSize;
 }
}

这又可以简化为

import QtQuick
Text {
 function a() : real {
     width = font.pixelSize;
     return font.pointSize;
 }
}

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

翻译器

通过该 pragma,您可以设置文件中翻译的上下文。

pragma Translator: myTranslationContext
pragma Translator: "myTranslationContext"

有关 QML 国际化的更多信息,请参阅Use qsTr

导入

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

与 C 或 C++ 不同的是,QML没有预处理器,在提交给QML engine 之前修改文档。import 语句不会复制和预置文档中的代码,而是指示 QML 引擎如何解决文档中的类型引用。QML 文档中的任何类型引用(如RectangleListView ),包括JavaScript 代码块属性绑定中的引用,都只能根据导入语句来解析。必须至少有一个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 对象类型定义的文档中进一步讨论。

另请参阅 Type annotations and assertions(类型注解和断言和 Type annotations and assertions(类型注解和断言)。

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.