QML(Qt)のエレメントを動的に生成をする

今回は、「Loader」エレメントを使用してオブジェクトの動的生成の方法です。
サンプルは「ほぼ」同じ動作をするアプリを用意して動的に生成した場合と静的にエレメントを配置した場合の動作の違いを比較しプロファイラで可視化します。
どちらが良いかはアプリの仕様や規模によって使いやすい方法を選択します。
(今回の内容は当然といえばすごく当然で予定調和な内容です。)

プロファイラの紹介はコチラ「Qt QuickアプリをQMLプロファイラで解析をする


/// サンプルについて ///
・クリックすると四角が順番に表示される
・四角の位置と色はランダム
・1~5回目は四角を1つずつ、6回目は5個の四角を一気に表示をループ
・背景に適当な画像をおくといいかも。

全部の四角が表示されてるところ。(これだけじゃ何もわからないですが)
 


/// ポイント ///
・起動時間(初回生成時間)
・エレメントがいつ生成されるか

主旨とは関係ないポイント・・・。 ・RepeaterにLoaderを配置することで任意のレイアウトを任意の数配置できる。
 ListViewなど通常delegateには固定のレイアウトを配置しますが、そんなの関係なく1アイテム毎に独立したレイアウトが作れます。
 ListViewでは試してないですけど。

/// 起動時間(初回生成時間) ///
・動的に配置すると起動が早くなって個々の表示が遅くなる。
・静的に配置すると起動が遅くなって個々の表示が早くなる。

簡単に言えば起動時に不要なものは生成しないってことができます。
(ただ今回のサンプルの用にロードとアンロードを繰り返しまくるのはどうなのだろうかと思ってみなくもない・・・。ボソボソ)


/// エレメントがいつ生成されるか ///
・動的に配置すると、ローダにコンポーネントを指定するたびにonCompletedが発生します。
・静的に配置すると起動時に全部生成されてonCompletedが発生します。

似たようなこと書いてしまいましたが、このあたりの動作の違いをプロファイラを使用して見てみましょう。
一目瞭然です。


/// 起動時の動作 ///
静的に配置した場合は「Creating」の終わりがけに「Signal Handler」と「Binding」が発生しているのがわかりますが動的の場合には何もありません。
つまり、「Dialog」エレメントのonCompletedイベントが発生してバインドの動作の有無がわかります。
・動的配置
 
・静的配置
 

/// クリックしたときの動作 ///
静的に配置した場合は、「Signal Handler」に「onClicked」イベントしか発生していませんが、動的配置の方は2段構成で後半に「onCompleted」と「onLoaded」が発生しています。
また、動的の場合は「Creating」が起きているのも特徴ですね。静的で「Creating」が細い線がありますが「State」エレメントが生成されていますが1回目だけです。
全部の情報は同時に出せないので端折ってますが明らかに違うことがわかります。
・動的配置
 
・静的配置
 

以上のような感じでエレメントの動的ロードが可能です。
別の方法としてドキュメントに「http://qt-project.org/doc/qt-4.8/qml-qt.html#dynamic-object-creationDynamic Object Creation」というのもあります。
ただしコチラの手法は呼び出し側と呼び出される側が完全に独立していないとちょっと面倒な事になります。
signalなどの名称がコード補完されないとかなると思います。

どのタイミングでどんな事がおきてるか説明した時にプロファイラって便利ですね!!

/// サンプル ///
3つのファイル構成です。
・共通のダイアログ(的)エレメント
// Dialog.qml
import QtQuick 1.0

Rectangle {
    id: _root
    width: 300
    height: 150
    opacity: 0.8
    visible: false

    property alias text: _text.text
    Text{
        id: _text
        anchors.centerIn: parent
        color: "#ffffff"
        font.pointSize: 14
    }

    Component.onCompleted: {
        var r = Math.random();
        var g = Math.random();
        var b = Math.random();

        _root.color = Qt.rgba(r, g, b, 1.0);
        if((r + g+ b) < 1.5){
            _text.color = "#ffffff";
        }else{
            _text.color = "#000000"
        }
    }
}


・動的に配置する場合
import QtQuick 1.1

Image {
    id: _root
    width: 640
    height: 480
    source: "./wallpaper.jpg"
    fillMode: Image.PreserveAspectCrop

    property int dlgNumber: 0

    Repeater{
        id: _repeater
        model:ListModel{}
        Loader{
            sourceComponent: _src
            onLoaded: {
                item.visible = true;
                item.text += dlgNumber;
            }
        }
    }
    Component{
        id: _dlg
        Dialog{ x: calcX(width);  y: calcY(height);  text: "ほげほげ";   }
    }

    function calcX(w){
        return Math.random() * (_root.width - w);
    }
    function calcY(h){
        return Math.random() * (_root.height - h);
    }

    MouseArea {
        anchors.fill: parent
        onClicked: {
            _repeater.model.clear();

            dlgNumber++;
            if(dlgNumber > 6){
                dlgNumber = 0;
            }else if(dlgNumber === 6){
                dlgNumber = 0;
                for(var i=0;i<5;i++){
                    dlgNumber++;
                    _repeater.model.append({"_src": _dlg});
                }
                dlgNumber++;
            }else{
                _repeater.model.append({"_src": _dlg});
            }
        }
    }
}


・静的に配置する場合
import QtQuick 1.0

Image {
    id: _root
    width: 640
    height: 480
    source: "./wallpaper.jpg"
    fillMode: Image.PreserveAspectCrop

    property int dlgNumber: 0

    Dialog{ id: _dlg1;  x: calcX(width);  y: calcY(height);  text: "ほげほげ1";   }
    Dialog{ id: _dlg2;  x: calcX(width);  y: calcY(height);  text: "ほげほげ2";   }
    Dialog{ id: _dlg3;  x: calcX(width);  y: calcY(height);  text: "ほげほげ3";   }
    Dialog{ id: _dlg4;  x: calcX(width);  y: calcY(height);  text: "ほげほげ4";   }
    Dialog{ id: _dlg5;  x: calcX(width);  y: calcY(height);  text: "ほげほげ5";   }

    function calcX(w){
        return Math.random() * (_root.width - w);
    }
    function calcY(h){
        return Math.random() * (_root.height - h);
    }

    MouseArea {
        anchors.fill: parent
        onClicked: {
            dlgNumber++;
            if(dlgNumber > 6){
                dlgNumber = 0;
            }
        }
    }
    states: [
        State {
            name: "State1"; when: dlgNumber == 1
            PropertyChanges {   target: _dlg1;  visible: true;   }
        },
        State {
            name: "State2"; when: dlgNumber == 2
            PropertyChanges {   target: _dlg2;  visible: true;   }
        },
        State {
            name: "State3"; when: dlgNumber == 3
            PropertyChanges {   target: _dlg3;  visible: true;   }
        },
        State {
            name: "State4"; when: dlgNumber == 4
            PropertyChanges {   target: _dlg4;  visible: true;   }
        },
        State {
            name: "State5"; when: dlgNumber == 5
            PropertyChanges {   target: _dlg5;  visible: true;   }
        },
        State {
            name: "State6"; when: dlgNumber == 6
            PropertyChanges {   target: _dlg1;  visible: true;   }
            PropertyChanges {   target: _dlg2;  visible: true;   }
            PropertyChanges {   target: _dlg3;  visible: true;   }
            PropertyChanges {   target: _dlg4;  visible: true;   }
            PropertyChanges {   target: _dlg5;  visible: true;   }
        }
    ]
}