c++listviewqmlqsortfilterproxymodel

QML ListView delegate selection not updated when out of scroll


I am working on a window called "Configuration Manager" that allows a user to :

The listview displays filtered/sorted items. When the user click on one item of the listview, I change the color of this item. If he clicks another item, the color of the previous item come back to the default one and the new item has its color change.

The data is stored in the backend in a class inheriting from QAsbtractListModel and I am using another class inheriting from QSortFilterProxyModel to show only element filtered and sorted.

The listview can show up to 100 elements, so I add a scrollbar to be able to select any delegate displayed on it.

If i select a first item, the color is updating normally. If I click on another item next to the first one, the change of color of these 2 items (the previous and new one) is okay also. I have a problem if a select an item on top, scroll on the bottom of the listview and select another item. In this way, the previous item is not updated and is still with the selected color. On the backend, with some qDebug, I can confirm the previous item is not updated because the method setData is not called like it is when I select an item (to update color on GUI and a boolean on the backend for this element on the model).

I am not talking here of the multiple selection with CTRL or SHIFT key of the keyboard but the problem is the same. Only some items are updated.

I think I am missing some properties of the listview because it seems not all delegate are in the cache (so not updated when scrolling and select a new item).

I add the code below, I can’t do shorter for now. Thank you in advance for any help.

Main files

#include <QGuiApplication>
#include <QQuickView>
#include <QQmlContext>
#include <QQmlApplicationEngine>

#include "FilenameListModel.h"
#include "FilterProxyModel.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;

    FilenameListModel model;
    FilterProxyModel filterModel;
    filterModel.setSourceModel(&model);

    engine.rootContext()->setContextProperty("_FilteredRunwayConfigurationFilesModel", &filterModel);

    engine.load(QUrl("qrc:/main.qml"));
    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.5
import QtQml.Models 2.12
import QtQuick.Layouts 1.12

ApplicationWindow
{
    id: root

    property bool isSaving: false

    minimumWidth: 600
    minimumHeight: 400
    visible: true
    modality: Qt.ApplicationModal

    Component.onCompleted:
    {
        // Update the backend with current saving mode
        _FilteredRunwayConfigurationFilesModel.isSavingMode = isSaving
    }

    DelegateModel
    {
        id: delegateModel

        model: _FilteredRunwayConfigurationFilesModel

        delegate: Rectangle
        {
            id: item_delegate

            property bool checked: false

            width: ListView.view.width - 10
            height:      name.implicitHeight

            color: model.isChecked? "lightgreen" : "lightblue"
            radius: 10

            onCheckedChanged:
            {
                listViewFilter.checkChanged()
            }

            Connections
            {
                target: listViewFilter

                // Called after the slot onCheckOne of the listViewFilter component
                onCheckOne:
                {
                    model.isChecked = (idx === index)
                    checked = model.isChecked
                }

                onCheckMul:
                {
                    if (idx > listViewFilter.mulBegin)
                    {
                        model.isChecked = (index >= listViewFilter.mulBegin && index <= idx)
                        checked = model.isChecked
                    }
                    else
                    {
                        model.isChecked = (index <= listViewFilter.mulBegin && index >= idx)
                        checked = model.isChecked
                    }
                }
            }

            Connections
            {
                target: textFilterString

                // Called after this signal has been catched by the textFilterString component
                onTextChanged:
                {
                    // Reset all status
                    model.isChecked = false
                    checked = model.isChecked
                }
            }

            Rectangle
            {
                id: leftRectangle
                anchors
                {
                    top: parent.top
                    bottom: parent.bottom
                    left: parent.left
                    right: rightRectangle.left
                }
                color: "transparent"

                Text
                {
                    id: name
                    anchors.fill: parent
                    anchors.margins: 5
                    verticalAlignment: Qt.AlignVCenter
                    horizontalAlignment: Qt.AlignHCenter

                    text: "%1".arg(model.name)
                }
            }

            Rectangle
            {
                id: rightRectangle
                anchors
                {
                    top: parent.top
                    bottom: parent.bottom
                    right: parent.right
                }
                color: "transparent"
                width: parent.width * 0.3

                Text
                {
                    id: date
                    anchors.fill: parent
                    anchors.margins: 5
                    verticalAlignment: Qt.AlignVCenter
                    horizontalAlignment: Qt.AlignHCenter

                    text:
                    {
                        var text = "%1".arg(model.date)
                        return text
                    }
                }
            }

            MouseArea
            {
                anchors.fill: parent
                acceptedButtons: Qt.LeftButton

                onClicked:
                {
                    if (!isSaving)
                    {
                        switch(mouse.modifiers)
                        {
                        case Qt.ControlModifier:
                            model.isChecked = !model.isChecked;
                            checked = model.isChecked
                            break
                        case Qt.ShiftModifier:
                            listViewFilter.checkMul(index)
                            break
                        default:
                            listViewFilter.checkOne(index)
                            listViewFilter.mulBegin = index
                            break
                        }
                    }
                    else
                    {
                        textFilterString.text = model.name
                    }
                }
            }
        }
    }

    ColumnLayout
    {
        anchors.fill: parent
        anchors.margins: 5

        TextField
        {
            id: textFilterString
            Layout.fillWidth: true
            Layout.preferredHeight: implicitHeight
            horizontalAlignment: Qt.AlignHCenter
            verticalAlignment: Qt.AlignVCenter
            placeholderText: isSaving ? "Write the filename with the '.atols' extension" : "Write the begin of the filename"

            onTextChanged:
            {
                _FilteredRunwayConfigurationFilesModel.filenameBeginning = text;
                listViewFilter.checkChanged()
            }
        }

        RowLayout
        {
            Layout.fillWidth: true
            Layout.preferredHeight: textFilterString.height
            Layout.alignment: Qt.AlignHCenter

            Button
            {
                id: buttonLoad
                Layout.preferredWidth: 100
                Layout.preferredHeight: parent.height
                text: "LOAD"
                enabled: false
                visible: !isSaving

                onClicked:
                {
                    _FilteredRunwayConfigurationFilesModel.loadConfigurationFile(listViewFilter.selectedIndex)
                }
            }

            Button
            {
                id: buttonDelete
                Layout.preferredWidth: 100
                Layout.preferredHeight: parent.height
                text: "DELETE"
                enabled: false
                visible: !isSaving

                onClicked:
                {
                    _FilteredRunwayConfigurationFilesModel.deleteConfigurationFile(listViewFilter.selectedIndex)
                }
            }

            Button
            {
                id: buttonSave
                Layout.preferredWidth: 100
                Layout.preferredHeight: parent.height
                text: "SAVE"
                enabled: textFilterString.text
                visible: isSaving

                onClicked:
                {
                    // TODO
                }
            }
        }

        ListView
        {
            id: listViewFilter

            property int mulBegin: 0
            property var selectedIndex: []

            signal checkOne(int idx)
            signal checkMul(int idx)

            // Called first when executing from a delegate item
            onCheckOne:
            {
                listViewFilter.mulBegin = idx
            }

            function checkChanged()
            {
                listViewFilter.selectedIndex = []

                // Check how many items are selected in the list
                var nSelected = 0
                for (var i = 0; i < listViewFilter.count; i++)
                {
                    if (listViewFilter.contentItem.children[i] && listViewFilter.contentItem.children[i].checked)
                    {
                        nSelected++

                        listViewFilter.selectedIndex.push(i)
                    }
                }

                // Enable or disable buttons to load or delete the selected items
                var loadEnable = false
                var deleteEnable = false

                switch(nSelected)
                {
                case 1:     // One item selected => Load and Delete available
                    loadEnable = true
                    deleteEnable = true
                    break
                case 0:     // No items selected => Nothing available
                    loadEnable = false
                    deleteEnable = false
                    break
                default:    // Multiple items selected => Only Delete available
                    loadEnable = false
                    deleteEnable = true
                    break
                }

                buttonLoad.enabled = loadEnable
                buttonDelete.enabled = deleteEnable
            }

            Layout.fillWidth: true
            Layout.fillHeight: true

            model: delegateModel

            ScrollBar.vertical: ScrollBar
            {
                anchors.top: parent.top
                anchors.bottom: parent.bottom
                anchors.right: parent.right
                policy: listViewFilter.contentHeight > listViewFilter.height ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff
                active: true
            }
        }

        TextField
        {
            Layout.fillWidth: true
            Layout.preferredHeight: textFilterString.height
            text: "No files with this filter"
            verticalAlignment: Qt.AlignTop
            horizontalAlignment: Qt.AlignHCenter
            visible: listViewFilter.count === 0
        }
    }
}

Base model

#ifndef FILENAMELISTMODEL_H
#define FILENAMELISTMODEL_H

#include <QAbstractListModel>
#include <QDate>

class FilenameListModel : public QAbstractListModel
{
    Q_OBJECT

public:
    explicit FilenameListModel(QObject *parent = nullptr);

public:
    enum Roles
    {
        NameRole = Qt::UserRole,
        DateRole,
        CheckedRole
    };

    int rowCount(const QModelIndex& parent) const override;
    QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
    virtual bool setData(const QModelIndex &index, const QVariant &value, int role) override;

    QHash<int, QByteArray> roleNames() const override;

private:
    struct Entry
    {
        QString name;
        QDate date;
        bool isChecked;
    };

    QVector<Entry> m_entries;
};

#endif // FILENAMELISTMODEL_H
#include "FilenameListModel.h"

#include <QDebug>

FilenameListModel::FilenameListModel(QObject *parent)
    : QAbstractListModel (parent)
{
    m_entries = {
        Entry{"Tata", QDate(2022, 5, 14)},
        Entry{"Tititata", QDate(2020, 3, 12)},
        Entry{"Toto", QDate(2018, 1, 10)},
        Entry{"Tata", QDate(2019, 2, 11)},
        Entry{"Toto", QDate(2021, 4, 13)},
        Entry{"Titi", QDate(2023, 6, 15)},
        Entry{"Tititata", QDate(2020, 3, 12)},
        Entry{"Toto", QDate(2018, 1, 10)},
        Entry{"Tata", QDate(2019, 2, 11)},
        Entry{"Toto", QDate(2021, 4, 13)},
        Entry{"Titi", QDate(2023, 6, 15)},
        Entry{"Tititata", QDate(2020, 3, 12)},
        Entry{"Toto", QDate(2018, 1, 10)},
        Entry{"Tata", QDate(2019, 2, 11)},
        Entry{"Toto", QDate(2021, 4, 13)},
        Entry{"Titi", QDate(2023, 6, 15)},
        Entry{"Tititata", QDate(2020, 3, 12)},
        Entry{"Toto", QDate(2018, 1, 10)},
        Entry{"Tata", QDate(2019, 2, 11)},
        Entry{"Toto", QDate(2021, 4, 13)},
        Entry{"Titi", QDate(2023, 6, 15)},
        Entry{"Tititata", QDate(2020, 3, 12)},
        Entry{"Toto", QDate(2018, 1, 10)},
        Entry{"Tata", QDate(2019, 2, 11)},
        Entry{"Toto", QDate(2021, 4, 13)},
        Entry{"Titi", QDate(2023, 6, 15)},
        Entry{"Tititata", QDate(2020, 3, 12)},
        Entry{"Toto", QDate(2018, 1, 10)},
        Entry{"Tata", QDate(2019, 2, 11)},
        Entry{"Toto", QDate(2021, 4, 13)},
        Entry{"Titi", QDate(2023, 6, 15)},
        Entry{"Tititata", QDate(2020, 3, 12)},
        Entry{"Toto", QDate(2018, 1, 10)},
        Entry{"Tata", QDate(2019, 2, 11)},
        Entry{"Toto", QDate(2021, 4, 13)},
        Entry{"Titi", QDate(2023, 6, 15)},
        Entry{"Tititata", QDate(2020, 3, 12)},
        Entry{"Toto", QDate(2018, 1, 10)},
        Entry{"Tata", QDate(2019, 2, 11)},
        Entry{"Toto", QDate(2021, 4, 13)},
        Entry{"Titi", QDate(2023, 6, 15)},
        Entry{"Tititata", QDate(2020, 3, 12)},
        Entry{"Toto", QDate(2018, 1, 10)},
        Entry{"Tata", QDate(2019, 2, 11)},
        Entry{"Toto", QDate(2021, 4, 13)},
        Entry{"Titi", QDate(2023, 6, 15)},
        Entry{"Tititata", QDate(2020, 3, 12)},
        Entry{"Toto", QDate(2018, 1, 10)},
        Entry{"Tata", QDate(2019, 2, 11)},
        Entry{"Toto", QDate(2021, 4, 13)},
        Entry{"Titi", QDate(2023, 6, 15)},
        Entry{"Tititata", QDate(2020, 3, 12)},
        Entry{"Toto", QDate(2018, 1, 10)},
        Entry{"Tata", QDate(2019, 2, 11)},
        Entry{"Toto", QDate(2021, 4, 13)},
        Entry{"Titi", QDate(2023, 6, 15)}
    };
}

int FilenameListModel::rowCount( const QModelIndex& parent) const
{
    if (parent.isValid())
        return 0;

    return m_entries.count();
}

QVariant FilenameListModel::data(const QModelIndex &index, int role) const
{
    if ( !index.isValid() )
        return QVariant();

    const Entry &entry = m_entries.at(index.row());

    switch(role)
    {
    case NameRole:
        return entry.name;
    case DateRole:
        return entry.date.toString("yyyy-MM-dd - hh::mm::ss");
    case CheckedRole:
    {
        return entry.isChecked;
    }
    default:
        return QVariant();
    }
}

bool FilenameListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    // Method called when QML change a parameter of the model
    if (!hasIndex(index.row(), index.column(), index.parent()) || !value.isValid())
        return false;

    if (m_entries[index.row()].isChecked != value.toBool())
        qDebug() << "backend:" << index.row() << "/" << value.toBool();

    m_entries[index.row()].isChecked = value.toBool();

    emit dataChanged(index, index, { role });
    return true;
}

QHash<int, QByteArray> FilenameListModel::roleNames() const
{
    static QHash<int, QByteArray> mapping {
        {NameRole, "name"},
        {DateRole, "date"},
        {CheckedRole, "isChecked"}
    };
    return mapping;
}

Proxy model

#ifndef FILTERPROXYMODEL_H
#define FILTERPROXYMODEL_H

#include <QSortFilterProxyModel>

class FilterProxyModel : public QSortFilterProxyModel
{
    Q_OBJECT

    Q_PROPERTY(QString filenameBeginning WRITE setFilenameBeginning READ getFilenameBeginnning NOTIFY filenameBeginningChanged)
    Q_PROPERTY(bool isSavingMode WRITE setIsSavingMode NOTIFY isSavingModeChanged)

public:
    explicit FilterProxyModel(QObject *parent = nullptr);

    void setFilenameBeginning(QString newStr);
    QString getFilenameBeginnning();
    void setIsSavingMode(bool newMode);

public slots:
    Q_INVOKABLE void deleteConfigurationFile(QVector<int> indexToDelete);
    Q_INVOKABLE void loadConfigurationFile(QVector<int> indexToLoad);

signals:
    void filenameBeginningChanged();
    void isSavingModeChanged();

protected:
    bool lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const override;
    bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;

private:
    QString m_filenameBeginning;
    bool m_isSavingMode;
};

#endif // FILTERPROXYMODEL_H
#include "FilterProxyModel.h"

#include "FilenameListModel.h"
#include <QDebug>

FilterProxyModel::FilterProxyModel(QObject *parent)
    : QSortFilterProxyModel (parent)
{
    m_isSavingMode = false;
    sort(0, Qt::SortOrder::DescendingOrder);
}

bool FilterProxyModel::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const
{
    const QDate leftDate = sourceLeft.data(FilenameListModel::DateRole).toDate();
    const QDate rightDate = sourceRight.data(FilenameListModel::DateRole).toDate();

    return leftDate < rightDate;
}

bool FilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
    // In saving mode, keep all files on the list
    if (m_isSavingMode)
        return true;

    // Otherwise, filter the rows
    const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);

    const QString name = index.data(FilenameListModel::NameRole).toString();

    return strncmp(name.toLower().toStdString().c_str(), m_filenameBeginning.toLower().toStdString().c_str(), m_filenameBeginning.size()) == 0;
}

void FilterProxyModel::setFilenameBeginning(QString newStr)
{
    m_filenameBeginning = newStr;

    emit filenameBeginningChanged();
    invalidateFilter();
}

QString FilterProxyModel::getFilenameBeginnning()
{
    return m_filenameBeginning;
}

void FilterProxyModel::deleteConfigurationFile(QVector<int> indexToDelete)
{
    for (int i = 0; i < indexToDelete.size(); i++)
    {
        qDebug() << this->data(this->index(indexToDelete[i], 0), FilenameListModel::Roles::NameRole);
    }
}

void FilterProxyModel::loadConfigurationFile(QVector<int> indexToLoad)
{
    // The input should be with only one element on it
    qDebug() << this->data(this->index(indexToLoad[0], 0), FilenameListModel::Roles::DateRole);
}

void FilterProxyModel::setIsSavingMode(bool newMode)
{
    m_isSavingMode = newMode;
}

Solution

  • Thanks to @JarMan advice, I simplify the QML part in the way there is no more algorithmic part on the delegate of the listview. Now the backend update itself the role CheckedRole of each item on the model I want. And the scroll is not anymore a problem.

    I put here the modification of the code:

    Main files

    import QtQuick 2.12
    import QtQuick.Window 2.12
    import QtQuick.Controls 2.5
    import QtQml.Models 2.12
    import QtQuick.Layouts 1.12
    
    ApplicationWindow
    {
        id: root
    
        property bool isSaving: false
    
        minimumWidth: 600
        minimumHeight: 400
        visible: true
        modality: Qt.ApplicationModal
    
        Component.onCompleted:
        {
            // Update the backend with current saving mode
            _FilteredRunwayConfigurationFilesModel.isSavingMode = isSaving
        }
    
        DelegateModel
        {
            id: delegateModel
    
            model: _FilteredRunwayConfigurationFilesModel
    
            delegate: Rectangle
            {
                id: item_delegate
    
                width:  ListView.view.width - 10
                height: name.implicitHeight
    
                color: model.isChecked? "lightgreen" : "lightblue"
                radius: 10
    
                Rectangle
                {
                    id: leftRectangle
                    anchors
                    {
                        top: parent.top
                        bottom: parent.bottom
                        left: parent.left
                        right: rightRectangle.left
                    }
                    color: "transparent"
    
                    Text
                    {
                        id: name
                        anchors.fill: parent
                        anchors.margins: 5
                        verticalAlignment: Qt.AlignVCenter
                        horizontalAlignment: Qt.AlignHCenter
    
                        text: "%1".arg(model.name)
                    }
                }
    
                Rectangle
                {
                    id: rightRectangle
                    anchors
                    {
                        top: parent.top
                        bottom: parent.bottom
                        right: parent.right
                    }
                    color: "transparent"
                    width: parent.width * 0.3
    
                    Text
                    {
                        id: date
                        anchors.fill: parent
                        anchors.margins: 5
                        verticalAlignment: Qt.AlignVCenter
                        horizontalAlignment: Qt.AlignHCenter
                        text: model.date
                    }
                }
    
                MouseArea
                {
                    anchors.fill: parent
                    acceptedButtons: Qt.LeftButton
    
                    onClicked:
                    {
                        if (!isSaving)
                        {
                            switch(mouse.modifiers)
                            {
                            case Qt.ControlModifier:
                                listViewFilter.selectCtrl(index)
                                break
                            case Qt.ShiftModifier:
                                listViewFilter.selectShift(index)
                                break
                            default:
                                listViewFilter.selectOne(index)
                                break
                            }
                        }
                        else
                        {
                            textFilterString.text = model.name
                        }
                    }
                }
            }
        }
    
        ColumnLayout
        {
            anchors.fill: parent
            anchors.margins: 5
    
            TextField
            {
                id: textFilterString
                Layout.fillWidth: true
                Layout.preferredHeight: implicitHeight
                horizontalAlignment: Qt.AlignHCenter
                verticalAlignment: Qt.AlignVCenter
                placeholderText: isSaving ? "Write the filename with the '.atols' extension" : "Write the begin of the filename"
    
                onTextChanged:
                {
                    _FilteredRunwayConfigurationFilesModel.filenameBeginning = text;
                }
            }
    
            RowLayout
            {
                Layout.fillWidth: true
                Layout.preferredHeight: textFilterString.height
                Layout.alignment: Qt.AlignHCenter
    
                Button
                {
                    id: buttonLoad
                    Layout.preferredWidth: 100
                    Layout.preferredHeight: parent.height
                    text: "LOAD"
                    enabled: false
                    visible: !isSaving
    
                    onClicked:
                    {
                        _FilteredRunwayConfigurationFilesModel.loadConfigurationFile(listViewFilter.selectedIndex)
                    }
                }
    
                Button
                {
                    id: buttonDelete
                    Layout.preferredWidth: 100
                    Layout.preferredHeight: parent.height
                    text: "DELETE"
                    enabled: false
                    visible: !isSaving
    
                    onClicked:
                    {
                        _FilteredRunwayConfigurationFilesModel.deleteConfigurationFile(listViewFilter.selectedIndex)
                    }
                }
    
                Button
                {
                    id: buttonSave
                    Layout.preferredWidth: 100
                    Layout.preferredHeight: parent.height
                    text: "SAVE"
                    enabled: textFilterString.text
                    visible: isSaving
    
                    onClicked:
                    {
                        // TODO
                    }
                }
            }
    
            ListView
            {
                id: listViewFilter
    
                property int indexBegin: -1
                property int indexEnd: -1
                property var selectedIndex: []
    
                signal selectOne(int idx)
                signal selectCtrl(int idx)
                signal selectShift(int idx)
    
                // Called first when executing from a delegate item
                onSelectOne:
                {
                    indexBegin = indexEnd = idx
                    selectedIndex = [indexBegin]
                    selectedIndexChanged()
                }
    
                onSelectShift:
                {
                    // Add all index between begin and end on the list
                    if (indexBegin === -1)
                    {
                        indexBegin = indexEnd = idx
                    }
                    else
                    {
                        indexEnd = idx
                    }
                    selectedIndex.length = 0
                    for (var i = Math.min(indexBegin, indexEnd); i <= Math.max(indexBegin, indexEnd); i++)
                        selectedIndex.push(i)
    
                    // Define the new begin with the last item selected
                    indexBegin = idx
                    selectedIndexChanged()
    
                }
    
                onSelectCtrl:
                {
                    // Add the index to the list of not already on it
                    indexBegin = idx
                    if (selectedIndex.indexOf(idx) === -1)
                        selectedIndex.push(idx)
                    selectedIndexChanged()
                }
    
                onSelectedIndexChanged:
                {
                    _FilteredRunwayConfigurationFilesModel.setCheckedIndexes(selectedIndex)
                }
    
                function checkChanged()
                {
                    listViewFilter.selectedIndex = []
    
                    // Check how many items are selected in the list
                    var nSelected = 0
                    for (var i = 0; i < listViewFilter.count; i++)
                    {
                        if (listViewFilter.contentItem.children[i] && listViewFilter.contentItem.children[i].checked)
                        {
                            nSelected++
    
                            listViewFilter.selectedIndex.push(i)
                        }
                    }
    
                    // Enable or disable buttons to load or delete the selected items
                    var loadEnable = false
                    var deleteEnable = false
    
                    switch(nSelected)
                    {
                    case 1:     // One item selected => Load and Delete available
                        loadEnable = true
                        deleteEnable = true
                        break
                    case 0:     // No items selected => Nothing available
                        loadEnable = false
                        deleteEnable = false
                        break
                    default:    // Multiple items selected => Only Delete available
                        loadEnable = false
                        deleteEnable = true
                        break
                    }
    
                    buttonLoad.enabled = loadEnable
                    buttonDelete.enabled = deleteEnable
    
                    _FilteredRunwayConfigurationFilesModel.setCheckedIndexes(listViewFilter.selectedIndex)
                }
    
                Layout.fillWidth: true
                Layout.fillHeight: true
    
                model: delegateModel
    
                ScrollBar.vertical: ScrollBar
                {
                    anchors.top: parent.top
                    anchors.bottom: parent.bottom
                    anchors.right: parent.right
                    policy: listViewFilter.contentHeight > listViewFilter.height ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff
                    active: true
                }
            }
    
            TextField
            {
                Layout.fillWidth: true
                Layout.preferredHeight: textFilterString.height
                text: "No files with this filter"
                verticalAlignment: Qt.AlignTop
                horizontalAlignment: Qt.AlignHCenter
                visible: listViewFilter.count === 0
            }
        }
    }
    

    Base model

    Proxy model

    #ifndef FILTERPROXYMODEL_H
    #define FILTERPROXYMODEL_H
    
    #include <QSortFilterProxyModel>
    
    class FilterProxyModel : public QSortFilterProxyModel
    {
        Q_OBJECT
    
        Q_PROPERTY(QString filenameBeginning WRITE setFilenameBeginning READ getFilenameBeginnning NOTIFY filenameBeginningChanged)
        Q_PROPERTY(bool isSavingMode WRITE setIsSavingMode NOTIFY isSavingModeChanged)
    
    public:
        explicit FilterProxyModel(QObject *parent = nullptr);
    
        void setFilenameBeginning(QString newStr);
        QString getFilenameBeginnning();
        void setIsSavingMode(bool newMode);
    
    public slots:
        Q_INVOKABLE void deleteConfigurationFile(QVector<int> indexToDelete);
        Q_INVOKABLE void loadConfigurationFile(QVector<int> indexToLoad);
        Q_INVOKABLE void setCheckedIndexes(QVector<int> listIndex); // ------> New slot <------
    
    signals:
        void filenameBeginningChanged();
        void isSavingModeChanged();
    
    protected:
        bool lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const override;
        bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
    
    private:
        QString m_filenameBeginning;
        bool m_isSavingMode;
    };
    
    #endif // FILTERPROXYMODEL_H
    
    #include "FilterProxyModel.h"
    
    #include "FilenameListModel.h"
    #include <QDebug>
    
    FilterProxyModel::FilterProxyModel(QObject *parent)
        : QSortFilterProxyModel (parent)
    {
        m_isSavingMode = false;
        sort(0, Qt::SortOrder::DescendingOrder);
    }
    
    bool FilterProxyModel::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const
    {
        const QDate leftDate = sourceLeft.data(FilenameListModel::DateRole).toDate();
        const QDate rightDate = sourceRight.data(FilenameListModel::DateRole).toDate();
    
        return leftDate < rightDate;
    }
    
    bool FilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
    {
        // In saving mode, keep all files on the list
        if (m_isSavingMode)
            return true;
    
        // Otherwise, filter the rows
        const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
    
        const QString name = index.data(FilenameListModel::NameRole).toString();
    
        return strncmp(name.toLower().toStdString().c_str(), m_filenameBeginning.toLower().toStdString().c_str(), m_filenameBeginning.size()) == 0;
    }
    
    void FilterProxyModel::setFilenameBeginning(QString newStr)
    {
        m_filenameBeginning = newStr;
    
        emit filenameBeginningChanged();
        invalidateFilter();
    }
    
    QString FilterProxyModel::getFilenameBeginnning()
    {
        return m_filenameBeginning;
    }
    
    void FilterProxyModel::deleteConfigurationFile(QVector<int> indexToDelete)
    {
        for (int i = 0; i < indexToDelete.size(); i++)
        {
            qDebug() << this->data(this->index(indexToDelete[i], 0), FilenameListModel::Roles::NameRole);
        }
    }
    
    void FilterProxyModel::loadConfigurationFile(QVector<int> indexToLoad)
    {
        // The input should be with only one element on it
        qDebug() << this->data(this->index(indexToLoad[0], 0), FilenameListModel::Roles::DateRole);
    }
    
    void FilterProxyModel::setIsSavingMode(bool newMode)
    {
        m_isSavingMode = newMode;
    }
    // ------> NEW CODE BELOW <------
    void FilterProxyModel::setCheckedIndexes(QVector<int> listIndex)
    {
        // First reset all checked value already to true
        for (int i = 0; i < sourceModel()->rowCount(); i++)
        {
            if (data(this->index(i, 0), FilenameListModel::Roles::CheckedRole).toBool())
                setData(this->index(i, 0), false, FilenameListModel::Roles::CheckedRole);
        }
    
        // Then set to true checked value only for selected delegate
        for (int i = 0; i < listIndex.size(); i++)
        {
            setData(this->index(listIndex[i], 0), true, FilenameListModel::Roles::CheckedRole);
        }
    }