从JavaScript动态创建QML对象

QML支持在JavaScript中动态创建对象。这有助于延迟对象的实例化,从而提高应用程序的启动时间。它还允许根据用户输入或其他事件动态创建和添加视觉对象到场景。

动态创建对象

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

动态创建组件

要动态加载在QLM文件中定义的组件,请在Qt对象中调用Qt.createComponent() 函数。此函数仅以QLM文件的URL作为其唯一参数,并根据此URL创建一个Component对象。

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

  • 第一个是新对象的父对象。父对象可以是图形对象(即Item类型)或非图形对象(即QtObject或C++ QObject类型)。只有具有图形父对象的图形对象将被渲染到Qt Quick视觉画布上。如果您稍后想要设置父对象,可以安全地将null传递给此函数。
  • 第二个是可选的,是一个定义对象初始属性值的属性-值对的映射。指定此参数的属性值在最终创建了对象的创建之前应用于对象,从而避免了可能因必须初始化某些属性以启用其他属性绑定而发生的绑定错误。此外,与在对象创建后定义属性值和绑定相比,这还有一定的性能优势。

以下是一个示例。首先是定义一个简单的QML组件的Sprite.qml

import QtQuick

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

我们的主应用程序文件,main.qml,导入了一个componentCreation.js JavaScript文件,该文件将创建Sprite对象

import QtQuick
import "componentCreation.js" as MyScript

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

    Component.onCompleted: MyScript.createSpriteObjects();
}

这是 componentCreation.js。注意它检查组件的状态是否是 Component.Ready,然后才调用 createObject() 方法,以防所加载的 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.qmlappWindow 对象的孩子,并在场景中显示。

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

为了将信号连接到(或从)动态创建的对象,使用信号 connect() 方法。有关更多信息,请参阅连接信号到方法和信号

你也可以通过 incubateObject() 函数来无阻塞地实例化组件。

从 QML 字符串创建对象

注意:从 QML 字符串创建对象非常慢,因为引擎必须每次都编译传递的 QML 字符串。此外,在程序性构建 QML 代码时很容易产生无效的 QML。将 QML 组件作为单独的文件保存,并为它们添加属性和方法以自定义其行为,比通过字符串操作创建新组件要好得多。

如果 QML 是在运行时定义的,你可以使用 Qt.createQmlObject() 函数从 QML 字符串创建 QML 对象,如下例所示

const newObject = Qt.createQmlObject(`
    import QtQuick

    Rectangle {
        color: "red"
        width: 20
        height: 20
    }
    `,
    parentItem,
    "myDynamicSnippet"
);

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

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

重要:在构建静态 QML 应用程序时,QML 文件被扫描以检测导入依赖项。这样,所有必要的插件和资源都在编译时得到解决。但是,只有显式的导入语句被考虑(那些出现在 QML 文件顶部的),而不是在字符串字面值中封装的导入语句。为了支持静态构建,因此你需要确保使用 Qt.createQmlObject() 的 QML 文件不仅在其字符串字面值内部,而且在文件顶部明确包含所有必需的导入。

维护动态创建的对象

在管理动态创建的对象时,你必须确保创建上下文持续存在于创建的对象之后。否则,如果创建上下文先被销毁,动态对象中的绑定和信号处理器将不再工作。

实际的创建上下文取决于对象是如何被创建的

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

动态删除对象

在许多用户界面中,将视觉对象的透明度设置为 0 或将视觉对象移出屏幕而不是删除它就足够了。如果您有大量动态创建的对象,那么删除未使用的对象可能会带来可观的性能提升。

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

可以使用 destroy() 方法删除项。此方法有一个可选的参数(默认为 0),指定在对象被销毁之前约需延迟的毫秒数。

以下是一个示例。该 application.qml 创建了五个 SelfDestroyingRect.qml 组件的实例。每个实例运行一个 NumberAnimation,当动画完成后,将对其根对象调用的 destroy() 以销毁自身。

application.qml
import QtQuick

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

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() 时不会立即销毁对象,而是在脚本块结束后、下一帧之间(除非您指定了非零延迟)进行清理。

请注意,如果像这样静态地创建了一个 SelfDestroyingRect 实例

Item {
    SelfDestroyingRect {
        // ...
    }
}

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

与使用 Qt.createQmlObject() 创建的对象类似,可以使用 destroy() 进行销毁。

const newObject = Qt.createQmlObject(`
    import QtQuick

    Rectangle {
        color: "red"
        width: 20
        height: 20
    }
    `,
    parentItem,
    "myDynamicSnippet"
);
newObject.destroy(1000);

© 2024 The Qt Company Ltd. 本文档中包含的文档贡献是各自所有者的版权。本提供在此的文档是根据自由软件基金会发布的 GNU 自由文档许可证版本 1.3 条款许可的。Qt 和相关标志是芬兰以及/或其他国家/地区 The Qt Company Ltd. 的商标。所有其他商标均为其各自所有者的财产。