Is it possible to show only certain indexes of a QML listview?
Asked Answered
M

4

10

Is it possible to show only certain indexes or a range of indexes in QML listviews?

I have a listmodel that has a bunch of info that I am reusing. Would it be possible to have a list showing, for example, only indices 5 to 8?

Mcclary answered 9/3, 2015 at 11:35 Comment(0)
A
19

It would be interesting to present a pure QML approach to the problem. This is not the shorted path, for sure, but it is a solution.

The approach is based on the DelegateModel available in the models QML module. It reads in the documentation:

The DelegateModel type encapsulates a model and the delegate that will be instantiated for items in the model.

It is usually not necessary to create a DelegateModel. However, it can be useful for manipulating and accessing the modelIndex when a QAbstractItemModel subclass is used as the model. Also, DelegateModel is used together with Package to provide delegates to multiple views, and with DelegateModelGroup to sort and filter delegate items.

DelegateModel is really a powerful type with a lot of functionalities (see the linked documentation for details). Two key properties of DelegateModel are groups and filterOnGroup. The former is basically a list of DelegateModelGroup which defines the items to be filtered or not. The latter is used to apply a specific filter, i.e. choose a specific DelegateModelGroup contained in groups, by simply setting the property to the name of the chosen group.

Note that referring to VisualDataModel or DelegateModel is the same since the first is provided for compatibility reasons (the same applies to VisualDataGroup w.r.t. DelegateModelGroup).

Summing up, it is possible to filter a model in full QML in this way:

  1. Create a model as a source of filtered models
  2. Feed the model to a VisualDataModel/DelegateModel
  3. Define a VisualDataGroup/DelegateModelGroup (or more than one) - includeByDefault set to false to avoid automatic addition of all items from the original model
  4. Define policies to populate the groups
  5. Set filterOnGroup to the chosen group
  6. Set the view model to the VisualDataModel model

In the next example, for simplicity, I just populate the group once, during the Component.onCompleted event handler. As said, policies should be chosen and that's depends on the specific use case.

In the example only the items with key role equal to 0 are added to the group key0 which is the one shown in the ListView. The checklist described above is highlighted in the code.

import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Window 2.0

ApplicationWindow {
    title: qsTr("DelegateModel test")
    width: 200
    height: 350
    visible: true

    ListView {
        id: displayListView
        anchors.fill: parent
        spacing: 5
        //
        model: displayDelegateModel             // 6
    }

    ListModel {                                 // 1
        id: myModel
        ListElement { vis: "One"; key: 0; }
        ListElement { vis: "two"; key: 1; }
        ListElement { vis: "Three"; key: 0; }
        ListElement { vis: "Four"; key: 0; }
        ListElement { vis: "Five"; key: 1; }
        ListElement { vis: "Six"; key: 1; }
        ListElement { vis: "Seven"; key: 0; }
    }

    VisualDataModel {
        id: displayDelegateModel

        delegate:  Rectangle {
            anchors.left: parent.left
            anchors.right: parent.right
            height: 25
            color: "steelblue"

            Text {
                text: vis
                anchors.centerIn: parent
                font.bold: true
                font.pixelSize: 20
            }
        }

        model: myModel                          // 2

        groups: [
            VisualDataGroup {                   // 3
                includeByDefault: false         // NECESSARY TO AVOID AUTOADDITION
                name: "key0"
            }
        ]

        filterOnGroup: "key0"                   // 5

        Component.onCompleted: {                // 4
            var rowCount = myModel.count;
            items.remove(0,rowCount);
            for( var i = 0;i < rowCount;i++ ) {
                var entry = myModel.get(i);
                if(entry.key == 0) {
                    items.insert(entry, "key0");
                }
            }
        }
    }
}
Anastasia answered 9/3, 2015 at 21:56 Comment(5)
thanks for the example, i have one question though. items.insert(entry, "key0") inserts the "key0" property to the item so that it can be sorted on later again?Mcclary
hey uhm, i just realized... is this delegateModel new? Because there was no support for treemodels in QML, and in the docs i am reading that it supports treemodel operationsMcclary
forgot to add the "@name" :D,, hmmm its not working,, nevermind this commentMcclary
Depends on the naming but it is supported since a long time ago. The "key0" acts like a filter: everything is inside that group can be sorted out from the full list.Anastasia
Is there a way to do a simple filter by role?Masquerade
N
3

Yes, it is possible. You need to override QSortFilterProxyModel::filterAcceptRow method.

MyFilterModel::filterAcceptsRow ( int source_row, const QModelIndex & source_parent ) const
{
  if ( source_row > 5 && source_row < 8 )
    return true;

  return false;
}
//...
MyFilterModel *filter = new MyFilterModel();
filter->setSourceMoldel( yourSourceModel );
view->setModel( filter );
Nipissing answered 9/3, 2015 at 13:1 Comment(0)
D
3

You could accomplish that by setting the delegate visibility to false on certain conditions:

  Grid {
    anchors.fill: parent
    rows: 4
    columns: 5
    spacing: 5
    Repeater {
      model: 20
      delegate: Rectangle {
        width: 50
        height: 50
        visible: index >= 5 && index <= 8 // show only certain indices
        Text {
          anchors.centerIn: parent
          text: index
        }
      }
    }
  }

enter image description here

It will have some overhead of creating hidden items in memory thou, so not optimal if you deal with very large models and showing only small portions of them.

Digitize answered 9/3, 2015 at 13:4 Comment(6)
and how to detect scrolling request to change the index?Huan
@DrlSherifOmran if you bind to an expression that references properties with change notifications, if their values change, it will cause visibility to be reevaluated automatically. Although for scrolling you have ready to use components.Digitize
This worked a treat! I used this to create paging. Something like visible: currentPage == 1 ? index < 6 : index >= 6 where currentPage is a root property. No caveats. Just worked.Weis
@HeathRaftery - there are better options for that, you can use filtering so that you don't end up with scores of invisible objects just taking up memory.Digitize
Scores? Better? Taking up memory? None of those seem to apply my application, so perhaps not in this case?Weis
@HeathRaftery for a few items don't bother. If you have dozens, hundreds or thousands... that's a different story.Digitize
B
1

This is another answer based on DelegateModel that:

  1. Implements two DelegateModelGroups "all" and "visible"
  2. The "all" property group will contain all records from your source model
  3. The "visible" property group will contain a subselection of records
  4. Also, I implement role, from, and to properties (for property binding)
  5. Whenever any property changes it calls an update() method
  6. Whenever update() is called, the "all" group is rescanned and only matching/filtered records are placed in the "visible" group

To demonstrate this I put 8 animals into a ListModel. I define a RangeSlider for controlling the from and to parameters that will control which elements I want to see in the DelegateModel.:

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
    ListModel {
        id: animals
        ListElement { aid: 1; animal: "Aardvark" }
        ListElement { aid: 2; animal: "Bear" }
        ListElement { aid: 3; animal: "Cat" }
        ListElement { aid: 4; animal: "Dog" }
        ListElement { aid: 5; animal: "Eagle" }
        ListElement { aid: 6; animal: "Fruitbat" }
        ListElement { aid: 7; animal: "Goat" }
        ListElement { aid: 8; animal: "Hedgehog" }
    }
    ColumnLayout {
        anchors.fill: parent
        Label { text: qsTr("Select Range") }
        RangeSlider { id: range; from: 1; to: 8; first.value: 3; second.value: 8 }
        Label { text: qsTr("Animals %1 .. %2").arg(range.first.value).arg(range.second.value) }
        ListView {
            Layout.fillWidth: true
            Layout.fillHeight: true
            model: DelegateModel {
                model: animals
                property string role: "aid"
                property real from: range.first.value
                property real to: range.second.value
                onRoleChanged: Qt.callLater(update)
                onFromChanged: Qt.callLater(update)
                onToChanged: Qt.callLater(update)
                delegate: Label { text: `${aid} ${animal}` }
                groups: [
                    DelegateModelGroup { id: all; name: "all"; includeByDefault: true },
                    DelegateModelGroup { name: "visible" }
                ]
                filterOnGroup: "visible"
                function update() {
                    all.setGroups(0, all.count, [ "all" ] );
                    for (let i = 0; i < all.count; i++) {
                        const item = all.get(i).model;
                        const visible = item[role] >= from && item[role] <= to;
                        if (visible) all.setGroups(i, 1, [ "all", "visible" ]);
                    }
                }
                Component.onCompleted: Qt.callLater(update)
            }
        }
    }
}

You can Try it Online!

Backandforth answered 16/10, 2022 at 3:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.