QML ListView delegate selection not updated when out of scroll

53 views Asked by At

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

  • see all files on a folder with specific extension in a ListView component,
  • filter the displayed items according a text the user enters in a TextEdit component,
  • sort the displayed items by the last modification date,
  • select only one item to load the related file,
  • select one or multiple to delete the related files,
  • enter a name to save data in a new file,

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

  • main.cpp
#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();
}
  • main.qml
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

  • FilenameListModel.h
#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
  • FilenameListModel.cpp
#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

  • FilterProxyModel.h
#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
  • FilterProxyModel.cpp
#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;
}
1

There are 1 answers

0
Mathieu Gauquelin On BEST ANSWER

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

  • main.cpp (No change)

  • main.qml (Simplify delegate and put logic on the listview)

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

  • FilenameListModel.h (No change)

  • FilenameListModel.cpp (No change)

Proxy model

  • FilterProxyModel.h : Add a new invokable public slot
#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
  • FilterProxyModel.cpp (Implement the new slot calling by the QML part)
#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);
    }
}