WinUI 3里FileOpenPicker
不能直接用
WinUI 3(尤其是打包为MSIX的应用)不支持传统UWP风格的
FileOpenPicker同步调用方式,直接new
FileOpenPicker()会编译失败或运行时报
System.Runtime.InteropServices.COMException: Error HRESULT E_FAIL。根本原因是WinUI 3剥离了UWP应用模型中部分依赖
Windows.UI.Popups和
Windows.Storage.Pickers的旧API,改用基于
Windows.AppSDK的新文件选择器——
Windows.System.Launcher.LaunchUriAsync配合
Windows.Storage.Pickers.FileOpenPicker的**异步委托模式**,但仅限于桌面桥接场景。
正确做法:用Windows.Storage.Pickers.FileOpenPicker
+ PickerLocationId
配置
必须确保项目已引用
Microsoft.WindowsAppSDK≥ 1.5,并在
Package.appxmanifest中声明
rescap:Capability Name="broadFileSystemAccess"(仅调试时需要,发布需合理申请权限)。实际代码中:
FileOpenPicker实例必须通过
InitializeWithWindow绑定主窗口句柄,否则弹窗无父窗口、可能被系统拦截 必须设置
SuggestedStartLocation(如
PickerLocationId.DocumentsLibrary),否则某些系统版本会静默失败
FileTypeFilter必须至少包含一个有效扩展名,例如
picker.FileTypeFilter.Add(".txt"),空集合会导致ArgumentException调用
PickSingleFileAsync()后,结果是
StorageFile,不是
Stream,需用
await file.OpenReadAsync()打开
var picker = new Windows.Storage.Pickers.FileOpenPicker();
picker.InitializeWithWindow(hWnd); // hWnd来自MainWindow's WindowHandle
picker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.DocumentsLibrary;
picker.FileTypeFilter.Add(".json");
picker.FileTypeFilter.Add(".xml");
var file = await picker.PickSingleFileAsync();
if (file != null) {
using var stream = await file.OpenReadAsync();
// 处理流
}
常见报错和绕过方案
如果遇到
UnauthorizedAccessException,说明未启用
broadFileSystemAccess或用户拒绝了权限;若弹窗一闪而逝,大概率是
InitializeWithWindow传入的
hWnd无效(比如在后台线程调用、或
WindowHandle尚未初始化完成)。 检查
hWnd是否为
MainWindow构造后获取:
App.MainWindow.WindowHandle,而非在
OnLaunched早期就取 避免在
Loaded事件外调用,确保UI线程就绪 不要尝试用
DispatcherQueue包装
PickSingleFileAsync——它本身已是异步且需UI线程上下文 若目标是打开任意路径(如C:\temp\*.log),
FileOpenPicker受限于沙盒,应改用
Windows.System.Launcher.LaunchUriAsync(new Uri("ms-settings:privacy-broadfilesystemaccess"))引导用户手动授权
替代方案:用Microsoft.WinUI.Controls.FileOpenPicker
(第三方封装)
社区有轻量封装库(如
CommunityToolkit.WinUI)提供更友好的API,但底层仍调用原生
FileOpenPicker。它的价值在于自动处理
hWnd绑定和异常分类,比如:
var picker = new Microsoft.WinUI.Controls.FileOpenPicker();
picker.FileTypeFilter.Add(".png");
var result = await picker.PickSingleFileAsync();
// result.Stream 可直接读取,无需再OpenReadAsync
不过要注意:该封装不改变权限模型,
broadFileSystemAccess仍需声明,且无法绕过系统对非库路径的限制。真正跨路径自由选择,目前WinUI 3没有纯客户端解法,必须结合
Windows App SDK的
IStorageItem+
StorageFolder.GetItemsAsync()做二次导航,或接受用户粘贴路径+
StorageFile.GetFileFromPathAsync()(需提前获得路径访问许可)。
