扩展 FolderBrowserDialog 控件

前段时间写了一个 winform 的工具,根据需要,扩展了 FolderBrowserDialog 这个控件。

.Net 中 FolderBrowserDialog 控件封装了 SHBrowseForFolder 函数,提供了选择本机文件系统中的文件夹的功能。

它允许我们指定某一个目录作为选择目录树的根目录,但是只能指定有限的几个系统内置的目录;它也提供了不能选择虚拟文件夹的功能,但是并不允许我们自定义哪些文件夹能够被选择。

所以根据需要,对 FolderBrowserDialog 在下面 2 个方面做了功能扩展:

(1)允许指定任意有效的文件夹作为根目录;

(2)允许自定义指定文件夹是否能够选择的逻辑;

 

1、生成 FolderBrowserDialog 扩展类

.Net 中的 FolderBrowserDialog 类是密封的,不能子类化,而且通过 Reflector 查看 FolderBrowserDialog 的实现,基本上没有办法使用对象适配器。

权宜之下,只好选择了一个最丑陋的方式:先从 CommonDialog 类直接继承一个 FolderBrowserDialogEx 类,然后参考 FolderBrowserDialog 的逻辑使 FolderBrowserDialogEx 类实现 FolderBrowserDialog 类的功能。

 

2、允许指定任意有效的文件夹作为根目录

FolderBrowserDialog 已经提供了指定目录作为根目录的功能,只不过受限制很大,就是只能指定 Environment.SpecialFolder 枚举中定义的那些系统能够明确知道其路径的目录。原来还以为这个枚举里面有一个 Custome 之类的以允许我们指定其他的目录,没想到找了很长时间都没有找到。看来微软目前还是不想让我们指定其他目录的。

那么 FolderBrowserDialog 中是如何实现指定根目录的呢?

通过分析发现,FolderBrowserDialog 能够提供选择目录的功能的根本在于它的 RunDialog 方法内部调用了 Win32 的 SHBrowseForFolder 函数,这个函数接受 BROWSEINFO 结构的指针作为参数,而这个结构中就有一个成员变量 pidlRoot 存储了根目录的信息。

typedef struct _browseinfo {
    HWND hwndOwner;
    PCIDLIST_ABSOLUTE pidlRoot;
    LPTSTR pszDisplayName;
    LPCTSTR lpszTitle;
    UINT ulFlags;
    BFFCALLBACK lpfn;
    LPARAM lParam;
    int iImage;
} BROWSEINFO

win32 中的 BROWSEINFO 中的 pidlRoot 是一个指针,对应到 .Net 中就应该是一个 IntPtr 类型的对象。如何获取到任意目录对应的这种指针呢?

先来看看 .Net 中如何根据 Environment.SpecialFolder 获取到这个指针的。

在 Reflector 反射出的代码中,发现了这么一行代码

UnsafeNativeMethods.Shell32.SHGetSpecialFolderLocation(hWndOwner, (int) this.rootFolder, ref root);

 

其实 windows 系统中的特殊文件夹早就有了,所以 win32 已经有了函数来提供根据特殊文件夹对应的 int32 值获取其对应的 PIDL(pointer to item identifier list,设计到 Windows Shell,个人了解不多,此处不作深入描述)。但是其他的文件夹在系统中又不好找到对应的 int32 值,看来这个函数不能为我们所用。

在查找了与 Windows Shell 相关的资料后,发现了一个函数

HRESULT SHParseDisplayName(
    LPCWSTR pszName,
    IBindCtx *pbc,
    PIDLIST_ABSOLUTE *ppidl,
    SFGAOF sfgaoIn,
    SFGAOF *psfgaoOut
);

这个函数刚好能够满足我们的要求,我们能够通过这个函数获取到指定的目录对应的 PIDL。

ok,下面就来开始扩展过程。

首先导入外部函数:

        [DllImport("shell32.dll", CharSet=CharSet.Unicode)]
        private static extern int SHParseDisplayName(string pszName, IntPtr pbc, ref IntPtr ppidl, ulong sfGaoIn, IntPtr psfGaoOut);

 

再修改 RootFolder 属性以接收指定的目录作为根目录:

        public string RootFolder
        {
            get
            {
                return _rootFolder;
            }
            set
            {
                if (null != value && 0 < value.Length && Directory.Exists(value))
                {
                    new FileIOPermission(FileIOPermissionAccess.PathDiscovery, value).Demand();
                }

                _rootFolder = (null == value ? string.Empty : value);
            }
        }

 

然后修改 RunDialog 方法的实现,获取 RootFolder 的 PIDL:

            IntPtr pidlRoot = IntPtr.Zero;
            bool success = false;

            SHGetSpecialFolderLocation(hwndOwner, (int)System.Environment.SpecialFolder.Desktop, ref pidlRoot);
            if (pidlRoot == IntPtr.Zero)
            {
                throw new Exception("No root folder");
            }

            if (null != _rootFolder && 0 < _rootFolder.Length)
            {
                IntPtr pidlTmp = IntPtr.Zero;
                IntPtr psfGaoOut = IntPtr.Zero;

                if (S_OK == SHParseDisplayName(_rootFolder, IntPtr.Zero, ref pidlTmp, 0, psfGaoOut))
                {
                    pidlRoot = pidlTmp;
                }
            }

其中 S_OK 的定义为:

private static readonly int S_OK = 0;

 

好了,经过前面修改之后,把期望的根目录路径设置给 FolderBrowserDialogEx 的 RootFolder 属性,然后就得到我们期望的目录树了。

下图显示了指定 C:\windows\Microsoft.Net\Framework 为根目录的文件夹选择对话框界面效果:

FolderBrowserDialogEx 效果

 

下一篇继续扩展这个对话框,支持自定义哪些文件夹能够被选择。

posted on 2010-11-12 16:19  hyping  阅读(4116)  评论(3编辑  收藏  举报

导航