培训
获取有关文件夹内容的信息
“获取文件夹的 ID”部分讨论了两种获取命名空间对象的指向项标识符列表 (PIDL) 的指针的两种方法。 一个明显的问题是:一旦有了 PIDL,你能用它做什么? 相关问题是:如果两种方法都无效或不适合应用程序,该怎么办? 这两个问题的答案都是需要仔细了解命名空间的实现方式。 关键在于 IShellFolder 接口。
在本文档前面的部分,命名空间文件夹称为对象。 虽然在当时,该术语的使用是宽泛的,但实际上在严格意义上也是如此。 每个命名空间文件夹都由组件对象模型 (COM) 对象表示。 每个文件夹对象会公开许多接口,可用于执行各种任务。 某些可选接口可能不会由所有文件夹公开。 但是,所有文件夹都必须公开基本接口 IShellFolder。
使用文件夹对象的第一步是检索指向其 IShellFolder 接口的指针。 除了提供对对象其他接口的访问权限之外,IShellFolder 还公开了一组处理许多常见任务的方法,本节对其中几个进行了讨论。
若要检索指向命名空间对象的 IShellFolder 接口的指针,必须先调用 SHGetDesktopFolder。 此函数返回指向命名空间根路径(桌面)的 IShellFolder 接口的指针。 拥有桌面 IShellFolder 界面后,即可通过多种方式继续操作。
例如,如果已有感兴趣的文件夹的 PIDL,请调用 SHGetFolderLocation,可以通过调用桌面的 IShellFolder::BindToObject 方法来检索其 IShellFolder 接口。 如果有文件系统对象的路径,则必须首先通过调用桌面的 IShellFolder::ParseDisplayName 方法来获取其 PIDL,然后调用 IShellFolder::BindToObject。 如果这两种方法都不适用,则可以使用其他 IShellFolder 方法来导航命名空间。 有关详细信息,请参阅导航命名空间。
通常想要对文件夹执行的第一件事是找出它包含的内容。 必须先调用文件夹的 IShellFolder::EnumObjects 方法。 该文件夹将创建一个标准 OLE 枚举对象,并返回其 IEnumIDList 接口。 此接口公开四种标准方法,Clone、Next、Reset 和 Skip,均可用于枚举文件夹的内容。
枚举文件夹内容的基本过程包括:
- 调用文件夹 IShellFolder::EnumObjects 方法,以检索指向枚举对象的 IEnumIDList 接口的指针。
- 将未分配的 PIDL 传递给 IEnumIDList::Next。 Next 负责分配 PIDL,但在不再需要时,应用程序必须将其解除分配。 当 Next 返回时,PIDL 将仅包含对象的项 ID 和终止 NULL 字符。 换言之,它是相对于文件夹的单级 PIDL,而不是完全限定的 PIDL。
- 重复步骤 2,直到 Next 返回 S_FALSE,以指示所有项都已枚举。
- 调用 IEnumIDList::Release 以释放枚举对象。
备注
请务必跟踪使用的是完整 PIDL 还是相对 PIDL。 某些函数和方法将接受两者任一,但其他函数将只接受其中之一。
如果需要重复枚举文件夹,那么其余三个 IEnumIDList 方法(Reset、Skip 和 Clone)非常有用。 它们让你可以重置枚举、跳过一个或多个对象,并创建枚举对象的副本以保留其状态。
在枚举了文件夹包含的所有 PIDL 后,即可了解它们所表示的对象类型。 IShellFolder 接口提供了许多有用的方法,在此处对其中两种方法进行了讨论。 稍后将讨论其他 IShellFolder 方法和其他 Shell 文件夹接口。
最有用的属性之一是对象的显示名称。 若要检索对象的显示名称,请将其 PIDL 传递给 IShellFolder::GetDisplayNameOf。 尽管该对象可以位于命名空间中父文件夹下方的任意位置,但其 PIDL 必须相对于该文件夹。
IShellFolder::GetDisplayNameOf 将返回显示名称作为 STRRET 结构的一部分。 由于从 STRRET 结构中提取显示名称可能会有点棘手,因此 Shell 提供了两个函数,StrRetToStr 和 StrRetToBuf,用于为你执行作业。 这两个函数采用 STRRET 结构,并将显示名称作为普通字符串返回。 它们仅在分配字符串的方式上有所不同。
除了显示名称之外,对象还可以具有许多属性,例如是否为文件夹,或是否可以移动。 可以通过将对象的 PIDL 传递给 IShellFolder::GetAttributesOf 来检索对象的属性。 属性的完整列表相当大,因此应查看有关详细信息的参考。 请注意,传递给 GetAttributesOf 的 PIDL 必须是单级。 具体而言,IShellFolder::GetAttributesOf 将接受 IEnumIDList::Next 返回的 PIDL。 可以传入 PIDL 数组,GetAttributesOf 将返回数组中所有对象共有的属性。
如果你有对象的完全限定路径或 PIDL,SHGetFileInfo 提供了一种简单方法来检索有关足以满足许多目的的对象的信息。 SHGetFileInfo 采用完全限定的路径或 PIDL,并返回有关对象的各种信息,包括:
- 对象的显示名称
- 对象的属性
- 对象的图标句柄
- 系统映像列表的句柄
- 包含对象图标的文件的路径
可以通过调用 IShellFolder::GetAttributesOf 并检查是否已设置 SFGAO_FOLDER 标志来确定文件夹是否包含任何子文件夹。 如果对象是文件夹,则可以绑定到该文件夹,该文件夹提供了指向其 IShellFolder 接口的指针。
若要绑定到子文件夹,请调用父文件夹的 IShellFolder::BindToObject 方法。 此方法采用子文件夹的 PIDL,并返回指向其 IShellFolder 接口的指针。 有了此指针后,可以使用 IShellFolder 方法枚举子文件夹内容、确定其属性等。
如果有对象的 PIDL,则可能需要处理其父文件夹公开的其中一个接口。 例如,如果要使用 IShellFolder::GetDisplayNameOf 来确定与 PIDL 关联的显示名称,则必须首先检索对象的父级的 IShellFolder 接口。 可以使用前面各节中讨论的技术执行此操作。 但是,更简单的方法是使用 Shell 函数 SHBindToParent。 此函数采用对象的完全限定 PIDL,并在父文件夹上返回指定的接口指针。 (可选)它还会返回项的单级 PIDL,以便在 IShellFolder::GetAttributesOf 等方法中进行使用。
以下示例控制台应用程序检索 System 特殊文件夹的 PIDL 并返回其显示名称。
#include <shlobj.h>
#include <shlwapi.h>
#include <iostream.h>
#include <objbase.h>
int main()
{
IShellFolder *psfParent = NULL;
LPITEMIDLIST pidlSystem = NULL;
LPCITEMIDLIST pidlRelative = NULL;
STRRET strDispName;
TCHAR szDisplayName[MAX_PATH];
HRESULT hr;
hr = SHGetFolderLocation(NULL, CSIDL_SYSTEM, NULL, NULL, &pidlSystem);
hr = SHBindToParent(pidlSystem, IID_IShellFolder, (void **) &psfParent, &pidlRelative);
if(SUCCEEDED(hr))
{
hr = psfParent->GetDisplayNameOf(pidlRelative, SHGDN_NORMAL, &strDispName);
hr = StrRetToBuf(&strDispName, pidlSystem, szDisplayName, sizeof(szDisplayName));
cout << "SHGDN_NORMAL - " <<szDisplayName << '\n';
}
psfParent->Release();
CoTaskMemFree(pidlSystem);
return 0;
}
应用程序首先使用 SHGetFolderLocation 来检索系统文件夹的 PIDL。 然后,它调用 SHBindToParent,该指针返回指向父文件夹的 IShellFolder 接口的指针,以及相对于其父文件夹的系统文件夹的 PIDL。 然后,它使用父文件夹的 IShellFolder::GetDisplayNameOf 方法检索系统文件夹的显示名称。 由于 GetDisplayNameOf 返回 STRRET 结构,因此 StrRetToBuf 用于将显示名称转换为普通字符串。 对显示名称进行显示后,将释放接口指针并释放系统 PIDL。 请注意,不得释放 SHBindToParent 返回的相对 PIDL。