12

I have a listView with a list of documents. To each of them I assigned an icon, using the following method:

private void SetDocumentIcon(ListViewItem item, FileInfo file)
{
    Icon iconForFile = Icon.ExtractAssociatedIcon(file.FullName);

    if (!documentsIconsImageList.Images.ContainsKey(file.Extension))
    {
        iconForFile = Icon.ExtractAssociatedIcon(file.FullName);
        documentsIconsImageList.Images.Add(file.Extension, iconForFile);
    }

    item.ImageKey = file.Extension;
}

I tried to use this method for a folder, but it fails. The problem, as far as I understand, is that Icon.ExtractAssociatedIcon is for files and not folders. So how can I extract the icon of a folder?

Thanks.

Michael Haddad
  • 4,085
  • 7
  • 42
  • 82
  • There is winapi function SHGetStockIconInfo for that, but it seems to be complicated to pinvoke it. If I were you - I would create dummy empty folder somewhere, extract icon using that answer I provided as duplicate (that will be default one), then delete folder. You of course need to do this only once on application startup. – Evk Mar 21 '17 at 16:11
  • @Evk `SHGetStockIconInfo` isn't complicated at all. – Herohtar Dec 01 '19 at 20:23

3 Answers3

15

SHGetStockIconInfo is the correct way to do it, and doesn't require the addition of unnecessary file IO. It's not any more complicated than SHGetFileInfo.

Here is an example class structured in a similar way to Evk's class. Some important things to note:

  1. When you get an icon handle from SHGetStockIconInfo (or even SHGetFileInfo, for that matter), the native icon must be cleaned up by calling DestroyIcon(), otherwise you'll create a resource leak.
  2. When you create an icon using Icon.FromHandle(), the object stores the handle you gave it and will use it for later operations. This means if you immediately call DestroyIcon() and then try to do something with the icon you just created, it will cause exceptions. You can avoid this by using Clone() to get an Icon that doesn't rely on your original native handle.
public static class DefaultIcons
{
    private static Icon folderIcon;

    public static Icon FolderLarge => folderIcon ?? (folderIcon = GetStockIcon(SHSIID_FOLDER, SHGSI_LARGEICON));

    private static Icon GetStockIcon(uint type, uint size)
    {
        var info = new SHSTOCKICONINFO();
        info.cbSize = (uint)Marshal.SizeOf(info);

        SHGetStockIconInfo(type, SHGSI_ICON | size, ref info);

        var icon = (Icon)Icon.FromHandle(info.hIcon).Clone(); // Get a copy that doesn't use the original handle
        DestroyIcon(info.hIcon); // Clean up native icon to prevent resource leak

        return icon;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct SHSTOCKICONINFO
    {
        public uint cbSize;
        public IntPtr hIcon;
        public int iSysIconIndex;
        public int iIcon;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string szPath;
    }

    [DllImport("shell32.dll")]
    public static extern int SHGetStockIconInfo(uint siid, uint uFlags, ref SHSTOCKICONINFO psii);

    [DllImport("user32.dll")]
    public static extern bool DestroyIcon(IntPtr handle);

    private const uint SHSIID_FOLDER = 0x3;
    private const uint SHGSI_ICON = 0x100;
    private const uint SHGSI_LARGEICON = 0x0;
    private const uint SHGSI_SMALLICON = 0x1;
}
lincolnk
  • 11,218
  • 4
  • 40
  • 61
Herohtar
  • 5,347
  • 4
  • 31
  • 41
  • 3
    a full list of available icon IDs is available here: https://www.pinvoke.net/default.aspx/Enums/SHSTOCKICONID.html – lincolnk May 06 '20 at 18:52
8

I bet there are other ways, but I think easiest to implement is just use SHGetFileInfo win api function over temp folder you create. Example code:

public static class DefaultIcons
{
    private static readonly Lazy<Icon> _lazyFolderIcon = new Lazy<Icon>(FetchIcon, true);

    public static Icon FolderLarge
    {
        get { return _lazyFolderIcon.Value; }
    }

    private static Icon FetchIcon()
    {
        var tmpDir = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())).FullName;
        var icon = ExtractFromPath(tmpDir);
        Directory.Delete(tmpDir);
        return icon;
    }

    private static Icon ExtractFromPath(string path)
    {
        SHFILEINFO shinfo = new SHFILEINFO();
        SHGetFileInfo(
            path,
            0, ref shinfo, (uint)Marshal.SizeOf(shinfo),
            SHGFI_ICON | SHGFI_LARGEICON);
        return System.Drawing.Icon.FromHandle(shinfo.hIcon);
    }

    //Struct used by SHGetFileInfo function
    [StructLayout(LayoutKind.Sequential)]
    private struct SHFILEINFO
    {
        public IntPtr hIcon;
        public int iIcon;
        public uint dwAttributes;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string szDisplayName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
        public string szTypeName;
    };

    [DllImport("shell32.dll")]
    private static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags);

    private const uint SHGFI_ICON = 0x100;
    private const uint SHGFI_LARGEICON = 0x0;
    private const uint SHGFI_SMALLICON = 0x000000001;
}

Usage is just

var icon = DefaultIcons.FolderLarge

It's trivial to add property for small icon too.

Evk
  • 98,527
  • 8
  • 141
  • 191
  • Be careful, you need to delete the icon handle that is returned from `SHGetFileInfo` after you use it to create the `System.Drawing.Icon`. – Herohtar Dec 01 '19 at 07:59
  • You may also want to subsribe on system's icon change event. Whatever it may be ^^ – AgentFire Dec 02 '19 at 21:18
0

public static class DefaultIcons { private static readonly Lazy _lazyFolderIcon = new Lazy(FetchIcon, true);

public static Icon FolderLarge
{
    get { return _lazyFolderIcon.Value; }
}

private static Icon FetchIcon()
{
    var tmpDir = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())).FullName;
    var icon = ExtractFromPath(tmpDir);
    Directory.Delete(tmpDir);
    return icon;
}

private static Icon ExtractFromPath(string path)
{
    SHFILEINFO shinfo = new SHFILEINFO();
    SHGetFileInfo(
        path,
        0, ref shinfo, (uint)Marshal.SizeOf(shinfo),
        SHGFI_ICON | SHGFI_LARGEICON);
    return System.Drawing.Icon.FromHandle(shinfo.hIcon);
}

//Struct used by SHGetFileInfo function
[StructLayout(LayoutKind.Sequential)]
private struct SHFILEINFO
{
    public IntPtr hIcon;
    public int iIcon;
    public uint dwAttributes;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
    public string szDisplayName;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
    public string szTypeName;
};

[DllImport("shell32.dll")]
private static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags);

private const uint SHGFI_ICON = 0x100;
private const uint SHGFI_LARGEICON = 0x0;
private const uint SHGFI_SMALLICON = 0x000000001;

}

  • 2
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Apr 25 '22 at 17:13