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

9

给定一个 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为项目的图标提供系统imagelist索引。我们使用SHGetFileInfo实现。SHGet­File­Info函数通过询问shell命名空间获取系统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还支持文件系统以外的其他内容。

来自IShellFolder的图标索引

获取shell名称空间中对象信息的通用解决方案来自于使用在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的首选API。在Windows 95时代,必须保留一个IShellFolder+pidl组合 非常繁琐且容易出错

问题来了:如何处理它?特别是,如何获取该项在系统图像列表中的图像索引?查看其方法,甚至没有一种方法可以获取其绝对pidl:

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

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

提取图标的方法

在Windows Shell命名空间中,有许多标准方法可以获取与“thing”相关联的图片:

没有一个:

  • 使用 IShellItem
  • 返回索引

2
使用IParentAndItem将IShellItem分解为IShellFolder和pidl。然后使用IShellIcon :: GetIconOf。 - Raymond Chen
@RaymondChen 当ShellFolder是zip shell扩展时,在IShellFolder上调用QueryInterface(IID_ShellIcon)会失败并返回E_NOINTERFACE。同时,使用由SHGetIDListFromObject返回的绝对pidl的SHGetFileInfo确实能够返回图标索引。直接访问IShellIcon是否不好,最好依赖于SHGetFileInfo来处理所有这些边缘情况的繁重工作? - Ian Boyd
1
SHGetFileInfo使用IExtractIcon,这比IShellIcon效率低(但是如果IShellIcon不可用,你别无选择)。 - Raymond Chen
2个回答

5
基本上,这似乎没有任何简单的方法。API中没有提供这个功能。
在你的问题中,你说:“但是shell支持文件系统以外的其他东西。”这让我想到你可能忽略了SHGetFileInfo实际上支持直接使用PIDLs(使用SHGFI_PIDL标志)-因此它可以用于非文件系统对象。如果你仍然有完整的PIDL,则获取图标索引的最简单方法是使用SHGetFileInfo。否则,像下面这样做应该能够工作:
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文件时。当发生这种情况时,IShellFolderQueryInterface请求IShellIcon会失败。
shellFolder.QueryInterface(IID_IShellIcon, out shellIcon); //<--fails with E_NOINTERFACE

然而,SHGetFileInfo 知道如何处理它。

因此最好不要尝试自己获取 IShellIcon 接口。把繁重的工作留给 SHGetFileInfo(至少在微软有人记录如何使用 IShellIcon 之前是这样的)。


2

在您的深入调查中,您忽略了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;

1
我会把它交给Jonathan,因为他有我需要的缺失部分:SHGetIDListFromObject。但是看起来一旦我有了IShellFolder和子级PIDL,直接使用IShellIcon可能是稍微更好的方式。而且你还提供了正确且可运行的IShellIcon示例用法。 - Ian Boyd

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接