如何在QML中递归遍历目录及子目录并在ListView展示指定扩展名文件?
嘿,这个需求我刚好折腾过,给你两种靠谱的实现方案——推荐用QML结合Qt C的方式,功能更灵活稳定;如果不想碰C,也可以试试纯QML的方案(虽然有一些局限性)。下面一步步给你拆解:
方案一:QML + Qt C++(推荐,适合复杂场景)
这个方案的核心是用C处理文件扫描逻辑(递归遍历文件这类操作,C的性能和跨平台性更优),再把结果传递给QML的ListView展示。
1. 编写C++文件扫描类
先写一个继承自QObject的类,负责递归扫描目录、筛选扩展名,然后通过信号把结果传给QML。
头文件(FileScanner.h)
#ifndef FILESCANNER_H #define FILESCANNER_H #include <QObject> #include <QStringList> #include <QDir> class FileScanner : public QObject { Q_OBJECT public: explicit FileScanner(QObject *parent = nullptr); // 暴露给QML调用的扫描方法:参数为目标目录、要筛选的扩展名(比如"jpg,png") Q_INVOKABLE void scanDirectory(const QString& dirPath, const QString& extensions = ""); signals: // 扫描完成后发送结果信号 void scanFinished(const QStringList& filePaths); // 可选:实时发送扫描到的文件(适合大目录) void fileFound(const QString& filePath); private: void recursiveScan(const QDir& dir, const QStringList& filter); QStringList m_results; }; #endif // FILESCANNER_H
源文件(FileScanner.cpp)
#include "FileScanner.h" FileScanner::FileScanner(QObject *parent) : QObject(parent) { } void FileScanner::scanDirectory(const QString &dirPath, const QString &extensions) { m_results.clear(); QDir dir(dirPath); if (!dir.exists()) { emit scanFinished(m_results); return; } // 处理扩展名筛选,转成QDir需要的格式,比如["*.jpg", "*.png"] QStringList filter; if (!extensions.isEmpty()) { QStringList extList = extensions.split(",", Qt::SkipEmptyParts); for (const QString& ext : extList) { filter.append(QString("*.%1").arg(ext.trimmed())); } } else { // 无指定扩展名则扫描所有文件 filter.append("*"); } recursiveScan(dir, filter); emit scanFinished(m_results); } void FileScanner::recursiveScan(const QDir &dir, const QStringList &filter) { // 获取当前目录下的所有文件 QFileInfoList fileInfos = dir.entryInfoList(filter, QDir::Files | QDir::NoDotAndDotDot); for (const QFileInfo& info : fileInfos) { m_results.append(info.absoluteFilePath()); emit fileFound(info.absoluteFilePath()); // 实时发送文件路径 } // 递归遍历子目录 QFileInfoList dirInfos = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); for (const QFileInfo& dirInfo : dirInfos) { recursiveScan(QDir(dirInfo.absoluteFilePath()), filter); } }
2. 注册C++类到QML
在主函数(main.cpp)里,把FileScanner注册到QML上下文,让QML能调用它的方法:
#include <QGuiApplication> #include <QQmlApplicationEngine> #include "FileScanner.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQmlApplicationEngine engine; // 注册FileScanner类到QML,命名为FileScanner qmlRegisterType<FileScanner>("com.example", 1, 0, "FileScanner"); const QUrl url(u"qrc:/main.qml"_qs); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url); return app.exec(); }
3. QML端实现UI和逻辑
写一个包含触发按钮、结果展示ListView的QML文件:
import QtQuick 2.15 import QtQuick.Controls 2.15 import com.example 1.0 ApplicationWindow { width: 600 height: 400 visible: true title: "文件扫描器" // 实例化C++扫描类 FileScanner { id: scanner onScanFinished: { // 扫描完成后更新ListModel fileModel.clear() for (let path of filePaths) { fileModel.append({filePath: path}) } } // 可选:实时添加文件到Model onFileFound: { fileModel.append({filePath: filePath}) } } // 存储文件路径的ListModel ListModel { id: fileModel } Column { anchors.fill: parent spacing: 10 padding: 10 Row { spacing: 10 TextField { id: dirPathInput width: 300 text: "/home/your/test_dir" // 替换成你的目标目录 placeholderText: "输入目标目录路径" } TextField { id: extInput width: 150 text: "jpg,png" placeholderText: "输入扩展名(逗号分隔)" } Button { text: "开始扫描" onClicked: { scanner.scanDirectory(dirPathInput.text, extInput.text) } } } ListView { anchors.top: parent.top anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 10 model: fileModel delegate: Text { text: filePath padding: 5 wrapMode: Text.Wrap width: parent.width } ScrollBar.vertical: ScrollBar {} } } }
方案二:纯QML实现(适合简单场景)
如果不想写C++,可以用QML的FolderListModel结合JavaScript递归逻辑,但要注意:这种方式扫描大目录时性能可能不佳,且部分平台(如iOS)的文件系统权限限制可能影响功能。
import QtQuick 2.15 import QtQuick.Controls 2.15 import Qt.labs.folderlistmodel 2.15 ApplicationWindow { width: 600 height: 400 visible: true title: "纯QML文件扫描器" ListModel { id: fileModel } Column { anchors.fill: parent spacing: 10 padding: 10 Row { spacing: 10 TextField { id: dirPathInput width: 300 text: "/home/your/test_dir" placeholderText: "输入目标目录路径" } TextField { id: extInput width: 150 text: "jpg" placeholderText: "输入扩展名" } Button { text: "开始扫描" onClicked: { fileModel.clear() recursiveScan(dirPathInput.text, extInput.text) } } } ListView { anchors.top: parent.top anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 10 model: fileModel delegate: Text { text: filePath padding: 5 wrapMode: Text.Wrap width: parent.width } ScrollBar.vertical: ScrollBar {} } } // JavaScript递归扫描函数 function recursiveScan(dirPath, extension) { let folderModel = FolderListModel {} folderModel.folder = dirPath folderModel.nameFilters = extension ? ["*." + extension] : ["*"] folderModel.showFiles = true folderModel.showDirs = false // 添加当前目录的文件 for (let i = 0; i < folderModel.count; i++) { fileModel.append({filePath: folderModel.get(i).filePath}) } // 遍历子目录,递归扫描 folderModel.nameFilters = ["*"] folderModel.showFiles = false folderModel.showDirs = true for (let i = 0; i < folderModel.count; i++) { let subDir = folderModel.get(i).filePath recursiveScan(subDir, extension) } } }
一些注意事项
- 权限问题:无论哪种方案,都要确保程序有目标目录的访问权限,比如Android/iOS上需要申请文件访问权限。
- 性能优化:扫描大目录时,C++方案记得把扫描逻辑放到子线程(用
QThread),避免卡住UI;纯QML方案尽量避开超大规模目录。 - 扩展名匹配:注意大小写问题,比如
JPG和jpg,C++里可以用QDir::IgnoreCase选项忽略大小写,QML则需要手动处理。
内容的提问来源于stack exchange,提问作者md612
相关产品推荐
相关产品推荐

