QML Dynamic View Ordering チュートリアル 4 - アイテムの並べ替え

ビューの項目を並べ替える方法はドラッグ&ドロップだけではありません。DelegateModel を使えば、モデルデータに基づいて項目を並べ替えることもできます。そのために、DelegateModel インスタンスをこのように拡張します:

    DelegateModel {
        id: visualModel
        property var lessThan: [
            function(left, right) { return left.name < right.name },
            function(left, right) { return left.type < right.type },
            function(left, right) { return left.age < right.age },
            function(left, right) {
                if (left.size == "Small")
                    return true
                else if (right.size == "Small")
                    return false
                else if (left.size == "Medium")
                    return true
                else
                    return false
            }
        ]

        property int sortOrder: orderSelector.selectedIndex
        onSortOrderChanged: items.setGroups(0, items.count, "unsorted")

        function insertPosition(lessThan, item) {
            let lower = 0
            let upper = items.count
            while (lower < upper) {
                const middle = Math.floor(lower + (upper - lower) / 2)
                const result = lessThan(item.model, items.get(middle).model)
                if (result) {
                    upper = middle
                } else {
                    lower = middle + 1
                }
            }
            return lower
        }

        function sort(lessThan) {
            while (unsortedItems.count > 0) {
                const item = unsortedItems.get(0)
                const index = insertPosition(lessThan, item)

                item.groups = "items"
                items.move(item.itemsIndex, index)
            }
        }

        items.includeByDefault: false
        groups: DelegateModelGroup {
            id: unsortedItems
            name: "unsorted"

            includeByDefault: true
            onChanged: {
                if (visualModel.sortOrder == visualModel.lessThan.length)
                    setGroups(0, count, "items")
                else
                    visualModel.sort(visualModel.lessThan[visualModel.sortOrder])
            }
        }
        model: PetsModel {}
        delegate: dragDelegate
    }
チュートリアル

DelegateModel のアイテムは、DelegateModelGroup タイプで表されるグループにフィルタリングされます。通常、モデル内のすべてのアイテムはデフォルトのitems グループに属しますが、このデフォルトは includeByDefault プロパティで変更することができます。ソートを実装するために、まずアイテムをソートされていないグループに追加し、そこからアイテムグループ内のソートされた位置に移動させます。そのために、アイテム・グループのincludeByDefaultをクリアし、新しいグループ名「unsorted」に設定します。

        items.includeByDefault: false
        groups: DelegateModelGroup {
            id: unsortedItems
            name: "unsorted"

            includeByDefault: true
        }

まずitemsグループの中でソートされていない最初のitemsを挿入する位置を探し、itemsグループにitemsを移動させてから、あらかじめ決められたインデックスに移動させ、ソートされていないグループが空になるまで繰り返します。

アイテムの挿入位置を見つけるために、get ()関数で未ソートグループからアイテムのハンドルを要求します。このハンドルのmodelプロパティを通して、その項目のデリゲートインスタンスで利用可能な同じモデルデータにアクセスし、他の項目と比較して相対位置を決定することができます。

        function insertPosition(lessThan, item) {
            let lower = 0
            let upper = items.count
            while (lower < upper) {
                const middle = Math.floor(lower + (upper - lower) / 2)
                const result = lessThan(item.model, items.get(middle).model)
                if (result) {
                    upper = middle
                } else {
                    lower = middle + 1
                }
            }
            return lower
        }

        function sort(lessThan) {
            while (unsortedItems.count > 0) {
                const item = unsortedItems.get(0)
                const index = insertPosition(lessThan, item)

                item.groups = "items"
                items.move(item.itemsIndex, index)
            }
        }

ソート関数の lessThan 引数は、リストの順序を決定する比較関数です。この例では、以下のいずれかになります:

        property var lessThan: [
            function(left, right) { return left.name < right.name },
            function(left, right) { return left.type < right.type },
            function(left, right) { return left.age < right.age },
            function(left, right) {
                if (left.size == "Small")
                    return true
                else if (right.size == "Small")
                    return false
                else if (left.size == "Medium")
                    return true
                else
                    return false
            }
        ]

新しい項目がソートされていないDelegateModel に追加されるたびに、ソートがトリガーされます。DelegateModelGroup onChanged ハンドラによって通知されます。もしソート関数が選択されていなければ、単純に未ソートのグループからアイテムグループへすべてのアイテムを転送し、そうでなければ選択されたソート関数でソートを呼び出す。

        groups: DelegateModelGroup {
            id: unsortedItems
            name: "unsorted"

            includeByDefault: true
            onChanged: {
                if (visualModel.sortOrder == visualModel.lessThan.length)
                    setGroups(0, count, "items")
                else
                    visualModel.sort(visualModel.lessThan[visualModel.sortOrder])
            }
        }

最後に、選択されたソート順が変更された場合、itemsグループからソートされていないグループにすべてのアイテムを移動させることで、リストの完全な再ソートをトリガーすることができます。これは、DelegateModelGroup onChanged ハンドラをトリガーし、アイテムを正しい順序でitemsグループに戻します。DelegateModelGroup onChanged ハンドラーは再帰的に起動されないので、ソート中に起動されても問題はない。

        property int sortOrder: orderSelector.selectedIndex
        onSortOrderChanged: items.setGroups(0, items.count, "unsorted")

サンプルプロジェクト @ code.qt.io

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.