扩展 FolderBrowserDialog 控件(二)
自定义 FolderBrowserDialog 控件要实现的功能有 2 个:
(1)允许指定任意有效的文件夹作为根目录;
(2)允许自定义指定文件夹是否能够选择的逻辑;
在前一篇扩展 FolderBrowserDialog 控件中,已经实现了允许指定任意有效的文件夹作为 FolderBrowserDialog 的根目录,这里就来实现允许自定义选择的文件夹是否能够被选择的逻,辑。
通过 Reflector 查看 FolderBrowserDialog 的代码,发现逻辑功能比较强的函数只有 2 个,一个是 RunDialog,另外一个就是 FolderBrowserDialog_BrowseCallbackProc。
RunDialog 是覆盖的基类函数,功能是实现特定对话框的展示;而 FolderBrowserDialog_BrowseCallbackProc 从名称上可以看出来是一个回调函数,那这个回调函数是被谁回调的呢?
在 RunDialog 函数中发现了这么一行代码:
this.callback = new UnsafeNativeMethods.BrowseCallbackProc(this.FolderBrowserDialog_BrowseCallbackProc);
牵出了一个定义在 UnsafeNativeMethods 中的 BrowseCallbackProc,但这只是一个委托:
public delegate int BrowseCallbackProc(IntPtr hwnd, int msg, IntPtr lParam, IntPtr lpData);
然后,在 RunDialog 中又发现了一行代码:
lpbi.lpfn = this.callback;
而从 上一篇 已经清楚了 lpbi 是一个传输显示文件夹选择对话框用的信息的输入结构,lpfn 是用于存储回调函数指针的成员变量。
好了,现在已经明确,FolderBrowserDialog_BrowseCallbackProc 是文件夹选择对话框生命周期中 Windows 用来通知自定义逻辑当前对话框产生了什么事件的途径。我们要实现自定义那些文件夹能够选择就只能从这个回调函数入手。
要实现自定义逻辑来指定文件夹是否能够被选择的步骤如下:
1、FolderBrowserDialog 获取到新选择的文件夹;
2、回调自定义逻辑,并通知当前新选择的文件夹的路径,自定义逻辑进行检测,并返回是否能够选择;
3、FolderBrowserDialog 根据自定义逻辑的指示设置 OK 按钮的启用或禁用。
显然,自定义逻辑的实现用委托就可以很方便地实现,现在的问题就是
a 如何获取到新选择的文件夹
b 如何启用或禁用 OK 按钮
先来看看 FolderBrowserDialog_BrowseCallbackProc 的代码:
private int FolderBrowserDialog_BrowseCallbackProc(IntPtr hwnd, int msg, IntPtr lParam, IntPtr lpData) { switch (msg) { case 1: if (this.selectedPath.Length != 0) { UnsafeNativeMethods.SendMessage(new HandleRef(null, hwnd), NativeMethods.BFFM_SETSELECTION, 1, this.selectedPath); } break; case 2: { IntPtr pidl = lParam; if (pidl != IntPtr.Zero) { IntPtr pszPath = Marshal.AllocHGlobal((int) (260 * Marshal.SystemDefaultCharSize)); bool flag = UnsafeNativeMethods.Shell32.SHGetPathFromIDList(pidl, pszPath); Marshal.FreeHGlobal(pszPath); UnsafeNativeMethods.SendMessage(new HandleRef(null, hwnd), 0x465, 0, flag ? 1 : 0); } break; } } return 0; }
而且 shlobj.h 中有
// message from browse #define BFFM_INITIALIZED 1 #define BFFM_SELCHANGED 2 // messages to browse #define BFFM_SETSTATUSTEXT (WM_USER + 100) #define BFFM_ENABLEOK (WM_USER + 101)
可以看出,FolderBrowserDialog_BrowseCallbackProc 处理了对话框初始化完成(BFFM_INITIALIZED)和文件夹选择发生改变(BFFM_SELCHANGED)2个事件。
很明显,要实现文件夹是否能够选择的逻辑,必须要依靠 BFFM_SELCHANGED 这个事件。但是我们怎么才能通过这个事件获取到被选择的文件夹的路径呢?
从 FolderBrowserDialog_BrowseCallbackProc 的实现中可以看到,新选择的文件夹可以通过函数的 lParam 参数获取到,而且 MSDN 中也有相关的描述,就是当 uMsg 是 BFFM_SELCHANGED 的时候,lParam 就是新选择的文件夹的 PIDL 标识。
FolderBrowserDialog_BrowseCallbackProc 的默认实现就是当选择了一个文件夹之后,调用 SHGetPathFromIDList 函数尝试把新选择的文件夹转换为文件系统路径,然后通过向窗体发送 BFFM_ENABLEOK 消息来允许或禁止 OK 按钮的启用或停用(取决于 SHGetPathFromIDList 执行的转换成功与否,也就是函数的返回值)。
由于 SHGetPathFromIDList 函数获取到的文件系统路径值是非托管字符串,所以还需要调用 Marshal 类中的 PtrToStringAuto 函数把非托管字符串填充到托管 string 对象中。
总结
好了,实现自定义逻辑来指定文件夹是否能够被选择详细步骤如下:
1、定义一个签名类似于 bool FolderSelectableDelegate(string folder) 的委托;
2、为 FolderBrowserDialog 新增加一个构造函数,接收一个 FolderSelectableDelegate 类型的参数,并保存到成员变量 _selectable 中;
3、修改 FolderBrowserDialog_BrowseCallbackProc 处理 BFFM_SELCHANGED 事件的实现如下:
3.1 调用 SHGetPathFromIDList 函数获取新选择文件夹的文件系统路径非托管字符串 pszPath;
3.2 调用 Marshal 类的 PtrToStringAuto 函数把 fszPath 的值复制到托管字符串对象 selectedPath 中;
3.3 用 selectedPath 作为参数调用委托 _selectable,并将返回值保存到 canSelectPath;
3.4 调用 SendMessage 函数,根据 canSelectPath 设置最后参数的值来启用或禁用 OK 按钮。SendMessage(new HandleRef(null, hwnd), BFFM_ENABLEOK, 0, canSelectPath ? ENABLE : DISABLE);
至此,扩展 FolderBrowserDialog 控件就全部完成了。