QMLモデル・ビュー構築入門

QMLにおけるモデル・ビュー構造の理解

Qtフレームワークの進化は著しく、QMLはその重要な構成要素として注目されています。QMLはもともとQt 4.7でモバイル開発向けに導入されましたが、現在ではデスクトップアプリケーション開発にも広く対応しています。

QMLは視覚表現とロジックの分離を実現し、HTMLとJavaScriptの関係に似ています。QtのC++ベースのMVCアーキテクチャと同様に、QMLでもモデル・ビュー機構が提供されています。

主要なビュー構成要素

  • GridView - 2次元グリッド形式のデータ表示
  • ListView - 縦方向にスクロール可能なリスト表示
  • PathView - カスタムパスに沿った柔軟なデータ配置

実装例と機能説明

1. 動的アイテム操作可能なGridView

以下のコードは、追加ボタンでアイテムを追加し、各アイテムをクリックで削除できるGridViewの実装例です。


import QtQuick 2.0

Rectangle {
    width: 480
    height: 300

    ListModel {
        id: itemModel
        ListElement { value: 0 }
        // 初期アイテム9個
        ListElement { value: 9 }
    }

    Rectangle {
        anchors.bottom: parent.bottom
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.margins: 20
        height: 40
        color: "#53d769"

        Text {
            anchors.centerIn: parent
            text: "アイテム追加"
        }

        MouseArea {
            anchors.fill: parent
            onClicked: {
                itemModel.append({"value": ++counter.current})
            }
        }
        property var counter: ({ current: 9 })
    }

    GridView {
        anchors.fill: parent
        anchors.margins: 20
        anchors.bottomMargin: 80
        clip: true
        model: itemModel
        cellWidth: 45
        cellHeight: 45
        delegate: itemDelegate
    }

    Component {
        id: itemDelegate
        Rectangle {
            width: 40
            height: 40
            gradient: Gradient {
                GradientStop { position: 0.0; color: "#f8306a" }
                GradientStop { position: 1.0; color: "#fb5b40" }
            }

            Text {
                anchors.centerIn: parent
                font.pixelSize: 10
                text: value
            }

            MouseArea {
                anchors.fill: parent
                onClicked: {
                    if (!parent.parent.GridView.delayRemove) {
                        itemModel.remove(index)
                    }
                }
            }

            GridView.onRemove: SequentialAnimation {
                PropertyAction { target: parent; property: "GridView.delayRemove"; value: true }
                NumberAnimation {
                    target: parent
                    property: "scale"
                    to: 0
                    duration: 250
                    easing.type: Easing.InOutQuad
                }
                PropertyAction { target: parent; property: "GridView.delayRemove"; value: false }
            }

            GridView.onAdd: NumberAnimation {
                target: parent
                property: "scale"
                from: 0
                to: 1
                duration: 250
                easing.type: Easing.InOutQuad
            }
        }
    }
}

2. 詳細表示可能なListView

以下のコードは、リスト項目クリック時に詳細情報を表示するListViewの実装例です。


import QtQuick 2.0

Item {
    width: 300
    height: 480

    Rectangle {
        anchors.fill: parent
        gradient: Gradient {
            GradientStop { position: 0.0; color: "#4a4a4a" }
            GradientStop { position: 1.0; color: "#2b2b2b" }
        }
    }

    ListView {
        id: planetList
        anchors.fill: parent
        delegate: planetDelegate
        model: planetData
    }

    ListModel {
        id: planetData
        ListElement {
            name: "水星"
            imageSource: "images/mercury.jpeg"
            details: "太陽系で最も小さい惑星で、太陽に最も近い軌道を回っています。"
        }
        // 他の惑星データも同様に定義
    }

    Component {
        id: planetDelegate
        Item {
            id: wrapper
            width: parent.width
            height: 30

            Rectangle {
                anchors.left: parent.left
                anchors.right: parent.right
                anchors.top: parent.top
                height: 30
                color: "#333"

                Text {
                    anchors.left: parent.left
                    anchors.verticalCenter: parent.verticalCenter
                    anchors.leftMargin: 4
                    font.pixelSize: parent.height-4
                    color: '#fff'
                    text: name
                }
            }

            Rectangle {
                id: icon
                width: 26
                height: 26
                anchors.right: parent.right
                anchors.top: parent.top
                anchors.rightMargin: 2
                anchors.topMargin: 2
                color: "yellow"

                Image {
                    anchors.fill: parent
                    fillMode: Image.PreserveAspectFit
                    source: imageSource
                }
            }

            MouseArea {
                anchors.fill: parent
                onClicked: parent.state = "expanded"
            }

            Item {
                id: detailView
                anchors.top: icon.bottom
                anchors.left: parent.left
                anchors.right: parent.right
                anchors.bottom: parent.bottom
                opacity: 0

                Rectangle {
                    anchors.fill: parent
                    gradient: Gradient {
                        GradientStop { position: 0.0; color: "#fed958" }
                        GradientStop { position: 1.0; color: "#fecc2f" }
                    }
                    border.color: '#000000'
                    border.width: 2

                    Text {
                        anchors.fill: parent
                        anchors.margins: 5
                        clip: true
                        wrapMode: Text.WordWrap
                        color: '#1f1f21'
                        font.pixelSize: 12
                        text: details
                    }
                }
            }

            Rectangle {
                id: closeBtn
                anchors.right: parent.right
                anchors.top: parent.top
                anchors.rightMargin: 2
                anchors.topMargin: 2
                width: 26
                height: 26
                color: "#157efb"
                opacity: 0

                MouseArea {
                    anchors.fill: parent
                    onClicked: wrapper.state = ""
                }
            }

            states: [
                State {
                    name: "expanded"
                    PropertyChanges { target: wrapper; height: parent.height }
                    PropertyChanges {
                        target: icon
                        width: parent.width
                        height: parent.width
                        anchors.rightMargin: 0
                        anchors.topMargin: 30
                    }
                    PropertyChanges { target: detailView; opacity: 1 }
                    PropertyChanges { target: closeBtn; opacity: 1 }
                    PropertyChanges {
                        target: wrapper.ListView.view
                        contentY: wrapper.y
                        interactive: false
                    }
                }
            ]

            transitions: [
                Transition {
                    NumberAnimation {
                        duration: 200
                        properties: "height,width,anchors.rightMargin,anchors.topMargin,opacity,contentY"
                    }
                }
            ]
        }
    }
}

3. カード効果付きPathView

以下のコードは、カスタムパスに沿ってカードが配置され、アニメーション効果を持つPathViewの実装例です。


import QtQuick 2.6

Rectangle {
    id: root
    width: 480
    height: 300

    PathView {
        anchors.fill: parent
        delegate: cardDelegate
        model: 100

        path: Path {
            startX: root.width / 2
            startY: 0

            PathAttribute { name: "angle"; value: -45.0 }
            PathAttribute { name: "scaleFactor"; value: 0.5 }
            PathAttribute { name: "depth"; value: 0 }

            PathLine { x: root.width/2; y: root.height*0.4 }
            PathPercent { value: 0.48 }

            PathLine { x: root.width/2; y: root.height*0.5 }
            PathAttribute { name: "angle"; value: 0.0 }
            PathAttribute { name: "scaleFactor"; value: 1.0 }
            PathAttribute { name: "depth"; value: 100 }

            PathLine { x: root.width/2; y: root.height*0.6 }
            PathPercent { value: 0.52 }

            PathLine { x: root.width/2; y: root.height }
            PathAttribute { name: "angle"; value: 45.0 }
            PathAttribute { name: "scaleFactor"; value: 0.5 }
            PathAttribute { name: "depth"; value: 0 }
        }

        pathItemCount: 17
        preferredHighlightBegin: 0.5
        preferredHighlightEnd: 0.5
    }

    Component {
        id: cardDelegate

        Rectangle {
            id: card
            width: 64
            height: 64
            antialiasing: true

            gradient: Gradient {
                GradientStop { position: 0.0; color: "#2ed5fa" }
                GradientStop { position: 1.0; color: "#2467ec" }
            }

            visible: PathView.onPath
            scale: PathView.scaleFactor
            z: PathView.depth

            transform: Rotation {
                axis { x: 1; y: 0; z: 0 }
                angle: PathView.angle
                origin.x: width/2
                origin.y: height/2
            }

            Text {
                anchors.centerIn: parent
                text: index
            }
        }
    }
}

タグ: QML QtQuick GridView ListView PathView

7月18日 17:56 投稿