How do I fetch the folder icon on Windows 7 using Shell32.SHGetFileInfo

17,472

Solution 1

You shouldn't specify null as yur first parameter to SHGeFileInfo. Use the path to a folder instead (please note that some folders have different (non-standard) icons). You could use the temp folder or your application's root folder for example.

Best practise would be to get the correct icon for each folder (in other words: Change the signature of GetFolderIcon to public static Icon GetFolderIcon(string folderPath, IconSize size, FolderType folderType) and call it for each folder you display).

There seems to be an open source library which already has a managed wrapper for fetching folder icons. Found on PInvoke.net (the entry for SHGetFileInfo):

However, this does not work if you want an icon of a drive or folder.

In that case, you can use the ExtendedFileInfo class provided by the ManagedWindowsApi project (http://mwinapi.sourceforge.net).

If you want to stick to a hand-crafted solution, this works for me (Win7 x64 RTM, .NET 3.5 SP1):

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace IconExtractor
{
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    public 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;
    };

    public enum FolderType
    {
        Closed,
        Open
    }

    public enum IconSize
    {
        Large,
        Small
    }

    public partial class Form1 : Form
    {
        [DllImport("shell32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, out SHFILEINFO psfi, uint cbFileInfo, uint uFlags);

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

        public const uint SHGFI_ICON = 0x000000100; 
        public const uint SHGFI_USEFILEATTRIBUTES = 0x000000010; 
        public const uint SHGFI_OPENICON = 0x000000002; 
        public const uint SHGFI_SMALLICON = 0x000000001; 
        public const uint SHGFI_LARGEICON = 0x000000000; 
        public const uint FILE_ATTRIBUTE_DIRECTORY = 0x00000010;

        public static Icon GetFolderIcon(IconSize size, FolderType folderType)
        {    
            // Need to add size check, although errors generated at present!    
            uint flags = SHGFI_ICON | SHGFI_USEFILEATTRIBUTES;    

            if (FolderType.Open == folderType)    
            {        
                flags += SHGFI_OPENICON;    
            }    
            if (IconSize.Small == size)    
            {       flags += SHGFI_SMALLICON;    
            }     
            else     
            {       
                flags += SHGFI_LARGEICON;    
            }    
            // Get the folder icon    
            var shfi = new SHFILEINFO();    

            var res = SHGetFileInfo(@"C:\Windows",                             
                FILE_ATTRIBUTE_DIRECTORY,                             
                out shfi,                             
                (uint) Marshal.SizeOf(shfi),                             
                flags );

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

            // Load the icon from an HICON handle  
            Icon.FromHandle(shfi.hIcon);    

            // Now clone the icon, so that it can be successfully stored in an ImageList
            var icon = (Icon)Icon.FromHandle(shfi.hIcon).Clone();    

            DestroyIcon( shfi.hIcon );        // Cleanup    

            return icon;}

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            try
            {

                Icon icon = GetFolderIcon(IconSize.Large, FolderType.Open);
                pictureBox1.Image = icon.ToBitmap();
                // Note: The image actually should be disposed somewhere
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
    }
}

Solution 2

You shouldn't specify null as yur first parameter to SHGeFileInfo.

That's right.

Use the path to a folder instead (please note that some folders have different (non-standard) icons). You could use the temp folder or your application's root folder for example.

It doesn't need to be a real (existing) folder path. Any non-empty string will do. e.g.:

SHGetFileInfo("AnyNonEmptyStringWillDo", FILE_ATTRIBUTE_DIRECTORY, sfi,
      SizeOf(sfi), SHGFI_USEFILEATTRIBUTES or SHGFI_SYSICONINDEX)
Share:
17,472
Raven
Author by

Raven

Been working on both .NET and Java and often have a diversity of roles in projects like technical lead, developer, sysadmin and more. Lately I've worked on a WPF application with a Java back end server running on Amazon EC2.

Updated on June 16, 2022

Comments

  • Raven
    Raven almost 2 years

    I have the following code which works on Windows XP and Vista - both 32 and 64 bit:

    public static Icon GetFolderIcon(IconSize size, FolderType folderType)
    {
        // Need to add size check, although errors generated at present!
        uint flags = Shell32.SHGFI_ICON | Shell32.SHGFI_USEFILEATTRIBUTES;
    
        if (FolderType.Open == folderType)
        {
            flags += Shell32.SHGFI_OPENICON;
        }
    
        if (IconSize.Small == size)
        {
           flags += Shell32.SHGFI_SMALLICON;
        } 
        else 
        {
           flags += Shell32.SHGFI_LARGEICON;
        }
    
        // Get the folder icon
        var shfi = new Shell32.SHFILEINFO();
        Shell32.SHGetFileInfo(  null, 
                                Shell32.FILE_ATTRIBUTE_DIRECTORY, 
                                ref shfi, 
                                (uint) Marshal.SizeOf(shfi), 
                                flags );
    
        Icon.FromHandle(shfi.hIcon);    // Load the icon from an HICON handle
    
        // Now clone the icon, so that it can be successfully stored in an ImageList
        var icon = (Icon)Icon.FromHandle(shfi.hIcon).Clone();
    
        User32Dll.DestroyIcon( shfi.hIcon );        // Cleanup
        return icon;
    }
    

    The constants are defined the following way:

    public const uint SHGFI_ICON = 0x000000100;
    public const uint SHGFI_USEFILEATTRIBUTES = 0x000000010;
    public const uint SHGFI_OPENICON = 0x000000002;
    public const uint SHGFI_SMALLICON = 0x000000001;
    public const uint SHGFI_LARGEICON = 0x000000000;
    public const uint FILE_ATTRIBUTE_DIRECTORY = 0x00000010;
    

    This gives the following results in windows 7 when fetching the folder icon:

    alt text

    While at Vista - using the same method result in the following folder icon:

    alt text

    I would like the "correct" Windows folder icon for Windows 7 also - not the icon used to indicate the drive where Windows is installed.

    I don't know the win32 API and my non-managed programming is next to none on the Windows platform.