原创 Windows Shell 编程 第五章 收藏

新一篇: Windows Shell 编程 第六章 | 旧一篇: Windows Shell 编程 第四章

我在第二章中给出了文件夹的概览和它在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()函数要求一个回调函数。要子类化由这个函数建立的对话框,你需要指派一个有效的函数指针到BROWSEINFOlpfn字段。这个指针必须指向有如下原型的函数:

int CALLBACK BrowseCallbackProc(HWND hwnd,

UINT uMsg,

LPARAM lParam,

LPARAM dwData);

其中hwnd是被钩住窗口的HandleuMsg接收到的消息。lParam是一个值,根据uMsg它有不同的意义,而最后这个dwData是用户定义的数据—与你通过BROWSEINFOlParam成员指定的数据相同。如果你需要回调函数在调用程序建立的数据上工作,而不是使用全程变量,应该使用一个32位值来填写BROWSEINFO结构的lParam成员,并且保证它自动地经由dwData变量传递给回调函数。为了适合多个数据的传递,可以使用指针,更好地,分配一个Handle内存块,锁定它,封包所有东西,解锁,然后把它存入lParam字段。下图显示了回调函数设置的情形:SHBrowseForFolder()调用了你所定义的函数,传送一些数据和通知某些事件。