1. 为什么我选 Qt Widgets 做本地文件管理器而不是别的
做本地文件管理器,第一反应往往是"这玩意儿不是遍地都是吗,有什么好写的"。但真到自己动手,才发现坑比想象中多得多。先交代背景:我要做的不是网盘客户端,也不是远程文件浏览器,就是纯粹的本地文件管理器——浏览磁盘目录、增删改查、文件信息查看、搜索过滤、常用目录快速跳转。界面不用炫,稳定、快速、跨平台,是我对这工具的全部要求。技术选型阶段其实纠结过几个方向,下面说说我的取舍过程。
Electron 还是 C++?用 Electron 写这个确实快,文件操作走 Node 的 fs 模块,界面用 HTML/CSS 也灵活。但我个人对这种东西有个执念:文件管理器是系统级工具,启动速度和内存占用很重要。一个 Electron 应用冷启动吃掉 100MB 内存是常态,而 Qt Widgets 的程序冷启动基本在 20-30MB 以内,打开大目录拖滚动条的时候差距尤其明显。另外本地文件管理涉及大量系统调用和底层操作,用 C++ 直接调 Win32/Linux 系统 API 更顺手,不用隔一层 Node 的桥接。
QML 还是 Widgets?Qt 本身有两套界面框架,QML 适合做流畅的动画和触摸交互,Widgets 则偏传统桌面风格。文件管理器这种大量表格、树形目录、右键菜单交互的场景,Widgets 的 QTableView/QTreeView 模型视图框架太成熟了,拖拽、排序、代理、自定义模型一套下来很顺手。QML 在这块不是不能做,但细腻的表格交互需要写更多底层控制,性价比不高。再说 Widgets 的样式表调一调也能做出还不错的界面,没必要为了酷炫动画牺牲开发效率。
最终确定的技术栈:
- C++17 + Qt 5.15.2(LTS 版本,稳定,坑少)
- Qt Widgets 模块,重点用 QFileSystemModel、QTableView、QTreeView、QSortFilterProxyModel
- 文件操作全部走 Qt 的 QFile/QDir/QFileInfo 体系,个别特殊场景(如符号链接判断)用系统 API 辅助
- 构建工具用 qmake 而不是 CMake——不是 CMake 不好,是这个项目规模小,qmake 配置几行就完事,省心
这套方案的核心优势在于:QFileSystemModel 是一个已经封装好的、自带线程安全的文件系统数据模型,它内部用 QFileSystemWatcher 监听目录变化。换句话说,你用资源管理器删一个文件,Qt 界面里的视图会实时刷新,不需要你手动 re-scan。这个特性在自研文件管理器里极其省事,你不用自己维护"当前目录文件列表"这个缓存,所有增删改查都自动反应到界面上。这是选择 QFileSystemModel 作为数据核心的最根本原因。
注意:这里说的是 Qt 5.15.2 的体验。Qt 6 之后 QFileSystemModel 有部分接口调整,但整体架构没变,思路可以平移。
2. 核心界面搭起来:模型视图框架三板斧
2.1 主窗口布局:左树右表的经典结构
文件管理器最经典的布局就是左侧目录树、右侧文件列表。左侧用 QTreeView 挂 QFileSystemModel,右侧用 QTableView 挂同一个 model 的同一个 index,通过一个 QItemSelectionModel 共享选中状态。这样你点左边的目录,右边自动切换内容,不需要任何手动信号连接。
右侧 QTableView 只保留几个有用列。默认 QFileSystemModel 会带 Name、Size、Type、Date Modified 四列,我实际处理了一下,把列头改成中文,并且设置 Name 列宽度自适应,Size 列右对齐,日期列格式改成yyyy-MM-dd hh:mm。这些都通过setHeaderData()和自定义 delegate 实现,不复杂但效果直接影响使用舒适度。
共享选中状态这块有个细节:要让左右两个 view 同步选中状态,用QItemSelectionModel实例同时塞给两个 view。这样在左侧树里选目录,右侧表格的选中项自动清空并定位到新的当前目录;而在右侧表格里选择多个文件时,左侧树不会闪动。试过用信号转发的方式实现,代码又啰嗦又容易出竞态,共享 selection model 是最优雅的解法。
2.2 地址栏编辑与目录跳转的配合
地址栏我用的 QLineEdit + 补全功能。补全数据源直接用 QFileSystemModel 自己——它的index()和fileInfo()能返回任意路径对应的 model index,正好喂给 QCompleter。这个组合代码量只有十几行,但是体验很接近系统文件管理器的地址栏:输入到一半会跳出路径补全,回车直达目录。
一个容易踩的坑是 QFileSystemModel 的根路径设置。如果你把根路径设为/(Unix)或盘符(Windows),那么整个文件系统都在模型里,补全时能列出所有路径;但如果只设了家目录,补全范围就只在家目录内。文件管理器当然要全局浏览,所以根路径必须设置为文件系统根。Windows 上我会用QDir::drives()动态拿盘符列表,而不是硬编码C:\——因为用户机器上可能有 D 盘、E 盘,用drives()才是正确做法。
2.3 路径合法性校验与目录切换逻辑
在地址栏输入路径后,不能直接setRootIndex()就完事,至少要过三层检查:
- 路径是否存在——用
QFileInfo::exists()判断,不存在就提示并放弃跳转。 - 路径是目录还是文件——如果是文件,不应该切换目录,而是选中该文件并高亮。这个我用
QFileInfo::isFile()判断,然后父目录作为 rootIndex,再定位到该文件的 index。 - 权限是否可读——用
QFileInfo::isReadable()判断,不可读目录进入后内容全空,容易让人以为程序坏了,主动弹个提示更友好。
这三层判断加起来不到二十行代码,但处理了实际使用中最常见的路径操作失误场景。我自己第一版跳转逻辑只做了第一层,结果输入一个文件路径时程序直接切到了该文件的父目录,选中状态还没定位,用户以为 bug 癔症了,后来补上 2 和 3 才顺。
3. 核心功能实现:删除、重命名的安全感和细节
文件管理器很多时间花在"看起来理所当然"的功能上。删除和重命名,听起来简单,实际做起来有不少细节决定用户是否愿意长期用。
3.1 删除操作:走回收站而不是直接删
直接QFile::remove()是文件管理器的大忌。误删之后用户一点挽回余地都没有。正确的做法是调用系统 API 把文件移入回收站。
Windows 上用SHFileOperationW或IFileOperation。前者是老 API,结构体填一下就能用,兼容性好;后者是 Vista 以后的推荐方式,支持更多选项但代码繁琐。考虑到目标系统覆盖 Win7 到 Win11,我直接用SHFileOperationW,这个 API 在 Win7 上没问题,到 Win11 也没废弃。
Linux 桌面环境下没有统一的回收站 API,但 follow freedesktop 规范是通用的:把文件移动到~/.local/share/Trash/files/,同时在~/.local/share/Trash/info/下写一个.trashinfo元数据文件记录原始路径和删除时间。这个规范几乎所有主流桌面环境(GNOME、KDE、XFCE)都认。实际代码里我封装了一个跨平台moveToTrash(path)函数,Windows 和 Linux 各一套实现,接口一致,上层调用无感知。
macOS 的NSFileManager有trashItemAtURL方法,如果以后要出 mac 版直接补一个实现即可,Qt 本身不封装这个能力,所以跨平台仍得自己写。
3.2 重命名的焦点处理与内联编辑
重命名我用的是 QTableView 的edit(index)触发内联编辑,用户可以直接在表格里改名字。但这个操作有个常见的体验问题:默认编辑状态下,如果用户输入的文件名不带扩展名后缀,回车后扩展名直接被吞了。
注意:内联编辑重命名时,默认会选中整个文件名(包括扩展名),用户如果懒得重新打字,直接输入新名字,扩展名就没了。必须在编辑开始前手动设置选中范围为主文件名部分。
实现方式是在触发重命名的槽函数里做两步:先edit(index)激活编辑器,然后找到对应的 QLineEdit,用setSelection(0, dotPos)只选中主文件名。QFileSystemModel 的fileName()可以用来算主文件名长度,遇到隐藏文件(名字以.开头)时不选任何部分,让用户从头输入。
这个细节看起来不起眼,但很多人做的文件管理器重命名后扩展名丢失,用户用一次就放弃了。我第一版也踩了这个坑,后来加了十几行代码修掉。
3.3 新建文件与文件夹的自动命名
新建文件/文件夹时,直接用用户输入的名字,容易出现重名覆盖风险。我的做法是:在创建前检查目标是否存在,如果存在就自动追加(1)、(2)这样的后缀,直到不冲突为止。这个逻辑不复杂,但需要小心处理大小写——Windows 文件系统不区分大小写,abc.txt和ABC.TXT在同一个目录下算重名;而 Linux 区分。所以判断冲突时,Windows 上统一toLower()后再比较。
新建文件夹我用QDir().mkdir(),新建文件我用QFile().open(QIODevice::WriteOnly)创建空文件然后立即关闭。创建完成后,自动进入重命名编辑状态(复用上一条的焦点处理逻辑),让用户直接输入名字。这个交互流程贴近系统资源管理器,用户上手很快。
4. 搜索与过滤:一个代理模型搞定还是得自己写?
4.1 QSortFilterProxyModel 处理名称过滤
QFileSystemModel + QSortFilterProxyModel 的组合是 Qt 文件浏览器的标准配置。过滤器规则用setFilterRegularExpression()设置正则,比如输入.cpp$就只显示 C++ 源文件。但这里必须注意:代理模型过滤时,setFilterCaseSensitivity()的默认值是不区分大小写的,如果你做代码文件过滤,.CPP也会匹配到,实际使用时要显式设置为Qt::CaseSensitive。
另外 QSortFilterProxyModel 有递归过滤子目录的能力,用setRecursiveFilteringEnabled(true)。但 QFileSystemModel 的数据量通常不会太大(几千个文件顶天了),递归过滤的性能影响可以忽略。如果你管理的目录里放了十万个文件的极深目录树,建议还是自己做索引,但那是另一个量级的项目了。
4.2 隐藏文件显示:QFileSystemModel 的过滤器
QFileSystemModel 默认继承了 QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs,所以ls -a里能看到的所有条目都应该能看到。但实际测试发现,它默认不显示隐藏文件——因为QDir::Filter组合里没带QDir::Hidden。显示隐藏文件的方法就是setFilter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::Hidden)。
这里有个容易混淆的点:QFileSystemModel 的 filter 和 QSortFilterProxyModel 的 filter 是两个层面。前者控制"模型从文件系统加载哪些条目",后者控制"这些已加载条目里哪些显示到视图"。两者都设置才会得到预期结果,漏掉任何一个都会让人困惑。我还遇到过一个案例:隐藏文件明明在磁盘上,但无论如何不显示,排查了半天才发现是 QFileSystemModel 层面的 filter 没加 Hidden 标志。
4.3 内容搜索策略:文件名匹配 vs 全文检索
文件名匹配的搜索靠 QSortFilterProxyModel 的过滤正则就能做。但真正好用的文件管理器还需要全文检索能力——比如我搜索"README"时,希望不只是文件名匹配的 README.md,也包括内容里提到 README 的那些文件。
这种需求如果做成实时搜索,文件系统扫描性能是个大问题。我采用的策略是:
- 搜索范围限定在当前目录 + 子目录,不全局扫描(全局扫描会卡 UI)。
- 搜索结果先做文件名匹配,这部分走代理模型,秒出。
- 文件名匹配没结果时,再启动一个后台线程对子目录文件做内容扫描,扫到结果后通过信号送给主线程插入到结果列表。
- 文件内容的扫描用 QTextStream 逐行读取,匹配子串即可,不做正则(正则太慢)。
后台扫描线程需要注意:不能碰 QFileSystemModel 和 QTableView,所有界面更新必须通过信号槽跨线程回到主线程。这里我直接用了 Qt::QueuedConnection 方式连接,代码简单不易出错。文件多了以后扫描时间会长,需要在界面底部显示一个进度条,这让用户体验提升明显。
5. 拖拽与剪贴板:跨应用协作的隐藏成本
文件管理器不能光能自己内部拖,还要能和系统文件管理器互相拖。Qt 的拖拽支持通过重写dragEnterEvent、dragMoveEvent、dropEvent实现,数据格式用 QMimeData 的标准 URL 列表格式。
5.1 从文件管理器拖文件到外部程序
实现方式是给 QTableView 设置setDragEnabled(true)和setDragDropMode(QAbstractItemView::DragOnly)。Qt 会自动把选中的文件路径打包成text/uri-listMIME,拖到系统资源管理器(比如 Windows Explorer)里就能直接复制或移动。
这里有个细节:默认 QFileSystemModel 支持的拖拽行为会把"复制"和"移动"事件都映射成文件复制/移动操作。但如果你希望拖到外部程序时是"复制文件内容"而不是"移动文件",需要在 model 的mimeData()里保留原始 URL,并且让 drop 事件里根据 Ctrl/Shift 键位判断是复制还是移动。我实现时统一用"复制到当前目录"作为拖拽落地的默认行为,按住 Ctrl 和按住 Shift 没有区分,减少误操作概率。
5.2 接收外部拖入的文件
要让文件管理器支持把外部文件拖进当前目录,需要设置setAcceptDrops(true),然后在dropEvent里取出event->mimeData()->urls(),每个 url 调用toLocalFile()判断是不是file://协议,然后统一复制到当前目录。如果是目录本身,还要递归复制子目录,这个用 QDir 的递归拷贝函数封装即可。
关于拖拽还有一个交互取舍:外部拖入是默认"移动"还是"复制"更合理?我选择"复制"——因为拖到文件管理器窗口的场景,用户通常是想把文件收纳到目标目录,复制比移动更安全。Ctrl 键可以切换为移动,但我不想在第一次版本里引入这个逻辑,留给后续优化。
5.3 剪贴板复制剪切粘贴
剪贴板这块,Qt 的标准做法是把文件路径以text/uri-list格式写入 QClipboard,粘贴时读取同样的格式。关键逻辑是:text/uri-list里放的是file:///path/to/file这样的 URL,粘贴时用QUrl::fromLocalFile()转回绝对路径,然后调用QFile::copy或QFile::rename(对应移动)。
剪切和复制的区分,我用了一个内部状态变量m_clipboardOp记录是 Copy 还是 Move,粘贴时据此决定调 copy 还是 rename。剪贴板关闭后这个状态会丢?关掉程序再打开就没法保持"剪切"状态了。这个问题我还没做持久化,因为 Windows 剪贴板里虽然有文件句柄(CF_HDROP),但那个是系统级 clipboard 格式,Qt 的 QClipboard 不支持直接操作 CF_HDROP 的语义。如果想做到系统级剪贴板互操作,得用QClipboard::setMimeData塞标准 MIME,同时额外塞一个自定义 MIME 存内部状态,才能跨应用粘贴时保留移动语义。这个我留在 v2 里做了。
6. 编译发布那些事儿:5.15.2 的坑和依赖打包
6.1 Qt 5.15.2 环境配置与编译
Qt 5.15.2 的安装很直接:官网下载安装包,选编译套件(MinGW 或 MSVC)。我用的是 MinGW 64 位版本,原因一是配置简单,二是不用装 Visual Studio,发布时也少一个 VC++ Redistributable 依赖。但注意:如果你用 MSVC 编译,出来的程序在用户机器上还需要安装对应版本的 VC++ 运行库,这个劲儿容易忽略。MinGW 版程序自带 libgcc 和 libstdc++ 运行库的静态链接方案,发布时体积会大一点,但省心。
编译阶段最容易踩的坑是编译器版本和 Qt 库不匹配。MinGW 版 Qt 5.15.2 要求 GCC 8.1.0 或更高版本(实际到 9.x 都没问题),但 10.x 就有概率碰到 ABI 兼容问题。我遇到过fatal: cannot mix incompatible Qt library (version ex50601) with this library这种报错,通常就是 Qt 库文件和当前编译器的版本不兼容。解决方式是:确认 QTDIR 环境变量没指错、确认 makefile 里 CXX 选的是 MinGW 的 g++ 而不是系统默认的 g++、确认没有同时装了多个版本 Qt 导致库路径串了。排除完这三项,99% 的此类报错都能解决。
6.2 插件平台缺失问题:could not find the Qt platform plugin
这是 Qt 程序最容易在发布阶段碰到的经典报错:qt.qpa.plugin: could not find the Qt platform plugin "linuxfb" in ""。说白了就是找不到平台的 QPA 插件。Linux 下通常是libqxcb.so或libqlinuxfb.so没拷到platforms/目录;Windows 下则是qwindows.dll缺失。发布目录的正确结构应该是:
发布目录/ ├── 你的程序.exe ├── Qt5Core.dll ├── Qt5Gui.dll ├── Qt5Widgets.dll ├── Qt5Network.dll (如果用网络) ├── platforms/ │ ├── qwindows.dll (Windows) │ └── qlinuxfb.so (Linux, 或用 qxcb.so) ├── styles/ └── imageformats/ (如果要用特定图片格式)windeployqt 工具能自动拷这些,但注意它不会拷你代码里动态加载的插件。如果程序里用了Q_IMPORT_PLUGIN动态加载某些模块(比如自定义样式,或某些图片格式插件),需要手动补目录。另外"linuxfb" 这种平台插件在嵌入式场景常用,普通的桌面 Linux 程序应该用qxcb.so,发布时两个都要带上更保险。
6.3 闪退排查:0000005 访问违例
程序在用户机器上闪退,报错是0xC0000005(access violation)。C++ 程序里这个错误几乎总是野指针、数组越界或 double free。我遇到过最典型的一个场景:删除文件时,当前选中 index 可能已经失效,继续操作就崩。排查思路有几个固定套路:
- 打开调试器看 call stack。GDB 断住后
bt命令能看到崩溃位置。如果是已发布的 release 版本,用 MinGW 的addr2line把地址翻译成代码行号。 - 检查所有与 QModelIndex 相关的缓存。QModelIndex 是临时对象,不能长期保存,保存要转成 QPersistentModelIndex。如果代码里
auto index = xxx;持有之,在文件操作后 model 数据变化,index 可能悬空。 - 检查跨线程访问。子线程不要直接操作 QWidget 或 QFileSystemModel,所有交互走信号槽。
- 看有没有 dll 版本不一致。把程序依赖的 Qt5Core.dll 替换成别的版本就会随机崩。
我实际的排查经验是:文件管理器这种模型密集型的程序,90% 的崩都出在 index 失效和跨线程访问这两个问题上。代码里凡是见到 model index 的地方,先问一句:这个 index 的生命周期能撑到我用它的时候吗?不行就转 QPersistentModelIndex。
6.4 发布体积优化与依赖检查
MinGW 静态链接后,程序大概 20-40MB,动态链接是 5-15MB 但需要一堆 dll。体积优化空间不大,但依赖检查很重要:发布前用一个干净的虚拟机(或容器)测一遍,保证程序能直接在全新系统里跑起来。我通常是下个精简 Windows/Linux 镜像装上测试,这种最接近真实用户的环境。
另外,Qt 5.15.2 的开放源码版在 LGPL 协议下,动态链接没问题,静态链接则要注意开源义务(提供 relink 用的目标文件或允许用户自行替换 Qt 库)。商业软件如果不想纠结,直接买商业版授权最省心。这个判断要自己做,我只能提醒你注意许可证边界,别发布完被告了。
7. 进阶优化:批量操作与分卷处理的工程化思路
7.1 批量重命名的实现
批量重命名是文件管理器一个高频需求。我从一开始就把批量重命名设计成两个入口:一是选中多个文件后右键,"按规则重命名",二是用%d表示序号,模板形如IMG_%03d.jpg。这两者最终都落到同一个重命名函数,依次处理选中项并做冲突检测。
批量重命名最需要小心的坑是重名覆盖。如果用户模板生成的名字和目录里一个已有文件重名,直接覆盖就毁了。我在重命名前先把所有目标路径算好,统一检查冲突,冲突时最后在 UI 上提示。顺序也讲究:先重命名文件再重命名目录(因为目录改名后子路径变化影响其他文件的目标路径),这个逻辑很多人第一次写会漏。
还有一个隐藏坑:Windows 上文件占用时重命名会失败(比如 Excel 正打开着 xlsx)。代码里要捕获QFile::rename的 false 返回值,并且显示"是否是占用导致"的排查建议,否则用户觉得程序没反应。
7.2 大目录加载性能与懒加载
一个目录下有两万张图片,全量加载 QTableView 的模型是撑得住的(QFileSystemModel 本身就是懒加载),但滚动时会有明显卡顿。优化点有三个:
- 开启
QTableView::setUniformRowHeights(true),表格高度固定,减少滚动时的布局计算。 - 用 delegate 自定义绘制,只在当前可见区域绘制图片缩略图,看不到的行不加载。
- 排序操作放到后台线程做,QFileSystemModel 自带排序线程安全机制,但是
sort()方法调用后 UI 线程会等待排序结果,这时候界面会冻结。用QSortFilterProxyModel的异步排序思路更稳。
我在实际项目里用的是第三个方案:所有排序操作都交给代理模型,代理模型背后再用 QtConcurrent 做排序,排序完成后更新视图滚动位置。实测两万个图片的目录,滚动流畅度提升非常明显。
7.3 CAN 通讯类文件管理器的异同
顺手提一句,有朋友问"Qt 写的 CAN 通讯软件容易闪退怎么办"。这跟文件管理器看似八竿子打不着,但排错的思路完全一样:多看几遍QIODevice::write的返回值、多 check 一次线程同步,多半是某个定时器回调里操作了已释放的控件。我做过一个 CAN 调试工具,原理上就是"数据波形文件"管理器,区别在于源数据是 CAN 帧而不是磁盘文件。后者难就难在实时采集和高频刷新,文件系统模型的 index 失效问题反而是小事。闪退通常集中在接收线程直接操作 UI 这一块:CAN 帧到达中断里刷新表格,如果不定时器里model->setData接触了正在重绘的 view,很容易 0000005。解决办法是在接收线程只 push 数据到环形缓冲,UI 用 QTimer 以 20ms 间隔刷一次表格,这样即使接收频率很高,UI 也不会崩。文件管理器里跨线程文件扫描也是同一个套路:子线程只产生结果列表,主线程用定时器或信号拉取并刷新。
8. 实操经验分享与代码性能优化心得
8.1 几个日常维护中容易忽略的事项
- 文件操作失败的错误处理。很多教程只写了成功路径,没人教你怎么处理"删不掉""复制被拒"这类情况。我在代码里但凡调用
QFile::copy、QFile::rename、QDir().mkdir()这类函数,都会认真读 false 返回值,并且通过QFileDevice::errorString()反馈给用户。这个习惯帮我提前暴露了多个真实用户才碰到的权限问题。 - 区分隐藏文件与系统文件。QDir::Hidden 过滤会显示
.git、.cache这类目录,但系统特殊文件(如 Windows 的System Volume Information)可能访问就报权限错误。遍历时遇到不可访问目录,直接跳过并记日志,别让程序卡死。 - 日志系统。自己写文件管理器也要留个日志文件(
~/.myfm/log.txt),记录文件操作、异常情况、路径变化。真出问题时,日志是排查利器。
8.2 性能优化心得
一个两万文件目录的测试,是我验证文件管理器性能的标准场景。实测下来有几个结论:
- QFileSystemModel 初始化这个目录大约要 1.5 秒(首次,之后是秒开)。这个数据取决于磁盘速度和文件数,但总体可接受。
- 排序和过滤尽量不要在主线程做。我用 QSortFilterProxyModel 的
setSortRole()和setDynamicSortFilter(true),实测大目录排序大约几百毫秒,还是有点卡但可接受。 - 图片缩略图用 delegate 延迟绘制,配合
QThreadPool后台生成,生成完信号通知 view 刷新对应行。实测滚动时缩略图不卡,但内存占用会高(一张大图缩略图几十KB,两千张也就几十MB,可接受)。 - 深目录重命名(比如把
C:\a\b\c改成C:\x\b\c)时,如果子目录里有软链接,重命名后链接可能会失效,Qt 不处理这个,得自己判断。
8.3 跨平台注意事项再补一刀
文件的换行符、路径分隔符、大小写敏感性、隐藏文件规则、回收站规则,五个维度各不相同。Windows 的路径是C:\...,Linux 是/home/...,macOS 是/Users/...,写代码时不要在任何地方硬编码/或\,统一用QDir::separator()或QDir().filePath()拼接。这个习惯一开始养成能省大量麻烦。
另外 Qt 5.15.2 的QFileSystemModel有个特性:它默认缓存了大量文件信息,如果你在模型外面直接用 QFile 改了某个文件的权限或时间戳,模型可能不刷新。这时候可以调用qApp->processEvents()或者model->refresh(index)强制刷新。实测某些场景下必须手动刷新才能看到新属性。
最后分享一个我自己的习惯:每次启动程序时,用QFileInfo预检查一遍常用目录(家目录、文档、下载、图片),看看是否存在,顺便把隐藏文件个数等统计信息在状态栏显示。这个小功能让文件管理器看起来更"聪明",实际代码量不到五十行。
最终做完这个小工具,我最大的体会是:本地文件管理器虽然是"老掉牙"的应用类型,但涉及的操作面非常广——文件系统、线程、剪贴板、拖拽、平台差异、发布打包——任何一个环节出问题都会直接影响用户体验。Qt 的模型视图框架和文件系统封装为我省了大量底层细节,但"正确性"仍然要靠自己把控。如果你也打算写类似工具,我建议把上面提到的 index 生命周期、跨线程访问、发布依赖这三座大山先修好,剩下的事就水到渠成了。