JavaScript动态创建QML对象#

使用JavaScript实例化和管理QML对象

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

动态创建对象#

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

动态创建组件#

要动态加载在QML文件中定义的组件,请在Qt对象 中调用 createComponent() 函数。此函数的唯一参数是QML文件的URL,并从该URL创建一个 组件 对象。

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

  • 第一个是新生成对象的父亲对象。父亲对象可以是图形对象(例如,Item类型的对象)或非图形对象(例如,QtObject 或 C++ QObject 类型的对象)。只有具有图形父对象的图形对象才会渲染到Qt Quick视觉画布上。如果您稍后想要设置父对象,您可以将 null 传递给此函数以安全地实现。

  • 第二个是可选的,它是一组属性值对的映射,用于定义对象的初始属性值。通过此参数指定的属性值在对象的创建最终确定之前应用于对象,避免了可能出现的绑定错误,这些错误可能是特定的属性必须初始化才能启用其他属性绑定所引发的。此外,与在对象创建之后定义属性值和绑定相比,这也有小的性能优势。

以下是一个示例。首先是有 Sprite.qml,它定义了一个简单的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。请注意,它在调用 createObject() 之前检查组件 状态 是否为 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");
}
}

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

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

要连接信号到(或接收来自)动态创建的对象,请使用 connect() 信号方法。有关更多信息,请参阅连接信号和方法和信号

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

从 QML 字符串创建对象#

警告

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

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

const newObject = Qt.createQmlObject(`
    import QtQuick

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

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

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

**重要:** 当构建静态 QML 应用程序时,QML 文件被扫描以检测导入依赖关系。这样,所有必要的插件和资源在编译时都会解析。但是,只有显式的导入语句会被考虑(在 QML 文件的顶部发现的),而不是字符串字面量中的导入语句。因此,为了支持静态构建,您需要确保使用 createQmlObject() 的 QML 文件在字符串字面量内在内侧,明确包含所有必要的导入,并且在文件顶部。

维护动态创建的对象#

在管理动态创建的对象时,您必须确保创建上下文持续到创建的对象。否则,如果首先销毁创建上下文,动态对象的绑定和信号处理程序将不再工作。

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

  • 如果使用createComponent(),创建上下文是调用此方法的QQmlContext

  • 如果调用了createQmlObject(),创建上下文是传递给此方法父对象的上下文

  • 如果定义了Component{}对象并在该对象上调用createObject()incubateObject(),创建上下文是定义Component的上下文

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

动态删除对象#

在许多用户界面中,将可视对象的透明度设置为0或将可视对象移出屏幕,而不是删除它,这对于可视对象来说就已经足够了。然而,如果你有很多动态创建的对象,如果你删除了未使用对象,那么你可能会获得可观的性能提升。

请注意,你绝不应该手动删除由便捷的QML对象工厂(如Loader和Repeater)动态创建的对象。此外,你也应避免删除你自己没有动态创建的对象。

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

以下是一个示例。应用程序创建五个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实例:

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

使用createQmlObject()创建的对象也可以使用destroy()方法销毁。

const newObject = Qt.createQmlObject(`
    import QtQuick

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