实现基本文件夹对象接口
实现命名空间扩展的程序与任何其他进程中组件对象模型 (COM) 对象的程序类似。 所有扩展必须支持三个主要接口,这些接口可为 Windows 资源管理器提供在树视图中显示扩展文件夹所需的基本信息。 但要充分利用 Windows 资源管理器的功能,则扩展还必须提供一个或多个可选接口,以支持更复杂的功能,如快捷菜单或拖放,并提供文件夹视图。
本文档讨论如何实现 Windows 资源管理器调用的主要接口和可选接口,以获取有关扩展内容的信息。 有关如何实现文件夹视图以及如何自定义 Windows 资源管理器的讨论,请参阅实现文件夹视图。
基本实现和注册
作为进程内 COM 服务器,DLL 必须公开几个标准函数和接口:
这些函数和接口的实现方式与大多数其他 COM 对象相同。 有关详细信息,请参阅 COM 文档。
注册扩展
与所有 COM 对象一样,必须为扩展创建一个类标识符 (CLSID) GUID。 通过在 HKEY_CLASSES_ROOT\CLSID 中创建一个子项并以扩展的 CLSID 命名,以便注册对象。 DLL 应注册为一个进程内服务器,并且应指定单元线程模型。 通过向扩展的 CLSID 项添加各种子项和值,可以自定义扩展根文件夹的行为。
其中几个值仅适用于具有虚拟结合点的扩展。 这些值不适用于结合点为文件系统文件夹的扩展。 有关详细讨论,请参阅指定命名空间扩展的位置。 要修改带有虚拟结合点的扩展的行为,请在扩展的 CLSID 项上添加一个或多个以下值:
- WantsFORPARSING。 具有虚拟结合点的扩展的解析名称通常为 ::{GUID}。 这种类型的扩展通常包含虚拟项目。 但是,有些扩展(如“我的文档”)虽然有虚拟结合点,但实际上对应的是文件系统文件夹。 如果扩展是以这种方式来表示文件系统对象,则可以设置 WantsFORPARSING 值。 然后,Windows 资源管理器将调用文件夹对象的 IShellFolder::GetDisplayNameOf 方法,并将 uFlags 设置为 SHGDN_FORPARSING,同时将 pidl 设置为指向项目标识符列表 (PIDL) 的单个空指针,从而请求根文件夹的解析名称。 空 PIDL 只包含一个终止符。 然后,方法应返回根文件夹的 ::{GUID} 解析名称。
- HideFolderVerbs。 在 HKEY_CLASSES_ROOT\Folder 下注册的谓词通常与所有扩展相关联。 它们显示在扩展的快捷菜单上,可通过 ShellExecute 调用。 要防止这些谓词与扩展关联,请设置 HideFolderVerbs 值。
- HideAsDelete。 如果用户试图删除扩展,则 Windows 资源管理器会隐藏该扩展。
- HideAsDeletePerUser。 此值的作用与 HideAsDelete 相同,但以每个用户为单位。 只有尝试删除扩展的用户才会发现它已被隐藏。 所有其他用户都可以看到扩展。
- QueryForOverlay。 设置此值表示根文件夹图标可以有图标覆盖。 文件夹对象必须支持 IShellIconOverlay 接口。 在 Windows 资源管理器显示根文件夹图标之前,它会调用两个 IShellIconOverlay 方法中的一个,并将 pidlItem 设置为空 PIDL,从而请求覆盖图标。
其余值和子项适用于所有扩展:
- 要指定扩展结合点文件夹的显示名称,请将扩展 CLSID 子项的默认值设置为适当的字符串。
- 当光标悬停在文件夹上时,通常会显示一个描述文件夹内容的信息提示。 要为扩展的根文件夹提供信息提示,请为扩展的 CLSID 项创建 InfoTip REG_SZ 值,并将其设置为适当的字符串。
- 要为扩展根文件夹指定自定义图标,请在扩展的 CLSID 子项中创建一个名为 DefaultIcon 的子项。 将 DefaultIcon 的默认值设置为 REG_SZ 值,该值包含包含图标的文件名,后面是逗号,后面是减号,后面是该文件中图标的索引。
- 默认情况下,扩展根文件夹的快捷菜单将包含 HKEY_CLASSES_ROOT\Folder 下定义的项目。 如果设置了相应的 SFGAO_XXX 标志,则会添加 Delete、Rename 和 Properties 项目。 可以在根文件夹的快捷菜单中添加其他项目,或覆盖现有项目,就像对文件类型一样。 在扩展的 CLSID 项下创建 Shell 子项,并按照扩展快捷菜单中的讨论定义命令。
- 如果需要更灵活的方法来处理根文件夹的快捷菜单,则可以实现快捷菜单处理程序。 要注册快捷菜单处理程序,请在扩展的 CLSID 项目下创建 ShellEx 项。 像传统的创建 Shell 扩展处理程序一样注册处理程序的 CLSID。
- 要在根文件夹的“属性”属性表中添加页面,请为文件夹赋予 SFGAO_HASPROPSHEET 属性,并实现属性表处理程序。 要注册属性表处理程序,请在扩展的 CLSID 项目下创建 ShellEx 项。 像传统的创建 Shell 扩展处理程序一样注册处理程序的 CLSID。
- 要指定根文件夹的属性,请在扩展的 CLSID 子项上添加 ShellFolder 子项。 创建属性值,并将其设置为 SFGAO_XXX 标志的适当组合。
下表列出了一些常用的根文件夹属性。
标志 | 值 | 说明 |
---|---|---|
SFGAO_FOLDER | 0x20000000 | 扩展的根文件夹包含一个或多个项目。 |
SFGAO_HASSUBFOLDER | 0x80000000 | 扩展的根文件夹包含一个或多个子文件夹。 Windows 资源管理器会在文件夹图标旁放置一个加号 ( + )。 |
SFGAO_CANDELETE | 0x00000020 | 用户可以删除扩展的根文件夹。 文件夹的快捷菜单将有一个“删除”项目。 应为放置在其中一个虚拟文件夹下的结合点设置此标志。 |
SFGAO_CANRENAME | 0x00000010 | 用户可以重命名扩展根文件夹。 文件夹的快捷菜单将有一个“重命名”项目。 |
SFGAO_HASPROPSHEET | 0x00000040 | 扩展的根文件夹有一个“属性”属性表。 文件夹的快捷菜单将有一个“属性”项目。 要提供属性表,必须实现属性表处理程序。 如前所述,在扩展的 CLSID 项下注册处理程序。 |
以下示例显示了显示名称为 MyExtension 的扩展的 CLSID 注册表项。 扩展有一个自定义图标,该图标包含在扩展的 DLL 中,索引为 1。 设置 SFGAO_FOLDER、SFGAO_HASSUBFOLDER 和 SFGAO_CANDELETE 属性。
HKEY_CLASSES_ROOT
CLSID
{Extension CLSID}
(Default) = MyExtension
InfoTip = Some appropriate text
DefaultIcon
(Default) = c:\MyDir\MyExtension.dll,-1
InProcServer32
(Default) = c:\MyDir\MyExtension.dll
ThreadingModel = Apartment
ShellFolder
Attributes = 0xA00000020
处理 PIDL
Shell 命名空间中的每个项目都必须有一个唯一的 PIDL。 Windows 资源管理器会为根文件夹分配一个 PIDL,并在初始化过程中将该值传递给扩展。 然后,扩展将负责为其每个对象分配一个正确构建的 PIDL,并根据要求将这些 PIDL 提供给 Windows 资源管理器。 当 Shell 使用 PIDL 来标识扩展的某个对象时,扩展必须能够解释 PIDL 并标识特定对象。 扩展还必须为每个对象分配显示名称和解析名称。 由于几乎每个文件夹接口都会使用 PIDL,因此扩展通常会实现一个 PIDL 管理器来处理所有这些任务。
PIDL 是 ITEMIDLIST 结构或指向此类结构的指针的简称,具体取决于上下文。 正如所声明的,ITEMIDLIST 结构只有一个成员,即 SHITEMID 结构。 对象的 ITEMIDLIST 结构实际上是两个或多个 SHITEMID 结构的打包数组。 这些结构的顺序定义了命名空间的路径,就像 c:\MyDirectory\MyFile 定义了文件系统的路径很相似。 通常,对象的 PIDL 由一系列 SHITEMID 结构组成,这些结构与定义命名空间路径的文件夹相对应,然后是对象的 SHITEMID 结构,后跟一个终止符。
终止符是一个 SHITEMID 结构,其中 cb 成员设置为 NULL。 由于对象 PIDL 中 SHITEMID 结构的数量取决于对象在 Shell 命名空间中的位置和路径的起点,因此必须使用终止符。 此外,各种 SHITEMID 结构的大小也可能不同。 在收到 PIDL 时无法简单地确定其大小,甚至无法确定 SHITEMID 结构的总数。 相反,必须逐个结构地“审核”打包的数组,直至到达终止符。
要创建 PIDL,应用程序需要:
创建 SHITEMID 结构
对象的 SHITEMID 结构可唯一标识其文件夹中的对象。 事实上,许多 IShellFolder 方法使用的一种 PIDL 仅包括对象的 SHITEMID 结构,后跟一个终止符。 SHITEMID 结构的定义是:
typedef struct _SHITEMID {
USHORT cb;
BYTE abID[1];
} SHITEMID, * LPSHITEMID;
abID 成员是对象的标识符。 由于 abID 的长度没有定义,而且可能会变化,因此 cb 成员将被设置为 SHITEMID 结构的大小(以字节为单位)。
由于 abID 的长度和内容都没有标准化,因此你可以使用任何方案为对象分配 abID 值。 唯一的要求是不能在同一文件夹中包含两个值相同的对象。 但出于性能考虑,SHITEMID 结构应采用 DWORD 对齐方式。 换句话说,应该构建 abID 值,使 cb 是 4 的整数倍。
通常,abID 指向扩展定义的结构。 除了对象的 ID 外,此结构通常还用于保存各种相关信息,如对象的类型或属性。 这样,扩展的文件夹对象就可以快速从 PIDL 中提取信息,而无需进行查询。
注意
为 PIDL 设计数据结构最重要的一点是使结构具有持久性和可传输性。 就 PIDL 而言,这些术语的含义是:
- 可持续。 系统经常将 PIDL 放在各种类型的长期存储器中,如快捷方式文件。 之后,它可以从存储中恢复这些 PIDL(可能是在系统重启之后)。 从存储中恢复的 PIDL 必须仍然有效,并且对扩展有意义。 例如,这一要求意味着不应在 PIDL 结构中使用指针或句柄。 包含此类数据的 PIDL 在系统从存储中恢复时通常毫无意义。
- 可传输。 当 PIDL 从一台计算机传输到另一台计算机时,必须保持其意义。 例如,可以将 PIDL 写入快捷方式文件,复制到软盘上,然后传送到另一台计算机。 该 PIDL 对在第二台计算机上运行的扩展仍然有意义。 例如,为确保 PIDL 可传输,可显示使用 ANSI 或 Unicode 字符。 避免使用 TCHAR 或 LPTSTR 等数据类型。 如果使用这些数据类型,在运行 Unicode 版本扩展的计算机上创建的 PIDL 将无法被在另一台计算机上运行 ANSI 版本扩展的计算机读取。
下面的声明显示了一个数据结构的简单示例。
typedef struct tagMYPIDLDATA {
USHORT cb;
DWORD dwType;
WCHAR wszDisplayName[40];
} MYPIDLDATA, *LPMYPIDLDATA;
cb 成员被设置为 MYPIDLDATA 结构的大小。 此成员使 MYPIDLDATA 本身成为一个有效的 SHITEMID 结构。 其余成员等同于 SHITEMID 结构中的 abID 成员,可以保存私有数据。 dwType 成员是一个扩展定义的值,指示对象的类型。 在此示例中,dwType 对文件夹设置为 TRUE,否则设置为 FALSE。 例如,此成员允许快速确定对象是否为文件夹。 wszDisplayName 成员包含对象的显示名称。 由于不会为同一文件夹中的两个不同对象指定相同的显示名称,因此显示名称也可用作对象 ID。 在此示例中,wszDisplayName 设置为 40 个字符,以确保 SHITEMID 结构将采用 DWORD 对齐方式。 要限制 PIDL 的大小,可以使用长度可变的字符数组,并相应调整 cb 的值。 用足够多的 '\0' 字符填充显示字符串,以保持结构 DWORD 的对齐方式。 结构中可能有用的其他成员包括对象的大小、属性或解析名称。
构建 PIDL
为对象定义 SHITEMID 结构后,就可以使用它们来构建 PIDL。 PIDL 的构建有多种目的,但大多数任务都使用两种类型的 PIDL 之一。 最简单的单级 PIDL 是相对于父文件夹来标识对象的。 许多 IShellFolder 方法都使用了这种类型的 PIDL。 单级 PIDL 包含对象的 SHITEMID 结构,后跟一个终止符。 完全限定 PIDL 定义了从桌面到对象的命名空间层次结构路径。 这种类型的 PIDL 从桌面开始,路径中的每个文件夹都包含一个 SHITEMID 结构,后跟对象和终止符。 完全限定 PIDL 可在整个 Shell 命名空间中唯一标识对象。
构建 PIDL 的最简单方法是直接使用 ITEMIDLIST 结构本身。 创建一个 ITEMIDLIST 结构,但要分配足够的内存来容纳所有 SHITEMID 结构。 此结构的地址将指向初始 SHITEMID 结构。 为这个初始结构的成员定义值,然后按适当顺序添加所需的其他 SHITEMID 结构。 以下过程概述了如何创建单级 PIDL。 它包含两个 SHITEMID 结构 — 一个 MYPIDLDATA 结构,后跟一个终止符:
- 使用 CoTaskMemAlloc 函数为 PIDL 分配内存。 为专用数据分配足够的内存,外加一个 USHORT(两个字节)作为终止符。 将结果强制转换为 LPMYPIDLDATA。
- 将第一个 MYPIDLDATA 结构的 cb 成员设置为该结构的大小。 在本例中,将 cb 设置为 sizeof(MYPIDLDATA)。 如果要使用可变长度结构,则必须计算 cb 的值。
- 为专用数据成员分配适当的值。
- 计算下一个 SHITEMID 结构的地址。 将当前 MYPIDLDATA 结构的地址强制转换为 LPBYTE,并将该值与步骤 3 中确定的 cb 的值相加。
- 在这种情况下,下一个 SHITEMID 结构就是终止符。 将结构的 cb 成员设置为零。
对于较长的 PIDL,请分配足够的内存,并对每一个额外的 SHITEMID 结构重复步骤 3-5。
下面的示例函数接收对象的类型和显示名称,并返回对象的单级 PIDL。 该函数假定显示名称(包括其终止 null 字符)不超过为 MYPIDLDATA 结构声明的字符数。 如果这一假设是错误的,StringCbCopyW 函数会将显示名称截断。 g_pMalloc 变量是一个 IMalloc 指针,在其他地方创建并存储在全局变量中。
LPITEMIDLIST CreatePIDL(DWORD dwType, LPCWSTR pwszDisplayName)
{
LPMYPIDLDATA pidlOut;
USHORT uSize;
pidlOut = NULL;
//Calculate the size of the MYPIDLDATA structure.
uSize = sizeof(MYPIDLDATA);
// Allocate enough memory for the PIDL to hold a MYPIDLDATA structure
// plus the terminator
pidlOut = (LPMYPIDLDATA)m_pMalloc->Alloc(uSize + sizeof(USHORT));
if(pidlOut)
{
//Assign values to the members of the MYPIDLDATA structure
//that is the PIDL's first SHITEMID structure
pidlOut->cb = uSize;
pidlOut->dwType = dwType;
hr = StringCbCopyW(pidlOut->wszDisplayName,
sizeof(pidlOut->wszDisplayName), pwszDisplayName);
// TODO: Add error handling here to verify the HRESULT returned
// by StringCbCopyW.
//Advance the pointer to the start of the next SHITEMID structure.
pidlOut = (LPMYPIDLDATA)((LPBYTE)pidlOut + pidlOut->cb);
//Create the terminating null character by setting cb to 0.
pidlOut->cb = 0;
}
return pidlOut;
一个完全限定 PIDL 必须具有 SHITEMID 结构,用于从桌面到你的对象的每个对象。 在 Shell 调用 IPersistFolder::Initialize 时,扩展会收到根文件夹的完全限定 PIDL。 要为对象构建完全限定 PIDL,可以使用 Shell 分配给根文件夹的 PIDL,并添加从根文件夹到对象所需的 SHITEMID 结构。
解释 PIDL
当 Shell 或应用程序调用扩展的某个接口来请求对象信息时,通常会使用 PIDL 来标识该对象。 有些方法(如 IShellFolder::GetUIObjectOf)使用的 PIDL 是相对于父文件夹的,并且可以直接解释。 但是,扩展可能也会收到完全限定 PIDL。 然后,PIDL 管理器必须确定 PIDL 引用的是哪个对象。
将对象与完全限定 PIDL 关联起来的工作之所以复杂,是因为 PIDL 中的一个或多个初始 SHITEMID 结构可能属于 Shell 命名空间中扩展之外的对象。 这些结构中 abID 成员的含义无法解释。 扩展必须“审核”SHITEMID 结构列表,直至找到与根文件夹相对应的结构。 此后,你将知道如何解释 SHITEMID 结构中的信息。
要审核 PIDL,请获取第一个 cb 值并将其添加到 PIDL 地址,从而将指针推进到下一个 SHITEMID 结构的开头。 然后,它将指向该结构的 cb 成员,可以用它将指针推进到下一个 SHITEMID 结构的开头,依此类推。 每次推进指针时,都要检查 SHITEMID 结构,以确定是否已到达扩展命名空间的根目录。
实现主接口
与所有 COM 对象一样,实现扩展在很大程度上就是实现一系列接口。 本部分将讨论所有扩展都必须实现的三个主要接口。 它们用于初始化,并为 Windows 资源管理器提供有关扩展内容的基本信息。 这些接口以及一个文件夹视图,就是功能扩展所需的全部内容。 但是,为了充分利用 Windows 资源管理器的功能,大多数扩展还需要实现一个或多个可选接口。
IPersistFolder 接口
调用 IPersistFolder 接口可初始化一个新的文件夹对象。 IPersistFolder::Initialize 方法将为新对象分配一个完全限定 PIDL。 将此 PIDL 储存起来以备日后使用。 例如,文件夹对象必须使用该 PIDL 为对象的子级构建完全限定 PIDL。 文件夹对象的创建者也可以调用 IPersist::GetClassID 来请求对象的 CLSID。
通常,文件夹对象由其父文件夹的 IShellFolder::BindToObject 方法来创建和初始化。 但在用户浏览扩展时,Windows 资源管理器会创建并初始化扩展的根文件夹对象。 根文件夹对象通过 IPersistFolder::Initialize 接收的 PIDL 包含从桌面到根文件夹的路径,需要为扩展构建完全限定 PIDL。
IShellFolder 接口
Shell 将扩展名视为文件夹对象的分层排序集合。 IShellFolder 接口是任何扩展实现的核心。 它表示一个文件夹对象,并为 Windows 资源管理器提供显示文件夹内容所需的大量信息。
IShellFolder 通常是除 IPersistFolder 以外唯一由文件夹对象直接公开的文件夹接口。 虽然 Windows 资源管理器会使用各种必要和可选的接口来获取文件夹内容的信息,但它还是会通过 IShellFolder 来获取指向这些接口的指针。
Windows 资源管理器可通过多种方式获取扩展根文件夹的 CLSID。 有关详细信息,请参阅指定命名空间扩展的位置或显示命名空间扩展的自包含视图。 然后,Windows 资源管理器将使用 CLSID 来创建和初始化根文件夹实例,并查询 IShellFolder 接口。 扩展会创建一个代表根文件夹的文件夹对象,并返回该对象的 IShellFolder 接口。 之后,扩展与 Windows 资源管理器之间的大部分互动都是通过 IShellFolder 进行的。 Windows 资源管理器调用 IShellFolder,以便:
- 请求一个可以枚举根文件夹内容的对象。
- 获取有关根文件夹内容的各类信息。
- 请求一个公开了其中一个可选接口的对象。 然后就可以通过这些接口来查询更多信息,如图标或快捷菜单。
- 请求一个表示根文件夹子文件夹的文件夹对象。
当用户打开根文件夹的子文件夹时,Windows 资源管理器会调用 IShellFolder::BindToObject。 扩展会创建并初始化一个新的文件夹对象来表示子文件夹,并返回其 IShellFolder 接口。 然后,Windows 资源管理器会调用该界面获取各类信息,如此循环,直到用户决定在 Shell 命名空间中浏览其他地方或关闭 Windows 资源管理器。
本部分的其余部分将简要讨论更重要的 IShellFolder 方法以及如何实现这些方法。
EnumObjects
在树形视图中显示文件夹内容之前,Windows 资源管理器必须首先通过调用 IShellFolder::EnumObjects 方法来确定文件夹所包含的内容。 此方法会创建一个标准 OLE 枚举对象,而该对象公开了一个 IEnumIDList 接口,同时返回该接口指针。 IEnumIDList 接口允许 Windows 资源管理器获取文件夹所包含的所有对象的 PIDL。 然后使用这些 PIDL 来获取文件夹所含对象的信息。 有关更多详细信息,请参阅 IEnumIDList 接口。
注意
IEnumIDList::Next 方法只能返回相对于父文件夹的 PIDL。 PIDL 应只包含对象的 SHITEMID 结构,后跟终止符。
CreateViewObject
在显示文件夹内容之前,Windows 资源管理器会调用此方法来请求一个指向 IShellView 接口的指针。 Windows 资源管理器使用此接口来管理文件夹视图。 创建文件夹视图对象并返回其 IShellView 接口。
调用 IShellFolder::CreateViewObject 方法还可以为文件夹本身请求一个可选接口,如 IContextMenu。 在执行此方法时,应创建一个能够公开所请求接口的对象,并返回接口指针。 如果 Windows 资源管理器需要文件夹所含对象之一的可选接口,它将调用 IShellFolder::GetUIObjectOf。
GetUIObjectOf
虽然有关文件夹内容的基本信息可通过 IShellFolder 方法来获取,但扩展还可为 Windows 资源管理器提供各种附加信息。 例如,可以为文件夹内容或对象的快捷菜单指定图标。 Windows 资源管理器调用 IShellFolder::GetUIObjectOf 方法,以尝试检索文件夹所包含对象的其他信息。 Windows 资源管理器会指定它需要哪个对象的信息,以及相关接口的 IID。 然后,文件夹对象会创建一个公开所请求接口的对象,并返回接口指针。
如果扩展允许用户通过拖放或剪贴板传输对象,则 Windows Explorer 将调用 IShellFolder::GetUIObjectOf 来请求 IDataObject 或 IDropTarget 接口。 有关详细信息,请参阅使用拖放和剪贴板传输 Shell 对象。
当 Windows 资源管理器需要文件夹本身的同类信息时,它会调用 IShellFolder::CreateViewObject。
BindToObject
当用户尝试打开扩展的一个子文件夹时,Windows 资源管理器会调用 IShellFolder::BindToObject 方法。 如果 riid 设置为 IID_IShellFolder,则应创建并初始化一个表示子文件夹的文件夹对象,并返回该对象的 IShellFolder 接口。
注意
目前,Windows 资源管理器仅在请求 IShellFolder 接口时才调用此方法。 但是,不要以为情况总是如此。 在继续操作前,应始终检查 riid 的值。
GetDisplayNameOf
Windows 资源管理器会调用 IShellFolder::GetDisplayNameOf 方法将文件夹对象之一的 PIDL 转换为名称。 该 PIDL 必须相对于对象的父文件夹。 换句话说,它必须包含一个非 NULL SHITEMID 结构。 由于命名对象的方法有很多,因此 Windows 资源管理器通过在 uFlags 参数中设置一个或多个 SHGDNF 标志来指定名称类型。 将设置 SHGDN_NORMAL 或 SHGDN_INFOLDER 这两个值中的一个,以指定名称是相对于文件夹还是相对于桌面。 可以设置 SHGDN_FOREDITING、SHGDN_FORADDRESSBAR 或 SHGDN_FORPARSING 三个值之一,以指定名称的用途。
必须以 STRRET 结构的形式返回名称。 如果未设置 SHGDN_FOREDITING、SHGDN_FORADDRESSBAR 和 SHGDN_FORPARSING,则会返回对象的显示名称。 如果设置了 SHGDN_FORPARSING 标志,indows 资源管理器将请求一个解析名称。 解析名称将传递给 IShellFolder::ParseDisplayName 以获取对象的 PIDL,即使该对象可能位于命名空间层次结构中当前文件夹之下的一个或多个层级。 例如,文件系统对象的解析名称就是其路径。 可以将文件系统中任何对象的完全限定路径传递给桌面的 IShellFolder::ParseDisplayName 方法,而它将返回该对象的完全限定 PIDL。
虽然解析名称是文本字符串,但不一定必须包括显示名称。 应根据调用 IShellFolder::ParseDisplayName 时最有效的方式来指定解析名称。 例如,Shell 的许多虚拟文件夹并非文件系统的一部分,也没有完全限定路径。 相反,会为每个文件夹分配一个 GUID,解析名称的形式为 ::{GUID} 。 无论使用哪种方案,它都应该能够可靠地“往返”。例如,如果调用方将一个解析名称传递给 IShellFolder::ParseDisplayName 以检索对象的 PIDL,然后将该 PIDL 传递给 IShellFolder::GetDisplayNameOf 并设置了 SHGDN_FORPARSING 标志,则调用方就应该恢复原来的解析名称。
GetAttributesOf
Windows 资源管理器会调用 IShellFolder::GetAttributesOf 方法来确定文件夹对象所包含的一个或多个项目的属性。 cidl 的值表示查询中的项目数,而 aPidl 指向项目的 PIDL 列表。
由于某些属性的测试可能会耗费大量时间,Windows 资源管理器通常会在 rfgInOut 中设置可用标志的值,从而将查询限制为可用标志的子集。 方法应该只测试在 rfgInOut 中设置了标志的属性。 保留有效标志并清除其余标志。 如果查询中包含多个项目,则只设置适用于所有项目的标志。
注意
必须正确设置属性才能正确显示项目。 例如,如果项目是一个包含子文件夹的文件夹,则必须设置 SFGAO_HASSUBFOLDER 标志。 否则,Windows 资源管理器将不会在树形视图中的项目图标旁显示一个 +。
ParseDisplayName
IShellFolder::ParseDisplayName 方法在某种意义上是 IShellFolder::GetDisplayNameOf 的镜像。 此方法最常用的用途是将对象的解析名称转换为相关的 PIDL。 解析名称可以指命名空间层次结构中位于文件夹之下的任何对象。 返回的 PIDL 是相对于公开方法的文件夹对象而言的,通常并非完全限定的。 换句话说,虽然 PIDL 可以包含多个 SHITEMID 结构,但第一个结构要么是对象本身的结构,要么是从文件夹到对象的路径中的第一个子文件夹。 调用方必须将此 PIDL 附加到文件夹的完全合格 PIDL 上,以获得对象的完全限定 PIDL。
也可以调用 IShellFolder::ParseDisplayName 以请求对象的属性。 由于确定所有适用属性可能会耗费大量时间,因此调用方将只设置那些代表调用方感兴趣的信息的 SFGAO_XXX 标志。 应确定对象中哪些属性为 true,并清除其余标志。
IEnumIDList 接口
当 Windows 资源管理器需要枚举文件夹所包含的对象时,它就会调用 IShellFolder::EnumObjects。 文件夹对象必须创建一个公开 IEnumIDList 接口的枚举对象,并返回该接口指针。 然后,Windows 资源管理器通常会使用 IEnumIDList 来枚举文件夹所包含的所有对象的 PIDL。
IEnumIDList 是一个标准的 OLE 枚举接口,并以通常的方式实现。 但请记住,返回的 PIDL 必须是相对于文件夹的,并且只包含对象的 SHITEMID 结构和一个终止符。
实现可选接口
扩展的文件夹对象可以支持许多可选的 Shell 接口。 其中许多(例如 IExtractIcon)都允许自定义用户查看扩展的方式的各个方面。 其他一些(如 IDataObject)则允许扩展支持拖放等功能。
文件夹对象不会直接公开任何可选接口。 相反,Windows 资源管理器会调用两个 IShellFolder 方法之一来请求接口:
- Windows 资源管理器会调用文件夹对象的 IShellFolder::GetUIObjectOf 来请求文件夹所含对象之一的接口。
- Windows 资源管理器调用文件夹对象的 IShellFolder::CreateViewObject 来请求文件夹本身的接口。
为了提供这些信息,文件夹对象会创建一个公开所请求接口的对象,并返回接口指针。 然后,Windows 资源管理器就会调用该接口来检索所需的信息。 本部分讨论最常用的可选接口。
IExtractIcon
Windows 资源管理器在显示文件夹内容之前,会请求一个 IExtractIcon 接口。 该接口允许扩展为文件夹所包含的对象指定自定义图标。 否则,将使用标准文件和文件夹图标。 要提供自定义图标,请创建一个公开 IExtractIcon 的图标提取对象,并返回指向该接口的指针。 有关进一步讨论,请参阅 IExtractIcon 参考文档或创建图标处理程序。
IContextMenu
当用户右键单击对象时,Windows 资源管理器会请求一个 IContextMenu 接口。 要为对象提供快捷菜单,请创建一个菜单处理程序对象,并返回其 IContextMenu 接口。
创建菜单处理程序对象的步骤与创建菜单处理程序 Shell 扩展的步骤非常相似。 有关详细信息,请参阅创建上下文菜单处理程序或 IContextMenu、IContextMenu2 或 IContextMenu3 参考资料。
IQueryInfo
Windows 资源管理器调用 IQueryInfo 接口来检索信息提示文本字符串。
IDataObject 和 IDropTarget
当 Windows 资源管理器显示对象时,文件夹对象无法直接知道用户何时试图剪切、复制或拖动对象。 相反,Windows 资源管理器会请求 IDataObject 接口。 要允许传输对象,请创建一个数据对象,并返回指向其 IDataObject 接口的指针。
同样,用户可能会尝试将数据对象放到 Windows 资源管理器中某个对象的表示形式上,如图标或地址栏路径。 然后,Windows 资源管理器会请求一个 IDropTarget 接口。 要允许丢弃数据对象,可创建一个公开 IDropTarget 接口的对象,并返回接口指针。
处理数据传输是编写命名空间扩展时比较棘手的问题之一。 有关详细讨论,请参阅使用拖放和剪贴板传输 Shell 对象。
使用默认 Shell 文件夹视图实现
使用默认 Shell 文件夹视图对象 (DefView) 的数据源必须实现这些接口:
(可选)它们还可以实现 IPersistFolder3。