QMLの基礎 - モデルとビュー

提供:MochiuWiki : SUSE, EC, PCB
2021年10月23日 (土) 01:23時点におけるWiki (トーク | 投稿記録)による版 (→‎ListViewのセクション)
ナビゲーションに移動 検索に移動

概要

ソフトウェアはデータを生成して、データを表示する必要がある。
Qt Quickには、データを表示するためのモデル、ビュー、デリゲートという概念が存在する。

モデル、ビュー、デリゲートは、データの可視化をモジュール化し、開発者がデータの様々な側面をコントロールできる。
また、データにほとんど変更を加えることなく、リストビューとグリッドビューを入れ替えることができる。
同様に、データのインスタンスをデリゲートにカプセル化することで、データの表示方法や処理方法を決定することができる。

  • モデル
    データとその構造を含む。
    モデルを作成するためのQMLタイプがいくつかある。

  • ビュー
    データを表示するコンテナである。
    ビューは、リストやグリッドでデータを表示する。

  • デリゲート
    データがビューにどのように表示されるかを指定する。
    デリゲートはモデルの各データを受け取り、それをカプセル化する。データはデリゲートを通してアクセスできる。
    デリゲートは、データを編集可能なモデルに書き戻すこともできる。(例. TextFieldonAcceptedハンドラ内)
    データを可視化するには、ビューのmodelプロパティをモデル、delegateプロパティをコンポーネントやその他の互換性のあるタイプにバインドする。


QML Model and View 1.png



ビューによるデータの表示

ビューは、アイテムのコレクションのためのコンテナである。
豊富な機能を持ち、スタイルや動作の要求に合わせてカスタマイズすることができる。

Qt Quickの基本セットには、以下に示す標準的なビューが用意されている。

  • ListView
    水平または垂直のリストにアイテムを配置する。

  • GridView
    利用可能なスペース内のグリッドにアイテムを配置する。

  • PathView
    アイテムをパス上に配置する。


上記のビューには、それぞれ専用のプロパティと動作がある。

ビューの装飾

ビューでは、headerfootersection等の装飾用プロパティを使用して、見た目をカスタマイズすることができる。

これらのプロパティに対して、オブジェクト(通常は別のビジュアルオブジェクト)をバインドすることにより、ビューを装飾することができる。
footerには、ボーダーを表示するRectangleアイテムや、ビューの上にロゴを表示するヘッダ等がある。

以下の例では、あるクラブがメンバーリストを自社のブランドカラーで装飾している。
メンバーリストはモデルの中にあり、デリゲートはモデルのコンテンツを表示している。

クラブは、ビジュアルアイテムをheaderプロパティとfooterプロパティにバインドすることにより、メンバーリストを装飾することができる。
ビジュアルアイテムは、同一ファイル、他のファイル、Componentアイテムで定義することができる。

 ListModel {
    id: nameModel
    ListElement { name: "Alice" }
    ListElement { name: "Bob" }
    ListElement { name: "Jane" }
    ListElement { name: "Harry" }
    ListElement { name: "Wendy" }
 }
 
 Component {
    id: nameDelegate
    Text {
       text: name;
       font.pixelSize: 24
    }
 }
 
 ListView {
    anchors.fill: parent
    clip: true
 
    model: nameModel
    delegate: nameDelegate
 
    header: bannercomponent
 
    footer: Rectangle {
       width: parent.width
       height: 30;
       gradient: clubcolors
    }
 
    highlight: Rectangle {
        width: parent.width
        color: "lightgray"
    }
 }
 
 Component {  // instantiated when header is processed
    id: bannercomponent
    Rectangle {
       id: banner
       width: parent.width
       height: 50
       gradient: clubcolors
 
       border {
          color: "#9EDDF2"
          width: 2
       }
 
       Text {
          anchors.centerIn: parent
          text: "Club Members"
          font.pixelSize: 32
       }
    }
 }
 
 Gradient {
    id: clubcolors
 
    GradientStop {
       position: 0.0
       color: "#8EE2FE"
    }
 
    GradientStop {
       position: 0.66
       color: "#7ED2EE"
    }
 }


QML Model and View 2.png


マウスとタッチの操作

ビューは、コンテンツのドラッグやフリックを処理するが、個々のデリゲートとのタッチ操作は処理しない。

デリゲートがタッチ入力に反応してcurrentIndexプロパティを設定するためには、適切なタッチ処理を持つMouseAreaアイテムをデリゲートに定義する必要がある。

※注意
highlightRangeModeプロパティがStrictlyEnforceRangeに設定されている場合、ビューは常にcurrentIndexプロパティが指定されたハイライト範囲内にあることを保証するため、
currentIndexプロパティはビューをドラッグおよびフリックしても影響を受けない。

ListViewのセクション

ListViewのコンテンツはセクションにグループ化されるため、関連するリストアイテムは、そのセクションに応じてラベル付けされる。
また、セクションは、デリゲートを使用して装飾することもできる。

以下の例では、ListViewに、人名とチームの所属を示すリストを作成している。

ListViewには、隣接するアイテムや関連するアイテムをセクションにまとめることができるpropertyプロパティが存在する。
propertyプロパティは、どのリストタイプのプロパティをセクションとして使用するかを決定する。

cliteriaプロパティは、セクション名の表示方法を決定する。
delegateプロパティは、ListView等のdelegateプロパティと同様である。

 ListModel {
    id: nameModel
    ListElement { name: "Alice"; team: "Crypto" }
    ListElement { name: "Bob"; team: "Crypto" }
    ListElement { name: "Jane"; team: "QA" }
    ListElement { name: "Victor"; team: "QA" }
    ListElement { name: "Wendy"; team: "Graphics" }
 }
 
 Component {
    id: nameDelegate
    Text {
       text: name;
       font.pixelSize: 24
       anchors.left: parent.left
       anchors.leftMargin: 2
    }
 }
 
 ListView {
    anchors.fill: parent
    model: nameModel
    delegate: nameDelegate
    focus: true
 
    highlight: Rectangle {
       color: "lightblue"
       width: parent.width
    }
 
    section {
       property: "team"
       criteria: ViewSection.FullString
       delegate: Rectangle {
          color: "#b0dfb0"
          width: parent.width
          height: childrenRect.height + 4
          Text {
             anchors.horizontalCenter: parent.horizontalCenter
             font.pixelSize: 16
             font.bold: true
             text: section
          }
       }
    }
 }


QML Model and View 3.png



ビューのデリゲート

ビューは、リストの中のアイテムを視覚的に表現するために、デリゲートが必要となる。

ビューは、デリゲートにより定義されたテンプレートにしたがって、各アイテムのリストを生成する。
モデル内のアイテムは、アイテムのプロパティだけでなく、インデックスのプロパティにもアクセスできる。

QML Model and View 4.png


 Component {  // delegate: <アイテム名> { を使用することもできる
    id: petdelegate
 
    Text {
       id: label
       font.pixelSize: 24
       text: index === 0 ? type + " (default)" : type
 
       required property int index
       required property string type
    }
 }



デリゲートからのビューやモデルへのアクセス

デリゲートがバインドされているビューは、viewプロパティを介してデリゲートからアクセスできる。(例. ListView.view, GridView.view等)
したがって、対応するモデルとそのプロパティは、view.modelを通じて使用できる。(例. ListView.view.model, GridView.view.model等)

さらに、モデルに定義されているシグナルやメソッドもアクセス可能である。
例えば、複数のビューに同じデリゲートを使用したいが、装飾等の機能は各ビューごとに異なるものにする場合や他の異なる設定を各ビューのプロパティにする場合に活用できる。
同様に、モデルの各プロパティにアクセスおよび表示することもできる。

以下の例では、デリゲート内の1つのTextアイテムは、ListModel内のlanguageプロパティを表示している。
また、デリゲート内の1つのTextアイテムの色は、ListView内のfruit_colorプロパティに依存している。

 Rectangle {
    width: 200
    height: 200
 
    ListView {
       model: fruitModel
       delegate: fruitDelegate
       anchors.fill: parent
 
       property color fruit_color: "green"
    }
 
    ListModel {
       id: fruitModel
       property string language: "en"
 
       ListElement {
          name: "Apple"
          cost: 2.45
       }
 
       ListElement {
          name: "Orange"
          cost: 3.25
       }
 
       ListElement {
          name: "Banana"
          cost: 1.95
       }
    }
 
    Component {
       id: fruitDelegate
 
       Row {
          id: fruit
 
          Text {
             text: " Fruit: " + name
             color: fruit.ListView.view.fruit_color
          }
 
          Text {
             text: " Cost: $" + cost
          }
 
          Text {
             text: " Language: " + fruit.ListView.view.model.language
          }
       }
    }
 }



モデル

データは、デリゲートがバインドすることができる名前付きのデータロールを介してデリゲートに提供される。

以下の例では、typeとageという2つのロールを持つListModel、および、これらのロールにバインドして値を表示するデリゲートを持つListViewを作成している。

 import QtQuick 2.15
 
 Item {
    width: 200
    height: 250
 
    ListModel {
       id: myModel
 
       ListElement {
          type: "Dog"
          age: 8
       }
 
       ListElement {
          type: "Cat"
          age: 5
       }
    }
 
    Component {
       id: myDelegate
       Text { text: type + ", " + age }
    }
 
    ListView {
       anchors.fill: parent
       model: myModel
       delegate: myDelegate
    }
 }


ロールのアクセスをより細かく制御する場合、および、デリゲートをビュー外部で使用する場合は、以下のように記述する。

 required property <データ型> <プロパティ名>


デリゲートに必須プロパティが含まれている場合、名前付きのロールは提供されない。
代わりに、QMLエンジンは、まず、必須プロパティ名がモデルロールの名前と一致するかどうかをチェックして、一致する場合、その必須プロパティはモデルの対応する値にバインドされる。

 import QtQuick 2.15
 
 Item {
    width: 200
    height: 250
 
    ListModel {
       id: myModel
 
       ListElement {
          type: "Dog"
          age: 8
          noise: "meow"
       }
 
       ListElement {
          type: "Cat"
          age: 5
          noise: "woof"
       }
    }
 
    component MyDelegate : Text {
       required property string type
       required property int age
 
       text: type + ", " + age
 
       // 間違った記述: Component.onCompleted: () => console.log(noise)
       // 必須プロパティであるnoiseがないため、ReferenceErrorが発生する
       // 必要プロパティが存在することで、スコープにノイズが注入されるのを防ぐことができる
    }
 
    ListView {
        anchors.fill: parent
        model: myModel
        delegate: MyDelegate {}
    }
}


モデルのプロパティとデリゲートのプロパティの間に名前の衝突がある場合、代わりに修飾されたモデル名でロールにアクセスすることができる。

例えば、モデル内とデリゲート内の両方に同名のプロパティが存在する時、
デリゲート内のプロパティではなく、モデル内のプロパティを参照する場合、<モデルのID名>.<プロパティ名>と記述することにより参照できる。

また、モデル内のインデックスを含む特別なインデックスロールもデリゲートで使用できる。

※注意
このインデックスは、アイテムがモデルから削除されると-1に設定されることに注意すること。
インデックスロールにバインドする場合、インデックスが-1になる可能性、つまり、アイテムが有効でなくなる可能性を考慮したロジックにする。
(通常、アイテムはすぐに破棄されるが、一部のビューではdelayRemoveプロパティによりデリゲートの破棄を遅らせることができる)

名前付きロールを持たないモデル(例. ListModel等)は、モデルデータロールによりデータが提供される。
モデルデータロールは、1つのロールしか持たないモデルにも提供される。この場合、モデルデータロールには、名前付きロールと同じデータが含まれる。

※注意
モデルロール、モデルデータロール、インデックスロールにおいて、デリゲートが必須プロパティを含んでいる場合、プロパティ名が一致しなければアクセスできないことに注意する。

QMLには、いくつかのデータモデルが用意されており、それらはQMLの組み込み型に含まれている。
加えて、モデルはQt C++で作成することができ、そのモデルをQQmlEngineクラスを使用してQMLコンポーネントから利用することもできる。

モデルの作成については、QMLの基礎 - モデルの作成QMLの基礎 - QMLタイプの作成を参照すること。

モデルからアイテムを配置する場合は、Repeaterを使用することで実現できる。