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的不足之处,并且讨论了改进目标,如何实现自定义文件系统模型,以及进一步改进的空间。
如果您认为这篇文章对您有所帮助,请您一定立即点赞+喜欢+收藏,本文作者将能从您的点赞+喜欢+收藏中获取到创作新的好文章的动力。如果您认为作者写的文章还有一些参考价值,您也可以关注这篇文章的作者。