转:Windows Shell 编程 第五章_1

来源:互联网 发布:淘宝我订购的服务 编辑:程序博客网 时间:2024/05/24 06:28

转自:http://yadang418.blog.163.com/blog/static/2684365620096533937590/

 

第五章浏览文件夹

        我在第二章中给出了文件夹的概览和它在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()调用了你所定义的函数,传送一些数据和通知某些事件。

                       

可以感知的事件

        SHBrowseForFolder()建立的对话框可以通知回调函数下列事件:

       对话框初始化完成

       选择已经改变

       用户在编辑框中键入了无效文件或文件夹。

通过发送下面消息完成这些功能:

BFFM_INITIALIZED

BFFM_SELCHANGED

BFFM_VALIDATEFAILED

这些消息由回调函数通过uMsg参数接收。每一个消息都在lParam变量带有一个LPARAM类型的值。现在就让我们逐个消息地观看一下lParam是怎样配置的:

消息

lParam意义

BFFM_INITIALIZED

无用消息—它总是NULL,这个消息在对话框窗口过程完成WM_INITDIALOG后发送。

BFFM_SELCHANGED

指向新选中文件夹的标识符列表,注意,就象其他Windows控件一样,在选择已经改变时通知改变事件。

BFFM_VALIDATEFAILED

指向编辑框的当前内容。就象它标志的那样,这个消息仅仅在Shell 4.71中支持。由返回0,回调函数可以强迫浏览对话框关闭,返回1,对话框保持活动。

 

可以发送的消息

        有几个回调函数可以发送给对话框窗口的消息,使它能执行一定的活动。它们是:

 

消息

描述

BFFM_ENABLEOK

根据lParam值,允许或禁止OK按钮。如果非零,按钮允许,此时可以确认当前选中的文件夹。wParam没有用。

BFFM_SETSELECTION

选择特殊文件或文件夹。lParam中存储一个指向PIDL或路径名的指针,wParam表示怎样解释这个指针。FALSE说明是一个PIDLTRUE说明路径名。

BFFM_SETSTATUSTEXT

设置你提供的文字到对话框的状态区域。实际文字由lParam指向,WParam无用。

这些消息正常地使用SendMessage()函数发送,通过组合它们,你可以实际地增强SHBrowseForFolder()的行为。

客户化用户界面

        通过使用回调函数,你可以介入和改变对话框的用户界面,因而,使它可以更好地适应你的需求。例如,你不希望?(帮助)按钮在标题条上出现,或使某些控件具有更显著的3D外观等。下面内容将说明这些功能是怎样实现的

删除关联帮助按钮

        从标题条中删除关联帮助按钮是一个关于窗口显示风格的简单问题:你只需要关闭处使Windows绘制和处理它的指示位即可。在扩展风格的任何窗口设置了WS_EX_CONTEXTHELP位时,这个按钮就显示。

   扩展风格首先在Windows3.x中提出,然后在Windows95SDK版本中得到加强。在正常风格和扩展风格之间的唯一不同在于它们占用的存储区域,而不是概念上的差别。

        你需要使用不同于访问窗口风格的代码来访问‘扩展’风格,为了关闭使帮助按钮显示的位,在回调函数响应BFFM_INITIALIZED消息时中所必须这样做:

DWORD dwStyle = GetWindowLong(hwnd, GWL_EXSTYLE);

SetWindowLong(hwnd, GWL_EXSTYLE, dwStyle & ~WS_EX_CONTEXTHELP);

首先,取得当前扩展风格(GWL_EXSTYLE代替GWL_STYLE),然后关闭指定位,最后回存这个风格值。

给状态文字加3D边框

        做这个比前一个稍微复杂一点,并且要求多行代码。然而你应该清楚,现在所做的并不能保证你的代码在所有存在的和未来的Windows版本中都能正常工作。它仅仅能工作在你已经成功地测试了这个功能的地方。

        现在,我们想要绘制一个带有3D边框的状态标签,就象状态条那样。BIF_STATUSTEXT标志可能有点误导—它不是象所期望的那样在窗口的底部加一个状态条,而是在树观察上方和标题之间加一个静态标签。这个标签窗口有一个控件ID,我们可以通过Spy++得知:

         

当你知道了这个控件的ID之后,一旦回调函数进入到对话框代码之中,获得任何子窗口的Handle就象调用下述代码那样容易:

HWND hwndChild = GetDlgItem(hDlg, controlID);

在上图中你可以看到,我们感兴趣的标签有ID0x3743,所以:

HWND hwndLabel = GetDlgItem(hwnd, 0x3743);

dwStyle = GetWindowLong(hwndLabel, GWL_EXSTYLE);

SetWindowLong(hwndLabel, GWL_EXSTYLE, dwStyle | WS_EX_STATICEDGE);

SetWindowPos(hwndLabel, NULL, 0, 0, 0, 0,

SWP_NOSIZE | SWP_NOMOVE | SWP_DRAWFRAME);

上述代码加了一个细边框到窗口中,外观如下图所示:

                          

注意,为了看到这个变化,你需要强迫窗口重画它的非客户区域,这就是SetWindowPos()函数出现的原因。再有就是,所有这些操作均在回调函数响应BFFM_INITIALIZED消息时完成。

        前面我已经警告过,这段代码潜在的风险,现在,在所有Win32平台上都能很好地工作,但是有一天微软决定改变这个ID时,将会怎样?一个好的方法可能是:

HWND hwndLabel = GetDlgItem(hwnd, 0x3743);

//检查是否为一可用的窗口

if(IsWindow(hwndLabel))

{

// 现在检查体是否为一个'static'窗口类

TCHAR szClass[MAX_PATH] = {0];

GetClassName(hwndLabel, szClass, MAX_PATH);

if(lstrcmpi(szClass, __TEXT("static")))

return;

}

else

return;

我们对GetDlgItem()函数返回的Handle执行了两个检查,头一个是使用IsWindow()来检查是否为有效的窗口,第二个是验证这个窗口是不是一个标签—一个‘static’类窗口。如果其中有一个检查失败,则退出,以避免非法操作。

改变对话框标题

        比加3D边框更有用的是改变对话框窗口的标题—使用新字符串调用SetWindowText(),即,在响应BFFM_INITIALIZED消息时,执行下面代码:

SetWindowText(hwnd, szNewCaption);

移动对话框窗口

        另一个在响应BFFM_INITIALIZED消息的初始化中可以做的是定位窗口到指定位置。典型地,可以移动对话框到屏幕中心:

RECT rc;

GetClientRect(hwnd, &rc);

SetWindowPos(hwnd, NULL,

(GetSystemMetrics(SM_CXSCREEN) - (rc.right - rc.left)) / 2,

(GetSystemMetrics(SM_CYSCREEN) - (rc.bottom - rc.top)) / 2,

0, 0, SWP_NOZORDER | SWP_NOSIZE);

变动状态标签

        状态标签的典型用途是显示当前选中的文件或文件夹名,就象在前面图中显示的那样。其实现方法就是响应BFFM_SELCHANGED消息:

TCHAR szText[MAX_PATH] = {0};

SHGetPathFromIDList(reinterpret_cast<LPITEMIDLIST>(lParam), szText);

SendMessage(hwnd, BFFM_SETSTATUSTEXT, 0, reinterpret_cast<LPARAM>(szText));

在接收到这个消息的时候,lParam变量指向一个新选中文件夹或文件的PIDL,说明文件或文件夹是存在的,因此可以调用SHGetPathFromIDList()函数获得可显示的路径名,注意,不是所有文件夹映射到物理目录—例如‘我的计算机’,是一个没有实际目录的文件夹。如果使用‘我的计算机’的PIDL调用SHGetPathFromIDList()函数,可能会获得一个NULL字符串。

   SHGetPathFromIDList()中恢复的字符串可以使用BFFM_SETSTATUSTEXT消息发送到状态窗口。

允许手动编辑

        自绑定到IE4.0Shell 4.71版开始,这个对话框的用户界面就加入了一个编辑框,而且不需要借助于回调方式就可以操作。你只要在调用SHBrowseForFolder()函数时简单地设置BIF_EDITBOX标志即可。结果显示如下:

                        

这个编辑框使你能够用输入文件夹的名字来选择它们。当你点击‘OK’时,函数将确认你的输入有效。如果编辑框中包含了一个文件夹的全路径名,或当前选择的文件夹名,则它的内容是正确的,如上图所示。如果BIF_VALIDATE标志设置,并且SHBrowseForFolder()函数发现编辑框的内容是不正确的,则它用BFFM_VALIDATEFAILED消息唤醒回调函数,在编辑框中的字符串经由lParam变量传递给回调函数。经由BROWSEINFO结构的lParam成员传递的任何用户数据,作为回调函时的第四个参数传递。因此,如果需要通过输入选择文件夹,绝对必须输入全路径名。

        下面列出的代码例子说明我们目前所看到的全部情况,后面章节中将给出整个应用的例程。

int CALLBACK BrowseCallbackProc(

HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM dwData)

{

switch(uMsg)

{

case BFFM_INITIALIZED:

{

// 删除标题中的?

DWORD dwStyle = GetWindowLong(hwnd, GWL_EXSTYLE);

SetWindowLong(hwnd, GWL_EXSTYLE, dwStyle & ~WS_EX_CONTEXTHELP);

// 给状态文字加3D边框

HWND hwndLabel = GetDlgItem(hwnd, 0x3743);

// 检查是否为有效的窗口

if(IsWindow(hwndLabel))

{

// 检查是否为'static'类窗口

TCHAR szClass[MAX_PATH] = {0};

GetClassName(hwndLabel, szClass, MAX_PATH);

if(lstrcmpi(szClass, __TEXT("static")))

break;

}

else

break;

dwStyle = GetWindowLong(hwndLabel, GWL_EXSTYLE);

SetWindowLong(hwndLabel, GWL_EXSTYLE, dwStyle | WS_EX_STATICEDGE);

SetWindowPos(hwndLabel, NULL, 0, 0, 0, 0,

SWP_NOSIZE | SWP_NOMOVE | SWP_DRAWFRAME);

}

break;

case BFFM_SELCHANGED:

{

TCHAR szText[MAX_PATH] = {0};

SHGetPathFromIDList(reinterpret_cast<LPITEMIDLIST>(lParam),

szText);

SendMessage(hwnd, BFFM_SETSTATUSTEXT, 0,

reinterpret_cast<LPARAM>(szText));

}

break;

case BFFM_VALIDATEFAILED:

Msg("\"%s\" is a wrong path name.",

reinterpret_cast<LPTSTR>(lParam));

return 1;

}

return 0;

}

指定初始文件夹

        SHBrowseForFolder()函数设计时的一个缺陷是没有一个方便的方法来指定开始浏览的初始目录。你可以指定显示层的根,就是这样,如果想要使用目录而不是文件夹,也不简单。为了在代码中设置初始选中的文件夹,我们必须借助于回调函数,特别是,必须探索BFFM_SETSELECTION消息,和请求函数移动焦点到一个特殊的文件夹。实现的最好方式是在响应BFFM_INITIALIZED通知消息的地方,下面就是实现的代码:

int CALLBACK BrowseCallbackProc(

HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM dwData)

{

switch(uMsg)

{

case BFFM_INITIALIZED:

{

...

SendMessage(hwnd, BFFM_SETSELECTION, TRUE, dwData);

}

break;

...

}

return 0;

}

BFFM_SETSELECTION消息需要知道lParam变量是一个PIDL还是一个路径名,在上面代码中因为设置(第三个参数)TRUE,所以dwData指向一个路径名,若dwData指向PIDL,则第三个参数应该设置为FALSE

指定根节点

        前面已经暗示,SHBrowseForFolder()函数允许指定桌面层上的哪一个节点作为根节点。换句话说,你可以选择想要浏览的探测器观察子树。能这样做的参数就是BROWSEINFO结构的pidlRoot成员。如果这个成员设置为NULL,树观察以桌面作为根。下面图中显示浏览对话框以‘打印机’作为根节点,并且设置了BIF_BROWSEINCLUDEFILES标志:

                        

附带地,这个例子还说明,BIF_BROWSEINCLUDEFILES的作用。代码如下:

LPITEMIDLIST pidl = NULL;

BROWSEINFO bi;

ZeroMemory(&bi, sizeof(BROWSEINFO));

bi.lpszTitle = __TEXT("Choose a printer:");

SHGetSpecialFolderLocation(NULL, CSIDL_PRINTERS, &pidl);

bi.pidlRoot = pidl;

bi.ulFlags = BIF_BROWSEINCLUDEFILES;

SHBrowseForFolder(&bi);

如果核对一下BROWSEINFO结构的声明,你将看到,pidlRoot成员应该有LPCITEMIDLIST类型—即,PIDL。在上面代码段中,通过在函数SHGetSpecialFolderLocation()第二个参数中传递CSIDL_PRINTERS,我们已经取得了这个值,这一点将在后面继续讨论。现在,概括地讲,你可以指定显示树的根节点,但是需要提供一个PIDL

使用目录作为根

        如果我们的目标正好就是浏览某个系统文件夹,如‘打印机’或‘字体’或‘Favorites’,没有问题—使用上面的代码段,只需把CSIDL_PRINTERS换成想要的文件夹ID即可。如果想要把普通的目录作为树观察的根,事情就有点严重了。

   对于特殊文件夹ID,翻看SHGetSpecialFolderLocation()函数的资料或研究shlobj.h源码就可以找到某些资料没有说明的IDs

转换路径名到PIDL

        没有别的办法,你只有转换路径名到PIDL。现在,你可能希望有一个ShellAPI函数做这个转换,不幸的是没有这样的函数。然而,有一种方法能够转换路径名到PIDL,这需要两个步骤:

                  获得指向IShellFolder接口的指针

       调用它的ParseDisplayName()的方法

ParseDisplayName()方法确实能实现你所要求的转换:它接受路径名,然后把它转换成PIDL,问题是我们怎样才能获得指向IShellFolder接口的指针。在你写一个命名空间扩展的时候,IShellFolder接口是一个需要实现的接口,并且,探测器也使用这个接口一起工作,请求绘制和枚举其内容。一个指向IShellFolder接口的指针由SHGetDesktopFolder()函数返回—精确地讲,它返回一个桌面文件夹的IShellFolder接口。就我们考虑的情况而言,我们只是需要一个提供实际实现ParseDisplayName()功能的对象指针,因而SHGetDesktopFolder()函数返回的是可用的。下面是一个新Shell函数的代码,它接受路径名和返回对应的PIDL,以微软命名习惯,我们称之为SHPathToPidl()

HRESULT SHPathToPidl(LPCTSTR szPath, LPITEMIDLIST* ppidl)

{

LPSHELLFOLDER pShellFolder = NULL;

OLECHAR wszPath[MAX_PATH] = {0};

ULONG nCharsParsed = 0;

// 获取IShellFolder接口指针

HRESULT hr = SHGetDesktopFolder(&pShellFolder);

if(FAILED(hr))

return hr;

// 转换路径名到Unicode

MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, szPath, -1, wszPath, MAX_PATH);

// 调用ParseDisplayName()函数

hr = pShellFolder->ParseDisplayName(

NULL, NULL, wszPath, &nCharsParsed, ppidl, NULL);

// 清理

pShellFolder->Release();

return hr;

}

ParseDisplayName()函数的原形如下:

HRESULT ParseDisplayName(HWND hwndOwner,

LPBC pbcReserved,

LPOLESTR lpszDisplayName,

ULONG* pchEaten,

LPITEMIDLIST* ppidl,

ULONG* pdwAttributes);

第一个变量是用作函数可能需要显示消息框的父窗口的Handle。第二个,pbcReserved没有使用,必须设置为NULL。头一个有意义的变量是lpszDisplayName,表示一个要求转换的Unicode格式的名字串,pchEaten是一个包含实际传递字符数的缓冲,而pdwAttributes(如果不空NULL),则包含由lpszDisplayName所指定文件夹的属性,这些属性都是SHGAO_前缀常量属性。如果不注意,可能会传递NULL属性。最后是ppidl变量,新生成的PIDL的返回缓冲。一旦对指定的路径成功地生成了PIDL,你就能够限制用户的浏览到指定的子树,而不能继续向上,例如:

                  

 

这个图显示C:\Program Files作为根文件夹的情形。

总结例程

        到目前为止,我们孤立地讨论了SHBrowseForFolder()函数,并且总是给出解决特殊问题的代码段,现在,我们构造一个完整的应用来总结上面所有我们看到的特性。

     

   

图中的对话框是测试SHBrowseForFolder()函数特征所使用过的,称之为SHBrowse。通过选择路径名(文件夹编辑框)PIDL(PIDL列表框)指定根文件夹—使用PIDL复选框确定是哪一种路径。可以在标题编辑框中设置对话框标题,以及几个与SHBrowseForFolder()函数标志大致匹配的复选框,结果显示在下面的区域,显示名,路径名和文件夹图标。增加的第一段代码在OnInitDialog()函数中,以便使用特殊文件夹的名字设置PIDL下拉列表框。

void OnInitDialog(HWND hDlg)

{

// 设置图标(T/F/小图标)

SendMessage(hDlg, WM_SETICON, FALSE, reinterpret_cast<LPARAM>(g_hIconSmall));

SendMessage(hDlg, WM_SETICON, TRUE, reinterpret_cast<LPARAM>(g_hIconLarge));

// 填写下拉列表框

HWND hwndCbo = GetDlgItem(hDlg, IDC_SPECIAL);

int i = ComboBox_AddString(hwndCbo, "Control Panel");

ComboBox_SetItemData(hwndCbo, i, CSIDL_CONTROLS);

i = ComboBox_AddString(hwndCbo, "Favorites");

ComboBox_SetItemData(hwndCbo, i, CSIDL_FAVORITES);

i = ComboBox_AddString(hwndCbo, "Printers");

ComboBox_SetItemData(hwndCbo, i, CSIDL_PRINTERS);

i = ComboBox_AddString(hwndCbo, "Fonts");

ComboBox_SetItemData(hwndCbo, i, CSIDL_FONTS);

i = ComboBox_AddString(hwndCbo, "SendTo");

ComboBox_SetItemData(hwndCbo, i, CSIDL_SENDTO);

ComboBox_SetCurSel(hwndCbo, 0);

}

此时你可以从这个列表中选择‘发送到’(SendTo)文件夹,这依赖于你的其它选择设置。如图:

                          

 

当然,这个对话框在你自己的计算机上可能稍有不同,这是因为‘SendTo’目录可能有不同的快捷方式。在此选择‘OutLook Express’,结果如下:

                   

整个项目是可用的,代码包含了BrowseCallbackProc()SHGetSystemIcon()SHPathToPidl()函数。编译之前一定要记住#include shlobj.h resource.h。这些函数在点击OK按钮时被执行。

void OnOK(HWND hDlg)

{

BROWSEINFO bi;

TCHAR szTitle[MAX_PATH] = {0};

TCHAR szPath[MAX_PATH] = {0};

TCHAR szDisplay[MAX_PATH] = {0};

LPITEMIDLIST pidl = NULL;

LPMALLOC pMalloc = NULL;

// 准备

ZeroMemory(&bi, sizeof(BROWSEINFO));

bi.hwndOwner = hDlg;

// 标题和显示名

GetDlgItemText(hDlg, IDC_TITLE, szTitle, MAX_PATH);

bi.lpszTitle = szTitle;

bi.pszDisplayName = szDisplay;

//初始目录

if(IsDlgButtonChecked(hDlg, IDC_USEPIDL))

{

HWND hwndCbo = GetDlgItem(hDlg, IDC_SPECIAL);

int i = ComboBox_GetCurSel(hwndCbo);

int nFolder = ComboBox_GetItemData(hwndCbo, i);

SHGetSpecialFolderLocation(NULL, nFolder, &pidl);

bi.pidlRoot = pidl;

}else{

// 转换路径名到PIDL

GetDlgItemText(hDlg, IDC_FOLDER, szPath, MAX_PATH);

if(lstrlen(szPath) == 0)

GetCurrentDirectory(MAX_PATH, szPath);

SHPathToPidl(szPath, &pidl);

bi.pidlRoot = pidl;

}

// 采集标志

UINT uiFlags = 0;

if(IsDlgButtonChecked(hDlg, IDC_NOBELOW))

uiFlags |= BIF_DONTGOBELOWDOMAIN;

if(IsDlgButtonChecked(hDlg, IDC_ONLYDIRS))

uiFlags |= BIF_RETURNONLYFSDIRS;

if(IsDlgButtonChecked(hDlg, IDC_INCLUDEFILES))

uiFlags |= BIF_BROWSEINCLUDEFILES;

if(IsDlgButtonChecked(hDlg, IDC_EDITBOX))

uiFlags |= BIF_EDITBOX | BIF_VALIDATE;

if(IsDlgButtonChecked(hDlg, IDC_STATUS))

uiFlags |= BIF_STATUSTEXT;

if(IsDlgButtonChecked(hDlg, IDC_COMPUTER))

uiFlags |= BIF_BROWSEFORCOMPUTER;

bi.ulFlags = uiFlags;

// 设置回调

bi.lpfn = BrowseCallbackProc;

bi.lParam = 0;

// 调用函数

LPITEMIDLIST pidlFolder = SHBrowseForFolder(&bi);

if(pidlFolder == NULL)

return;

// 显示结果...

// 显示显示名

SetDlgItemText(hDlg, IDC_DISPLAYNAME, bi.pszDisplayName);

// 显示路径名

SHGetPathFromIDList(pidlFolder, szPath);

SetDlgItemText(hDlg, IDC_PATHNAME, szPath);

// 显示文件夹图标

HICON hIcon = SHGetSystemIcon(bi.iImage);

SendDlgItemMessage(

hDlg, IDI_ICON, STM_SETICON, reinterpret_cast<WPARAM>(hIcon), 0);

// 释放

SHGetMalloc(&pMalloc);

pMalloc->Free(pidl);

pMalloc->Free(pidlFolder);

pMalloc->Release();

}

上面函数的工作方式对你来讲是很显然的—除了,最后面的一段。为了进一步解释它,我们需要更深地了解关于PIDL的知识。

有点变态的PIDL

        在第二章中我们检测了PIDLs,在这里,我们得到了它的特殊应用:使用PIDL浏览文件夹的内容,无论这个内容是什么。每一个Windows Shell 的元素都有它自己的PIDL并且包含在某种文件夹中,因而,对于每一个元素,都有一段代码来处理文件夹和依据文件夹自有的规则和需求提供PIDL。也就是说,我们从不能设定PIDL结构或它所组合成的数据,而是必须使用通用接口来处理它。例如,如果希望探索SHITEMID结构链,你就应该在每一步检查下一个块的长度。就像你已经看到过的那样,一个ITEMIDLIST—或PIDL—是由一个或多个连续分配的SHITEMID组成的,这个链在cb字段为0的元素上终止。下面是从MSDN上摘录的函数,它可以说明怎样遍历这个列表的项。这与普通的列表操作没有太大的差别。

LPITEMIDLIST GetNextItemID(LPITEMIDLIST pidl)

{

// 取得制定项的尺寸

int cb = pidl->mkid.cb;

// 尺寸为零则列表终止

if(cb == 0)

return NULL;

// cbpidl (生成字节增量)

pidl = (LPITEMIDLIST)(((LPBYTE)pidl) + cb);

// 如果NULL终止则返回NULL,否则返回一个pidl

return (pidl->mkid.cb == 0) ? NULL : pidl;

}

你不可以设定PIDL的格式,对于一个文件夹,一种方法可能工作的很好,对于另一个可能会失败。例如,为了保证两个项是相同的,你必须通过IShellFolder::CompareIDs()方法请求文件夹自己来比较它们。

释放PIDL

        在进一步讨论之前,有必要解释一下上面例子中的最后一段代码。在文件夹建立PIDLs时,通常必须由其它模块销毁,这就是我们在OnOK()函数最后所做的。标识符列表的内存从Shell应用的分配器上取得,向第二章中所见,我们可以调用SHGetMalloc()函数获得指向分配器的指针。一般,调用顺序如下:

LPMALLOC pMalloc;

SHGetMalloc(&pMalloc); //取得IMalloc接口

pMalloc->Free(pidl); //释放标识符列表

pMalloc->Release(); //释放IMalloc接口

怎样使用PIDL

        回到我们的题目当中,使PIDLs有某些实际的用途,这里有两个主要的目标,头一个,我们想要能够枚举任何文件夹的内容,第二个是希望重复探测器在Shell4.71和更高版本中所支持的特征。为了展示这个想法,下面是探测器在地址栏输入‘打印机’后,键入‘回车’截图:

               

原创粉丝点击