我在第二章中给出了文件夹的概览和它在Windows Shell中的地位,在这一章中我们打算更详细地讨论它们。我们主要集中精力阐述涉及文件夹所有层面的Shell函数,以及保证所有操作顺利进行的潜在机理。因此,我们需要深入研究两个起着非常重要作用的概念:快捷方式和PIDLs。前者是下一章的题目,在这一章中我们将研究PIDLs,其中包括:
SHBrowseForFolder()函数的用途
关于PIDLs进一步的讨论,以及怎样使用PIDLs
虚拟文件夹和位置
怎样获得文件夹的设置
我们将要论述的例子包含了一个增强版本的API函数SHBrowseForFolder(),一些使它更容易同PIDLs一道工作的辅助函数,以及一些怎样枚举某些指定位置(如,‘发送到’,‘Favorites’,以及‘我的资料’)内容的样例程序。
选择文件夹
让我们先从各种文件夹选择方法开始我们的讨论。对于让用户能够从特定驱动器上选择特殊目录的应用程序,这是一个普通的需求。Windows3.x API没有为这提供任何内建的工具,所以必须建立自己的辅助函数,然而,有一项通用技术可以使用,它由修改通用对话框模版组成,例如删除象列表框那样的包含文件名的控件。
然而,推出这个方案到Win32有一个障碍:你必须抛弃新探测器风格的用户界面,而仍然忠实地使用老界面:

在Win32平台上,探测器风格的‘打开’对话框是一个单一实体,其中的任何控件都是不能摆脱掉的(比如:文件列表框)。
要选择采用老的Windows3.x界面,另一个选择是安排一个VC++显示方式的对话框到新项目中来请求一个特殊的文件夹。

跳出Win32关于GetOpenFileName()函数的资料,能够发现更多的东西。
更现代的方法
从Wondwos95 开始,Win32 SDK 就包含了浏览文件夹的系统解决方案:这个函数称为SHBrowseForFolder()。其主要的特征是使用类似于我们已知的和探测器钟情的树观察:

与前两章中我们测试过的函数一样,SHBrowseForFolder()函数有一个简单的原型,但是,它实际上包含了一个带有大量设置和标志的结构,可以把这个函数看作是文件夹的中心函数,其目的之一就是使我们能够在桌面命名空间中选择可用的文件夹。
SHBrowseForFolder()函数的原型
现在看一下SHBrowseForFolder()的原型,它声明在shlobj.h中:
LPITEMIDLIST WINAPI SHBrowseForFolder(LPBROWSEINFO lpbi);
参数只有一个BROWSEINFO结构的指针,它的声明也在同一个文件中:
typedef struct _browseinfo
{
HWND hwndOwner;
LPCITEMIDLIST pidlRoot;
LPSTR pszDisplayName;
LPCSTR lpszTitle;
UINT ulFlags;
BFFCALLBACK lpfn;
LPARAM lParam;
int iImage;
} BROWSEINFO, *PBROWSEINFO, *LPBROWSEINFO;
成员的说明如下表:
|
名称
|
描述
|
|
hwndOwner
|
拥有这个对话框的窗口Handle
|
|
pidlRoot
|
被表述层次对象的根节点标识。是一个PIDL。
|
|
pszDisplayName
|
必须是一个已分配缓冲区的指针,它将包含选择对象的显示名。
|
|
lpszTitle
|
必须是一个缓冲区指针,包含一个作为树观察标题的串。
|
|
ulFlags
|
指定外观和窗口行为(后面将介绍有效的值)。
|
|
Lpfn
|
用于钩住对话框的回调函数。
|
|
lParam
|
32位传递给回调函数的客户数据。通常是一个指针或Handle。
|
|
Iimage
|
包含选中文件夹或文件的图标索引。是相对系统图像列表的索引。
|
调用SHBrowseForFolder()最简单的方法是:
BROWSEINFO bi;
ZeroMemory(&bi, sizeof(BROWSEINFO));
bi.hwndOwner = hDlg;
LPITEMIDLIST pidl = SHBrowseForFolder(&bi);
这段代码显示一个前面看到过的对话框,并且恢复选中文件夹的PIDL。如果文件夹有一个对应的路径,你可以通过下述代码获得:
TCHAR szPath[MAX_PATH] = {0};
SHGetPathFromIDList(pidl, szPath);
Msg(szPath);
有几个有趣的结果与使用SHBrowseForFolder()函数有关。下面给出概述,在整个下一节我们都将详细地讨论这些问题:
这个函数透明地处理PIDLs和路径名
这个函数允许浏览特殊的系统文件夹
这个函数返回大量的信息,是SHGetFileInfo()所不能的。
对话框稍微可以客户化,这总是一个好消息。
SHBrowseForFolder()函数的用法
SHBrowseForFolder()函数所能做的事情由BROWSEINFO结构的ulFlags成员所限制,其合法的值由下述标志的组合构成:
|
标志
|
描述
|
|
BIF_RETURNONLYFSDIRS
|
如果设置,仅在用户选择了文件系统的目录后,OK按钮才被允许。例如,你选择‘网上邻居’节点,如果这个标志设置,OK按钮是灰的。
|
|
BIF_DONTGOBELOWDOMAIN
|
不显示网络文件夹,仅有域名节点。
|
|
BIF_STATUSTEXT
|
对话框模版含有可以显示任何文字的标签,特别是在子类化这个对话框窗口后(后面将详细讲解)。
|
|
BIF_EDITBOX
|
这是Shell 4.71版以后的新特征,它允许一个编辑框,在这里可以手动输入文件夹。
|
|
BIF_VALIDATE
|
这是Shell 4.71版的另一个新特征,它是对BIF_EDITBOX标志功能的补充。如果你设置了这个标志,并且子类化了这个对话框,则用户每次在编辑框键入和确认一个不正确的文件或文件夹名时,你都能收到通知(后面将详细讲解)。
|
|
BIF_BROWSEFORCOMPUTER
|
允许用户仅选择计算机名。浏览正常发生,但是OK按钮总是灰的,除非选择了计算机名。
|
|
BIF_BROWSEFORPRINTER
|
与上相同,但是是打印机。
|
|
BIF_BROWSEINCLUDEFILES
|
如果这个标志设置,不管其它标志如何,在树观察中都显示文件名,而不仅仅是文件夹名。这就提供了设置对话框显示系统中所有打印机或可用字体的机会。
|
在调用SHBrowseForFolder()函数时,有两种方法来客户化最终对话框的外观,经由回调函数子类化这个窗口更有力一些,我们将在这一章的以后部分讨论这个内容。获得有限程度客户化的较简单方法是修改树观察上面的文字。BROWSEINFO结构的lpszTitle成员负责这一点。它声明为一个指针,所以,你必须传递一个有效的内存缓冲区:
TCHAR szBuf[MAX_PATH] = {0};
lstrcpy(szBuf, __TEXT("Choose a folder:"));
bi.lpszTitle = static_cast<LPCSTR>(szBuf);
对pszDisplayName成员,也一样,它是一个返回缓冲区。如果你对选中文件夹的显示名感兴趣,就需要传递一个有效的缓冲区,首先声明或分配它,然后 把指针赋值到pszDisplayName。
TCHAR szDisp[MAX_PATH] = {0};
bi.pszDisplayName = static_cast<LPSTR>(szDisp);
函数认为pszDisplayName至少有MAX_PATH字节尺寸。
正如前几章说明的,文件夹的显示名是探测器用来显示文件夹的名字。例如,(C:)的显示名是C:\。
函数返回了什么
技术上讲,函数返回的是PIDL,它标识一个选中的文件或文件夹。如果‘取消’了对话框,函数返回NULL,非常简单。然而,这个函数还能够通过传递的BROWSEINFO结构返回其它有用的信息。这一点的特殊例子是包含选中对象的显示名(上面已经提到了),和代表它的图标。
获取文件夹的图标
即使SHBrowseForFolder()看起来似乎正在重复我们已经从SHGetFileInfo()函数获得的功能。然而,就获得和显示图标仍然有相当的工作需要做。
在函数返回时,BROWSEINFO结构的iImage成员含有一个数字,它是图标在系统图像列表中的位置索引。因而,如果想要绘制图标—或更简单,想要它的HICON Handle—你就必须首先获得这个图像列表的Handle。
在前一章中已经讲到了怎样取得图标,但是,采用这里的方法要容易一些。如果使用SHGFI_ICON和标志调用SHGetFileInfo(),并且设置了SHGFI_SYSICONINDEX,函数则返回系统图像列表的Handle。
HICON SHGetSystemIcon(int iIconIndex)
{
SHFILEINFO sfi;
ZeroMemory(&sfi, sizeof(SHFILEINFO));
// 不指定文件名,因为我们只想要一个Handle...
HIMAGELIST himl = reinterpret_cast<HIMAGELIST>(SHGetFileInfo(
"*.*", 0, &sfi, sizeof(SHFILEINFO), SHGFI_ICON | SHGFI_SYSICONINDEX));
HICON hIcon = ImageList_ExtractIcon(0, himl, iIconIndex);
return hIcon;
}
上面的代码是一个辅助例程,给定一个索引,返回系统图像列表中对应的图标。要运行这段代码需要包含shellapi.h,和通过调用InitCommonControls()或InitCommonControlsEx()初始化公共控件库。就象附录A中讨论的那样,第一个方法适用于老版本的Shell,第二才被推荐到Shell 4.71及其以后的版本。
使用回调函数
有趣的是SHBrowseForFolder()函数要求一个回调函数。要子类化由这个函数建立的对话框,你需要指派一个有效的函数指针到BROWSEINFO的lpfn字段。这个指针必须指向有如下原型的函数:
int CALLBACK BrowseCallbackProc(HWND hwnd,
UINT uMsg,
LPARAM lParam,
LPARAM dwData);
其中hwnd是被钩住窗口的Handle,uMsg接收到的消息。lParam是一个值,根据uMsg它有不同的意义,而最后这个dwData是用户定义的数据—与你通过BROWSEINFO的lParam成员指定的数据相同。如果你需要回调函数在调用程序建立的数据上工作,而不是使用全程变量,应该使用一个32位值来填写BROWSEINFO结构的lParam成员,并且保证它自动地经由dwData变量传递给回调函数。为了适合多个数据的传递,可以使用指针,更好地,分配一个Handle内存块,锁定它,封包所有东西,解锁,然后把它存入lParam字段。下图显示了回调函数设置的情形:SHBrowseForFolder()调用了你所定义的函数,传送一些数据和通知某些事件。
