1

Using the Win32 API to retrieve file/folder icons in C# is a trivial process that has been documented countless times, going all the way back to the beginning of .Net.

Every documented solution is just a variation on the same approach: P/Invoking SHGetFileInfo(), passing the appropriate attributes/flags.

I am developing on Windows 10, which I assume uses Shell32.dll v6.1 -- the same version that shipped with Windows 7, and the last one released according to Microsoft's documentation. The version displayed in the Details tab of the Properties window in Explorer on my development system for C:\Windows\system32\shell32.dll is "10.0.18362.719".

My objective is to use this approach to generate a "generic" FOLDER icon. I need it to be SMALL (vs large) and in the OPEN (vs closed) state.

What I have noticed is that if one requests a small generic folder icon from SHGetFileInfo(), the result always appears in the "closed" state.

Conversely, if one requests a large generic folder icon from SHGetFileInfo(), the result always appears in the "open" state.

The following code represents the simplest possible demonstration of this. For a large icon, change the SmallIcon flag to LargeIcon. For a closed icon, remove the OpenIcon flag.

public static Icon GetMyIcon()
{
    var flags = (uint)(ShellAttribute.Icon | ShellAttribute.SmallIcon | ShellAttribute.OpenIcon);

    var fileInfo = new ShellFileInfo();
    var size = (uint)Marshal.SizeOf(fileInfo);
    var result = Interop.SHGetFileInfo(string.Empty, (uint)FileAttribute.Directory, out fileInfo, size, flags);

    if (result == IntPtr.Zero)
        throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());

    try
    {
        return (Icon)Icon.FromHandle(fileInfo.hIcon).Clone();
    }
    finally
    {
        Interop.DestroyIcon(fileInfo.hIcon);
    }
}

With Interop methods declared as follows:

public static class Interop
{
    [DllImport("shell32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr SHGetFileInfo(string path,
        uint attributes,
        out ShellFileInfo fileInfo,
        uint size,
        uint flags);

    [DllImport("user32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool DestroyIcon(IntPtr pointer);
}

Is there some kind of black magic required to obtain a generic folder icon that is either small & open, or large & closed?

I'm also able to use the Windows API Code Pack in my project, so if it is possible to bypass the Win32 API and achieve my desired result using this library alone, this would satisfy.

UPDATE: At Ian H's suggestion, I tried using the SHGetStockIconInfo API, but the result was unfortunately the same:

private static Icon GetStockIcon()
{
    SHSTOCKICONINFO sii = new SHSTOCKICONINFO();
    sii.cbSize = (UInt32)Marshal.SizeOf(typeof(SHSTOCKICONINFO));

    Marshal.ThrowExceptionForHR(SHGetStockIconInfo(SHSTOCKICONID.SIID_FOLDEROPEN,
            SHGSI.SHGSI_ICON | SHGSI.SHGSI_SMALLICON,
            ref sii));
    return Icon.FromHandle(sii.hIcon);
}
Pancake
  • 739
  • 1
  • 5
  • 20
  • 2
    Have you looked into the [SHGetStockIconInfo](https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shgetstockiconinfo) function? Seems like it's just what you need. The `ssid` can take values of, for example, `SIID_FOLDER` and `SIID_FOLDEROPEN`. – Ian H. Apr 11 '20 at 20:14
  • Thanks @IanH. for the suggestion -- I had not tried it, but I just did and unfortunately the result is the same. – Pancake Apr 11 '20 at 20:31
  • @IanH.but I will say that approach is more elegant. I wasn't aware of that API. Pity. – Pancake Apr 11 '20 at 20:43
  • Any chance that the icon you're retrieving has multiple pictures inside it and you always happen to use one you don't want? – Caius Jard Apr 11 '20 at 20:56
  • @CaiusJard perhaps -- but it's not clear to me how to access the other items or anticipate which one will be correct – Pancake Apr 11 '20 at 20:58
  • 1
    Have you seen the shell itself display an open small folder or a closed large one? – Simon Mourier Apr 11 '20 at 21:48
  • @SimonMourier - no, but I don't know of a place where I'm "supposed" to see either. In the Explorer navigation pane (which uses small icons), icons on all nodes (including folders) remain the same, regardless of whether the node is expanded. This is where I MIGHT expect to see an open small icon .. but in this environment at least, they always appear closed. – Pancake Apr 11 '20 at 22:16
  • Absolutely, that's my point. You can open shell32.dll and imageres.dll with a tool capable of displaying icons and see if there's anything you like (note icon index in there are not supposed to be contractual), but I don't think you'll find what you're looking for. – Simon Mourier Apr 12 '20 at 06:34
  • @SimonMourier, this sounds like a promising way to get some clarification on this. Can you recommend a suitable tool? Also, on your note that "icon index in there are not supposed to be contractual", are you saying I can't rely on the ordering of the icons within the DLL? Just want to make sure I understand. – Pancake Apr 12 '20 at 07:07
  • Yep, icon indices *could* change (in practice I don't think it happens a lot but I may be wrong), so API is better, see https://stackoverflow.com/questions/31975302/can-i-trust-imageres-dll-system-icons-indexes?rq=1 PS: I personally use Axialis IconWorkshop for years – Simon Mourier Apr 12 '20 at 07:40
  • 1
    @Pancake [`SHGFI_OPENICON | SHGFI_ICON`](https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shgetfileinfoa) and `SHGFI_LARGEICON | SHGFI_ICON` get 32*32 icon. `SHGFI_SMALLICON | SHGFI_ICON` get 16*16 icon. What [icon size](https://learn.microsoft.com/en-us/windows/win32/menurc/about-icons#icon-sizes) do you want to get? – Rita Han Apr 14 '20 at 09:56
  • @RitaHan-MSFT my requirement is a small (16x16) icon with "open" appearance. – Pancake Apr 16 '20 at 01:57
  • @Pancake Does the answer work for you? – Rita Han Apr 23 '20 at 09:57

1 Answers1

0

The system open icon size is 32*32 which you can get using GetObject.

my requirement is a small (16x16) icon with "open" appearance.

The following is an example in C++ using IShellItemImageFactory for creating size 16*16 BITMAP based on system open icon appearance you can refer to as a workaround.

        SHFILEINFOW sfi = { 0 };
        ICONINFO iconinfo;
        HBITMAP hBitmap;
        IShellItemImageFactory *pImageFactory;
        SIZE size = { 16, 16 };

        CoInitialize(NULL);

        hr = SHGetFileInfo(L"D:\\Test",
            -1,
            &sfi,
            sizeof(sfi),
            SHGFI_SYSICONINDEX | SHGFI_OPENICON | SHGFI_ICON);

        if (FAILED(hr))
        {
            OutputDebugString(L"SHGetFileInfo fails.\n");
        }

        GetIconInfo(sfi.hIcon, &iconinfo);
        hBitmap = iconinfo.hbmColor;

        BITMAP bmp;
        ZeroMemory(&bmp, sizeof(bmp));

        if (iconinfo.hbmColor)
        {
            // Get initial size is 32*32
            int nWrittenBytes = GetObject(iconinfo.hbmColor, sizeof(bmp), &bmp);

            hr = SHCreateItemFromParsingName(L"D:\\Test", NULL, IID_PPV_ARGS(&pImageFactory));
            if (SUCCEEDED(hr))
            {
                // Get expected size 16*16
                hr = pImageFactory->GetImage(size, SIIGBF_BIGGERSIZEOK, &hBitmap);
                if (FAILED(hr))
                {
                    OutputDebugString(L"IShellItemImageFactory::GetImage failed with error code %x");
                }
                pImageFactory->Release();
            }

            // Confirm if the size is 16*16
            nWrittenBytes = GetObject(hBitmap, sizeof(bmp), &bmp);
        }

The small open icon will show like this:

enter image description here

Rita Han
  • 9,574
  • 1
  • 11
  • 24