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

我正在尝试使用C#,Visual Studio 2010中的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方法的调用,则会出现exception: “尝试读取或写入受保护的内存。这通常表示其他内存已损坏。”

我试图在.Net 2.0甚至4.0上运行。

这里有什么错误? 为什么只调用Show方法,但如果我在失败之前尝试另一种方法?

适用于Microsoft®.NETFramework的Windows®API代码包可以提供帮助。 下载后,查看“\ Windows API Code Pack 1.1 \ source \ WindowsAPICodePack \ Shell \ CommonFileDialogs \ CommonFileDialog.cs”以了解它如何使用来自COM的IFileDialogCustomize。

希望这可以帮助。

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

这是一个使用.Net私有IFileDialog接口打开Vista风格的文件夹选择器的类,而不直接在代码中使用interop(.Net为您处理)。 如果没有足够高的Windows版本,它可以回到Vista之前的对话框。 应该适用于Windows 7,8,9,10和更高版本(理论上)。

 using System; using System.Reflection; using System.Windows.Forms; namespace Ris.Shuriken { ///  /// Present the Windows Vista-style open file dialog to select a folder. Fall back for older Windows Versions ///  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); } /// Handle of the control or window to be the parent of the file dialog /// true if the user clicks OK 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; } } } } } 

我将此开发为lyquidity.com的Bill Seddon的.NET Win 7式文件夹选择对话框的清理版本(我没有隶属关系)。 我编写了自己的解决方案,因为他的解决方案需要一个额外的Reflection类,这个聚类目的不需要,使用基于exception的流控制,不会缓存其reflection调用的结果。 请注意,嵌套的静态VistaDialog类是这样的,如果从不调用Show方法,它的静态reflection变量不会尝试填充。

在Windows窗体中使用它是这样的:

 var dialog = new FolderSelectDialog { InitialDirectory = musicFolderTextBox.Text Title = "Select a folder to import music from" }; if (dialog.Show(Handle)) { musicFolderTextBox.Text = dialog.FileName; } 

您当然可以使用它的选项以及它所暴露的属性。 例如,它允许在Vista风格的对话框中进行多选。

正如他在这个post中的答案所暗示的那样, Simon Mourier在另一个问题上的答案显示了如何直接使用interop来完成这项工作。 不幸的是,当我编写解决方案时,我还没有找到。 说出你的毒药!

有点晚了,但你可以在我的答案中找到C#中IFileDialog接口的例子: 在PropertyGrid中显示详细的文件夹浏览器和另一个允许选择非文件系统文件夹(如My Computer / This PC)的变体: C#如何允许选择带有folderBrowserDialog的“我的电脑”