0

I'm adding data loaded form a file on disk to a QTreeView , the file contains more than 60.000 rows and new 'rows'/data will be constantly added to it while its being executed, so each time the GUI is reloaded/reopened the file will contain more data.

I tried to load this data using another thread just to figure out that QAbstractItemModel is not thread safe.

Then I tried to created a 'lazy load' subclass of a QAbstractItemModel , that 'process' the items as they get visible in the QTreeview. At the beginning it worked great, as it doesn't freeze the entire GUI for like 10~ seconds at runtime anymore.

However when i scroll the QTreeView it get frozen for many seconds, i think its 'loading' the data.

I was reading the documentation of QAbstractItemModel and see that it have these two functions: fetchMore() and canFetchMore(), to control how the data is loaded, but i didn't understand how to use it and if they could help in this case.

How I could dynamically load/unload the data as it get visible in the QTreeView? and free the memory of the rows that are not visible/not visible anymore, instead of having the entire file loaded into the memory.

Here is what I already did, using Qt 6.4.

Reproducible example:

#include "treeview.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindowClass())
{
    ui->setupUi(this);

    QGridLayout* layout = new QGridLayout(this);
    ui->centralWidget->setLayout(layout);
    auto treeView = new TreeView(this);
    layout->addWidget(treeView);
  
    QShortcut *shortcut = new QShortcut(QKeySequence(Qt::Key_F2), this);
    connect(shortcut, &QShortcut::activated, [=] {
        treeView->setInfo();
        qDebug() << "rowCount: " << treeView->model->rowCount();
    });
}

treeview.h

class LazyLoadingModel : public QAbstractItemModel
{
    Q_OBJECT

public:
    LazyLoadingModel(QObject *parent = nullptr) : QAbstractItemModel(parent) {}

    int rowCount(const QModelIndex &parent = QModelIndex()) const override
    {
        return m_rows.count();
    }

    int columnCount(const QModelIndex &parent = QModelIndex()) const override
    {
        return m_columns.count();
    }

    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
    {
        if (!index.isValid())
            return QVariant();

        int row = index.row();
        int column = index.column();

        if (row >= m_rows.count() || column >= m_columns.count())
            return QVariant();

        if (role == Qt::DisplayRole)
        {
            return m_rows[row][column];
        }

        return QVariant();
    }

    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override
    {
        if (!index.isValid())
            return false;

        int row = index.row();
        int column = index.column();

        if (row >= m_rows.count() || column >= m_columns.count())
            return false;

        if (role == Qt::EditRole)
        {
            m_rows[row][column] = value.toString();
            emit dataChanged(index, index);
            return true;
        }

        return false;
    }

    QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override
    {
        if (!parent.isValid())
            return createIndex(row, column);
        else
            return QModelIndex();
    }

    QModelIndex parent(const QModelIndex &index) const override
    {
        return QModelIndex();
    }

    void setColumns(const QStringList &columns)
    {
        m_columns = columns;
    }

    void addData(const QVector<QStringList> &row_info)
    {
        beginInsertRows(QModelIndex(), m_rows.count(), m_rows.count() + row_info.count() - 1);
        for (const auto &row : row_info)
            m_rows.append(row);
        endInsertRows();
    }

private:
    QStringList m_columns;
    QList<QStringList> m_rows;
};



class TreeView : public QTreeView {
    Q_OBJECT
public:
    LazyLoadingModel* model = new LazyLoadingModel();

    TreeView(QWidget* parent = 0) : QTreeView(parent) {
        setModel(model);
    }

    void setInfo() {
        QElapsedTimer timer;
        timer.start();

        model->setColumns({ "Column 0", "Column 1", "Column 2", "Column 3" });

        //
        // Load the data from disk and parse it into a qvector
        // or:
        // Create some random data, just to debug the model:
        QVector<QStringList> info{};
        QStringList list;
        for (size_t i = 0; i < 60000; i++) {
            list = {};
            QString str = QString::number(i);

            for (size_t i = 0; i < 4; i++)
                list << str;

            info.emplace_back(list);
        }

        model->addData(info);

        qint64 elapsed = timer.elapsed();
        qDebug() << "[T] Elapsed time (ms):" << elapsed;
    }

};
Cesar
  • 41
  • 2
  • 5
  • 16
  • Sounds like quite a challenge! Perhaps you could preprocess the input file into an optimized/pre-indexed format that makes looking up data you need efficient (i.e. possible to do without having to scan through the entire file)... then you could use Windows' `CreateFileMapping()` API (or similar) to map that file into memory and access it from your `QAbstractItemModel`'s callback-methods as necessary. – Jeremy Friesner Feb 10 '23 at 06:26
  • I was curious about your question and found https://stackoverflow.com/questions/38506808/pyqt4-force-view-to-fetchmore-from-qabstractitemmodel which may give you some insight. It is in python, but Qt python code is generally not too difficult to port to C++. – Dean Johnson Feb 10 '23 at 07:21
  • Did you try to profile it? E.g. measure time spent on filling `QVector info{};` and calling `model->addData(info);`. It looks like you iterate 120K times (two loops): one in the `TreeView::setInfo()` function and another in `LazyLoadingModel::addData()` function. – vahancho Feb 10 '23 at 08:41

0 Answers0