QT疑难杂症之如何使用自定义模型实现文件系统模型?类似QFileSystemModel,却比QFileSystemModel更好用

简介

本文讨论了QT文件系统模型QFileSystemModel的不足之处,并且讨论了改进目标,如何实现自定义文件系统模型,以及进一步改进的空间。

目录

QFileSystemModel的不足之处

改进目标

自定义文件系统模型

进一步改进的空间

正文

QFileSystemModel的不足之处

QFileSystemModel文件系统模型在实际使用过程中,经常先调用setRootPath()函数设置 一个目录作为根路径。

QFileSystemModel的setRootPath函数
QFileSystemModel实际使用过程中遇到两个问题:

一个问题是在搭配QTreeView和QTreeWidget使用时, 即使已经调用了这个函数,还需进一步调用QTreeView的setRootIndex()函数,才能在控件中只显示根路径中的文件和目录;否则仍然是显示计算机中的所有驱动器的信息。

QTreeView的setRootIndex()函数
另一个问题是在搭配QML的TreeView使用时,在QML中TreeView没有setRootIndex之类的函数,因此总是显示计算机中的所有驱动器的信息。

改进目标

本次的改进目标只有一个,就是实现一个自定义的文件系统模型,只要调用了setRootPath()函数指定了根路径,那么不管是和 QTreeView或QTreeWidget搭配使用,还是和QML的TreeView搭配使用,总是只显示根路径中的文件和目录。比如在根路径设置为 d:\butianyun的情况下,效果如图所示:

ButianyunFileSystemModel和QTreeView搭配使用

ButianyunFileSystemModel和QML中的TreeView搭配使用

自定义文件系统模型

使用自定义模型实现的文件系统模型时,从QAbstractItemModel派生一个新的类型ButianyunFileSystemModel。

头文件如下所示:

/**********************************************************************************
****************** Butianyun QT Video Lesson V2 ***********************************
*********** BUTIANYUN, QT Programming Trainning 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& value);
    QString rootPath() const;
    QModelIndex rootIndex() const;
    ButianyunFileInfo fileInfo(const QModelIndex& 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 &index, int role = Qt::DisplayRole) const override;
    QHash<int,QByteArray> roleNames() const override;
    Qt::ItemFlags flags(const QModelIndex &index) const override;
    QModelIndex index(const QString& filePath, const QModelIndex &parent = QModelIndex()) const;


signals:
    void rootPathChanged();

public slots:
    void clearCache();

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

#endif // BUTIANYUNFILESYSTEMMODEL_H

C++实现文件如下:

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

#include "butianyunfilesystemmodel.h"


void ButianyunFileSystemModel::setRootPath(const QString& 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& 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& 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 &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 && !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 &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 &parent) const
{
    quintptr pid = parent.internalId();
    auto parent_fileinfo = fileInfo(parent);
    if (0 == pid && !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 &parent) const
{
    return ButianyunFileInfoRoleCount;
}

bool ButianyunFileSystemModel::hasChildren(const QModelIndex &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 &index, int role) const
{
    if (Qt::DisplayRole != role && Qt::DecorationRole != role)
    {
        return QVariant();
    }

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

    quintptr pid = index.internalId();
    if (0 == pid && !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 &index) const
{
    return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}

QModelIndex ButianyunFileSystemModel::index(const QString& filePath, const QModelIndex &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 && !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 && fileinfo.level > 0)
    {
        return QModelIndex();
    }
    return createIndex(fileinfo.row, 0, pid);
}

和QTreeView搭配使用的测试代码如下所示:

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

和 QML 的 TreeView搭配使用的测试代码如下所示:

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

进一步改进的空间

目前这个实现版本的文件系统模型 ButianyunFileSystemModel,初步实现了前面讨论的改进目标。这个类型还存在一些进一步的改进空间,比如:

根目录中的文件变化的处理:QFileSystemModel能够感知到文件变化通知,并能做出对应的响应,而ButianyunFileSystemModel还不能感知到文件变化通知。ButianyunFileSystemModel提供了resetCache()函数用于手动触发重置操作。

没有实现QT模型类型中的常见操作canFetchMore()和 fetchMore()函数: QFileSystemModel实现了这两个函数。ButianyunFileSystemModel在内部使用 QHash 做了一定的缓存,已经避免了在目录中有很多文件时一次查询过多文件。

本文相关的另一个文章:

QT QML:QT疑难杂症之QML程序中如何使用文件系统模型QFileSystemModel?

总结

本文讨论了QT文件系统模型QFileSystemModel的不足之处,并且讨论了改进目标,如何实现自定义文件系统模型,以及进一步改进的空间。

如果您认为这篇文章对您有所帮助,请您一定立即点赞+喜欢+收藏,本文作者将能从您的点赞+喜欢+收藏中获取到创作新的好文章的动力。如果您认为作者写的文章还有一些参考价值,您也可以关注这篇文章的作者。

posted @ 2023-07-14 11:02  QT界面美化性能优化  阅读(87)  评论(0编辑  收藏  举报  来源