You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

C#中OpenFileDialog选择Windows媒体设备文件时冻结问题求助

解决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.OpenFile.Copy这类普通文件方法。

内容的提问来源于stack exchange,提问作者Derek

火山引擎 最新活动