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

How to get system image list icon index of an IShellItem?

给定 Windows Vista 或更高版本 IShellItem,我如何获取与该项目关联的系统图像列表图标索引?

例如(伪码):

IShellItem networkFolder = SHGetKnownFolderItem(FOLDERID_NetworkFolder, 0, 0, IShellItem);
Int32 iconIndex = GetSmallSysIconIndex(networkFolder);


Int32 GetSmallSysIconIndex(IShellItem item)
{
   //TODO: Ask Whosebug
}

背景

在过去(Windows 95 和更高版本),我们可以要求 shell 为我们提供项目图标的系统图像列表索引。我们使用 SHGetFileInfo. The SHGet­File­Info function gets the icon by asking the shell namespace for the icon index in the system imagelist:

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 支持文件系统中除文件和文件夹之外的东西。

来自 IShell文件夹的图标索引

获取有关 shell 命名空间中对象信息的一般解决方案来自使用您在 shell 命名空间中表示 item 的方式:

由此有ways to get the system image list index:

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

但是我Shell文件夹不在了;我们现在使用 IShellItem

从 Windows Vista 开始,IShellItem 变成了 the preferred API for navigating the shell. The Windows 95 era API of having to keep an IShellFolder+pidl pair around was cumbersome, and error prone

问题变成了:如何处理它?特别是,如何获取项目的系统图像列表中的图像索引?查看它的方法,甚至没有办法获得它的绝对 pidl:

我希望 Windows Property System, accessible through IShellItem2 有一个与 shell 图像列表图标索引关联的 属性。不幸的是,我没有看到:

提取图标的方法

在 Windows Shell 命名空间中 are many standard ways 获取与 thing 相关的图片:

None 其中:

基本上似乎没有什么简单的方法可以做到这一点。它根本没有在 API.

中提供

在你的问题中你说 "But the shell supports things besides files and folders in the filesystem.",这让我觉得你忽略了 SHGetFileInfo 实际上支持直接使用 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;
}

或使用 Raymond Chen 的建议:

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 文件。发生这种情况时,IShellFolderQueryInterface for IShellIcon 会失败。

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

然而SHGetFileInfo知道如何处理它。

所以最好不要尝试自己获取 IShellIcon 接口。将繁重的工作留给 SHGetFileInfo(至少在 Microsoft 的某人记录如何使用 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;