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