Below is the DelegateModel
I am using to filter a ListModel
.I have used five DelegateModelGroup
's.The filtering is working for visible
, folder
and falsie
DeleagteModelGroup
.Now i want to implement a new DelegateModelGroup
, visibility
with which i want to be visible only the model items that have visibility role false
.I tried a lot examples but I had not have the desired result.How can I configure the update function
so I can filter the results to visibility
role?
visible
DelegateModelGroup
is filtered for false
.
folder
DelegateModelGroup
is filtering the items if folder
role is empty string or not.
falsie
DeleagetModelGroup
filters the items so they displayed in pages of 35 items.
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQml.Models 2.4
DelegateModel {
property string roleThree
property string roleTwo
property string role
property int from: 0
property int to: 10
onRoleChanged: Qt.callLater(update)
onFromChanged: Qt.callLater(update)
onToChanged: Qt.callLater(update)
groups: [
DelegateModelGroup {
id: newItems
name: "new"
includeByDefault: true
onCountChanged:{
Qt.callLater(update)
}
},
DelegateModelGroup {
id: visibleItems
name: "visible"
includeByDefault: false
},
DelegateModelGroup {
id: folderItems
name: "folder"
includeByDefault: false
},
DelegateModelGroup {
id: visibilityItems
name: "visibility"
includeByDefault: false
},
DelegateModelGroup {
id: falsieItems
name: "falsie"
includeByDefault: false
}
]
filterOnGroup: "falsie"
function update() {
newItems.setGroups(0, newItems.count, [ "new" ] );
for (let i = 0; i < newItems.count; i++) {
let item = newItems.get(i).model;
let visible = item[role] === false ;
let folder = item[roleTwo] !== "";
let visibility = item[roleThree] ===false;
if (!visible && !folder && visibility) continue;
newItems.setGroups(i, 1, [ "new", "visible" ]);
visibleItems.setGroups(i, 1, [ "visible","folder"]);
}
visibleItems.setGroups(from, to - from , ["visible","falsie"]);
}
Component.onCompleted: Qt.callLater(update)
}
For any question fill free to comment.
Thanks in advance.
I've updated the pattern I've used for DelegateModelGroup.
I tend to have "all" which is a master list of all items, "new" which is a list of incoming items, and then DelegateModelGroups for your business rules.
I have two functions
The reason why we are processing the newItems from count-1 backward to 0 is every item being processed will get removed from the newItems list. If we went in ascending order it messes up the for loop and it processes every second record instead of every record. So, by processing it in descending order, we remove items from the end and get to process every item.
DelegateModelGroup {
groups: [
DelegateModelGroup {
id: allItems
name: "all"
includeByDefault: true
},
DelegateModelGroup {
id: newItems
name: "new"
includeByDefault: true
onChanged: Qt.callLater(updateNew)
},
DelegateModelGroup { name: "visible" }
DelegateModelGroup { name: "folder" }
DelegateModelGroup { name: "visibility" }
DelegateModelGroup { name: "falsie" }
]
filterOnGroup: "visible"
function invalidate() {
allItems.setGroups(0, allItems.count, ["all","new"]);
}
function updateNew() {
if (!newItems.count) return;
for (let index = newItems.count-1; index >= 0; index--) {
let item = newItems.get(index).model;
let grps = [ "all" ];
if (item[role] === false) grps.push("visible");
if (item[rowTwo] !== "") grps.push("folder");
if (item[roleThree] === false) grps.push("visibility");
newItems.setGroups(index, 1, grps);
}
}
}
[EDIT]
In the following example, I have created FilterDelegateModel.qml
// FilterDelegateModel.qml
DelegateModel {
property string role
property string filter
property double from
property double to
property bool invert
property int page
property int pageSize
//...
}
which handles both strings (for indexOf searches) and numbers (for from...to range). I made role
, filter
, from
, to
, invert
, page
and pageSize
as configurable inputs, which, when changed, will cause the "visible"
and "page"
DelegateModelGroups to refresh.
role
can be "country"
, "city"
, or "population"
. If it's the first two, it will limit the results by "filter"
using indexOf()
. If you set it to "population"
it will use "from"
and "to"
to limit your results by range. The results will appear in the "visible"
DelegateModelGroup.
page
and pageSize
is used to further filter the results by what results is visible on the current page. These result will appear in the "page"
DelegateModelGroup.
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
ColumnLayout {
anchors.fill: parent
Label { text: "Role" }
ComboBox { id: roleCombo; model: [ "country", "city", "population" ] }
ColumnLayout {
visible: roleCombo.currentText.match(/country|city/)
Label { text: "Search" }
TextField { id: search }
}
ColumnLayout {
visible: roleCombo.currentText === 'population'
Label { text: "Range %1 to %2".arg(range.first.value).arg(range.second.value) }
RangeSlider { id: range; from: 0; to: 2000; stepSize: 10; first.value: 0; second.value: 2000 }
}
CheckBox { id: chkInvert; text: "Invert" }
Label { text: "Page" }
ComboBox { id: pageCombo; model: ["1","2","3","4","5"] }
Label { text: "Cities (count:%1 of %2)"
.arg(filterDelegateModel.pageCount)
.arg(filterDelegateModel.visibleCount) }
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
header: RowLayout {
width: ListView.view.width
Text { Layout.preferredWidth: 30; Layout.fillWidth: true; text: "Index" }
Text { Layout.preferredWidth: 100; Layout.fillWidth: true; text: "Country" }
Text { Layout.preferredWidth: 100; Layout.fillWidth: true; text: "City"
}
Text { Layout.preferredWidth: 70; Layout.fillWidth: true; text: "Population" }
}
model: FilterDelegateModel {
id: filterDelegateModel
model: Cities { }
role: roleCombo.currentText
filter: search.text
invert: chkInvert.checked
from: range.first.value
to: range.second.value
page: pageCombo.currentIndex
delegate: RowLayout {
width: ListView.view.width
Text { Layout.preferredWidth: 30; Layout.fillWidth: true; text: DelegateModel.visibleIndex + 1 }
Text { Layout.preferredWidth: 100; Layout.fillWidth: true; text: model.country }
Text { Layout.preferredWidth: 100; Layout.fillWidth: true; text: model.city }
Text { Layout.preferredWidth: 70; Layout.fillWidth: true; text: model.population.toFixed(1) }
}
}
}
}
}
// FilterDelegateModel.qml
import QtQuick
import QtQuick.Controls
import QtQml.Models
DelegateModel {
property string role
onRoleChanged: Qt.callLater(invalidate)
property string filter
onFilterChanged: Qt.callLater(invalidate)
property double from: 0
onFromChanged: Qt.callLater(invalidate)
property double to: 1.0
onToChanged: Qt.callLater(invalidate)
property bool invert: false
onInvertChanged: Qt.callLater(invalidate)
property int page: 0
onPageChanged: Qt.callLater(invalidate)
property int pageSize: 10
onPageSizeChanged: Qt.callLater(invalidate)
readonly property alias visibleCount: visibleItems.count
readonly property alias pageCount: pageItems.count
groups: [
DelegateModelGroup {
id: allItems
name: "all"
includeByDefault: true
},
DelegateModelGroup {
id: newItems
name: "new"
includeByDefault: true
onChanged: Qt.callLater(updateNew)
},
DelegateModelGroup {
id: visibleItems
name: "visible"
onChanged: Qt.callLater(updatePage)
},
DelegateModelGroup {
id: pageItems
name: "page"
}
]
filterOnGroup: "page"
function invalidate() {
allItems.setGroups(0, allItems.count, [ "all","new" ] );
}
function updateNew() {
for (let index = newItems.count-1; index >= 0; index--) {
let item = newItems.get(index);
let groups = ["all"];
let visible = true;
if (role) {
let value = item.model[role];
switch (typeof(value)) {
case 'string':
visible = !filter || value.toLowerCase().indexOf(filter.toLowerCase()) !== -1;
break;
case 'number':
visible = value >= from && value <= to;
break;
}
}
if (invert) visible = !visible;
if (visible) groups.push("visible");
newItems.setGroups(index, 1, groups);
}
}
function updatePage() {
let pageStart = page * pageSize;
let pageEnd = pageStart + pageSize;
if (pageStart < 0) pageStart = 0;
if (pageStart >= visibleItems.count) return;
if (pageEnd > visibleItems.count) pageEnd = visibleItems.count;
visibleItems.setGroups(pageStart, pageEnd-pageStart, ["all", "visible", "page"] );
}
}
// Cities.qml
import QtQuick
import QtQuick.Controls
ListModel {
property var cities: `Afghanistan,Kabul,39835428
Argentina,Buenos Aires,45993511
Australia,Canberra,25766340
Bangladesh,Dhaka,166303498
Brazil,Brasília,213993437
Canada,Ottawa,37742154
China,Beijing,1444216107
Colombia,Bogotá,51107063
Czech Republic,Prague,10708981
Egypt,Cairo,104258327
France,Paris,65480710
Germany,Berlin,83190556
Greece,Athens,10715549
India,New Delhi,1393409038
Indonesia,Jakarta,273523615
Iran,Tehran,83290141
Italy,Rome,60550075
Japan,Tokyo,125960000
Mexico,Mexico City,129163276
Netherlands,Amsterdam,17141544
Nigeria,Abuja,214028302
Pakistan,Islamabad,233500636
Peru,Lima,33405000
Philippines,Manila,111046913
Poland,Warsaw,38028278
Portugal,Lisbon,10191409
Russia,Moscow,146599183
Saudi Arabia,Riyadh,35541063
South Africa,Pretoria,61622110
South Korea,Seoul,51780579
Spain,Madrid,46723749
Sri Lanka,Colombo,21413249
Sweden,Stockholm,10171524
Switzerland,Bern,8741992
Thailand,Bangkok,69799978
Turkey,Ankara,85042739
Ukraine,Kyiv,44622516
United Kingdom,London,66647112
United States,Washington D.C.,332915073
Vietnam,Hanoi,97338579`.split(/\n/).map(c => c.split(/,/))
Component.onCompleted: {
for (let c of cities) append( { "country":c[0], "city":c[1], "population":c[2]/1000000 } )
}
}
You can Try it Online!