c++windowsqtdesktop

QTreeView makes extra calls to QFileSystemModel


I am using QTreeView together with QFileSystemModel. I created a subclass of QFileSystemModel to track calls to the sort() and data() methods.

When QTreeView accesses the model, for some reason the sort() method is called three times instead of once.

Also, despite the fact that I set a path in QTreeView::setRootIndex that contains only 3 files, the data() method is somehow called 360 times.

Moreover, if I move the mouse or resize the window, the data() method is called again.

Why does all this happen and how can I avoid unnecessary calls to the sort() and data() methods?

Ideally, I would like sort() to be called once and only if the user has selected sorting, and the data() method to be called only if there is a real need for it, for example, if the contents of a folder have changed.

I am using Qt 6.6.2 on Windows 11. Here is my code

#include <QApplication>
#include <QTreeView>
#include <QFileSystemModel>

class Model : public QFileSystemModel
{
public:

    void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override
    {
        static int i = 0;
        qDebug() << "sort() " << ++i;
        QFileSystemModel::sort(column, order);
    }

    QVariant data(const QModelIndex& index,int role) const override
    {
        static int i = 0;
        qDebug() << "data() " << ++i;
        return QFileSystemModel::data(index, role);
    }
};


int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    Model *model = new Model;
    model->setRootPath(QDir::rootPath());
    QTreeView *treeView = new QTreeView;

    treeView->setModel(model);
    treeView->setColumnWidth(0, 250);
    treeView->setWindowTitle("QFileSystemModel Example");
    treeView->resize(600, 400);
    treeView->setRootIndex(model->index("C:\\Users\\Username\\Desktop\\qt"));

    treeView->show();
    treeView->setSortingEnabled(true);

    return app.exec();
}

Solution

  • tl;dr

    There is fundamentally nothing you can do about both problems.

    Frequent data() calls

    Qt item views, by default, have absolutely no caching mechanism. That's by design: Qt is naturally a cross-platform framework also born in the Linux architecture, which, by its own nature, allows extreme and unexpected possible combinations.

    The complexity of item views and models also requires considering two important aspects:

    About the first point, the view may initially set arbitrary defaults based on system and style aspects (for instance, the font metrics), but then other aspects may come in play, especially if the view has contents that require considering them:

    Item delegates may also be queried by the view in order to consider the possible geometries of all items, and the default QStyledItemDelegate always queries the model with the above roles in order to provide an adequate size hint for each visible item, if not for every one (those not currently visible as they're outside the visible scroll area, but also hidden ones).

    Also, item views may provide the possibility of resizing their items based on all the contents of every column or row, meaning that all the above data() calls are done, even if the model never returns a valid QVariant() for any of those roles.

    The second point also adds further complexities: Qt styles (implemented across very different OS) may need painting updates or model queries even when not expected, and even when nothing actually changes in the view. It would be pointless for Qt to always query the style to know whether it needs an update or not on an item based on its capabilities: while that approach may be appropriate for styles that do not need constant updates, doing it would actually add unnecessary overhead (and implementation) for any other.

    The fact that you don't see any change when entering/leaving the view is completely irrelevant: Qt is by its own nature cross-platform and provides advanced styling, which means that a view may change its own appearance (and that of all its items) whenever it's hovered or not. And since all these aspect may change also based on the above mentioned roles, that means that it's mandatory to query data() for any item the view may believe as relevant.

    Simply put, you must expect that data() will always be called tens, hundreds, if not thousand times whenever the model is shown in a view, even when just moving the mouse around or by simply pressing a key even though that won't seem to have any effect.

    Multiple calls to QFileSystemModel::sort()

    First of all, it's important to note that the two Qt views that support setSortingEnabled() (QTableView and QTreeView, since they display models using more than one dimension) both internally call their own sortByColumn() (see the related functions for QTableView and QTreeView) whenever setSortingEnabled(true) is called, even if the user hasn't done nothing yet.

    The other standard views don't call it as fundamentally irrelevant: QListView is mono dimensional, meaning that it just requires calling sort() on the model; QColumnView is actually a collection of QListViews; QHeaderView is a convenience extension of QAbstractItemView and cannot obviously support sorting due to its purpose.

    The problem with QFileSystemModel is mostly based on the fact that it's a "lazy" model: to prevent freeze when loading complex directory structures (possibly containing thousands of items for each parent), and considering the slow access time to file devices (which may be slow due to hardware or even network aspects for remote units) it uses threading to query the contents of each of its nodes. Therefore sort() will most certainly be called at least once more as soon as it's accessed by a sorted view or its setRootPath() is called.

    Each OS, and each file system it eventually supports, may return the contents of each directory in any arbitrary order and at any given time. Similarly to what explained above, Qt cannot (and should not) obviously know any possible capability, and therefore it needs to do the best it can: populate the model as soon (and often) as reasonably possible to prevent excessive freezing, while keeping the proper sorting every time new items are added. This means that the model may try to sort itself even while populating, which may be cause further (even if not necessary) delayed calls to its own sort() implementation.

    Conclusions

    As said at the beginning, there's fundamentally nothing you can do about any of the above.

    About the view (including headers and delegate) calls to data(), you may consider some form of caching, but considering the extensibility of the Qt model/view paradigm it's probably not worth it: you may probably end up with an extremely complex implementation that could not only cause unnecessary overhead in most cases, but also cause and potentially unsecure unexpected behavior, especially if you're not experienced enough with Qt views and models (and I mean extreme and thorough experience, including profound studying of Qt sources and awareness of different platform issues).

    About the sort() calls, trying to do better may be an interesting and useful exercise, but it would only be appropriate and acceptable as long as you have complete awareness of the possible OS/hw/sw configuration that may come in play. If you aim for generic usage, then either you take your (long) time to study how the QFileSystemModel works along with system calls and related OS/device aspects, or you just follow the "if it ain't broke, don't fix it" principle: don't try to reinvent the wheel unless you really know everything about how the wheel works and why it's considered appropriate to keep using it.