C#中OpenFileDialog选择Windows媒体设备文件时冻结问题求助
我之前在处理MTP设备(比如iPad这类Windows媒体设备)的文件选择时,也碰到过一模一样的OpenFileDialog冻结问题——本质就是系统默认会把选中的设备文件同步复制到临时文件夹,这个过程如果文件偏大就会卡住UI。给你几个实用的解决方案:
1. 禁用自动复制,直接获取设备原生路径
这是最彻底的解决方法,通过修改对话框的底层选项,阻止系统复制文件到临时文件夹,直接返回设备的原生路径。你可以调用COM接口IFileOpenDialog来设置FOS_NOCOPYHOOKS标志:
using System; using System.Runtime.InteropServices; using System.Windows.Forms; // 定义必要的COM接口 [ComImport] [Guid("DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IFileOpenDialog { void SetOptions(uint options); void Show(IntPtr parent); void GetResult(out IShellItem item); // 其他接口方法可省略,仅保留需要的部分 } [ComImport] [Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IShellItem { void GetDisplayName(uint sigdnName, out IntPtr ppszName); // 其他接口方法可省略 } [ComImport] [Guid("DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7")] private class FileOpenDialog { } public static string GetMtpFilePath(IntPtr ownerWindowHandle) { IFileOpenDialog dialog = new FileOpenDialog() as IFileOpenDialog; const uint FOS_NOCOPYHOOKS = 0x00000200; // 设置禁用复制钩子的选项 dialog.SetOptions(FOS_NOCOPYHOOKS); dialog.Show(ownerWindowHandle); IShellItem item; dialog.GetResult(out item); const uint SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000; IntPtr pathPtr; item.GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, out pathPtr); string devicePath = Marshal.PtrToStringUni(pathPtr); Marshal.FreeCoTaskMem(pathPtr); return devicePath; }
调用这个方法后,你得到的是类似 ::{GUID}\iPad\Internal Storage\... 的原生设备路径,不会触发文件复制,自然也就不会冻结了。注意:不能用普通的File类方法读取这个路径,需要用Shell API来操作文件内容,比如SHCreateStreamOnFileEx或者后面提到的工具库。
2. 使用Windows API Code Pack简化开发
如果不想自己写复杂的COM接口代码,可以用微软官方的Windows API Code Pack,它封装了更易用的文件对话框,支持直接获取MTP设备文件:
首先安装NuGet包:Install-Package Microsoft.WindowsAPICodePack-Shell
然后用CommonOpenFileDialog实现:
using Microsoft.WindowsAPICodePack.Dialogs; using Microsoft.WindowsAPICodePack.Shell; private void SelectMtpFileBtn_Click(object sender, EventArgs e) { using (var dialog = new CommonOpenFileDialog()) { dialog.AllowNonFileSystemItems = true; // 允许选择非本地文件系统的项目 dialog.IsFolderPicker = false; if (dialog.ShowDialog() == CommonFileDialogResult.Ok) { // 获取原生设备路径 string devicePath = dialog.FileName; // 使用ShellFile读取文件内容 using (var shellFile = ShellFile.FromFilePath(devicePath)) using (var stream = shellFile.OpenRead()) { // 这里写你的后续处理逻辑,比如读取流内容 } } } }
这个方法更简洁,不需要自己处理COM接口的细节,兼容性也不错(支持Windows 7及以上)。
3. 退而求其次:后台处理临时文件(仅缓解)
如果你的业务逻辑必须依赖本地临时文件,这个方法只能缓解UI冻结,但无法阻止对话框本身的卡顿——因为文件复制是在对话框内部同步发生的。你可以在对话框关闭后,把后续处理放到后台线程:
private async void SelectFileBtn_Click(object sender, EventArgs e) { using (var ofd = new OpenFileDialog()) { if (ofd.ShowDialog() == DialogResult.OK) { // 对话框关闭后,在后台线程处理临时文件 await Task.Run(() => { string tempFilePath = ofd.FileName; // 这里写你的后续处理,比如复制到指定目录、读取内容等 }); } } }
不过还是推荐前两种方法,从根源解决冻结问题。
最后提醒
不管用哪种方法,处理MTP设备的文件时都要记住:这些路径不是本地文件系统的路径,必须用Shell相关的API来操作,不能直接用File.Open、File.Copy这类普通文件方法。
内容的提问来源于stack exchange,提问作者Derek




