Ale*_*ang 4 c# interface com-interop openfiledialog
我正在尝试使用 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 方法的调用,则会出现异常:“尝试读取或写入受保护的内存。这通常表明其他内存已损坏。”
我尝试在 .Net 2.0 甚至 4.0 上运行。
这里的错误是什么?为什么只调用 Show 方法有效,但如果我在失败之前尝试另一种方法?
适用于 Microsoft® .NET Framework的Windows® API 代码包可以提供帮助。下载后,查看“\Windows API Code Pack 1.1\source\WindowsAPICodePack\Shell\CommonFileDialogs\CommonFileDialog.cs”以了解它如何使用来自 COM 的 IFileDialogCustomize。
希望这可以帮助。
这是一个使用 .Net 私有IFileDialog接口打开 Vista 风格的文件夹选择器的类,而不直接在代码中使用互操作(.Net 会为您处理)。如果 Windows 版本不够高,它会退回到 Vista 之前的对话框。应该适用于 Windows 7、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; } }
        }
    }
}
我将其开发为.NET Win 7 样式文件夹选择对话框的清理版本,由lyquidity.com的 Bill Seddon (我没有从属关系)。我自己编写了自己的解决方案,因为他的解决方案需要一个额外的 Reflection 类,此重点目的不需要该类,使用基于异常的流控制,不缓存其反射调用的结果。请注意,VistaDialog如果Show从未调用该方法,则嵌套静态类使其静态反射变量不会尝试填充。
它在 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 风格的对话框中进行多选。
正如在本主题中的回答中提到的那样,Simon Mourier 对另一个问题的回答展示了如何直接使用互操作来完成这项工作。不幸的是,当我制定我的解决方案时,我还没有找到。说出你的毒药!
| 归档时间: | 
 | 
| 查看次数: | 9416 次 | 
| 最近记录: |