JavaScriptからの動的なQMLオブジェクト生成

QML では、JavaScript からオブジェクトを動的に生成することができます。これは、オブジェクトのインスタンス化を必要なときまで遅らせ、 アプリケーションの起動時間を短縮するのに便利です。また、ビジュアルオブジェクトを動的に生成し、ユーザー入力やその他のイベントに応じてシーンに追加することもできます。

オブジェクトの動的作成

JavaScriptから動的にオブジェクトを作成するには、2つの方法があります。Qt.createComponent() を呼び出してComponent オブジェクトを動的に作成する方法と、Qt.createQmlObject() を使って QML の文字列からオブジェクトを作成する方法です。QML文書に既存のコンポーネントが定義されていて、そのコンポーネントのインスタンスを動的に生成したい場合は、コンポーネントを作成する方がよいでしょう。そうでない場合は、QMLの文字列からオブジェクトを生成するのが便利です。

コンポーネントの動的生成

QMLファイルで定義されたコンポーネントを動的にロードするには、Qt objectQt.createComponent() 関数を呼び出します。この関数はQMLファイルのURLを唯一の引数として受け取り、このURLからComponent オブジェクトを生成します。

Component を作成したら、createObject() メソッドを呼び出して、 コンポーネントのインスタンスを作成することができます。この関数は1つまたは2つの引数を取ります:

  • 最初の引数は新しいオブジェクトの親です。親はグラフィカル・オブジェクト(すなわちItem 型)または非グラフィカル・オブジェクト(すなわちQtObject または C++QObject 型)です。グラフィカルな親オブジェクトを持つグラフィカルなオブジェクトだけが、Qt Quick のビジュアル・キャンバスにレンダリングされます。後で親を設定したい場合は、null をこの関数に渡すと安全です。
  • 2番目はオプションで、オブジェクトの初期プロパティ値を定義するプロパティ値のペアのマップです。この引数で指定されたプロパティ値は、オブジェクトの作成が確定する前にオブジェクトに適用され、他のプロパティのバインディングを有効にするために特定のプロパティを初期化しなければならない場合に発生する可能性のあるバインディングエラーを回避します。さらに、オブジェクトが作成された後にプロパティ値とバインディングを定義する場合と比較すると、パフォーマンス上の利点はわずかです。

以下はその例です。まず、シンプルなQMLコンポーネントを定義したSprite.qml があります:

import QtQuick

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

メイン・アプリケーション・ファイルであるmain.qml は、Sprite オブジェクトを生成するcomponentCreation.js JavaScript ファイルをインポートしています:

import QtQuick
import "componentCreation.js" as MyScript

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

    Component.onCompleted: MyScript.createSpriteObjects();
}

以下はcomponentCreation.js です。createObject() を呼び出す前に、コンポーネントstatusComponent.Ready であるかどうかをチェックしています。

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 を親の引数として渡して呼び出されます。作成されたオブジェクトは、main.qmlappWindow オブジェクトの子オブジェクトとなり、シーンに表示されます。

相対パスでファイルを使用する場合、パスはQt.createComponent() が実行されるファイルからの相対パスでなければなりません。

動的に作成されるオブジェクトにシグナルを接続する(またはオブジェクトからシグナルを受信する)には、シグナルconnect() メソッドを使用します。詳しくは、シグナルをメソッドやシグナルに接続するを参照してください。

また、incubateObject() 関数を使用して、ブロックせずにコンポーネントをインスタンス化することも可能です。

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の文字列です。最初の引数は、作成するQMLの文字列です。新規ファイルと同じように、使いたい型があればインポートする必要があります。第2引数は新しいオブジェクトの親オブジェクトです。コンポーネントに適用される親引数のセマンティクスはcreateQmlObject() にも同様に適用されます。第3引数には新しいオブジェクトに関連付けるファイルパスを指定します。

QMLの文字列が相対パスを使ってファイルをインポートする場合、パスは親オブジェクト(メソッドの第2引数)が定義されているファイルからの相対パスでなければなりません。

重要: 静的QMLアプリケーションをビルドする際、QMLファイルはインポート依存性を検出するためにスキャンされます。これにより、必要なプラグインやリソースはコンパイル時にすべて解決されます。ただし、考慮されるのは明示的なimport文(QMLファイルの先頭にあるもの)のみで、文字列リテラルで囲まれたimport文は考慮されません。そのため、静的ビルドをサポートするためには、Qt.createQmlObject()を使用するQMLファイルでは、文字列リテラルの中だけでなく、ファイルの先頭にも必要なインポートを明示的に記述する必要があります。

動的に作成されるオブジェクトの管理

動的に作成されたオブジェクトを管理する場合、作成されたオブジェクトよりも作成コンテキストの方が長生きするようにしなければなりません。そうしないと、作成コンテキストが先に破棄された場合、動的オブジェクトのバインディングやシグナル・ハンドラは動作しなくなります。

実際の作成コンテキストは、オブジェクトの作成方法によって異なります:

  • Qt.createComponent() が使用された場合、作成コンテキストはこのメソッドが呼び出されたQQmlContext となります。
  • Qt.createQmlObject() が呼び出された場合、作成コンテキストはこのメソッドに渡された親オブジェクトのコンテキストになります。
  • Component{} オブジェクトが定義され、そのオブジェクトに対してcreateObject() またはincubateObject() が呼び出された場合、作成コンテキストはComponent が定義されたコンテキストとなります。

また、動的に生成されたオブジェクトは他のオブジェクトと同じように使用することができますが、QMLではidを持たないことに注意してください。

オブジェクトの動的削除

多くのユーザーインターフェースでは、ビジュアルオブジェクトを削除するのではなく、ビジュアルオブジェクトの不透明度を0に設定したり、ビジュアルオブジェクトを画面外に移動させるだけで十分です。しかし、動的に生成されるオブジェクトが多い場合、未使用のオブジェクトを削除した方がパフォーマンスに有利な場合があります。

なお、便利な QML オブジェクトファクトリ(LoaderRepeater など)によって動的に生成されたオブジェクトを手動で削除してはいけません。また、自分で動的に生成していないオブジェクトの削除も避けるべきです。

アイテムの削除にはdestroy() メソッドを使います。このメソッドにはオプションの引数 (デフォルトは 0) があり、 オブジェクトが破棄されるまでのおおよその遅延時間をミリ秒単位で指定します。

以下はその例です。application.qmlSelfDestroyingRect.qml コンポーネントのインスタンスを5つ作成する。各インスタンスは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);

ここに含まれる文書の著作権はそれぞれの所有者に帰属します 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。