QML 支持从 JavaScript 中动态创建对象。这对于延迟实例化对象很有用,从而缩短应用程序启动时间。它还允许动态创建可视对象并将其添加到场景中以响应用户输入或其他事件。

有关此页面上讨论的概念的演示,请参阅 Dynamic Scene example

5.2.1 动态创建对象

有两种方法可以从 JavaScript 动态创建对象。您可以调用 Qt.createComponent() 动态创建 Component 对象,也可以使用 Qt.createQmlObject() 从 QML 字符串创建对象。如果您在 QML 文档中定义了一个现有组件,并且您希望动态创建该组件的实例,则创建组件会更好。否则,当对象 QML 本身在运行时生成时,从 QML 字符串创建对象很有用。

5.2.1.1 动态创建组件

要动态加载组件,请调用 Qt 对象中的 Qt.createComponent() 函数创建一个 Component 对象,然后调用它的 createObject() 方法来创建一个组件的实例。这个函数可以接受一两个参数:

  • 第一个是新对象的父对象。父对象可以是图形对象(即 Item 类型)或非图形对象(即 QtObject 或 C++ QObject 类型)。只有带有图形父对象的图形对象才会被渲染到 Qt Quick 可视画布上。如果您希望稍后设置父级,您可以安全地将 null 传递给此函数。
  • 第二个是可选的,是一个 property-value 对的映射,它定义了对象的初始任何属性值。此参数指定的属性值在对象创建完成之前应用于对象,避免在必须初始化特定属性以启用其他属性绑定时可能发生的绑定错误。此外,与在创建对象后定义属性值和绑定相比,性能优势很小。

这是一个例子。首先是 Sprite.qml,它定义了一个简单的 QML 组件:

  1. import QtQuick 2.0
  2. Rectangle { width: 80; height: 50; color: "red" }

主应用程序文件 main.qml 导入了 componentCreation.js JavaScript 文件,该文件用于创建 Sprite 对象:

  1. import QtQuick 2.0
  2. import "componentCreation.js" as MyScript
  3. Rectangle {
  4. id: appWindow
  5. width: 300; height: 300
  6. Component.onCompleted: MyScript.createSpriteObjects();
  7. }

以下是 componentCreation.js 文件。请注意,它会在调用 createObject() 之前检查组件 status 是否为 Component.Ready,以防 QML 文件通过网络加载而未立即就绪。

  1. var component;
  2. var sprite;
  3. function createSpriteObjects() {
  4. component = Qt.createComponent("Sprite.qml");
  5. if (component.status == Component.Ready)
  6. finishCreation();
  7. else
  8. component.statusChanged.connect(finishCreation);
  9. }
  10. function finishCreation() {
  11. if (component.status == Component.Ready) {
  12. sprite = component.createObject(appWindow, {x: 100, y: 100});
  13. if (sprite == null) {
  14. // Error Handling
  15. console.log("Error creating object");
  16. }
  17. } else if (component.status == Component.Error) {
  18. // Error Handling
  19. console.log("Error loading component:", component.errorString());
  20. }
  21. }

如果加载本地文件,则可以省略 finishCreation() 函数并立即调用 createObject()

  1. function createSpriteObjects() {
  2. component = Qt.createComponent("Sprite.qml");
  3. sprite = component.createObject(appWindow, {x: 100, y: 100});
  4. if (sprite == null) {
  5. // Error Handling
  6. console.log("Error creating object");
  7. }
  8. }

请注意,在这两种情况下,调用 createObject() 时都会将 appWindow 作为父参数传递,因为动态创建的对象是一个可视化 (Qt Quick) 对象。创建的对象将成为 main.qml 中 appWindow 对象的子对象,并出现在场景中。

当使用具有相对路径的文件时,路径应该是相对于执行 Qt.createComponent() 的文件。

要将信号连接到(或从)动态创建的对象接收信号,请使用信号 connect() 方法。有关更多信息,请参阅 Connecting Signals to Methods and Signals。也可以通过 incubateObject() 函数在不阻塞的情况下实例化组件。

5.2.1.2 从 QML 字符串创建对象

如果 QML 直到运行时才定义,您可以使用 Qt.createQmlObject() 函数从 QML 字符串创建 QML 对象,如下例所示:

  1. var newObject = Qt.createQmlObject('import QtQuick 2.0; Rectangle {color: "red"; width: 20; height: 20}',
  2. parentItem,
  3. "dynamicSnippet1");

第一个参数是要创建的 QML 字符串。就像在新文件中一样,您需要导入任何您想使用的类型。第二个参数是新对象的父对象,适用于组件的父参数语义同样适用于 createQmlObject()。第三个参数是与新对象关联的文件路径;这用于错误报告。

如果 QML 字符串使用相对路径导入文件,则路径应相对于定义父对象(方法的第二个参数)的文件。

重要提示:构建静态 QML 应用程序时,会扫描 QML 文件以检测导入依赖项。这样,所有必要的插件和资源都会在编译时解决。但是,只考虑显式导入语句(位于 QML 文件顶部的语句),而不考虑包含在字符串文字中的导入语句。因此,为了支持静态构建,您需要确保使用 Qt.createQmlObject() 的 QML 文件在文件顶部明确包含所有必要的导入,以及字符串文字内部。

5.2.2 维护动态创建的对象

在管理动态创建的对象时,您必须确保创建上下文比创建的对象寿命更长。否则,如果首先销毁创建上下文,则动态对象中的绑定和信号处理程序将不再起作用。

实际的创建上下文取决于对象的创建方式:

另外,请注意,虽然动态创建的对象可以与其他对象一样使用,但它们在 QML 中没有 id。

5.2.3 动态删除对象

在许多用户界面中,将可视对象的不透明度设置为 0 或将可视对象移出屏幕而不是将其删除就足够了。但是,如果您有大量动态创建的对象,则删除未使用的对象可能会获得有价值的性能优势。

请注意,永远不要手动删除由便利 QML 对象工厂(例如 LoaderRepeater)动态创建的对象。 此外,您应该避免删除不是您自己动态创建的对象。

可以使用 destroy() 方法删除项目。 此方法有一个可选参数(默认为 0),用于指定销毁对象之前的近似延迟(以毫秒为单位)。

这是一个例子。 application.qml 创建 SelfDestroyingRect.qml 组件的五个实例。 每个实例运行一个 NumberAnimation,当动画完成时,调用它的根对象上的 destroy() 来销毁自己:

  1. //application.qml
  2. import QtQuick 2.0
  3. Item {
  4. id: container
  5. width: 500; height: 100
  6. Component.onCompleted: {
  7. var component = Qt.createComponent("SelfDestroyingRect.qml");
  8. for (var i=0; i<5; i++) {
  9. var object = component.createObject(container);
  10. object.x = (object.width + 10) * i;
  11. }
  12. }
  13. }
  14. //SelfDestroyingRect.qml
  15. import QtQuick 2.0
  16. Rectangle {
  17. id: rect
  18. width: 80; height: 80
  19. color: "red"
  20. NumberAnimation on opacity {
  21. to: 0
  22. duration: 1000
  23. onRunningChanged: {
  24. if (!running) {
  25. console.log("Destroying...")
  26. rect.destroy();
  27. }
  28. }
  29. }
  30. }

或者,application.qml 可以通过调用 object.destroy() 销毁创建的对象。

请注意,对该对象内的对象调用 destroy() 是安全的。 对象不会在调用 destroy() 的瞬间被销毁,而是在该脚本块结束和下一帧之间的某个时间被清除(除非您指定了非零延迟)。

另请注意,如果 SelfDestroyingRect 实例是像这样静态创建的:

  1. Item {
  2. SelfDestroyingRect {
  3. // ...
  4. }
  5. }

这将导致错误,因为对象只有在动态创建时才能动态销毁。

使用 Qt.createQmlObject() 创建的对象可以类似地使用 destroy() 销毁:

  1. var newObject = Qt.createQmlObject('import QtQuick 2.0; Rectangle {color: "red"; width: 20; height: 20}',
  2. parentItem,
  3. "dynamicSnippet1");
  4. newObject.destroy(1000);