このページでは

C

QMLのベストプラクティス

アプリケーションの QML コードは、フラッシュメモリおよびランダムアクセスメモリの占有量に大きな影響を与える可能性があります。重複のない整然とした QML コードを記述することで、生成される C++ コードの量と、それによるフラッシュメモリの占有量を削減できます。以下のセクションでは、メモリ占有量を削減し、パフォーマンスを向上させるために活用できるその他の手法について説明します。

再利用可能なコンポーネントの作成

同じコードパターンを複数の場所で重複させるのではなく、そのパターンを別のQMLファイルにカプセル化することを検討してください。

たとえば、以下のコードには、それぞれテキストと画像を含むラベルのリストがあります:

Column {
    spacing: 15
    anchors.centerIn: parent

    Rectangle {
        width: 250
        height: 120
        color: "#e7e3e7"

        Row {
            anchors.centerIn: parent
            spacing: 10

            Text {
                anchors.verticalCenter: parent.verticalCenter
                text: "Entry 1"
                color: "#22322f"
                font.pixelSize: 22
            }

            Image {
                anchors.verticalCenter: parent.verticalCenter
                source: "img1.png"
            }
        }
    }

    Rectangle {
        width: 250
        height: 120
        color: "#e7e3e7"

        Row {
            anchors.centerIn: parent
            spacing: 10

            Text {
                anchors.verticalCenter: parent.verticalCenter
                text: "Entry 2"
                color: "#22322f"
                font.pixelSize: 22
            }

            Image {
                anchors.verticalCenter: parent.verticalCenter
                source: "img2.png"
            }
        }
    }
}

Label.qml ファイルを作成し、以下に示すように設定可能なプロパティやプロパティエイリアスを定義することで、このコードを簡略化できます:

import QtQuick 2.15

Rectangle {
    property alias imageSource: imageItem.source
    property alias text: textItem.text

    width: 250
    height: 120
    color: "#e7e3e7"

    Row {
        anchors.centerIn: parent
        spacing: 10

        Text {
            id: textItem
            anchors.verticalCenter: parent.verticalCenter
            color: "#22322f"
            font.pixelSize: 22
        }

        Image {
            id: imageItem
            anchors.verticalCenter: parent.verticalCenter
        }
    }
}

このQMLコンポーネントを元のQMLコードで再利用することで、コードの重複を避けることができます:

Column {
    spacing: 15
    anchors.centerIn: parent

    Label {
        text: "Entry 1"
        imageSource: "img1.png"
    }

    Label {
        text: "Entry 2"
        imageSource: "img2.png"
    }
}

プロパティ変更の制限

PropertyChanges を通じて状態の影響を受けるプロパティが多数存在するQMLファイルでは、大規模かつ複雑なC++コードが生成されます。生成されるコードのサイズはN x M となります。ここで、Nは状態の数、Mはそれらの状態においてPropertyChanges によって更新される一意のプロパティの数です。

ここでは、状態が2つ、プロパティが2つだけの例を示しますが、同じQMLコンポーネント内でさまざまなビューを切り替える際に、これと同様の状態がさらに多数存在することを想像してみてください:

Item {
    state: "0"
    states: [
        State {
            name: "0"
            PropertyChanges { target: viewA; visible: true }
        },
        State {
            name: "1"
            PropertyChanges { target: viewB; visible: true }
        }
    ]
    ViewA {
        id: viewA
        visible: false
    }
    ViewB {
        id: viewB
        visible: false
    }
}

状態に基づいてビューのvisibleプロパティを直接設定することで、これを最適化できます:

Item {
    id: root
    state: "0"
    states: [
        State { name: "0" },
        State { name: "1" }
    ]
    ViewA {
        id: viewA
        visible: root.state == "0"
    }
    ViewB {
        id: viewB
        visible: root.state == "1"
    }
}

空のコンテナ項目は避ける

`Item` タイプは、他のアイテムをグループ化して、それらの表示状態や位置を一括して設定するのに役立ちます。コンテナアイテムはメモリ使用量を増加させるため、その使用は最小限に抑えてください。たとえば、以下のコードスニペットにある外側の `Item` は不要です:

Item {
    Image {
        anchors.fill: parent
        source: "img.png"
    }
}

代わりに、内部の Image アイテムを直接使用することができます:

Image {
    anchors.fill: parent
    source: "img.png"
}

コンポーネントを動的に読み込む

アプリケーションには、多くのアイテムを含む複雑なQMLコンポーネントが含まれており、それらは異なるタイミングで表示される場合があります。Loader 型を使用して、そのようなコンポーネントを動的に読み込むことで、RAMの使用量を削減できます。

メモリ使用量の急増を防ぐため、新しいアイテムを読み込む前に、既存の非表示アイテムを明示的にアンロードしてください。アプリケーションのUI設計やメモリの制約に応じて、任意の時点で読み込まれるアイテムの数を限定するようにしてください。以下の例は、SwipeView 内のページのうち、任意の時点でメモリに読み込まれるものを1つに制限する方法を示しています:

SwipeView {
    id: theSwipe
    width: parent.width * 0.5
    height: parent.height * 0.5
    anchors.centerIn: parent
    clip: true

    function updateLoaderStates() {
        console.log("updating loader states ...")
        if (theSwipe.currentIndex === 0) {
            loader1.source = ""
            loader0.source = "FirstPage.qml"
        } else if (theSwipe.currentIndex === 1) {
            loader0.source = ""
            loader1.source = "SecondPage.qml"
        }
    }

    Component.onCompleted: updateLoaderStates()
    onCurrentIndexChanged: updateLoaderStates()

    Loader {
        id: loader0
        onItemChanged: {
            if (item) {
                console.log("loader0 loaded")
            } else {
                console.log("loader0 free")
            }
        }
    }

    Loader {
        id: loader1
        onItemChanged: {
            if (item) {
                console.log("loader1 loaded")
            } else {
                console.log("loader1 free")
            }
        }
    }
}

一般的なルールとして、バインディングの評価順序に依存しないでください。次の例では、アイテムの読み込みおよびアンロードの順序を制御できません。これにより、アプリケーションの両方のページに対して一時的にメモリが割り当てられる可能性があります:

SwipeView {
    id: mySwipe
    width: parent.width * 0.5
    height: parent.height * 0.5
    anchors.centerIn: parent
    clip: true

    onCurrentIndexChanged: {
        console.log("index changed ...")
    }

    Loader
    {
        source: "FirstPage.qml"
        active: mySwipe.currentIndex === 0
        onItemChanged: {
            if (item) {
                console.log("loader0 loaded")
            } else {
                console.log("loader0 free")
            }
        }
    }

    Loader
    {
        source: "SecondPage.qml"
        active: mySwipe.currentIndex === 1
        onItemChanged: {
            if (item) {
                console.log("loader1 loaded")
            } else {
                console.log("loader1 free")
            }
        }
    }
}

ビジュアルコンポーネントの数を減らす

通常、各ビジュアル項目には、実行時に一定の処理およびレンダリングのオーバーヘッドが伴います。可能であれば、UIを構成するために必要なビジュアル項目の数を減らしてください。以下に、その方法の例をいくつか示します。

画像の重なりを減らす

UI上で2つの画像が常に重なっている場合は、それらを1つの画像に統合したほうがよい場合があります。重なり合う画像が多すぎると、パフォーマンスが低下したり、メモリを多く消費したりする可能性があります。たとえば、以下のコードスニペットにあるinner.png は、outer.png という画像よりもサイズが小さくなっています:

Image {
    id: outer
    source: "outer.png"
}
Image {
    anchors.centerIn: outer
    source: "inner.png"
}

これらを別々に使用するのではなく、inner.pngouter.png を1つの画像にまとめます:

Image {
    source: "combined.png"
}

静的なテキストが画像と重なっている場合は、個別の「Text」や「StaticText 」アイテムを使用するのではなく、そのテキストを画像内に組み込むことをお勧めします。

「Text」アイテムの数を減らす

複数の「Text」アイテムを1つの「Text」アイテムにまとめられる場合は、それらを横一列に並べるのを避けてください。たとえば、次のコードスニペットでは、2つの「Text」アイテムが横一列に配置されています:

Row {
    Text {
        text: "Temperature: "
    }
    Text {
        text: root.temperature
    }
}

これらを1つのテキスト項目にまとめることができます:

Text {
    text: "Temperature: " + root.temperature
}

バインディングの数を減らす

バインディングの数を減らすことで、ROMを節約できます

暗黙の寸法を使用する

可能な場合は、暗黙の寸法を使用することで、バインディングの数を減らすことができます

画像に対する暗黙的なディメンションの使用

画像を使用するたびに幅や高さのプロパティを指定する必要がないよう、正しいサイズで画像を作成してください。

Image {
    width: 64
    height: 64
    fillMode: Image.pad
    source: "image/background.png"
}

代わりに、暗黙的な幅と高さを指定してください:

Image {
    source: "image/background.png"
}

コンポーネントに暗黙的な寸法を使用する

暗黙的な寸法を使用することで、頻繁に使用されるコンポーネントのバインディング数を減らすことができます。

たとえば、IconButton.qml で定義されているIconButtonには、暗黙的なサイズが指定されていません:

MouseArea {
    property alias iconSource: img.source

    Image {
        id: img
        source: ""
    }
}

これにより、コンポーネントの利用者は、コンポーネントの幅と高さを指定せざるを得なくなります

IconButton {
    width: 64
    height: 64
    iconSource: "home.png"
}

代わりに、以下のようにIconButtonを作成します:

MouseArea {
    implicitWidth: img.implicitWidth
    implicitHeight: img.implicitHeight

    property alias iconSource: img.source

    Image {
        id: img
        source: ""
    }
}

これにより、バインディングの数が減ります。

IconButton {
    iconSource: "home.png"
}

この場合、コンポーネントのサイズは大きくなりますが、そのコンポーネントが頻繁に使用されるのであれば、ROMの節約効果は全体として大きくなります。

テキストと画像の表示状態

アプリケーション内のテキストおよび画像項目の表示/非表示を制御するには、それぞれのtext プロパティとsource プロパティの値として空の文字列を指定します。

たとえば、次のコードで定義されたコンポーネントの場合:

Item {
    property alias iconVisible: img.visible
    property alias textVisible: txt.visible

    property alias imageSource: img.source
    property alias text: txt.text

    Image {
       id: img
       source: ""
    }

    Text {
        id: txt
        text: ""
    }
}

このようなコンポーネントは、次のように使用できます:

MyComponent {
    textVisible: false
    text: ""
    iconVisible: true
    imageSource: "images/background.png"
}

また、可視性プロパティを使用せずに、同じ結果を得ることもできます:

Item {
    property alias imageSource: img.source
    property alias text: txt.text

    Image {
       id: img
       source: ""
    }

    Text {
        id: txt
        text: ""
    }
}

次の例のようにコンポーネントを使用すると、画像アイテムは表示されますが、テキストアイテムは表示されません:

MyComponent {
    imageSource: "images/background.png"
}

ステートを使用してプロパティをまとめる

このアプローチを、よく使われるコンポーネントのほとんどに適用してください。たとえば、次のHeader コンポーネントの場合:

Row {
    property alias button1Text: btn1.text
    property alias button2Text: btn2.text
    property alias button3Text: btn3.text

    Button {
        id: btn1
        text: ""
    }
    Button {
        id: btn2
        text: ""
    }
    Button {
        id: btn3
        text: ""
    }
}

3つのバインディングを指定する必要があります:

Header {
    button1Text: "Back"
    button2Text: "OK"
    button3Text: "Info"
}

代わりに、これらのプロパティをstateにまとめておくこともできます:

Row {
    Button {
        id: btn1
        text: ""
    }
    Button {
        id: btn2
        text: ""
    }
    Button {
        id: btn3
        text: ""
    }

    states: [
        State {
            name: "VariantA"
            PropertyChanges {
                target: btn1
                text: "Back"
            }
            PropertyChanges {
                target: btn2
                text: "OK"
            }
            PropertyChanges {
                target: btn3
                text: "Info"
            }
        }
    ]
}

これにより、バインディングの数が1つに減ります:

Header {
    state: "VariantA"
}

シグナルはシンプルに保つ

可能な限り、シグナルは常に簡素化してください。たとえば、MyComponent.qml コンポーネント内に、論理的に類似したボタンが多数ある場合:

Item {
    id: root

    signal button1Clicked
    signal button2Clicked

    Row {
        Button {
            text: "Ok"
            onClicked: {
                root.button1Clicked()
            }
        }

        Button {
            text: "Cancel"
            onClicked: {
                root.button2Clicked()
            }
        }
    }
}

各シグナルごとに1つずつ、計2つのバインディングが必要になります:

Rectangle {
    MyComponent {
        onButton1Clicked: {
            console.log("Ok")
        }
        onButton2Clicked: {
            console.log("Cancel")
        }
    }
}

代わりに、どのボタンがクリックされたかを識別するインデックスを渡す1つのシグナルを使用します

Item {
    id: root

    signal buttonClicked(index: int)

    Row {
        Button {
            text: "Ok"

            onClicked: {
               root.buttonClicked(0)
            }
        }

        Button {
            text: "Cancel"

            onClicked: {
               root.buttonClicked(1)
            }
        }
    }
}

そうすれば、バインディングは1つだけで済みます:

Rectangle {
    MyComponent {
        onButtonClicked: {
            switch(index) {
            case 0: {
                console.log("Ok")
                break;
            }
            case 1: {
               console.log("Cancel")
                break;
            }
            }
        }
    }
}

モデルのサイズを縮小する

モデルにすべてのデリゲートプロパティを含めないでください。ビュー内で直接指定することで、使用されるプロパティの数を減らしてください。

たとえば、次のようなモデルは作成しないでください:

Rectangle {
    property ListModel myModel : ListModel {
        ListElement {
            textcolor: "blue"
            name: "John"
            age: 20
        }
        ListElement {
            textcolor: "blue"
            name: "Ochieng"
            age: 30
        }
    }

    ListView {
        anchors.fill: parent
        model: myModel
        delegate: Text {
            width: 50
            height: 50
            color: model.textcolor
            text: "Name: %1 Age: %2".arg(model.name).arg(model.age)
        }
    }
}

textcolorプロパティの値がすべてのデータで同じ場合は、モデルから削除し、プロパティとして宣言することで、モデルのサイズを縮小し、不要な重複を回避します:

Rectangle {
    property ListModel myModel : ListModel {
        ListElement {
            name: "John"
            age: 20
        }
        ListElement {
            name: "Ochieng"
            age: 30
        }
    }

    property string textcolor

    ListView {
        anchors.fill: parent
        model: myModel
        delegate: Text {
            width: 50
            height: 50
            color: textcolor
            text: "Name: %1 Age: %2".arg(model.name).arg(model.age)
        }
    }
}

大規模な ListModel の共有

アプリケーションの異なる部分間でListModel を共有するには、qml SingletonのListModel プロパティを使用してください。これにより、ROMの節約につながります。

// AppConfig.qml
pragma Singleton
..
QtObject {
    property ListModel mySharedModel: ListModel {
       ListElement { bgcolor: 'red' }
       ListElement { bgcolor: 'yellow' }
       ListElement { bgcolor: 'blue' }
       ListElement { bgcolor: 'green' }
       ListElement { bgcolor: 'orange' }
       ListElement { bgcolor: 'black' }
       ListElement { bgcolor: 'gray' }
       ...
    }
}
// Page1.qml
Repeater {
    model: AppConfig.mySharedModel
    delegate: ..
}

// Page2.qml
ListView {
    model: AppConfig.mySharedModel
    delegate: ..
}

大規模なListModelの分割

ListElement に対して多数のプロパティを持つ大規模なListModel がある場合は、データを縦方向に分割して、QML基本型のリストを複数作成することを検討してください。これによりROMを節約でき、バインディング用に生成されるC++コードの量を削減できます。詳細については、「バインディングの数を減らす」を参照してください。

例:

ListModel {
    id: myModel
    ListElement { name: "Hans"; age: 25; city: "Berlin"; x: 10; y: 20; }
    ListElement { name: "Marie"; age: 30; city: "Paris"; x: 15; y: 25; }
    ListElement { name: "Luca"; age: 35; city: "Rome"; x: 20; y: 30; }
    ListElement { name: "Eva"; age: 40; city: "Madrid"; x: 25; y: 35; }
    // ...
}
Repeater {
    model: myModel
    Text {
        text: model.name + ", " + model.age + ", " + model.city
    }
}

Qt Quick Ultraliteは、前述の例における各プロパティに対して20個(5 * number of ListElements )のバインディングを生成します。以下のアプローチを使用することで、これをわずか4個に減らすことができます:

readonly property list user_name: ["Hans", "Marie", "Luca", "Eva", ...]
readonly property list user_age: [25, 30, 35, 40, ...]
readonly property list user_city: ["Berlin", "Paris", "Rome", "Madrid", ...]
readonly property list user_position: [Qt.point(10, 20), Qt.point(15, 25), Qt.point(20, 30), Qt.point(25, 35), ...]
Repeater {
    model: parent.names.length
    Text {
        x: user_position[index].x
        y: user_position[index].y
        text: user_name[index] + ", " + user_age[index] + ", " + user_city[index]
    }
}

注: Qt Quick Ultraliteは、オブジェクトのリスト内の各プロパティに対してバインディングを生成します。このアプローチは{QML基本型}でのみ使用してください。

現在、length プロパティはコピーされるだけで、モデルにはバインドされません。つまり、リストのサイズに合わせてモデル値が確実に更新されるよう、明示的にモデル値を更新する必要があります。

特定のQtライセンスの下で利用可能です。
詳細はこちら。