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` は不要です:
代わりに、内部に含まれる 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 という画像よりもサイズが小さくなっています:
これらを別々に使用する代わりに、inner.png とouter.png を1つの画像にまとめます:
Image { source: "combined.png" }
静的なテキストが画像と重なっている場合は、個別の「Text」や「StaticText 」アイテムを使用するのではなく、そのテキストを画像に組み込むことをお勧めします。
「Text」アイテムの数を減らす
複数の「Text」アイテムを1つの「Text」アイテムにまとめられる場合は、それらを横一列に並べるのを避けてください。たとえば、次のコードスニペットは、2つの「Text」アイテムが横一列に配置されている例です:
これらを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には、暗黙のサイズが設定されていません:
これにより、コンポーネントの利用者は、コンポーネントの幅と高さを指定せざるを得なくなります
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 ライセンスで利用可能です。
詳細はこちら。