Roundhouse
Roundhouse

Reputation: 98

How to auto-sort QML ListElements in sections?

I have a ListModel with a few ListElemts in it. I want these Elements to be sorted on section in a ListView. When I run this:

Page {
    ListModel {
        id: listModel
        ListElement {
            title: "life"
            section: "a"
        }
        ListElement {
            title: "universe"
            section: "b"
        }
        ListElement {
            title: "and everything"
            section: "a"
        }
    }
    ListView {
        model: listModel
        section {
            property: 'section'
            delegate: SectionHeader {
                text: section
            }
        }
        delegate: ListItem {
            Label {
                text: model.title
            }
        }
    }
}

It gives something like:

        a
life
        b
universe
        a
and everything

However I want it to return something like:

        a
life
and everything
        b
universe

I can achieve this manually by arranging the ListElements in the model in the above order but, since the ListElements are dynamic, this isn't perfect. How can I make sure the ListElements are automatically sorted the way I want it?

Upvotes: 1

Views: 5369

Answers (3)

Stephen Quan
Stephen Quan

Reputation: 25981

I have been having success with using DelegateModel to create sorted views of my ListModel and have been rendering them in a ListView.

Below is an example:

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQml.Models 2.15

Page {
    anchors.fill: parent
    ListView {
        anchors.fill: parent
        section {
            property: 'section'
            delegate: Frame {
                width: ListView.view.width
                Text {
                    text: section
                }
            }
        }
        model: DelegateModel {
            id: sortDelegateModel
            model: listModel
            property var sortCompare: (a,b) => a.section.localeCompare(b.section) || a.title.localeCompare(b.title)
            onSortCompareChanged: Qt.callLater(sort)
            property int sortIndex: 0
            groups: [
                DelegateModelGroup {
                    id: visibleItems
                    name: "visible"
                    includeByDefault: true
                    onCountChanged: {
                        if (!visibleItems.count) Qt.callLater(sortDelegateModel.sort, 0);
                        if (visibleItems.count) Qt.callLater(sortDelegateModel.sort, sortDelegateModel.sortIndex);
                    }
                }]
            filterOnGroup: "visible"
            delegate: Frame {
                id: frame
                width: ListView.view.width
                background: Rectangle {
                    color: (frame.DelegateModel.visibleIndex & 1) ? "#f0f0f0" : "#e0e0e0"
                    border.color: "#c0c0c0"
                }
                RowLayout {
                    width: parent.width
                    Text {
                        text: (frame.DelegateModel.visibleIndex + 1)
                        color: "#c0c0c0"
                    }
                    Text {
                        Layout.fillWidth: true
                        Layout.preferredWidth: 200
                        text: title
                    }
                }
            }
            function getVisibleItem(index) {
                return visibleItems.get(index).model;
            }
            function findInsertIndex(item, head, tail) {
                if (head >= count) return head;
                let cmp = sortCompare(item, getVisibleItem(head));
                if (cmp <= 0) return head;
                cmp = sortCompare(item, getVisibleItem(tail));
                if (cmp === 0) return tail;
                if (cmp > 0) return tail + 1;
                while (head + 1 < tail) {
                    let mid = (head + tail) >> 1;
                    cmp = sortCompare(item, getVisibleItem(mid));
                    if (cmp === 0) return mid;
                    if (cmp > 0) head = mid; else tail = mid;
                }
                return tail;
            }
            function sort(startIndex) {
                startIndex = startIndex ?? 0;
                if (startIndex < 0) return;
                if (startIndex >= visibleItems.count) {
                    sortIndex = visibleItems.count;
                    return;
                }
                sortIndex = startIndex;
                for (let ts = Date.now(); sortIndex < visibleItems.count && Date.now() < ts + 50; sortIndex++) {
                    if (!sortIndex) continue;
                    let newIndex = findInsertIndex(getVisibleItem(sortIndex), 0, sortIndex - 1);
                    if (newIndex === sortIndex) continue;
                    visibleItems.move(sortIndex, newIndex, 1);
                }
                if (sortIndex < visibleItems.count) Qt.callLater(sort, sortIndex);
            }
            Component.onCompleted: Qt.callLater(sort)
        }
    }
    ListModel {
        id: listModel
        ListElement {
            title: "life"
            section: "a"
        }
        ListElement {
            title: "universe"
            section: "b"
        }
        ListElement {
            title: "and everything"
            section: "a"
        }
    }
}

You can try it online!

I have also refactored a SortDelegateModel which is wrapper on the DelegateModel here:

Upvotes: 0

milad
milad

Reputation: 94

for use section you must create Qstandart item model and set sortRole(section) and append your item to qstandard item and and item to model. after it your must set model->sort(column) function to sort your model.

look to below code:

and c++ code will be: you have a model class like this

#include <QStandardItemModel>

enum TypeContact
{
    TitleRole = Qt::UserRole + 1,
    SectionRole = Qt::UserRole + 2,

};

class MyModel : public QStandardItemModel
{
    Q_OBJECT

public:

    MyModel(QObject *parent = 0)
    : QStandardItemModel(parent)
    {
    }


protected:
    QHash<int, QByteArray> roleNames() const
    {
        QHash<int, QByteArray> m_roles;

        m_roles[TitleRole] = "titleRole";
        m_roles[SectionRole] = "sectionRole";

        return m_roles;
    }
};

and in your another class and where you want to append your data to model write:

//in .h file
QStandardItemModel *model {nullptr};    

//.in .cpp file
model = new MyModel(this);
model->clear();
model->setSortRole(sectionRole);

and after that insert your data.

QStandardItem *item = new QStandardItem;
item->setData( "life", titleRole);
item->setData( "a", sectioRole);
model->appedRow(item);

QStandardItem *item2 = new QStandardItem;
item2->setData( "universal", titleRole);
item2->setData( "b", sectioRole);
model->appedRow(item2);

QStandardItem *item3 = new QStandardItem;
item3->setData( "and every think", titleRole);
item3->setData( "a", sectioRole);
model->appedRow(item3);

and sort model like this:

model->sort(0);

qml code will be:

ListView {
    anchors.fill: parent
    model: MyModel

    delegate: Label {
        text: titleRole
        width: 100
        height: 50
    }

    section.property: "sectionRole"
    section.criteria: ViewSection.FullString
    section.delegate: Label {
        text: section
        font.bold: true
        font.pixelSize: 25
    }
}

i dont test it exactly but it must be work. if you search about Qstandard item model and how to append it to model and set it to model you know exactly what you must to do. but importan thing in here setSortRole() and sort() was.

thanks

Upvotes: 0

You could define a function sortModel and call in in Component.onCompleted

ListModel {
    id: listModel

    ListElement {
        title: "life"
        section: "a"
    }
    ListElement {
        title: "universe"
        section: "b"
    }
    ListElement {
        title: "and everything"
        section: "a"
    }

    function sortModel()
    {
        for(var i=0; i<count; i++)
        {
            for(var j=0; j<i; j++)
            {
                if(get(i).section == get(j).section)
                    move(i,j,1)
                break
            }
        }
    }

    Component.onCompleted: sortModel()
}

Upvotes: 1

Related Questions