在Qt里面,有一种Model/View框架,Model负责收集信息,View负责显示信息。QFileSystemModel可以读取文件大小,但是默认情况下不能读取文件夹大小。
QFileSystemModel里面有一个size()函数,获取一个index对应的文件的大小。
1 qint64 QFileSystemModel::size(const QModelIndex &index) const 2 { 3 Q_D(const QFileSystemModel); 4 if (!index.isValid()) 5 return 0; 6 return d->node(index)->size(); 7 }
这里的Q_D指针通过宏定义指向QFileSystemModel的私有类:QFileSystemPrivate。
1 #define Q_D(Class) Class##Private * const d = d_func()
写一个类MyQFileSystemInfo继承QFileSystemInfo,重写它的size()函数,对具体文件直接返回文件大小,而对文件夹则递归加和计算每个子文件夹的大小以得到它的大小。
可惜,仍然还是不能在view中实时显示文件夹的大小,看来view中使用的size并不是model的size()函数。github的代码链接:https://github.com/1171597779/big_file_inspector。
在QFileSystemModelPrivate里面也有一个size()函数。
1 QString QFileSystemModelPrivate::size(const QModelIndex &index) const 2 { 3 if (!index.isValid()) 4 return QString(); 5 const QFileSystemNode *n = node(index); 6 if (n->isDir()) { 7 #ifdef Q_OS_MAC 8 return QLatin1String("--"); 9 #else 10 return QLatin1String(""); 11 #endif 12 // Windows - "" 13 // OS X - "--" 14 // Konqueror - "4 KB" 15 // Nautilus - "9 items" (the number of children) 16 } 17 return size(n->size()); 18 }
上面函数的17行调用了重载size()函数:
1 QString QFileSystemModelPrivate::size(qint64 bytes) 2 { 3 // According to the Si standard KB is 1000 bytes, KiB is 1024 4 // but on windows sizes are calculated by dividing by 1024 so we do what they do. 5 const qint64 kb = 1024; 6 const qint64 mb = 1024 * kb; 7 const qint64 gb = 1024 * mb; 8 const qint64 tb = 1024 * gb; 9 if (bytes >= tb) 10 return QFileSystemModel::tr("%1 TB").arg(QLocale().toString(qreal(bytes) / tb, 'f', 3)); 11 if (bytes >= gb) 12 return QFileSystemModel::tr("%1 GB").arg(QLocale().toString(qreal(bytes) / gb, 'f', 2)); 13 if (bytes >= mb) 14 return QFileSystemModel::tr("%1 MB").arg(QLocale().toString(qreal(bytes) / mb, 'f', 1)); 15 if (bytes >= kb) 16 return QFileSystemModel::tr("%1 KB").arg(QLocale().toString(bytes / kb)); 17 return QFileSystemModel::tr("%1 bytes").arg(QLocale().toString(bytes)); 18 }
QFileSystemModel的头文件里声明了私有类QFileSystemPrivate,转到这个私有类的头文件里面去,在这个类里定义了另外一个类QFileSystemNode。在这个类的构造函数中,出现一个QExtendedInformation*指针类型的info实参。这个实参在QFileSystemPrivate类里面是一个公有成员,文件或者文件夹的大小就是通过下面的QFileSystemNode的内联函数实现的。
inline qint64 size() const { if (info && !info->isDir()) return info->size(); return 0; }
QExtendedinformation类中存在一个QFileInfo类的成员mFileInfo,并通过size()方法来根据具体情况获取文件大小:
1 qint64 size() const { 2 qint64 size = -1; 3 if (type() == QExtendedInformation::Dir) 4 size = 0; 5 if (type() == QExtendedInformation::File) 6 size = mFileInfo.size(); 7 if (!mFileInfo.exists() && !mFileInfo.isSymLink()) 8 size = -1; 9 return size; 10 }
而QFileInfo的size()方法如下:
1 qint64 QFileInfo::size() const 2 { 3 Q_D(const QFileInfo); 4 if (d->isDefaultConstructed) 5 return 0; 6 if (d->fileEngine == 0) { 7 if (!d->cache_enabled || !d->metaData.hasFlags(QFileSystemMetaData::SizeAttribute)) 8 QFileSystemEngine::fillMetaData(d->fileEntry, d->metaData, QFileSystemMetaData::SizeAttribute); 9 return d->metaData.size(); 10 } 11 if (!d->getCachedFlag(QFileInfoPrivate::CachedSize)) { 12 d->setCachedFlag(QFileInfoPrivate::CachedSize); 13 d->fileSize = d->fileEngine->size(); 14 } 15 return d->fileSize; 16 }
问题来了,要计算一个文件夹的大小,需要很长的时间,不能用内联函数,因此需要重载一下,放弃内联函数的写法。
总结一下,QFileSystemModel里面的size()调用QFileSystemModelPrivate的size(),而这个函数里会使用的QFileSystemNode的内联函数size(),这里面又转到QExtendedinformation里面去,最后是QFileInfo的size()方法。通过重写QFileSystemModel的size()函数并不能实现treeview中文件夹大小的实时显示,需要找出treeview中什么时候调用size()函数。
从QFileSystemModelPrivate类的init()函数可以看到一些端倪:
1 void QFileSystemModelPrivate::init() 2 { 3 Q_Q(QFileSystemModel); 4 qRegisterMetaType<QVector<QPair<QString,QFileInfo> > >(); 5 #ifndef QT_NO_FILESYSTEMWATCHER 6 q->connect(&fileInfoGatherer, SIGNAL(newListOfFiles(QString,QStringList)), 7 q, SLOT(_q_directoryChanged(QString,QStringList))); 8 q->connect(&fileInfoGatherer, SIGNAL(updates(QString,QVector<QPair<QString,QFileInfo> >)), 9 q, SLOT(_q_fileSystemChanged(QString,QVector<QPair<QString,QFileInfo> >))); 10 q->connect(&fileInfoGatherer, SIGNAL(nameResolved(QString,QString)), 11 q, SLOT(_q_resolvedName(QString,QString))); 12 q->connect(&fileInfoGatherer, SIGNAL(directoryLoaded(QString)), 13 q, SIGNAL(directoryLoaded(QString))); 14 #endif // !QT_NO_FILESYSTEMWATCHER 15 q->connect(&delayedSortTimer, SIGNAL(timeout()), q, SLOT(_q_performDelayedSort()), Qt::QueuedConnection); 16 17 roleNames.insertMulti(QFileSystemModel::FileIconRole, QByteArrayLiteral("fileIcon")); // == Qt::decoration 18 roleNames.insert(QFileSystemModel::FilePathRole, QByteArrayLiteral("filePath")); 19 roleNames.insert(QFileSystemModel::FileNameRole, QByteArrayLiteral("fileName")); 20 roleNames.insert(QFileSystemModel::FilePermissions, QByteArrayLiteral("filePermissions")); 21 }
通过看到QFileSystemModelPrivate里面的方法addNote()才看到一些希望:
1 QFileSystemModelPrivate::QFileSystemNode* QFileSystemModelPrivate::addNode(QFileSystemNode *parentNode, const QString &fileName, const QFileInfo& info) 2 { 3 // In the common case, itemLocation == count() so check there first 4 QFileSystemModelPrivate::QFileSystemNode *node = new QFileSystemModelPrivate::QFileSystemNode(fileName, parentNode); 5 #ifndef QT_NO_FILESYSTEMWATCHER 6 node->populate(info); 7 #else 8 Q_UNUSED(info) 9 #endif 10 #if defined(Q_OS_WIN) && !defined(Q_OS_WINRT) 11 //The parentNode is "" so we are listing the drives 12 if (parentNode->fileName.isEmpty()) { 13 wchar_t name[MAX_PATH + 1]; 14 //GetVolumeInformation requires to add trailing backslash 15 const QString nodeName = fileName + QLatin1String("\\"); 16 BOOL success = ::GetVolumeInformation((wchar_t *)(nodeName.utf16()), 17 name, MAX_PATH + 1, NULL, 0, NULL, NULL, 0); 18 if (success && name[0]) 19 node->volumeName = QString::fromWCharArray(name); 20 } 21 #endif 22 Q_ASSERT(!parentNode->children.contains(fileName)); 23 parentNode->children.insert(fileName, node); 24 return node; 25 }
上面的node->populate(),这里面就提取了文件信息:
1 void populate(const QExtendedInformation &fileInfo) { 2 if (!info) 3 info = new QExtendedInformation(fileInfo.fileInfo()); 4 (*info) = fileInfo; 5 }