Dynamic QML Object Creation from JavaScript

QML支持JavaScript的动态创建对象. 对象仅在必要时创建实例, 可以缩短应用程序的启动时间. 它还允许根据用户输入或其他事件动态创建视觉对象, 并将其添加到场景.

示例参见 Dynamic Scene example.

创建动态对象

QML提供两种方式创建JavaScript动态对象. 你可以调用Qt.createComponent()动态创建Component对象, 也可以调用Qt.createQmlObject(), 使用QML字符串创建对象. 如果已在QML文档中定义组件, 并且希望动态创建这个组件的实例, 则创建组件更好. 否则, 在运行时, 从QML的字符串创建对象是有用的.

动态创建组件

在QML文件中, 调用Qt对象的Qt.createComponent()函数创建动态组件. 这个函数将QML文件的URL作为唯一参数, 并根据URL创建一个Component对象.

一旦有了Component, 就可以调用createObject()方法创建组件的实例. 这个函数可以接受一个或两个参数:

  • 第一参数是新对象的父对象. 父对象可以是图形对象(即. Item类型)或非图形对象(即QtObject或C++ QObject类型). 只有具有图形父对象的图形对象才会渲染到Qt Quick视觉画布. 如果你希望稍后设置父级, 则可以将null传递给函数.
  • 第二个参数是可选的, 属性对映射, 定义对象初始属性值. 属性值将在对象创建完成之前应用于对象, 从而避免在必须初始化特定属性以启用其他属性绑定时可能出现的绑定错误. 此外, 与在创建对象后定义属性值和绑定相比, 还有一些小的性能优势.

如下所代码位于Sprite.qml, 定义一个简单的QML组件:


  import QtQuick 2.0

  Rectangle { width: 80; height: 50; color: "red" }

主程序文件main.qml, 导入JavaScript文件componentCreation.js, 并创建Sprite对象:


  import QtQuick 2.0
  import "componentCreation.js" as MyScript

  Rectangle {
      id: appWindow
      width: 300; height: 300

      Component.onCompleted: MyScript.createSpriteObjects();
  }

下面是componentCreation.js. 调用createObject()之前, 先检测组件的status是否Component.Ready, 防止QML文件是从网络加载, 不能立即读取.


  var component;
  var sprite;

  function createSpriteObjects() {
      component = Qt.createComponent("Sprite.qml");
      if (component.status == Component.Ready)
          finishCreation();
      else
          component.statusChanged.connect(finishCreation);
  }

  function finishCreation() {
      if (component.status == Component.Ready) {
          sprite = component.createObject(appWindow, {"x": 100, "y": 100});
          if (sprite == null) {
              // Error Handling
              console.log("Error creating object");
          }
      } else if (component.status == Component.Error) {
          // Error Handling
          console.log("Error loading component:", component.errorString());
      }
  }

如果你确定QML文件是从本地加载, 你可以省略finishCreation()函数, 立即调用createObject():


  function createSpriteObjects() {
      component = Qt.createComponent("Sprite.qml");
      sprite = component.createObject(appWindow, {"x": 100, "y": 100});

      if (sprite == null) {
          // Error Handling
          console.log("Error creating object");
      }
  }

注意: 在这两个示例中, createObject()appWindow作为参数传递, 这是因为动态创建的对象是一个可视(Qt Quick)对象. 创建的对象将成为 main.qml文件中appWindow对象的子对象, 并显示在场景中.

使用相对路径的文件时, 路径应该相对Qt.createComponent()的执行路径.

要将信号连接到动态创建的对象(或从中接收信号), 使用信号的connect()方法. 详见 Connecting Signals to Methods and Signals.

调用 incubateObject() 函数异步实例化组件.

使用QML字符串创建对象

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


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

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

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

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

维护动态创建的对象

管理动态创建的对象时, 你必须确保创建上下文环境比创建对象的存在时间长. 否则, 上下文环境销毁, 动态对象的属性绑定和信号处理程序将不再工作.

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

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

动态删除对象

在许多用户界面中, 将界面的透明度设为0或移出屏幕可以满足要求. 但是, 如果有许多动态创建的对象, 删除未使用的对象可以提高性能.

注意: 你永远不应该手动删除由QML对象工厂(如LoaderRepeater)动态创建的对象. 此外, 你应该避免删除不是你动态创建的对象.

调用destroy()删除item. 这个方法有一个可选参数(默认为0), 指定销毁对象的时延(单位为ms).

在下面的示例中, application.qml创建5个实例组件SelfDestroyingRect.qml. 每个实例运行一个NumberAnimation, 动画完成后, 在根对象上调用destroy()销毁自身:

application.qml

  import QtQuick 2.0

  Item {
      id: container
      width: 500; height: 100

      Component.onCompleted: {
          var component = Qt.createComponent("SelfDestroyingRect.qml");
          for (var i=0; i<5; i++) {
              var object = component.createObject(container);
              object.x = (object.width + 10) * i;
          }
      }
  }

SelfDestroyingRect.qml

  import QtQuick 2.0

  Rectangle {
      id: rect
      width: 80; height: 80
      color: "red"

      NumberAnimation on opacity {
          to: 0
          duration: 1000

          onRunningChanged: {
              if (!running) {
                  console.log("Destroying...")
                  rect.destroy();
              }
          }
      }
  }

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

注意: 在对象里调用destroy()删除对象是安全的. 调用destroy()不会立即删除对象, 而是在代码块结尾和下一帧之间的某一时刻清除对象(除非设置非0时延).

注意: 如果SelfDestroyingRect实例以如下静态方式创建:


  Item {
      SelfDestroyingRect {
          // ...
      }
  }

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

调用Qt.createQmlObject()创建的对象, 同样可以调用destroy()销毁:


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