C# 尝试使用C中新的IFileDialog和IFileOpendDialog接口以最少的代码打开文件对话框

C# 尝试使用C中新的IFileDialog和IFileOpendDialog接口以最少的代码打开文件对话框,c#,interface,com-interop,openfiledialog,C#,Interface,Com Interop,Openfiledialog,我正在尝试显示一个标准的打开文件对话框,它可以使用Visual Studio 2010 C中的IFileOpenDialog界面来选择文件夹 我试图使用最少的代码,所以我只在接口中定义了我需要的方法: using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // Disable warning CS0108: 'x' hides inherited member 'y'

我正在尝试显示一个标准的打开文件对话框,它可以使用Visual Studio 2010 C中的IFileOpenDialog界面来选择文件夹

我试图使用最少的代码,所以我只在接口中定义了我需要的方法:

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// Disable warning CS0108: 'x' hides inherited member 'y'. Use the new keyword if hiding was intended.
\#pragma warning disable 0108

namespace FolderDialog
{

internal static class IIDGuid
{
    internal const string IModalWindow = "b4db1657-70d7-485e-8e3e-6fcb5a5c1802";
    internal const string IFileDialog = "42f85136-db7e-439c-85f1-e4075d135fc8";
    internal const string IFileOpenDialog = "d57c7288-d4ad-4768-be02-9d969532d960";
}

internal static class CLSIDGuid
{
    internal const string FileOpenDialog = "DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7";
}

static class NativeMethods
{

    [Flags]
    internal enum FOS : uint
    {
        FOS_PICKFOLDERS = 0x00000020
    }

}

[ComImport(),
Guid(IIDGuid.IModalWindow),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IModalWindow
{

    [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime),
    PreserveSig]
    int Show([In] IntPtr parent);
}

[ComImport(),
Guid(IIDGuid.IFileDialog),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IFileDialog : IModalWindow
{
    // Defined on IModalWindow - repeated here due to requirements of COM interop layer
    // --------------------------------------------------------------------------------
    [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime),
    PreserveSig]
    int Show([In] IntPtr parent);

    [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
    void SetOptions([In] NativeMethods.FOS fos);
}

[ComImport(),
Guid(IIDGuid.IFileOpenDialog),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IFileOpenDialog : IFileDialog
{
    // Defined on IModalWindow - repeated here due to requirements of COM interop layer
    // --------------------------------------------------------------------------------
    [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime),
    PreserveSig]
    int Show([In] IntPtr parent);

    [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
    void SetOptions([In] NativeMethods.FOS fos);
}


// ---------------------------------------------------
// .NET classes representing runtime callable wrappers
[ComImport,
ClassInterface(ClassInterfaceType.None),
TypeLibType(TypeLibTypeFlags.FCanCreate),
Guid(CLSIDGuid.FileOpenDialog)]
internal class FileOpenDialogRCW
{
}

// ---------------------------------------------------------
// Coclass interfaces - designed to "look like" the object 
// in the API, so that the 'new' operator can be used in a 
// straightforward way. Behind the scenes, the C# compiler
// morphs all 'new CoClass()' calls to 'new CoClassWrapper()'
[ComImport,
Guid(IIDGuid.IFileOpenDialog),
CoClass(typeof(FileOpenDialogRCW))]
internal interface NativeFileOpenDialog : IFileOpenDialog
{
}
}

只要我打电话

        IFileDialog dialog = null;
        try
        {
            dialog = new NativeFileOpenDialog();
            dialog.Show(IntPtr.Zero);
        }
        finally
        {
            if (dialog != null)
                System.Runtime.InteropServices.Marshal.FinalReleaseComObject(dialog);
        }
它工作正常,打开文件对话框时没有任何错误

如果我尝试:

        IFileDialog dialog = null;
        try
        {
            dialog = new NativeFileOpenDialog();
            dialog.SetOptions(NativeMethods.FOS.FOS_PICKFOLDERS);
            dialog.Show(IntPtr.Zero);
        }
        finally
        {
            if (dialog != null)
                System.Runtime.InteropServices.Marshal.FinalReleaseComObject(dialog);
        }
因此,如果我在Show方法调用之前添加对SetOptions方法的调用,我会得到一个异常:尝试读取或写入受保护内存。这通常表示其他内存已损坏

我尝试在.NET2.0甚至4.0上运行时得到这个


这里的错误是什么?为什么只调用Show方法有效,但如果我在失败之前尝试其他方法?

如果您只是尝试打开文件夹浏览器对话框,可能会使问题过于复杂。改用这个类

这可能会有所帮助。下载后,请查看\Windows API Code Pack 1.1\source\WindowsAPICodePack\Shell\CommonFileDialogs\CommonFileDialog.cs,了解它如何从COM使用IFileDialogCustomize


希望这能有所帮助。

有点晚了,但你可以在我的回答中找到C语言中IFileDialog接口的示例:还有另一个变体,允许选择非文件系统文件夹,如我的电脑/这台电脑:

这里有一个类,它使用.Net专用IFileDialog接口打开Vista风格的文件夹选择器,在代码中不直接使用互操作。Net会为您解决这一问题。如果Windows版本不够高,它会返回到Vista之前的对话框。理论上应该可以在Windows7、8、9、10和更高版本中工作

using System;
using System.Reflection;
using System.Windows.Forms;

namespace Ris.Shuriken {
    /// <summary>
    /// Present the Windows Vista-style open file dialog to select a folder. Fall back for older Windows Versions
    /// </summary>
    public class FolderSelectDialog {
        private string _initialDirectory;
        private string _title;
        private string _fileName = "";

        public string InitialDirectory {
            get { return string.IsNullOrEmpty(_initialDirectory) ? Environment.CurrentDirectory : _initialDirectory; }
            set { _initialDirectory = value; }
        }
        public string Title {
            get { return _title ?? "Select a folder"; }
            set { _title = value; }
        }
        public string FileName { get { return _fileName; } }

        public bool Show() { return Show(IntPtr.Zero); }

        /// <param name="hWndOwner">Handle of the control or window to be the parent of the file dialog</param>
        /// <returns>true if the user clicks OK</returns>
        public bool Show(IntPtr hWndOwner) {
            var result = Environment.OSVersion.Version.Major >= 6
                ? VistaDialog.Show(hWndOwner, InitialDirectory, Title)
                : ShowXpDialog(hWndOwner, InitialDirectory, Title);
            _fileName = result.FileName;
            return result.Result;
        }

        private struct ShowDialogResult {
            public bool Result { get; set; }
            public string FileName { get; set; }
        }

        private static ShowDialogResult ShowXpDialog(IntPtr ownerHandle, string initialDirectory, string title) {
            var folderBrowserDialog = new FolderBrowserDialog {
                Description = title,
                SelectedPath = initialDirectory,
                ShowNewFolderButton = false
            };
            var dialogResult = new ShowDialogResult();
            if (folderBrowserDialog.ShowDialog(new WindowWrapper(ownerHandle)) == DialogResult.OK) {
                dialogResult.Result = true;
                dialogResult.FileName = folderBrowserDialog.SelectedPath;
            }
            return dialogResult;
        }

        private static class VistaDialog {
            private const string c_foldersFilter = "Folders|\n";

            private const BindingFlags c_flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
            private readonly static Assembly s_windowsFormsAssembly = typeof(FileDialog).Assembly;
            private readonly static Type s_iFileDialogType = s_windowsFormsAssembly.GetType("System.Windows.Forms.FileDialogNative+IFileDialog");
            private readonly static MethodInfo s_createVistaDialogMethodInfo = typeof(OpenFileDialog).GetMethod("CreateVistaDialog", c_flags);
            private readonly static MethodInfo s_onBeforeVistaDialogMethodInfo = typeof(OpenFileDialog).GetMethod("OnBeforeVistaDialog", c_flags);
            private readonly static MethodInfo s_getOptionsMethodInfo = typeof(FileDialog).GetMethod("GetOptions", c_flags);
            private readonly static MethodInfo s_setOptionsMethodInfo = s_iFileDialogType.GetMethod("SetOptions", c_flags);
            private readonly static uint s_fosPickFoldersBitFlag = (uint) s_windowsFormsAssembly
                .GetType("System.Windows.Forms.FileDialogNative+FOS")
                .GetField("FOS_PICKFOLDERS")
                .GetValue(null);
            private readonly static ConstructorInfo s_vistaDialogEventsConstructorInfo = s_windowsFormsAssembly
                .GetType("System.Windows.Forms.FileDialog+VistaDialogEvents")
                .GetConstructor(c_flags, null, new[] { typeof(FileDialog) }, null);
            private readonly static MethodInfo s_adviseMethodInfo = s_iFileDialogType.GetMethod("Advise");
            private readonly static MethodInfo s_unAdviseMethodInfo = s_iFileDialogType.GetMethod("Unadvise");
            private readonly static MethodInfo s_showMethodInfo = s_iFileDialogType.GetMethod("Show");

            public static ShowDialogResult Show(IntPtr ownerHandle, string initialDirectory, string title) {
                var openFileDialog = new OpenFileDialog {
                    AddExtension = false,
                    CheckFileExists = false,
                    DereferenceLinks = true,
                    Filter = c_foldersFilter,
                    InitialDirectory = initialDirectory,
                    Multiselect = false,
                    Title = title
                };

                var iFileDialog = s_createVistaDialogMethodInfo.Invoke(openFileDialog, new object[] { });
                s_onBeforeVistaDialogMethodInfo.Invoke(openFileDialog, new[] { iFileDialog });
                s_setOptionsMethodInfo.Invoke(iFileDialog, new object[] { (uint) s_getOptionsMethodInfo.Invoke(openFileDialog, new object[] { }) | s_fosPickFoldersBitFlag });
                var adviseParametersWithOutputConnectionToken = new[] { s_vistaDialogEventsConstructorInfo.Invoke(new object[] { openFileDialog }), 0U };
                s_adviseMethodInfo.Invoke(iFileDialog, adviseParametersWithOutputConnectionToken);

                try {
                    int retVal = (int) s_showMethodInfo.Invoke(iFileDialog, new object[] { ownerHandle });
                    return new ShowDialogResult {
                        Result = retVal == 0,
                        FileName = openFileDialog.FileName
                    };
                }
                finally {
                    s_unAdviseMethodInfo.Invoke(iFileDialog, new[] { adviseParametersWithOutputConnectionToken[1] });
                }
            }
        }

        // Wrap an IWin32Window around an IntPtr
        private class WindowWrapper : IWin32Window {
            private readonly IntPtr _handle;
            public WindowWrapper(IntPtr handle) { _handle = handle; }
            public IntPtr Handle { get { return _handle; } }
        }
    }
}
当然,您可以使用它的选项和它公开的属性。例如,它允许在“视景样式”对话框中进行多重选择


正如他在这篇文章的回答中提到的,他展示了如何直接使用互操作来完成这项工作。不幸的是,我在制定解决方案时还没有发现这一点。说出你的毒药

为什么使用本机方法而不是托管版本?我希望能够打开文件夹,而不是文件,托管版本只能打开文件。FolderBrowserDialog的问题是,它不允许您键入文本框或复制或粘贴,并且不显示收藏夹,有时也不显示某些网络位置。这真的很难相处。它也不允许多选。请参阅我的。我认为如果你只是复制并粘贴你链接的整个答案,这个答案会有很大的改进。@ErikE-好吧,谢谢你打电话给我,但是我现在有两个相关的答案,所以我有点在不同的版本之间纠结:-上面指向API代码包的链接断了,但是,请看。是否有任何方法(例如某些标志)允许用户同时选择文件夹或文件?或者:当用户浏览选择文件夹时,是否有任何方法也可以显示每个文件夹中的文件?@S.serpoosan只需问一个新问题!
var dialog = new FolderSelectDialog {
    InitialDirectory = musicFolderTextBox.Text
    Title = "Select a folder to import music from"
};
if (dialog.Show(Handle)) {
    musicFolderTextBox.Text = dialog.FileName;
}