152 lines
12 KiB
Markdown
152 lines
12 KiB
Markdown
# Fluid 文件资源管理器(WinUI 3 重构版)
|
||
|
||
用 **Windows App SDK / WinUI 3** 从零实现的 Windows 11 文件资源管理器替代品。
|
||
视觉与布局对齐原版(标签栏在标题栏内、Mica、命令栏、面包屑地址栏、导航窗格、详情/图标视图、状态栏),
|
||
但针对原版被吐槽最多的几个痛点做了结构性改造。
|
||
|
||
> 只用 **Windows 11 原生那一套**:WinUI 3 自带控件(NavigationView 风格侧边栏用 TreeView、TabView、BreadcrumbBar、AutoSuggestBox、InfoBar、ContentDialog…)、
|
||
> **Segoe Fluent Icons** 官方图标字体、**外壳原版文件图标**(`SHGetFileInfo` / `IShellItemImageFactory`,即资源管理器显示的同一批 imageres.dll 图标)、
|
||
> 以及系统自带的 Mica 材质与 WinUI 原生动画。**没有引入任何第三方 UI/图标/动画库**。
|
||
|
||
---
|
||
|
||
## 1. 直接运行
|
||
|
||
```powershell
|
||
dotnet build E:\deepseek\FluidExplorer\FluidExplorer.csproj -c Debug
|
||
E:\deepseek\FluidExplorer\bin\Debug\net8.0-windows10.0.26100.0\win-x64\FluidExplorer.exe
|
||
```
|
||
|
||
- 目标框架 `net8.0-windows10.0.26100.0`,x64,非打包(`WindowsPackageType=None`)+ **Windows App SDK 自包含**,
|
||
因此不需要预装 Windows App SDK 运行时,双击 exe 即可。
|
||
- 本机 NuGet 离线(`nuget.config` 里 `<clear/>`):所有依赖已在全局包缓存中,可直接还原。
|
||
依赖仅 `Microsoft.WindowsAppSDK 2.2.0` 与 `CommunityToolkit.Mvvm 8.4.2`(后者只用于减少 `INotifyPropertyChanged` 样板代码)。
|
||
- 设置文件:`%LOCALAPPDATA%\FluidExplorer\settings.json`(写入失败时退回程序目录)。
|
||
- 启动日志:程序目录下 `startup.log`(用于定位启动期异常)。
|
||
|
||
---
|
||
|
||
## 2. 针对原版痛点的改造
|
||
|
||
| 原版痛点 | 本实现的做法 |
|
||
|---|---|
|
||
| **搜索慢/搜不到**(原版走 Windows Search 索引,往往等半天甚至永远"正在搜索") | Everything 式 **NTFS 索引**:`FSCTL_ENUM_USN_DATA` 直读 MFT 全集 + 原始 MFT 解析取真实大小/时间 + `FSCTL_READ_USN_JOURNAL` 增量监听;查询是内存并行扫描,**20 万条目下 0.3–9 ms**(见 §5)。没有索引时自动退化为可取消的实时扫描,并在界面上明确标注"实时扫描",绝不假装在搜 |
|
||
| **搜索框必须回车、没有即时反馈** | 键入即搜(110 ms 防抖)+ 搜索框下拉即时建议 + 结果区实时显示"N 项结果 · X 毫秒 · 数据来源" |
|
||
| **搬文件很麻烦**(开两个窗口来回拖) | ① **双窗格**(Ctrl+Shift+D,中间可拖动分隔条)② 跨窗格剪贴板 ③ 拖到文件夹行/侧边栏即可移动(按住 Ctrl 复制)④ "移动到/复制到"以外的批量操作统一进队列 |
|
||
| **复制/移动卡死、无法暂停/取消** | 独立**文件操作引擎**:后台队列、**可暂停/继续/取消**、字节级进度 + 速度 + 剩余时间、失败重试(100/300/900 ms)、单文件失败不中断整批、长路径(`\\?\`)支持、同卷移动走 `File.Move`(1000 个文件 15 ms) |
|
||
| **覆盖冲突弹系统模态框、还打断操作** | 应用内冲突对话框(保留两者/替换/跳过 + "对后续所有冲突应用"),作业在等待用户选择时**保持运行态、UI 完全不卡** |
|
||
| **误操作无法挽回** | **Ctrl+Z 一步撤销**:移动/重命名搬回原位;删除默认进回收站并记录 `$I`→`$R` 映射,撤销即从回收站还原(不依赖系统弹窗) |
|
||
| **打开大文件夹/网络盘就"无响应"** | 枚举走 `FileSystemEnumerable`(底层 NtQueryDirectoryFile 批量缓冲,一次拿回名称+属性+大小+时间),**分批回调**(首批立刻上屏)、全程可取消、错误只在 InfoBar 提示不阻塞;属性列的真类型名走注册表并缓存 |
|
||
| **状态栏信息少** | 底部状态栏常驻:项目数 / 选中项数与合计体积 / 索引进度与条数 / 一键建索引 / 视图切换 / **操作队列入口** |
|
||
| **深浅色与强调色不跟随** | 全部颜色取自 WinUI 内置主题资源(不写死任何色值),`ElementTheme.Default` + `MicaBackdrop` 跟随系统;标题栏按钮颜色随主题切换;设置里可手动覆盖为浅/深色 |
|
||
| **动画要么没有要么过度** | 只用 WinUI 原生动画(列表项增删、悬停/选中反馈、TabView、对话框、Mica 过渡);设置里可一键关闭(内部用 `Timeline.AllowDependentAnimations` 总开关),不额外叠加自造动效 |
|
||
|
||
其他对齐原版的细节:真实路径面包屑(此电脑 › 本地磁盘 › …)、侧边栏(主页/图库/快速访问/此电脑/网络/回收站,可展开、跟随当前路径高亮)、
|
||
按文件夹记住视图方式与排序(写入 `settings.json`)、"名称"列用系统 `StrCmpLogicalW` 自然排序(文件2 排在 文件10 前)、文件夹恒排在文件前、
|
||
显示/隐藏隐藏项与扩展名、**系统原版右键菜单**(外壳 `IContextMenu`,第三方扩展条目也在)。
|
||
|
||
键盘:`Ctrl+T/W/Tab` 标签页、`Ctrl+Shift+D` 双窗格、`Alt+←/→/↑` 前进后退上级、`Backspace` 上级、`F5` 刷新、`F2` 重命名、
|
||
`Delete`/`Shift+Delete` 删除/彻底删除、`Ctrl+X/C/V`、`Ctrl+Shift+C` 复制路径、`Ctrl+Shift+N` 新建文件夹、`Ctrl+Z` 撤销、`Ctrl+F` 聚焦搜索、`Ctrl+L` 编辑地址。
|
||
|
||
---
|
||
|
||
## 3. 代码结构
|
||
|
||
```
|
||
FluidExplorer/
|
||
├─ App.xaml(.cs) 应用入口、启动阶段日志、全局异常兜底
|
||
├─ MainWindow.xaml(.cs) 窗口外壳:Mica、扩展标题栏、标题栏按钮配色、快捷键、冲突对话框
|
||
├─ Views/
|
||
│ ├─ ShellView.xaml(.cs) 标签栏(位于标题栏区域内)+ 操作队列面板
|
||
│ ├─ ExplorerTabView.xaml(.cs) 单/双窗格布局与分隔条
|
||
│ ├─ ExplorerPaneView.xaml(.cs) 一个浏览窗格:命令栏 / 地址栏 / 导航窗格 / 内容区 / 状态栏
|
||
│ └─ SettingsDialog.xaml(.cs) 设置
|
||
├─ ViewModels/
|
||
│ ├─ MainViewModel.cs 标签页、侧边栏、索引状态、作业快照、设置
|
||
│ ├─ ExplorerTabViewModel.cs 标签页 = 1~2 个窗格
|
||
│ ├─ ExplorerPaneViewModel.cs 导航状态机:枚举、排序、搜索、选择、文件操作、回收站、图库
|
||
│ ├─ JobRowViewModel.cs UI 线程的作业快照(作业在后台线程更新,不能直接绑 UI)
|
||
│ └─ SidebarNode.cs 侧边栏节点 / 面包屑段 / 搜索结果行
|
||
├─ Models/ FileEntry / ExplorerItem / FolderListing / 排序与视图枚举
|
||
├─ Navigation/ 位置模型(本机/图库/此电脑/回收站/驱动器/路径)+ 前进后退历史
|
||
├─ Services/
|
||
│ ├─ AppServices.cs 组合根
|
||
│ ├─ AppSettings.cs 设置持久化(含按文件夹视图状态)
|
||
│ ├─ FileSystem/ 快速枚举(NtQueryDirectoryFile 批量缓冲)+ 路径工具
|
||
│ ├─ Search/ 查询语法解析、搜索门面、NTFS 索引(Usn/:MFT 解析、名字池、索引存储、通配符)
|
||
│ ├─ Icons/ 外壳原版图标与缩略图(SHGetFileInfo / IShellItemImageFactory,LRU + 同键合并)
|
||
│ ├─ Operations/ 文件操作队列引擎(复制/移动/删除/重命名/撤销/回收站定位)
|
||
│ ├─ ItemVisuals/ 列表行的图标/缩略图按需加载(并发上限 + 去重)
|
||
│ └─ Shell/ 外壳能力:已知文件夹、类型名、打开/属性/剪贴板、回收站视图、原版右键菜单
|
||
├─ Themes/ Styles.xaml(尺寸/样式)、Glyphs.xaml(Segoe Fluent Icons 码点)
|
||
└─ Helpers/ DispatcherQueue 异步封装、值转换器
|
||
```
|
||
|
||
设计要点:
|
||
1. **接口先冻结再并行开发**:`IFileIndex` / `IIconService` / `IFileOperationService` / `IFileSystemService` 先定义,
|
||
索引、图标、操作三个模块独立实现(互不依赖),最后由 `AppServices` 装配。
|
||
2. **UI 线程零阻塞**:所有文件系统交互都是异步 + 可取消;集合更新经 `DispatcherQueue` 分批提交。
|
||
3. **后台对象不直接绑 UI**:作业在后台线程更新,界面绑的是 250 ms 刷新的 UI 线程快照(`JobRowViewModel`)。
|
||
4. **失败降级而不是崩溃**:假索引不可用时搜索自动退化;外壳取图失败返回 null 并回退到扩展名图标;启动异常写入 `startup.log`。
|
||
|
||
---
|
||
|
||
## 4. 搜索语法(对齐 Everything 习惯)
|
||
|
||
```
|
||
keyword 名称包含(大小写不敏感,多个词 = AND)
|
||
"两个 词" 带空格的短语
|
||
!keyword 排除
|
||
*.json / pre* 通配符
|
||
ext:log;txt 扩展名(可多值)
|
||
size:>100mb 大小(kb/mb/gb,支持 > < =)
|
||
dm:today / dm:7d 修改时间(today/yesterday/thisweek/thismonth/thisyear/Nd/Nh/Nw 或具体日期)
|
||
dc:today 创建时间(需要索引提供创建时间,当前版本未启用)
|
||
folder: / file: 只看文件夹 / 只看文件
|
||
path:Windows 在完整路径中匹配
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 验证情况(都基于本机实测,不采信"应该能用")
|
||
|
||
**实测通过**
|
||
- 工程编译:`0 error`;XAML 全部编译为 xbf(含主题字典)。
|
||
- 端到端启动:进程存活、窗口标题「文件资源管理器」、UI Automation 树确认——标签栏在标题栏区域内(系统按钮区已正确让位)、
|
||
命令栏 12 个按钮、地址栏(后退/前进/上级/刷新 + 2 段面包屑 + 搜索框)、导航窗格 16 个节点、
|
||
详情视图 4 个列头(名称/修改日期/类型/大小)、**真实列出 107 行文件**、状态栏与操作队列入口在位。
|
||
- 搜索:通过 UI Automation 向搜索框写入关键字,界面返回结果行并显示「N 项结果 · X 毫秒 · 数据来源」。
|
||
- 文件操作引擎(独立探针,62/62 断言 PASS):300 MB 复制的字节级进度;**暂停 1 秒增长 0 字节**;取消保留已完成部分;
|
||
同卷移动 1000 文件 **15 ms**(零字节流量);`KeepBoth/Replace/Skip/Ask+ApplyToAll` 全部正确;
|
||
308→312 字符长路径复制/移动成功;跨卷移动可撤销;永久删除不入撤销栈。
|
||
- NTFS 索引(独立探针,走生产代码路径):20 万条目合成数据集 **77 字节/条**;
|
||
查询 `*.json` 3.3 ms、`ext:log` 1.4 ms、`size:>100mb` 0.66 ms、`dm:today` 0.3 ms、`path:Windows` 9.3 ms;
|
||
2000 条路径还原 1.66 ms;增量(创建/改名/删除)与墓碑正确;大小未知的条目不会被 `size:` 误判为 0 字节。
|
||
过程中修掉一个会**静默丢 64% 结果**的扩容 bug(名字池被换新导致老记录名字失效)。
|
||
- 图标:`.txt/.exe/目录/驱动器` 均取到系统原生图标(32×32 原生尺寸、真 alpha);`shell:RecycleBinFolder` 等外壳对象正常;
|
||
图片缩略图 256×160 正常;1000 次扩展名取图 86 ms;300 次取图后 GDI/USER 句柄**零增长**。
|
||
|
||
**已知限制(不隐瞒)**
|
||
- **当前会话没有管理员权限**:`CreateFile(@"\\.\C:")` 被拒(`FSCTL_*` 返回 `ERROR_INVALID_FUNCTION`),
|
||
因此 NTFS 索引在本机本次验证中走的是 `RequiresElevation` 分支;界面状态栏会提示并可一键「以管理员身份重启」。
|
||
索引本身的正确性用同一份生产代码 + 合成数据集验证(见上)。**要拿到真实全盘索引,请以管理员身份运行一次。**
|
||
- 非管理员降级模式下文件大小全部为"未知"(USN 记录本身不含 size,真实大小依赖原始 MFT 解析),此时 `size:` 过滤会偏宽松。
|
||
- 拖放到**其他应用程序**、拖出标签新建窗口、网络邻居枚举为简化实现;图库依赖索引(无索引时给提示而非空列表)。
|
||
- 缩略图/图标语义、字体字形只做了"码点在字体中存在"的校验(60/60 存在,不会出现方框),
|
||
但**字形语义未经人眼确认**(本会话的模型不能读图),个别按钮图标若观感不佳可直接改 `Themes/Glyphs.xaml` 里的码点。
|
||
|
||
---
|
||
|
||
## 6. 复现验证
|
||
|
||
```powershell
|
||
# 编译
|
||
dotnet build E:\deepseek\FluidExplorer\FluidExplorer.csproj -c Debug
|
||
|
||
# 启动 + UI 自动化结构检查 + 截图(截图落在 E:\deepseek\artifacts\shell_window.png)
|
||
powershell -NoProfile -ExecutionPolicy Bypass -File E:\deepseek\tools\smoke_test.ps1
|
||
|
||
# 字形存在性校验(Segoe Fluent Icons)
|
||
powershell -NoProfile -ExecutionPolicy Bypass -File E:\deepseek\tools\check_glyphs.ps1
|
||
```
|