How to use the custom model to implement the file system model of QT intractable diseases? Similar to QFileSystemModel, but better than QFileSystemModel

Introduction

This article discusses the shortcomings of the QT file system model QFileSystemModel, and discusses the improvement goals, how to implement a custom file system model, and the room for further improvement.

Table of contents

Inadequacies of QFileSystemModel

Improvement goals

Custom file system model

room for further improvement

text

The shortcomings of QFileSystemModel

In the actual use of the QFileSystemModel file system model, the setRootPath() function is often called first to set a directory as the root path.

QFileSystemModel’s setRootPath function
There are two problems encountered in the actual use of QFileSystemModel:

One problem is that when used with QTreeView and QTreeWidget, even if this function has been called, it is necessary to further call the setRootIndex() function of QTreeView to display only the files and directories in the root path in the control; otherwise, it still displays the files and directories in the computer. Information about all drives.

QTreeView’s setRootIndex() function
Another problem is that when used with QML’s TreeView, in QML, TreeView does not have functions such as setRootIndex, so it always displays the information of all drives in the computer.

Improvement goals

There is only one improvement goal this time, which is to implement a custom file system model. As long as the setRootPath() function is called to specify the root path, whether it is used with QTreeView or QTreeWidget, or with QML’s TreeView, it will always Only files and directories in the root path are displayed. For example, when the root path is set to d:\butianyun, the effect is as shown in the figure:

Using ButianyunFileSystemModel with QTreeView

Using ButianyunFileSystemModel with TreeView in QML

Custom file system model

When using the file system model implemented by a custom model, derive a new type ButianyunFileSystemModel from QAbstractItemModel.

The header file looks like this:

/********************************************** *************************************
******************* Butianyun QT Video Lesson V2 *************************** ***********
*********** BUTIANYUN, QT Programming Training Professional ***********************
***************************************************** ***********************************/

#ifndef BUTIANYUNFILESYSTEMMODEL_H
#define BUTIANYUNFILESYSTEMMODEL_H

#include <QAbstractItemModel>


struct ButianyunFileSystemModelPrivate;

class ButianyunFileSystemModel : public QAbstractItemModel
{<!-- -->
    Q_OBJECT
    Q_DECLARE_PRIVATE(ButianyunFileSystemModel)
    Q_PROPERTY(QString rootPath READ rootPath WRITE setRootPath NOTIFY rootPathChanged)
    Q_PROPERTY(QModelIndex rootIndex READ rootIndex CONSTANT)

public:
    Q_INVOKABLE explicit ButianyunFileSystemModel(QObject* p = nullptr);
    ~ButianyunFileSystemModel();

    void setRootPath(const QString & amp; value);
    QString rootPath() const;
    QModelIndex rootIndex() const;
    ButianyunFileInfo fileInfo(const QModelIndex & amp; index) const;
    bool isValid(const QModelIndex & index) const;
    QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const override;
    QModelIndex parent(const QModelIndex & child) const override;
    int rowCount(const QModelIndex & parent = QModelIndex()) const override;
    int columnCount(const QModelIndex & parent = QModelIndex()) const override;
    bool hasChildren(const QModelIndex & parent = QModelIndex()) const override;
    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
    QVariant data(const QModelIndex & amp;index, int role = Qt::DisplayRole) const override;
    QHash<int,QByteArray> roleNames() const override;
    Qt::ItemFlags flags(const QModelIndex & amp;index) const override;
    QModelIndex index(const QString & amp; filePath, const QModelIndex & amp; parent = QModelIndex()) const;


signals:
    void rootPathChanged();

public slots:
    void clearCache();

private:
    QSharedPointer<ButianyunFileSystemModelPrivate> d_ptr;
    Q_DISABLE_COPY(ButianyunFileSystemModel)
};

#endif // BUTIANYUNFILESYSTEMMODEL_H

The C++ implementation file is as follows:

/********************************************** *************************************
******************* Butianyun QT Video Lesson V2 *************************** ***********
*********** BUTIANYUN, QT Programming Training Professional ***********************
***************************************************** ***********************************/

#include "butianyunfilesystemmodel.h"


void ButianyunFileSystemModel::setRootPath(const QString & amp; value)
{<!-- -->
    Q_D(ButianyunFileSystemModel);
    QFileInfo fi(value);
    if (fi.absoluteFilePath() == d->_rootPath)
    {<!-- -->
        return;
    }

    d->_id_to_info_cache.clear();
    d->_path_to_id_cache.clear();
    d->_rootPath = fi.absoluteFilePath();
    quintptr id = d->_id_to_info_cache. size() + 1;
    d->_rootIndex = createIndex(0, 0, reinterpret_cast<const void*>(id));
    emit rootPathChanged();
}

QString ButianyunFileSystemModel::rootPath() const
{<!-- -->
    const ButianyunFileSystemModelPrivate* d = d_func();
    return d->_rootPath;
}

QModelIndex ButianyunFileSystemModel::rootIndex() const
{<!-- -->
    const ButianyunFileSystemModelPrivate* d = d_func();
    return d->_rootIndex;
}

ButianyunFileInfo ButianyunFileSystemModel::fileInfo(const QModelIndex & amp; index) const
{<!-- -->
    const ButianyunFileSystemModelPrivate* d = d_func();
    quintptr pid = index. internalId();
    if (0 == pid)
    {<!-- -->
        if (index. parent(). isValid())
        {<!-- -->
            return ButianyunFileInfo();
        }
        pid = 1;
    }
    auto it = d->_id_to_info_cache.find(pid);
    if (it == d->_id_to_info_cache. end())
    {<!-- -->
        return ButianyunFileInfo();
    }
    return it. value();
}

bool ButianyunFileSystemModel::isValid(const QModelIndex & amp; index) const
{<!-- -->
   const ButianyunFileSystemModelPrivate* d = d_func();
   if (index.column() >= ButianyunFileInfoRoleCount)
   {<!-- -->
       return false;
   }

    quintptr pid = index. internalId();
    if (0 == pid)
    {<!-- -->
        if (index. parent(). isValid())
        {<!-- -->
            return false;
        }
        pid = 1;
    }
    auto it = d->_id_to_info_cache.find(pid);
    if (it == d->_id_to_info_cache. end())
    {<!-- -->
        return false;
    }
    return it.value().isValid(pid);
}

static QFileInfoList butianyun_get_file_info_list(const QString & filePath)
{<!-- -->
    QDir dir(filePath);
    return dir.entryInfoList(QDir::Filter::NoDotAndDotDot | QDir::Drives | QDir::Dirs |QDir::Files,
                              QDir::DirsFirst |QDir::Name |QDir::Type |QDir::IgnoreCase);
}

QModelIndex ButianyunFileSystemModel::index(int row, int column, const QModelIndex & amp; parent) const
{<!-- -->
   ButianyunFileSystemModelPrivate* d = const_cast<ButianyunFileSystemModel*>(this)->d_func();
   if (row < 0 || column < 0 || column >= ButianyunFileInfoRoleCount)
   {<!-- -->
       return QModelIndex();
   }
   quintptr pid = parent. internalId();
   auto parent_fileinfo = fileInfo(parent);

   if (0 == pid & amp; & amp; !parent. parent(). isValid())
   {<!-- -->
       pid = 1;
   }
   if (!parent_fileinfo.isValid(pid))
   {<!-- -->
       return QModelIndex();
   }

   QFileInfoList list = butianyun_get_file_info_list(parent_fileinfo.filePath);
   if (row >= list. length())
   {<!-- -->
       return QModelIndex();
   }

   quintptr this_id = 0;
   return createIndex(row, column, reinterpret_cast<const void *>(this_id));
}

QModelIndex ButianyunFileSystemModel::parent(const QModelIndex & amp;child) const
{<!-- -->
    const ButianyunFileSystemModelPrivate* d = d_func();
    if (!child. isValid())
    {<!-- -->
        return QModelIndex();
    }

    quintptr id = child. internalId();
    auto fileinfo = fileInfo(child);
    if (!fileinfo.isValid(id) || 0 == fileinfo.level)
    {<!-- -->
       return QModelIndex();
    }

    int index = fileinfo.filePath.lastIndexOf("/");
    if (index <= 0)
    {<!-- -->
        return QModelIndex();
    }

    QString filePath = fileinfo.filePath.left(index);
    if (filePath. isEmpty())
    {<!-- -->
        return QModelIndex();
    }

    auto it = d->_path_to_id_cache.find(filePath);
    if (it == d->_path_to_id_cache. end())
    {<!-- -->
        return QModelIndex();
    }

    if (it. value() != fileinfo. pid)
    {<!-- -->
        return QModelIndex();
    }

    quintptr pid = fileinfo.pid;
    auto it2 = d->_id_to_info_cache.find(pid);
    if (it2 == d->_id_to_info_cache. end())
    {<!-- -->
        return QModelIndex();
    }
    fileinfo = it2. value();
    if (pid != fileinfo.id)
    {<!-- -->
        return QModelIndex();
    }
    return createIndex(fileinfo. row, 0, pid);
}

int ButianyunFileSystemModel::rowCount(const QModelIndex & amp;parent) const
{<!-- -->
    quintptr pid = parent. internalId();
    auto parent_fileinfo = fileInfo(parent);
    if (0 == pid & amp; & amp; !parent. parent(). isValid())
    {<!-- -->
        pid = 1;
    }
    if (!parent_fileinfo.isValid(pid))
    {<!-- -->
        return 0;
    }

    QFileInfoList list = butianyun_get_file_info_list(parent_fileinfo.filePath);
    return list. length();
}

int ButianyunFileSystemModel::columnCount(const QModelIndex & amp;parent) const
{<!-- -->
    return ButianyunFileInfoRoleCount;
}

bool ButianyunFileSystemModel::hasChildren(const QModelIndex & amp;parent) const
{<!-- -->
    return rowCount(parent) > 0;
}

QVariant ButianyunFileSystemModel::headerData(int section, Qt::Orientation orientation, int role) const
{<!-- -->
    if (Qt::Horizontal == orientation)
    {<!-- -->
        if (Qt::DisplayRole == role)
        {<!-- -->
        switch (section + Qt::UserRole + 1)
        {<!-- -->
            case Role_ButianyunFileName:
                 return tr("Name");
            case Role_ButianyunFileSize:
                 return tr("Size");
            case Role_ButianyunFileType:
                 return tr("Type");
            case Role_ButianyunFileLastModified:
                 return tr("Data Modified");
            default:
                 return QVariant();
            }
        }
    }
    else
    {<!-- -->
       if (Qt::DisplayRole == role)
       {<!-- -->
         return QString::number(section);
       }
    }
    return QVariant();
}

QVariant ButianyunFileSystemModel::data(const QModelIndex & amp;index, int role) const
{<!-- -->
    if (Qt::DisplayRole != role & amp; & amp; Qt::DecorationRole != role)
    {<!-- -->
        return QVariant();
    }

    if (!index.isValid())
    {<!-- -->
        return QVariant();
    }
    if (index.column() >= ButianyunFileInfoRoleCount)
    {<!-- -->
        return false;
    }

    quintptr pid = index. internalId();
    if (0 == pid & amp; & amp; !index. parent(). isValid())
    {<!-- -->
        pid = 1;
    }
    auto fileinfo = fileInfo(index);

    if (!fileinfo.isValid(pid))
    {<!-- -->
        return QVariant();
    }

    QFileInfo fi(fileinfo. filePath);
    switch (index.column() + Qt::UserRole + 1)
    {<!-- -->
    case Role_ButianyunFileName:
        if (Qt::DisplayRole == role)
        {<!-- -->
            return fi. fileName();
        }
        else if (Qt::DecorationRole == role)
        {<!-- -->
            return fileinfo. fileicon();
        }

    case Role_ButianyunFileSize:
    {<!-- -->
         return fileinfo.filesize();
    }

    case Role_ButianyunFileType:
        return fileinfo. filetype();

    case Role_ButianyunFileLastModified:
        return fi.lastModified().toString("yyyy-MM-dd hh:mm");

    default:
         return QVariant();
    }
    return QVariant();
}

QHash<int,QByteArray> ButianyunFileSystemModel::roleNames() const
{<!-- -->
    QHash<int,QByteArray> roles;
    roles = QAbstractItemModel::roleNames();
    roles[Role_ButianyunFileName] = QStringLiteral("FileName").toUtf8();
    roles[Role_ButianyunFileSize] = QStringLiteral("FileSize").toUtf8();
    roles[Role_ButianyunFileType] = QStringLiteral("FileType").toUtf8();
    roles[Role_ButianyunFileLastModified] = QStringLiteral("LastModified").toUtf8();
    return roles;
}

Qt::ItemFlags ButianyunFileSystemModel::flags(const QModelIndex & amp;index) const
{<!-- -->
    return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}

QModelIndex ButianyunFileSystemModel::index(const QString & amp; filePath, const QModelIndex & amp; parent) const
{<!-- -->
    ButianyunFileSystemModelPrivate* d = const_cast<ButianyunFileSystemModel*>(this)->d_func();
    auto it = d->_path_to_id_cache.find(filePath);
    if (it == d->_path_to_id_cache. end())
    {<!-- -->
        return QModelIndex();
    }
    quintptr pid = parent. internalId();
    if (0 == pid & amp; & amp; !parent. parent(). isValid())
    {<!-- -->
        pid = 1;
    }
    auto it2 = d->_id_to_info_cache.find(it.value());
    if (it2 == d->_id_to_info_cache. end())
    {<!-- -->
        return QModelIndex();
    }
    ButianyunFileInfo fileinfo = it2. value();
    if (fileinfo.pid != pid & amp; & amp; fileinfo.level > 0)
    {<!-- -->
        return QModelIndex();
    }
    return createIndex(fileinfo. row, 0, pid);
}

The test code used with QTreeView is as follows:

 ButianyunFileSystemModel* model = new ButianyunFileSystemModel(this);
    model->setRootPath(R"(d:\butianyun)");
    QTreeView* tree = new QTreeView();
    tree->setModel(model);
    tree->setRootIndex(model->index(model->rootPath()));

The test code used with QML’s TreeView is as follows:

TreeView {
id: tree
anchors.topMargin: header.height + button_reset.height
anchors. fill: parent
model: fsmodel
columnWidthProvider: get_column_width
columnSpacing: 10
delegate: TreeViewDelegate {}
}

Room for further improvement

The current implementation version of the file system model ButianyunFileSystemModel has initially achieved the improvement goals discussed above. There are still some room for further improvement in this type, such as:

Handling of file changes in the root directory: QFileSystemModel can perceive file change notifications and respond accordingly, but ButianyunFileSystemModel cannot yet perceive file change notifications. ButianyunFileSystemModel provides the resetCache() function to manually trigger the reset operation.

The common operations canFetchMore() and fetchMore() functions in QT model types are not implemented: QFileSystemModel implements these two functions. ButianyunFileSystemModel internally uses QHash to do a certain amount of caching, which has avoided querying too many files at once when there are many files in the directory.

Another article related to this article:

QT QML: How to use the file system model QFileSystemModel in the QML program of QT intractable diseases?

Summarize

This article discusses the shortcomings of the QT file system model QFileSystemModel, and discusses the improvement goals, how to implement a custom file system model, and the room for further improvement.

If you think this article is helpful to you, please like + like + bookmark immediately, and the author of this article will be able to start from your like + like + bookmark Get the motivation to create new and good articles from it. If you think the article written by the author has some reference value, you can also follow the author of this article.