Qt Quickのモデルとビュー

ほとんどのアプリケーションでは、データをフォーマットして表示する必要があります。Qt Quickには、データを表示するためのモデルビューデリゲートという概念があります。これらは、開発者やデザイナーがデータの様々な側面をコントロールできるように、データの視覚化をモジュール化しています。開発者は、データにほとんど変更を加えることなく、リスト・ビューとグリッド・ビューを入れ替えることができる。同様に、デリゲートにデータのインスタンスをカプセル化することで、開発者はデータの表示方法や処理方法を指定することができます。

  • モデル- データとその構造を含みます。モデルを作成するためのQMLの型がいくつかあります。
  • ビュー- データを表示するコンテナ。ビューはデータをリストやグリッドで表示します。
  • デリゲート- データがどのようにビューに表示されるかを指定します。デリゲートは、モデル内のデータの各単位を受け取り、それをカプセル化します。データはデリゲートを通してアクセスすることができます。デリゲートは、編集可能なモデルにデータを書き戻すこともできます(例えば、TextField'のonAcceptedハンドラで)。

データを可視化するには、ビューのmodel プロパティをモデルに、delegate プロパティをコンポーネントまたは他の互換性のある型にバインドします。

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

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

Qt Quick グラフィカル・タイプの基本セットには、標準的なビューのセットが用意されています:

これらのタイプには、それぞれのタイプ専用のプロパティと動作があります。詳しくはそれぞれのドキュメントをご覧ください。

さらに、Qt Quick Controlsには、HorizontalHeaderViewVerticalHeaderView のように、アプリケーションのスタイルに応じてスタイルが設定されるビューやデリゲートが追加されています。

ビューの装飾

ビューでは、headerfootersection プロパティなどの装飾プロパティによって、ビジュアルをカスタマイズすることができます。これらのプロパティにオブジェクト(通常は別のビジュアルオブジェクト)をバインドすることで、ビューは装飾可能になります。フッターには、ボーダーを表示するRectangle タイプや、リストの上にロゴを表示するヘッダーを含めることができます。

例えば、あるクラブがメンバーリストをブランドカラーで装飾したいとします。メンバーリストはmodeldelegate にはモデルのコンテンツが表示されます。

ListModel {
    id: nameModel
    ListElement { name: "Alice" }
    ListElement { name: "Bob" }
    ListElement { name: "Jane" }
    ListElement { name: "Harry" }
    ListElement { name: "Wendy" }
}
Component {
    id: nameDelegate
    Text {
        required property string name
        text: name
        font.pixelSize: 24
    }
}

クラブは、headerfooter プロパティにビジュアルオブジェクトをバインドすることで、メンバーリストを装飾することができます。ビジュアル・オブジェクトは、インラインで定義することも、別のファイルで定義することも、Component タイプで定義することもできます。

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"}
}

マウスとタッチのハンドリング

ビューはコンテンツのドラッグやフリックを処理しますが、個々のデリゲートとのタッチインタラクションは処理しません。デリゲートがタッチ入力に反応するためには、例えば、currentIndex 、適切なタッチハンドリングロジックを持つMouseArea 、デリゲートによって提供されなければならない。

highlightRangeModeStrictlyEnforceRange に設定されている場合、ビューは常にcurrentIndex が指定されたハイライト範囲内にあることを保証するため、currentIndex はビューのドラッグ/フリックによって影響を受けることに注意すること。

リストビューのセクション

ListView コンテンツはセクションにグループ化することができ、関連するリスト項目はそのセクションに従ってラベル付けされます。さらに、セクションはデリゲートで装飾することができます。

リストには、人の名前とその人が所属するチームを示すリストを含めることができる。

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 型にはsection attached プロパティがあり、隣接する型や関連する型をセクションにまとめることができる。section.property は、セクションとして使用するリスト型のプロパティを決定します。section.criteria は、セクション名の表示方法を指定でき、section.delegate は、ビューのdelegateプロパティに似ています。

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
            }
        }
    }
}

ビューのデリゲート

ビューはリスト内のアイテムを視覚的に表現するためにデリゲートを必要とします。ビューは、デリゲートによって定義されたテンプレートに従って、各アイテムのリストを視覚化します。モデル内のアイテムは、アイテムのプロパティと同様にindex プロパティを通してアクセスすることができます。

Component {
    id: petdelegate
    Text {
        id: label
        font.pixelSize: 24
        text: index === 0 ? type + " (default)" : type

        required property int index
        required property string type
    }
}

ビューデリゲートの配置

ビューの種類によって、アイテムの配置方法が決まります。ListView は、orientation に応じて、アイテムを直線上に配置します。一方、GridView は、アイテムを2次元のグリッドに配置することができます。xy に直接バインドすることはお勧めしません。ビューのレイアウト動作が、位置バインドよりも常に優先されるからです。

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

デリゲートがバインドされているリストビューには、デリゲートからListView.view プロパティを通してアクセスできます。同様に、GridView GridView.view はデリゲートから利用可能です。したがって、対応するモデルとそのプロパティは、ListView.view.model を通して利用可能です。さらに、モデル内で定義されたシグナルやメソッドにもアクセスできます。

このメカニズムは、例えば、複数のビューに同じデリゲートを使用したいが、装飾やその他の機能をビューごとに異なるものにしたい場合、そして、これらの異なる設定を各ビューのプロパティにしたい場合に便利です。同様に、モデルのいくつかのプロパティにアクセスしたり、表示したりすることも興味深いかもしれません。

以下の例では、デリゲートはモデルのプロパティlanguageを表示し、フィールドの1つの色はビューのプロパティfruit_colorに依存します。

Rectangle {
     width: 200; height: 200

    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 }
        }
    }

    ListView {
        property color fruit_color: "green"
        model: fruitModel
        delegate: fruitDelegate
        anchors.fill: parent
    }
}

モデル

データは、デリゲートがバインドすることができる名前付きデータロールを通して、デリゲートに提供されます。以下は、typeと ageという2つのロールを持つListModel 、これらのロールにバインドして値を表示するデリゲートを持つListView

import QtQuick

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
        // WRONG: Component.onCompleted: () => console.log(noise)
        // The above line would cause a ReferenceError
        // as there is no required property noise,
        // and the presence of the required properties prevents
        // noise from being injected into the scope
    }

    ListView {
        anchors.fill: parent
        model: myModel
        delegate: MyDelegate {}
    }
}

ほとんどの場合、必須プロパティを使用してデリゲートにモデルデータを渡す必要があります。デリゲートに必須プロパティが含まれている場合、QMLエンジンは必須プロパティの名前がモデルのロールの名前と一致するかどうかをチェックします。もしそうであれば、そのプロパティはモデルの対応する値にバインドされます。

まれに、必須プロパティとしてではなく、QMLコンテキストを通してモデルのプロパティを転送したい場合があります。デリゲートに必須プロパティが存在しない場合、名前付きロールはコンテキスト・プロパティとして提供されます:

import QtQuick

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
    }
}

コンテキスト・プロパティはツールからは見えず、Qt Quick Compilerによるコードの最適化を妨げます。コンテキスト・プロパティはツールからは見えず、Qt Quick Compilerがコードを最適化するのを妨げます。QML から QML コンテキストを明示的に入力する方法はありません。もしあなたのコンポーネントが QML コンテキスト経由でデータが渡されることを期待するのであれば、 ネイティブな手段によって適切なコンテキストが利用可能になっている場所でのみ、 QML コンテキストを使用することができます。これは、あなた自身のC++コードであったり、周囲の要素の特定の実装であったりします。逆に、必要なプロパティはQMLから、あるいはネイティブな手段を使って、さまざまな方法で設定することができます。したがって、QMLのコンテキストを経由してデータを渡すことは、コンポーネントの再利用性を低下させることになります。

モデルのプロパティとデリゲートのプロパティの間に名前の衝突がある場合、代わりに修飾されたモデル名でロールにアクセスすることができます。例えば、Text 型が(必須ではない)typeプロパティやageプロパティを持っていた場合、上記の例のテキストは、モデル項目からのtype値やage値の代わりに、それらのプロパティ値を表示することになります。この場合、デリゲートがモデルアイテムのプロパティ値を表示するようにするために、代わりにプロパティをmodel.type およびmodel.age として参照することができます。これを動作させるには、デリゲートにmodel プロパティを要求する必要があります(コンテキスト・プロパティを使用している場合を除く)。

モデル内のアイテムのインデックスを含む特別なインデックスロールもデリゲートで利用可能です。アイテムがモデルから削除された場合、このインデックスは-1に設定されることに注意してください。indexロールにバインドする場合は、indexが-1になる可能性、つまりアイテムが無効になっている可能性を考慮したロジックであることを確認してください。(通常、アイテムはすぐに破棄されますが、delayRemove 添付プロパティによって、ビューによっては破棄を遅らせることができます)。

モデルとして整数や配列を使用できることを覚えておいてください:

Repeater {
    model: 5
    Text {
        required property int modelData
        text: modelData
    }
}
Repeater {
    model: ["one", "two", "three"]
    Text {
        required property string modelData
        text: modelData
    }
}

このようなモデルは、デリゲートの各インスタンスに単一の匿名データを提供します。このデータ片にアクセスすることがmodelData を使う主な理由ですが、他のモデルもmodelData を提供します。

モデルロールを通じて提供されるオブジェクトには、空の名前を持つプロパティがあります。この匿名プロパティはmodelDataを保持します。さらに、モデル・ロールを介して提供されるオブジェクトには、modelDataという別のプロパティがあります。このプロパティは非推奨であり、modelDataを保持します。

モデル・ロールに加えて、modelDataロールが提供されています。modelDataロールは、modelDataプロパティと、modelロールを介して提供されるオブジェクトのanonymousプロパティと同じデータを保持します。

modelロールとmodelDataにアクセスする様々な手段との違いは以下の通りです:

  • 名前付きロールを持たないモデル(整数や文字列の配列など)は、modelDataロールを介してデータが提供されます。この場合、modelDataロールは必ずしもオブジェクトを含んでいるとは限りません。整数モデルの場合は、整数(現在のモデル項目のインデックス)が含まれます。文字列の配列の場合は、文字列が含まれます。モデルのロールにはオブジェクトが含まれますが、名前付きロールのプロパティは含まれません。モデルには通常のmodelDataと匿名プロパティが含まれます。
  • モデルに名前付きロールが1つしかない場合、modelDataロールには名前付きロールと同じデータが含まれます。これは必ずしもオブジェクトではなく、通常のように名前付きロールを名前付きプロパティとして含むわけではありません。モデルロールには、名前付きロールをプロパティとして持つオブジェクトと、この場合のmodelDataプロパティとanonymousプロパティが含まれます。
  • 複数のロールを持つモデルでは、modelDataロールは必須プロパティとしてのみ提供され、コンテキストプロパティとしては提供されません。これは、古いバージョンのQtとの後方互換性のためです。

modelのanonymousプロパティを使用すると、モデル・データと反応するロール名の両方を外部からプロパティとして受け取るデリゲートをきれいに記述することができます。roleとして空文字列を指定します。その場合、model[role] にアクセスするだけのバインディングは、期待通りの動作をします。この場合のために特別なコードを追加する必要はありません。

注: デリゲートが必須プロパティを含んでいる場合、モデルインデックスmodelDataロールはアクセスできません。

QMLでは、組み込みのQML型セットの中にいくつかのタイプのデータモデルを用意しています。さらに、Qt C++でモデルを作成し、QQmlEngine 、QMLコンポーネントで利用できるようにすることもできます。これらのモデルの作成については、C++モデルをQt Quick Viewsで使用するQML型を作成する、といった記事を参照してください。

モデルからのアイテムの配置は、Repeater を使って行うことができます。

リストモデル

ListModel はQMLで指定された型の単純な階層構造です。利用可能な役割は、 プロパティで指定します。ListElement

ListModel {
    id: fruitModel

    ListElement {
        name: "Apple"
        cost: 2.45
    }
    ListElement {
        name: "Orange"
        cost: 3.25
    }
    ListElement {
        name: "Banana"
        cost: 1.95
    }
}

上記のモデルには、nameと costの2つのロールがあります。これらは、例えばListView デリゲートによってバインドすることができます:

ListView {
    anchors.fill: parent
    model: fruitModel
    delegate: Row {
        id: delegate
        required property string name
        required property real cost

        Text { text: "Fruit: " + delegate.name }
        Text { text: "Cost: $" + delegate.cost }
    }
}

ListModel はJavaScriptで直接 を操作するメソッドを提供します。この場合、最初に挿入された項目が、モデルを使用しているビューで利用可能なロールを決定します。例えば、空の が作成され、JavaScript によって入力される場合、最初の挿入によって提供されるロールが、ビューに表示される唯一のロールとなります:ListModel ListModel

ListModel { id: fruitModel }
    ...
MouseArea {
    anchors.fill: parent
    onClicked: fruitModel.append({"cost": 5.95, "name":"Pizza"})
}

MouseArea がクリックされると、fruitModelコストと 名前の2つのロールを持つことになります。それ以降のロールが追加されたとしても、モデルを使用するビューで扱われるのは最初の2つのみです。モデルで使用可能なロールをリセットするには、ListModel::clear ()を呼び出します。

XMLモデル

XmlListModel を使用すると、XMLデータ・ソースからモデルを構築できます。ロールは 型を介して指定されます。型をインポートする必要があります。XmlListModelRole

import QtQml.XmlListModel

以下のモデルには、titlelinkpubDateの3つのロールがあります:

XmlListModel {
     id: feedModel
     source: "http://rss.news.yahoo.com/rss/oceania"
     query: "/rss/channel/item"
     XmlListModelRole { name: "title"; elementName: "title" }
     XmlListModelRole { name: "link"; elementName: "link" }
     XmlListModelRole { name: "pubDate"; elementName: "pubDate" }
}

query プロパティは、XmlListModel が XML 文書の各<item> に対してモデル項目を生成することを指定します。

RSSニュースのデモではXmlListModel を使用して RSS フィードを表示する方法を示しています。

オブジェクトモデル

ObjectModel には、ビューで使用するビジュアルアイテムが含まれています。 がビューで使用されるとき、 がすでにビジュアルデリゲート(アイテム)を含んでいるので、ビューはデリゲートを必要としません。ObjectModel ObjectModel

以下の例では、ListView に 3 つの色の付いた長方形を配置しています。

import QtQuick 2.0
import QtQml.Models 2.1

Rectangle {
    ObjectModel {
        id: itemModel
        Rectangle { height: 30; width: 80; color: "red" }
        Rectangle { height: 30; width: 80; color: "green" }
        Rectangle { height: 30; width: 80; color: "blue" }
    }

    ListView {
        anchors.fill: parent
        model: itemModel
    }
}

モデルとしての整数

整数は、ある数の型を含むモデルとして使用することができます。この場合、モデルはデータの役割を持ちません。

以下の例では、5つの要素を持つListView を作成します:

Item {
    width: 200; height: 250

    Component {
        id: itemDelegate

        Text {
            required property int index
            text: "I am item number: " + index
        }
    }

    ListView {
        anchors.fill: parent
        model: 5
        delegate: itemDelegate
    }

}

注釈 整数モデルの項目数の上限は、100,000,000 です。

モデルとしてのオブジェクト・インスタンス

オブジェクト・インスタンスは、単一のオブジェクト型を持つモデルを指定するために使用することができます。オブジェクトのプロパティはロールとして提供されます。

以下の例では、myTextテキストの色を示す 1 つの項目を持つリストを作成しています。デリゲート内のText型のcolorプロパティとの衝突を避けるために、完全修飾されたmodel.colorプロパティを使用していることに注意してください。

Rectangle {
    width: 200; height: 250

    Text {
        id: myText
        text: "Hello"
        color: "#dd44ee"
    }

    Component {
        id: myDelegate

        Text {
            required property var model
            text: model.color
        }
    }

    ListView {
        anchors.fill: parent
        anchors.topMargin: 30
        model: myText
        delegate: myDelegate
    }
}

C++データモデル

C++でモデルを定義し、QMLで利用できるようにすることができます。この仕組みは、既存のC++データモデルや複雑なデータセットをQMLに公開するのに便利です。

詳しくはUsing C++ Models with Qt Quick Viewsをご覧ください。

配列モデル

JavaScriptの配列や、様々な種類のQMLのリストをモデルとして使用することができます。リストの要素は、上で説明したルールに従って、modelとmodelDataとして利用できるようになります:整数や文字列のような単数のデータは、単数のmodelDataとして利用可能です。JavaScriptのオブジェクトやQObjectのような構造化されたデータは、構造化されたモデルやmodelDataとして利用できます。

個々のモデルの役割も、必須プロパティとして要求されれば利用可能になります。どのようなオブジェクトが配列に現れるかを事前に知ることはできないため、デリゲート内の必須プロパティは、おそらくundefined を必須型に強制することで入力されます。しかし、個々のモデルの役割は、QMLのコンテキストを経由して利用可能になることはありません。それらは、他のすべてのコンテキスト・プロパティに影を落とすことになります。

リピーター

リピーターは、モデルからのデータを使って、ポジショナーで使用するアイテムをテンプレートから作成します。リピーターとポジショナーを組み合わせることは、たくさんのアイテムをレイアウトする簡単な方法です。Repeater アイテムはポジショナーの中に置かれ、囲んでいるポジショナーが配置するアイテムを生成する。

各リピーターは、model プロパティを使って指定されたモデルからのデータの各要素を、リピーター内の子アイテムとして定義されたテンプレートアイテムと組み合わせることによって、多数のアイテムを作成します。アイテムの総数は、モデルのデータ量によって決まります。

次の例では、リピータをグリッド・アイテムと共に使用して、矩形アイテムのセットを配置しています。リピーターアイテムは、Gridアイテムが5×5で配置できるように、24個の長方形を作成します。

import QtQuick

Rectangle {
    width: 400; height: 400; color: "black"

    Grid {
        x: 5; y: 5
        rows: 5; columns: 5; spacing: 10

        Repeater { model: 24
                   Rectangle { width: 70; height: 70
                               color: "lightgreen"

                               Text { text: index
                                      font.pointSize: 30
                                      anchors.centerIn: parent } }
        }
    }
}

リピータによって作成されるアイテムの数は、count プロパティによって保持されます。このプロパティを設定して、作成されるアイテムの数を決定することはできません。代わりに、上記の例のように、モデルとして整数を使用します。

詳しくはQML データモデルを参照してください。

モデルが文字列リストの場合、デリゲートは文字列を保持する通常の読み取り専用のmodelData プロパティにも公開されます。例えば

Column {
    Repeater {
        model: ["apples", "oranges", "pears"]
        Text {
            required property string modelData
            text: "Data: " + modelData
        }
    }
}

リピータが作成するアイテムのテンプレートとしてデリゲートを使用することも可能です。これはdelegate プロパティを使用して指定します。

モデルデータの変更

モデル・データを変更するには、model プロパティに更新値を代入します。QMLListModel はデフォルトで編集可能ですが、C++のモデルは編集可能になるために setData() を実装する必要があります。整数モデルと JavaScript 配列モデルは読み込み専用です。

setData メソッドを実装したQAbstractItemModel ベースの C++ モデルがEditableModel という QML 型として登録されているとします。そして、このモデルに次のようにデータを書き込むことができます:

ListView {
    anchors.fill: parent
    model: EditableModel {}
    delegate: TextEdit {
        required property var model

        width: ListView.view.width
        height: 30
        text: model.edit
        Keys.onReturnPressed: model.edit = text
    }
}

注: edit のロールは、Qt::EditRole と等しくなります。 組み込みのロール名については、roleNames ()を参照してください。しかし、実際のモデルでは、カスタムロールを登録するのが普通でしょう。

注意 : モデル・ロールが必須プロパティにバインドされている場合、そのプロパティに代入してもモデルは変更されません。代わりにモデルへのバインディングが解除されます(他のプロパティへの代入が既存のバインディングを解除するのと同じです)。必須プロパティを使用してモデルデータを変更したい場合は、modelも必須プロパティにしてmodel.propertyNameに代入してください。

詳細については、「Qt Quick Views で C++ モデルを使用する」を参照してください。

トランジションの使用

トランジションは、ポジショナーに追加されたアイテム、ポジショナー内で移動したアイテム、ポジショナーから削除されたアイテムをアニメーション化するために使用できます。

アイテムを追加するトランジションは、ポジショナーの一部として作成されたアイテムや、ポジショナーの子になるために再ペアレントされたアイテムに適用されます。

アイテムを削除するトランジションは、ポジショナー内のアイテムが削除される場合と、ポジショナーから削除され、ドキュメント内の新しい親になる場合に適用されます。

注: アイテムの不透明度をゼロに変更しても、ポジショナーから消えることはありません。それらは、visibleプロパティを変更することで、削除したり再追加したりすることができます。

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