Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Given a Windows Vista or newer IShellItem, how do i get the system image list icon index associated with that item?

For example (pseudo-code):

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


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

Background

In the olden days (Windows 95 and newer), we could ask the shell to give us the system imagelist index for an item's icon. We did it using 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;
    }
}

That works when you are using an item in the shell namespace that corresponds to a file. But the shell supports things besides files and folders in the filesystem.

Icon Index from IShellFolder

The general solution for getting information about an object in the shell namespace comes from using the way you represent an item in the shell namespace:

  • IShellFolder: the folder that the thing sits in, along with
  • child PIDL: the id of the thing in that folder

From that there are 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.
}

But IShellFolder is out; we're using IShellItem now

Starting with Windows Vista, IShellItem became 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.

The question becomes: How to do stuff with it? In particular, how do i get the image index in the system image list of the item? Looking at its methods, there isn't even a way to get its absolute pidl:

  • BindToHandler: Binds to a handler for an item as specified by the handler ID value (BHID).
  • Compare: Compares two IShellItem objects.
  • GetAttributes: Gets a requested set of attributes of the IShellItem object.
  • GetDisplayName: Gets the display name of the IShellItem object.
  • GetParent: Gets the parent of an IShellItem object.

I was hoping that the Windows Property System, accessible through IShellItem2, would have a property associated with the shell imagelist icon index. Unfortunately, i don't see any:

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

Ways of extracting icons

There are many standard ways of getting the picture that goes with a thing in the Windows Shell Namespace:

  • IExtractIcon. Returns an HICON. Requires IShellFolder+pidl. If it fails you can use SHDefExtractIcon
  • SHDefExtractIcon. Returns an HICON. Requires full path to icon file
  • IThumbnailCache. Requires IShellItem. Returns thumbnail, not icon
  • IShellImageFactory. Gets a bitmap that represents an IShellItem
  • IThumbnailProvider. Windows Vista replacement for IExtractImage
  • IExtractImage. Requires IShellFolder+pidl.
  • SHGetFileInfo. Requires full file path, or an absolute pidl

None of them:

  • take an IShellItem
  • return an index
like image 725
Ian Boyd Avatar asked Feb 19 '16 04:02

Ian Boyd


2 Answers

Basically it doesn't seem that there's any easy method for this. It simply hasn't been provided in the API.

In your question you say "But the shell supports things besides files and folders in the filesystem.", which makes me think you have overlooked that SHGetFileInfo does actually support using PIDLs directly (with the SHGFI_PIDL flag) - so it can be used on non-filesystem objects. If you still have the full PIDL this is the easiest way to get an icon index, otherwise something like this should hopefully work:

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;
}

Or using Raymond Chen's suggestion:

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;
}

Turns out that IShellFolder doesn't support the IShellIcon sometimes. For example attempting to browse inside a zip file. When that happens, the QueryInterface of IShellFolder for IShellIcon fails.

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

Yet SHGetFileInfo knows how to handle it.

So best to not try to get an IShellIcon interface yourself. Leave the heavy lifting to SHGetFileInfo (at least until someone from Microsoft documents how to use IShellIcon).

like image 177
Jonathan Potter Avatar answered Oct 12 '22 14:10

Jonathan Potter


In your great investigation you forget about IShellIcon interface. It available even in 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;
like image 33
Denis Anisimov Avatar answered Oct 12 '22 12:10

Denis Anisimov