QML(Qt)でListViewの使い方

今までの記事でもさっくり使ってるのですが、改めてListViewの使い方をまとめて見ました。


/// ListViewの動作ポイント ///
・Model情報を解析して表示可能範囲のアイテムが1つずつ動的にインスタンス化される
・delegateに設定されたレイアウトが使い回される
・動的にインスタンス化されるのでいつもとidの値などの扱いが違う

サンプルで上位に自分のインスタンスを「_layout1」をクリックイベント内で「clickList」へ渡してます。
見た目は共通になるイメージですが、実体はそれぞれのアイテムで別になります。
そもそも「Listview.delegate: ListLayout{ ... }」としているので他の言語ならクラスをnewするイメージです。
逆にエレメントのプロパティに他のエレメントのidを指定する時はポインタを渡してる感じですね。

リストのアイテムは画面外にでると設定状態にあわせて破棄されます。

こんな感じ
 

このListViewとModelとDelegateの関係がちゃんと理解できると「Repeater」を使用して、Column/Row/Gridの利用幅が広がります。
以前の記事の「QML(Qt)でコンテキストメニューを表示する」のコンテキストメニューの部分で利用しています。



/// サンプル動作 ///
・リストのアイテムをクリックすると画面中心に情報を表示します。
・赤いボタンを押すとリストへフォーカスが移動してカーソルキーでハイライトが移動します。
・赤いボタンを押すと現在のハイライトアイテムの情報を表示します。


/// サンプルのポイント ///
・クリックしたアイテムの情報を取得する
・ハイライトの共通化
・クリックしたアイテムをハイライトする


/// クリックしたアイテムの情報を取得する ///
 「ItemLayout.qml」内でMouseAreaを設定してクリックイベントをシグナルで上位に伝えれるようにしています。
 その時、引数を定義すれば自分の保持している文字列などを渡せます。
 delegateに指定したItemLayout{ /* ここ */} の中にStep1のイベントを受けて更に上位が持っているメソッドを呼び出せます。
 この時、上にも書いてますがItemLayoutに指定したidをメソッドに渡すとクリックしたアイテムを操作できます。
 

/// ハイライトの共通化 ///
リストの現在のアイテムをハイライトする事ができます。
ListViewのヘルプや「Models and Views: ListView Example」を見ると簡単にできます。
ただ、この方法だとListViewが複数配置されているとハイライト用の情報を複数記述しないといけなくなってしまい冗長です。

なので共通化する方法です。

で、何が問題かと言うと。
下にListViewのヘルプにあるサンプルを用意しました。
ハイライトされているアイテムを目立たせるための四角の位置やサイズをListViewの情報を使ってるので共通化がしにくいのです。

公式サンプル転記
 Component {
     id: highlight
     Rectangle {
         width: 180; height: 40
         color: "lightsteelblue"; radius: 5
         y: list.currentItem.y
         Behavior on y {
             SpringAnimation {
                 spring: 3
                 damping: 0.2
             }
         }
     }
 }

 ListView {
     id: list
     width: 180; height: 200
     model: ContactModel {}
     delegate: Text { text: name }

     highlight: highlight
     highlightFollowsCurrentItem: false
     focus: true
 }

で、ハイライト用のコンポーネントはエレメントとして別ファイルに分けます「ItemHightlight.qml」がそうです。
ListView側は「highlight: ItemHightlight{ ... }」としてエレメントをidではなくインスタンスを生成して指定します。
こおすれば各ListViewに独立した情報を指定できるのです。



/// クリックしたアイテムをハイライトする ///
「ListView.currentIndex」を指定すればOKで簡単です。
でもクリックしたアイテムがリストの中で何番目かどのようにして知るか?です。
それも実はすごい簡単で「ListView.indexAt(x, y)」メソッドを使えば簡単にできます。




/// サンプルのスクリーンショット ///
 


// main.qml

import QtQuick 1.0

Rectangle {
    id: _root
    width: 350
    height: 250

    // リストの情報を表示
    function clickList(list, object, itemText){
        // クリックアイテムをハイライトする
        list.currentIndex = list.indexAt(object.x, object.y);
        // デバッグ表示
        _debugText.text = "num : " + object.number
                      + "\ntext : " + object.text
                      + "\nitemText : " + itemText;
    }

    // リストの配置
    Row{
        spacing: 3

        ListView{
            id: _listLeft
            width: _root.width / 2
            height: _root.height
            // ハイライト設定
            highlight: ItemHightlight{
                _listObject: _listLeft
            }
            // モデル
            model: Items1{}
            // デリゲート
            delegate: ItemLayout{
                // インスタンスごとに生成される範囲
                id: _layout1
                // 表示レイアウトへ具体的な値をセットする
                number: _number
                text: arrangeText(_text)
                // 表示するデータをアレンジもOK
                function arrangeText(text){
                    return text + "(in left)";
                }
                // クリックイベント
                onClicked: {
                    // こんな風に更に上位へデータや自分のインスタンスを渡すのもできる
                    clickList(_listLeft, _layout1, itemText);
                }
            }
        }

        ListView{
            id: _listRight
            width: _root.width / 2
            height: _root.height
            // ハイライト設定
            highlight: ItemHightlight{
                _listObject: _listRight
            }
            // モデル
            model: Items2{}
            // デリゲート
            delegate: ItemLayout{
                // インスタンスごとに生成される範囲
                id: _layout2
                // 表示レイアウトへ具体的な値をセットする
                number: _number
                text: arrangeText(_text)
                // 表示するデータをアレンジもOK
                function arrangeText(text){
                    return text + "(in right)";
                }
                // クリックイベント
                onClicked: {
                    // こんな風に更に上位へデータや自分のインスタンスを渡すのもできる
                    clickList(_listRight, _layout2, itemText);
                }
            }
        }
    }
    // 左のリストへフォーカスセットして現在の情報表示
    Rectangle{
        anchors.horizontalCenter: parent.horizontalCenter
        y: 0
        width: 70
        height: 20
        color: "#dd0000"
        Text{
            anchors.centerIn: parent
            text: "Focus left"
        }
        MouseArea{
            anchors.fill: parent
            onClicked: {
                _listLeft.forceActiveFocus();
                clickList(_listLeft, _listLeft.currentItem
                          , "current selected");
            }
        }
    }
    // 右のリストへフォーカスセットして現在の情報表示
    Rectangle{
        anchors.horizontalCenter: parent.horizontalCenter
        y: 22
        width: 70
        height: 20
        color: "#dd0000"
        Text{
            anchors.centerIn: parent
            text: "Focus right"
        }
        MouseArea{
            anchors.fill: parent
            onClicked: {
                _listRight.forceActiveFocus();
                clickList(_listRight, _listRight.currentItem
                          , "current selected");
            }
        }
    }

    // 情報表示
    Rectangle{
        x: _debugText.x
        y: _debugText.y
        width: _debugText.width
        height: _debugText.height
        color: "#555555"
    }
    Text{
        id: _debugText
        anchors.centerIn: parent
        text: ""
        color: "#ffffff"
    }
}


リストの表示アイテムのレイアウト
// ItemLayout.qml

import QtQuick 1.0

Rectangle {
    id: _root
    width: 160
    height: 60
    // 枠線
    border.color: "#dddddd"
    border.width: 1

    // 実際に表示する部分へのエイリアス
    property alias number: _layoutNumber.text
    property alias text: _layoutText.text

    // 上位への通知用のシグナル
    signal clicked(string itemText)

    // 表示アイテムを配置
    Row{
        anchors.centerIn: parent
        spacing: 5
        Text{
            id: _layoutNumber
            width: 20
            text: ""
        }
        Text{
            id: _layoutText
            width: 80
            text: ""
        }
    }

    // クリック判定
    MouseArea{
        anchors.fill: parent
        onClicked: {
            // クリックシグナルを上位へ
            _root.clicked(_layoutNumber.text + ":" + _layoutText.text);
        }
    }
}


ハイライト用の情報
// ItemHightlight.qml

import QtQuick 1.0

Rectangle {
    id: _root

    property variant _listObject: null

    width: (_listObject.currentItem === null) ? 0 : _listObject.currentItem.width
    height: (_listObject.currentItem === null) ? 0 : _listObject.currentItem.height
    y: (_listObject.currentItem === null) ? 0 : _listObject.currentItem.y;
    color: "#dddd00"
    Behavior on y { SpringAnimation { spring: 2; damping: 0.1 } }
}
リストへ表示する内容(左用)
// Items1.qml

import QtQuick 1.0

ListModel{
    id: _items
    ListElement{
        _number: 0
        _text: "first"
    }
    ListElement{
        _number: 1
        _text: "second"
    }
    ListElement{
        _number: 2
        _text: "third"
    }
    ListElement{
        _number: 3
        _text: "fourth"
    }
    ListElement{
        _number: 4
        _text: "fifth"
    }
    ListElement{
        _number: 5
        _text: "sixth"
    }
    ListElement{
        _number: 6
        _text: "seventh"
    }
}


リストへ表示する内容(右用)
// Items2.qml

import QtQuick 1.0

ListModel{
    id: _items
    ListElement{
        _number: 0
        _text: "FIRST"
    }
    ListElement{
        _number: 1
        _text: "SECOND"
    }
    ListElement{
        _number: 2
        _text: "THIRD"
    }
    ListElement{
        _number: 3
        _text: "FOURTH"
    }
    ListElement{
        _number: 4
        _text: "FIFTH"
    }
    ListElement{
        _number: 5
        _text: "SIXTH"
    }
    ListElement{
        _number: 6
        _text: "SEVENTH"
    }
}