自定义文件夹选择对话框

选择文件使用OpenFileDialog,选择文件夹只有选择 FolderBrowserDialog,这种选择对话框让人有些抓狂,特别当文件目录比较深、需要多次选择文件夹操作时。

那么,如何定制一款既符合用户使用习惯,又能方便选择文件夹的对话框呢?

方案一 Microsoft类库

引用Microsoft.WindowsAPICodePack.dll 和Microsoft.WindowsAPICodePack.Shell.dll类库文件,就可以使用CommonOpenFileDialog类实现选择文件夹了。

var openFolder = new CommonOpenFileDialog();
openFolder.AllowNonFileSystemItems = true;
openFolder.Multiselect = true;
openFolder.IsFolderPicker = true;
openFolder.Title = "Select folders with jpg files";
 
if (openFolder.ShowDialog() != CommonFileDialogResult.Ok)
{
    MessageBox.Show("No Folder selected");
    return;
}
 
// get all the directories in selected dirctory
var dirs = openFolder.FileNames.ToArray();

方案二 第三方工具

为了解决这一痛点,很多第三方工具都给出了解决方案,如DevExpress重写了自己的XtraFolderBrowserDialog,当DialogStyle为Compact时,即为默认文件夹浏览对话框,当DialogStyle为Wide时,则为类似于OpenFileDialog的文件夹选择对话框,由开发者控制显示方式。

方案三 Windows API

自己动手,丰衣足食。

类库如下:

public class OpenFolderDialog
    {
        #region Internal Enums

        internal enum FDAP
        {
            FDAP_BOTTOM = 0,
            FDAP_TOP = 1
        }

        internal enum FDE_OVERWRITE_RESPONSE
        {
            FDEOR_DEFAULT = 0x00000000,
            FDEOR_ACCEPT = 0x00000001,
            FDEOR_REFUSE = 0x00000002
        }

        internal enum FDE_SHAREVIOLATION_RESPONSE
        {
            FDESVR_DEFAULT = 0x00000000,
            FDESVR_ACCEPT = 0x00000001,
            FDESVR_REFUSE = 0x00000002
        }

        [Flags]
        internal enum FOS : uint
        {
            FOS_OVERWRITEPROMPT = 0x00000002,
            FOS_STRICTFILETYPES = 0x00000004,
            FOS_NOCHANGEDIR = 0x00000008,
            FOS_PICKFOLDERS = 0x00000020,
            FOS_FORCEFILESYSTEM = 0x00000040, // Ensure that items returned are filesystem items.
            FOS_ALLNONSTORAGEITEMS = 0x00000080, // Allow choosing items that have no storage.
            FOS_NOVALIDATE = 0x00000100,
            FOS_ALLOWMULTISELECT = 0x00000200,
            FOS_PATHMUSTEXIST = 0x00000800,
            FOS_FILEMUSTEXIST = 0x00001000,
            FOS_CREATEPROMPT = 0x00002000,
            FOS_SHAREAWARE = 0x00004000,
            FOS_NOREADONLYRETURN = 0x00008000,
            FOS_NOTESTFILECREATE = 0x00010000,
            FOS_HIDEMRUPLACES = 0x00020000,
            FOS_HIDEPINNEDPLACES = 0x00040000,
            FOS_NODEREFERENCELINKS = 0x00100000,
            FOS_DONTADDTORECENT = 0x02000000,
            FOS_FORCESHOWHIDDEN = 0x10000000,
            FOS_DEFAULTNOMINIMODE = 0x20000000
        }

        internal enum SIATTRIBFLAGS
        {
            SIATTRIBFLAGS_AND = 1,
            SIATTRIBFLAGS_APPCOMPAT = 3,
            SIATTRIBFLAGS_OR = 2
        }

        internal enum SIGDN : uint
        {
            SIGDN_DESKTOPABSOLUTEEDITING = 0x8004c000,
            SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000,
            SIGDN_FILESYSPATH = 0x80058000,
            SIGDN_NORMALDISPLAY = 0,
            SIGDN_PARENTRELATIVE = 0x80080001,
            SIGDN_PARENTRELATIVEEDITING = 0x80031001,
            SIGDN_PARENTRELATIVEFORADDRESSBAR = 0x8007c001,
            SIGDN_PARENTRELATIVEPARSING = 0x80018001,
            SIGDN_URL = 0x80068000
        }

        #endregion Internal Enums

        #region 公共属性

        /// <summary>
        /// 文件
        /// </summary>
        public string Folder { get; set; }
        /// <summary>
        /// 标题
        /// </summary>
        public string Title { get; set; }

        #endregion 公共属性

        #region 公共方法
        /// <summary>
        /// 显示对话框架
        /// </summary>
        /// <param name="owner"></param>
        /// <returns></returns>
        public DialogResult ShowDialog(IWin32Window owner = null)
        {
            IntPtr hwndOwner = owner != null ? owner.Handle : NativeMethods.GetActiveWindow();

            NativeInterfaces.IFileOpenDialog dialog = (NativeInterfaces.IFileOpenDialog)new FileOpenDialog();
            try
            {
                NativeInterfaces.IShellItem item;

                if (!string.IsNullOrEmpty(Folder))
                {
                    Guid _shellItemGuid = typeof(NativeInterfaces.IShellItem).GUID;
                    item = (NativeInterfaces.IShellItem)NativeMethods.SHCreateItemFromParsingName(Folder, null, ref _shellItemGuid);

                    if (item != null)
                        dialog.SetFolder(item);
                }

                dialog.SetOptions(FOS.FOS_PICKFOLDERS | FOS.FOS_FORCEFILESYSTEM);

                if (!string.IsNullOrEmpty(Title))
                    dialog.SetTitle(Title);

                int hr = dialog.Show(hwndOwner);
                if (hr == NativeMethods.ERROR_CANCELLED)
                    return DialogResult.Cancel;

                if (hr != 0)
                    return DialogResult.Abort;

                dialog.GetResult(out item);

                item.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out string path);
                Folder = path;

                return DialogResult.OK;
            }
            finally { Marshal.ReleaseComObject(dialog); }
        }

        #endregion 公共方法

        #region Internal Structs

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 4)]
        internal struct COMDLG_FILTERSPEC
        {
            [MarshalAs(UnmanagedType.LPWStr)]
            public string pszName;

            [MarshalAs(UnmanagedType.LPWStr)]
            public string pszSpec;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 4)]
        internal struct PROPERTYKEY
        {
            public Guid fmtid;

            public uint pid;
        }

        #endregion Internal Structs

        #region Internal Classes

        internal static class NativeInterfaces
        {
            #region Public Interfaces

            [ComImport, Guid("42f85136-db7e-439c-85f1-e4075d135fc8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
            public interface IFileDialog : IModalWindow
            {
                [PreserveSig]
                new int Show([In] IntPtr parent);

                void SetFileTypes([In] uint cFileTypes, [In][MarshalAs(UnmanagedType.LPArray)] COMDLG_FILTERSPEC[] rgFilterSpec);

                void SetFileTypeIndex([In] uint iFileType);

                void GetFileTypeIndex(out uint piFileType);

                void Advise([In, MarshalAs(UnmanagedType.Interface)] IFileDialogEvents pfde, out uint pdwCookie);

                void Unadvise([In] uint dwCookie);

                void SetOptions([In] FOS fos);

                void GetOptions(out FOS pfos);

                void SetDefaultFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi);

                void SetFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi);

                void GetFolder([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);

                void GetCurrentSelection([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);

                void SetFileName([In, MarshalAs(UnmanagedType.LPWStr)] string pszName);

                void GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName);

                void SetTitle([In, MarshalAs(UnmanagedType.LPWStr)] string pszTitle);

                void SetOkButtonLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszText);

                void SetFileNameLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel);

                void GetResult([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);

                void AddPlace([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, int alignment);

                void SetDefaultExtension([In, MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension);

                void Close([MarshalAs(UnmanagedType.Error)] int hr);

                void SetClientGuid([In] ref Guid guid);

                void ClearClientData();

                void SetFilter([MarshalAs(UnmanagedType.Interface)] IntPtr pFilter);
            }

            [ComImport, Guid("973510DB-7D7F-452B-8975-74A85828D354"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
            public interface IFileDialogEvents
            {
                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), PreserveSig]
                int OnFileOk([In, MarshalAs(UnmanagedType.Interface)] IFileDialog pfd);

                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), PreserveSig]
                int OnFolderChanging([In, MarshalAs(UnmanagedType.Interface)] IFileDialog pfd,
                             [In, MarshalAs(UnmanagedType.Interface)] IShellItem psiFolder);

                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
                void OnFolderChange([In, MarshalAs(UnmanagedType.Interface)] IFileDialog pfd);

                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
                void OnSelectionChange([In, MarshalAs(UnmanagedType.Interface)] IFileDialog pfd);

                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
                void OnShareViolation([In, MarshalAs(UnmanagedType.Interface)] IFileDialog pfd,
                          [In, MarshalAs(UnmanagedType.Interface)] IShellItem psi,
                          out FDE_SHAREVIOLATION_RESPONSE pResponse);

                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
                void OnTypeChange([In, MarshalAs(UnmanagedType.Interface)] IFileDialog pfd);

                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
                void OnOverwrite([In, MarshalAs(UnmanagedType.Interface)] IFileDialog pfd,
                         [In, MarshalAs(UnmanagedType.Interface)] IShellItem psi,
                         out FDE_OVERWRITE_RESPONSE pResponse);
            }

            [ComImport, Guid("d57c7288-d4ad-4768-be02-9d969532d960"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
            public interface IFileOpenDialog : IFileDialog
            {
                void AddPlace([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, FileDialogCustomPlace fdcp);

                void GetResults([MarshalAs(UnmanagedType.Interface)] out IShellItemArray ppenum);

                void GetSelectedItems([MarshalAs(UnmanagedType.Interface)] out IShellItemArray ppsai);
            }

            [ComImport, Guid("b4db1657-70d7-485e-8e3e-6fcb5a5c1802"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
            public interface IModalWindow
            {
                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), PreserveSig]
                uint Show([In] IntPtr parent);
            }

            [ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
            public interface IShellItem
            {
                void BindToHandler([In, MarshalAs(UnmanagedType.Interface)] IntPtr pbc, [In] ref Guid bhid, [In] ref Guid riid, out IntPtr ppv);

                void GetParent([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);

                void GetDisplayName([In] SIGDN sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName);

                void GetAttributes([In] uint sfgaoMask, out uint psfgaoAttribs);

                void Compare([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, [In] uint hint, out int piOrder);
            }

            [ComImport, Guid("B63EA76D-1F85-456F-A19C-48159EFA858B"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
            public interface IShellItemArray
            {
                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
                void BindToHandler([In, MarshalAs(UnmanagedType.Interface)] IntPtr pbc, [In] ref Guid rbhid,
                        [In] ref Guid riid, out IntPtr ppvOut);

                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
                void GetPropertyStore([In] int Flags, [In] ref Guid riid, out IntPtr ppv);

                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
                void GetPropertyDescriptionList([In] ref PROPERTYKEY keyType, [In] ref Guid riid, out IntPtr ppv);

                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
                void GetAttributes([In] SIATTRIBFLAGS dwAttribFlags, [In] uint sfgaoMask, out uint psfgaoAttribs);

                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
                void GetCount(out uint pdwNumItems);

                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
                void GetItemAt([In] uint dwIndex, [MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);

                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
                void EnumItems([MarshalAs(UnmanagedType.Interface)] out IntPtr ppenumShellItems);
            }

            #endregion Public Interfaces
        }

        [SuppressUnmanagedCodeSecurity]
        internal static class NativeMethods
        {
            #region Public Fields

            public const int ERROR_CANCELLED = unchecked((int)0x800704C7);

            #endregion Public Fields

            #region Public Methods

            [DllImport("user32.dll")]
            public static extern IntPtr GetActiveWindow();

            [DllImport("shell32.dll", SetLastError = true, CharSet = CharSet.Unicode, PreserveSig = false)]
            [return: MarshalAs(UnmanagedType.Interface)]
            public static extern object SHCreateItemFromParsingName(
                [MarshalAs(UnmanagedType.LPWStr)] string pszPath, IBindCtx pbc, ref Guid riid);

            #endregion Public Methods
        }

        #endregion Internal Classes

        #region Private Classes

        [ComImport, Guid("DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7")]
        private class FileOpenDialog { }

        #endregion Private Classes
    }
posted @ 2022-03-23 10:14  我也是个傻瓜  阅读(1103)  评论(1编辑  收藏  举报