如何获取IShellItem的系统映像列表图标索引?

鉴于Windows Vista或更新的IShellItem ,我如何获得与该项目相关联的系统映像列表图标索引?

例如(伪代码)

 IShellItem networkFolder = SHGetKnownFolderItem(FOLDERID_NetworkFolder, 0, 0, IShellItem); Int32 iconIndex = GetSmallSysIconIndex(networkFolder); Int32 GetSmallSysIconIndex(IShellItem item) { //TODO: Ask Stackoverflow } 

背景

在过去的时代(Windows 95和更新版本),我们可以让shell给我们一个项目的图标的系统图像列表索引。 我们使用SHGetFileInfo 。 SHGetFileInfo函数通过询问shell命名空间获取系统图像列表中的图标索引来获取图标 :

 HICON GetIconIndex(PCTSTR pszFile) { SHFILEINFO sfi; HIMAGELIST himl = SHGetFileInfo(pszFile, 0, &sfi, sizeof(sfi), SHGFI_SYSICONINDEX)); if (himl) { return sfi.iIcon; } else { return -1; } } 

当在shell命名空间中使用与文件相对应的项目时,这将起作用。 但是shell支持除了文件系统中的文件和文件夹之外的东西。

来自IShellFolder的图标索引

获取shell命名空间中的对象信息的一般解决scheme来自于使用您在shell命名空间中表示项目的方式:

  • IShellFolder东西所在的文件夹,以及
  • PIDL :该文件夹中的东西的ID

从这里可以得到系统映像列表索引 :

 Int32 GetIconIndex(IShellFolder folder, PITEMID_CHILD childPidl) { //Note: I actually have no idea how to do this. } 

但是IShellFolder出来了; 我们现在正在使用IShellItem

从Windows Vista开始, IShellItem成为用于导航shell 的首选AP我。 不得不保持一个IShellFolder + pidl对的Windows 95时代的API 是麻烦的,并且容易出错 。

问题就变成了:如何用它做东西? 特别是,我如何获得项目的系统图像列表中的图像索引? 看看它的方法,甚至没有办法得到它的绝对pidl:

  • BindToHandler :绑定到由处理程序ID值(BHID)指定的项目的处理程序。
  • 比较 :比较两个IShellItem对象。
  • GetAttributes :获取一组请求的IShellItem对象的属性。
  • GetDisplayName :获取IShellItem对象的显示名称。
  • GetParent :获取一个IShellItem对象的父对象。

我希望可以通过IShellItem2访问的Windows属性系统将有一个与shell imagelist图标索引关联的属性。 不幸的是,我没有看到任何:

  • System.DescriptionID
  • System.InfoTipText
  • System.InternalName
  • System.Link.TargetSFGAOFlagsStrings
  • System.Link.TargetUrl
  • System.NamespaceCLSID
  • System.Shell.SFGAOFlagsStrings

提取图标的方法

在Windows Shell命名空间中有许多标准的获取图片的方法 :

  • IExtractIcon 。 返回一个HICON 。 需要IShellFolder + pidl。 如果失败,可以使用SHDefExtractIcon
  • SHDefExtractIcon 。 返回一个HICON 。 需要图标文件的完整path
  • IThumbnailCache 。 需要IShellItem 。 返回缩略图,而不是图标
  • IShellImageFactory 。 获取一个表示一个IShellItem的位图
  • IThumbnailProvider 。 Windows Vistareplace为IExtractImage
  • IExtractImage 。 需要IShellFolder + pidl
  • SHGetFileInfo 。 需要完整的文件path,或绝对pidl

他们都不是:

  • 采取一个IShellItem
  • 返回一个索引

基本上,这似乎没有任何简单的方法。 它只是没有在API中提供。

在你的问题中,你会说“但是shell支持除文件系统中的文件和文件夹之外的东西。” ,这让我觉得你忽略了SHGetFileInfo实际上直接支持使用SHGFI_PIDL (带有SHGFI_PIDL标志) – 所以它可以在非文件系统对象上使用。 如果你仍然有完整的PIDL,这是得到一个图标索引的最简单的方法,否则像这样的事情应该有希望的工作:

 int GetIShellItemSysIconIndex(IShellItem* psi) { PIDLIST_ABSOLUTE pidl; int iIndex = -1; if (SUCCEEDED(SHGetIDListFromObject(psi, &pidl))) { SHFILEINFO sfi{}; if (SHGetFileInfo(reinterpret_cast<LPCTSTR>(pidl), 0, &sfi, sizeof(sfi), SHGFI_PIDL | SHGFI_SYSICONINDEX)) iIndex = sfi.iIcon; CoTaskMemFree(pidl); } return iIndex; } 

或者使用雷蒙德的建议:

 int GetIconIndex(IShellItem item) { Int32 imageIndex; PIDLIST_ABSOLUTE parent; IShellFolder folder; PITEMID_CHILD child; //Use IParentAndItem to have the ShellItem //cough up the IShellObject and child pidl it is wrapping. (item as IParentAndItem).GetParentAndItem(out parent, out folder, out child); try { //Now use IShellIcon to get the icon index from the folder and child (folder as IShellIcon).GetIconOf(child, GIL_FORSHELL, out imageIndex); } finally { CoTaskMemFree(parent); CoTaskMemFree(child); } return imageIndex; } 

原来, IShellFolder有时不支持IShellIcon 。 例如试图浏览一个zip文件。 发生这种情况时, IShellIconIShellFolderQueryInterface失败。

 shellFolder.QueryInterface(IID_IShellIcon, out shellIcon); //<--fails with E_NOINTERFACE 

然而SHGetFileInfo知道如何处理它。

所以最好不要尝试自己获得一个IShellIcon接口。 将重任留给SHGetFileInfo (至少直到微软的某个人文档如何使用IShellIcon )。

在你的伟大的调查中,你忘记了IShellIcon接口。 它甚至可以在Windows XP中使用。

 function GetIconIndex(AFolder: IShellFolder; AChild: PItemIDList): Integer; overload; var ShellIcon: IShellIcon; R: HRESULT; begin OleCheck(AFolder.QueryInterface(IShellIcon, ShellIcon)); try R := ShellIcon.GetIconOf(AChild, 0, Result); case R of S_OK:; S_FALSE: Result := -1; // icon can not be obtained for this object else OleCheck(R); end; finally ShellIcon := nil; end; end;