扩展 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 控件就全部完成了。

代码:FolderBrowserDialogEx

posted on 2010-12-20 14:38  hyping  阅读(1982)  评论(1编辑  收藏  举报

导航