Jan Böhm
Jan Böhm

Reputation: 109

C# / WPF: Get icons used by shell

I'm trying to develop an application that can show you the content of directories like the Windows Explorer (with file name and icon). I'm currently using this code:

public class IconManager
{
    public static ImageSource GetIcon(string path, bool smallIcon, bool isDirectory)
    {
        uint flags = SHGFI_ICON | SHGFI_USEFILEATTRIBUTES;
        if (smallIcon)
            flags |= SHGFI_SMALLICON;

        uint attributes = FILE_ATTRIBUTE_NORMAL;
        if (isDirectory)
            attributes |= FILE_ATTRIBUTE_DIRECTORY;

        SHFILEINFO shfi;
        if (0 != SHGetFileInfo(path, attributes, out shfi, (uint)Marshal.SizeOf(typeof(SHFILEINFO)), flags))
        {
            return System.Windows.Interop.Imaging.CreateBitmapSourceFromHIcon(shfi.hIcon, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
        }
        return null;
    }

    [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")]
    private static extern int SHGetFileInfo(string pszPath, uint dwFileAttributes, out SHFILEINFO psfi, uint cbFileInfo, uint flags);

    private const uint FILE_ATTRIBUTE_READONLY = 0x00000001;
    private const uint FILE_ATTRIBUTE_HIDDEN = 0x00000002;
    private const uint FILE_ATTRIBUTE_SYSTEM = 0x00000004;
    private const uint FILE_ATTRIBUTE_DIRECTORY = 0x00000010;
    private const uint FILE_ATTRIBUTE_ARCHIVE = 0x00000020;
    private const uint FILE_ATTRIBUTE_DEVICE = 0x00000040;
    private const uint FILE_ATTRIBUTE_NORMAL = 0x00000080;
    private const uint FILE_ATTRIBUTE_TEMPORARY = 0x00000100;
    private const uint FILE_ATTRIBUTE_SPARSE_FILE = 0x00000200;
    private const uint FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400;
    private const uint FILE_ATTRIBUTE_COMPRESSED = 0x00000800;
    private const uint FILE_ATTRIBUTE_OFFLINE = 0x00001000;
    private const uint FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000;
    private const uint FILE_ATTRIBUTE_ENCRYPTED = 0x00004000;
    private const uint FILE_ATTRIBUTE_VIRTUAL = 0x00010000;

    private const uint SHGFI_ICON = 0x000000100;
    private const uint SHGFI_DISPLAYNAME = 0x000000200;
    private const uint SHGFI_TYPENAME = 0x000000400;
    private const uint SHGFI_ATTRIBUTES = 0x000000800;
    private const uint SHGFI_ICONLOCATION = 0x000001000;
    private const uint SHGFI_EXETYPE = 0x000002000;
    private const uint SHGFI_SYSICONINDEX = 0x000004000;
    private const uint SHGFI_LINKOVERLAY = 0x000008000;
    private const uint SHGFI_SELECTED = 0x000010000;
    private const uint SHGFI_ATTR_SPECIFIED = 0x000020000;
    private const uint SHGFI_LARGEICON = 0x000000000;
    private const uint SHGFI_SMALLICON = 0x000000001;
    private const uint SHGFI_OPENICON = 0x000000002;
    private const uint SHGFI_SHELLICONSIZE = 0x000000004;
    private const uint SHGFI_PIDL = 0x000000008;
    private const uint SHGFI_USEFILEATTRIBUTES = 0x000000010;
}

I use this code to get the files and directories with their icons:

foreach (string d in Directory.GetDirectories(Path))
        {
            string name = "";
            foreach (string p in d.Split('\\'))
                name = p;
            ImageSource icon = IconManager.GetIcon(d + "\\", false, true);
            colDesktopItems.Add(new DesktopItem() { ItemName = name, ItemPath = d });
        }
        foreach (string f in Directory.GetFiles(Path))
        {
            if (File.GetAttributes(f) != FileAttributes.Hidden && File.GetAttributes(f) != FileAttributes.System)
            {
                string name = "";
                foreach (string p in f.Split('\\'))
                    if (p.Contains("."))
                        name = p;
                ImageSource icon = IconManager.GetIcon(f, false, false);
                if (name != "desktop.ini" && name != "Thumbs.db")
                    colDesktopItems.Add(new DesktopItem() { ItemName = name.Split('.')[0], ItemPath = f, ItemIcon = icon });
            }
        }

I also use a DispatcherTimer for automatic refresh (interval = 5 seconds):

private void dpt_Tick(object sender, EventArgs e)
    {
        if (dPath != null)
        {
            if (GetContent(dPath).ToString() != diCollection.ToString())
            {
                diCollection.Clear();
                foreach (DesktopItem i in GetContent(dPath))
                    diCollection.Add(i);
            }
        }
    }

diCollection is the collection that contains the elements to display and dPath is the path of the directory from which the files will be displayed.

But there are two problems:

The exception happens in this line:

return System.Windows.Interop.Imaging.CreateBitmapSourceFromHIcon(shfi.hIcon, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());

Upvotes: 1

Views: 2409

Answers (1)

Trae Moore
Trae Moore

Reputation: 1787

i use the same methodology. Here is how i have mine set up in my personal library.

public static class ImageUtilities
{
    public static System.Drawing.Icon GetRegisteredIcon(string filePath)
    {
        var shinfo = new SHfileInfo();
        Win32.SHGetFileInfo(filePath, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), Win32.SHGFI_ICON | Win32.SHGFI_SMALLICON);
        return System.Drawing.Icon.FromHandle(shinfo.hIcon);
    }
}

[StructLayout(LayoutKind.Sequential)]
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;
}


internal sealed class Win32
{
    public const uint SHGFI_ICON = 0x100;
    public const uint SHGFI_LARGEICON = 0x0; // large
    public const uint SHGFI_SMALLICON = 0x1; // small

    [System.Runtime.InteropServices.DllImport("shell32.dll")]
    public static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHfileInfo psfi, uint cbSizeFileInfo, uint uFlags);
}

Usage:

var icon = ImageUtilites.GetRegisteredIcon(string path)

the Extension method i use to make create an ImageSource for WPF:

public static System.Windows.Media.ImageSource ToImageSource(this System.Drawing.Bitmap bitmap, int width, int height)
{
    var hBitmap = bitmap.GetHbitmap();

    System.Windows.Media.ImageSource wpfBitmap = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
        hBitmap,
        IntPtr.Zero,
        System.Windows.Int32Rect.Empty,
        System.Windows.Media.Imaging.BitmapSizeOptions.FromWidthAndHeight(width, height));

    if (!DeleteObject(hBitmap))
    {
        throw new System.ComponentModel.Win32Exception();
    }

    return wpfBitmap;
}

I forgot to add this, I don't know if you are utilizing it..

 [DllImport("gdi32.dll", SetLastError = true)]
 private static extern bool DeleteObject(IntPtr hObject);

Upvotes: 1

Related Questions