Reputation: 98
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
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
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
Reputation: 5336
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