Friday, January 18, 2013

Displaying System Icon in C#, WPF

How can we display the file system image in our Image object?

In C#, some system API is needed before we can manage to manipulate with the system objects. In some cases, we are doing the technique of Interoperability to do the inter-operation manipulation outside the application.

This blog will show you one technique how to use system APIs with the Shell functionality (file system manipulation) implementation.

Shelling is the process and technique of file system manipulation. Before we can do this, we need to import some basic functions from system API. Our target is to display the system icon base on the full path or extension of the file. Let's start with the system APIs.

Shell System API (functions and variables)

[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)]
public 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 const uint FILE_ATTRIBUTE_FILE = 0x00000100;

The DllImport is an attribute used to import the system library into our application. For more information regarding DllImport, please visit Microsoft documentation. We used the SHGetFileInfo and DestroyIcon system function because the target of this blog is to only display the system icons (by file path and extension).

SHGetFileInfo: is the system function used to retrieve the information of file/folder from the file system (Windows operating system).

DestroyIcon: is the system function used to destroy the icon.

Note: For you to follow our implementation, please place the codes above in one static class named Interop.

After declaring the system functions/constants above, you need to create additional struct and named it SHFileInfo. Please see the code below.

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

The struct above is the one who will be used by the SHGetFileInfo system function and will be the object who will handle all the information of the system file attribute.

After declaring the struct, we need to call the SHGetFileInfo system function passing the right attribute and the struct above so we can get the information of the system file. We have to create our own function to get the proper icon from the file system (with the the help of the system variables/functions above). Please see below our codes.

public class ShellManager
{
    public static Icon GetIcon(string path, ItemType type, IconSize size, ItemState state)
    {
        var flags = (uint)(Interop.SHGFI_ICON | Interop.SHGFI_USEFILEATTRIBUTES);
        var attribute = (uint)(object.Equals(type, ItemType.Folder) ? Interop.FILE_ATTRIBUTE_DIRECTORY : Interop.FILE_ATTRIBUTE_FILE);
        if (object.Equals(type, ItemType.Folder) && object.Equals(state, ItemState.Open))
        {
            flags += Interop.SHGFI_OPENICON;
        }
        if (object.Equals(size, IconSize.Small))
        {
            flags += Interop.SHGFI_SMALLICON;
        }
        else
        {
            flags += Interop.SHGFI_LARGEICON;
        }
        var shfi = new SHFileInfo();
        var res = Interop.SHGetFileInfo(path, attribute, out shfi, (uint)Marshal.SizeOf(shfi), flags);
        if (object.Equals(res, IntPtr.Zero)) throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
        try
        {
            Icon.FromHandle(shfi.hIcon);
            return (Icon)Icon.FromHandle(shfi.hIcon).Clone();
        }
        catch
        {
            throw;
        }
        finally
        {
            Interop.DestroyIcon(shfi.hIcon);
        }
    }
}

What the code is doing is. We created our own class named ShellManager, inside this class we created a method named GetIcon to return the Icon object based on the file path (or extension) by using the SHGetFileInfo system function and SHFileInfo defined struct. Once you had called the system function, the hIcon property of the struct will be the one who will handle the Pointer (Handle of IntPtr object) of the system icon. From there, we can load the actual icon using the FromHandle method of Icon class.

Note: It is an Icon class of System.Drawing namespace.

Additional supportive enumeration (personalize enumerations).

public enum IconSize : short
{
    Small,
    Large
}

public enum ItemState : short
{
    Undefined,
    Open,
    Close
}

Now, after placing everything in proper place. You can call the ShellManager GetIcon method to get the icon. Code below is the sample one.

var icon = ShellManager.GetIcon(Path.GetExtension(filename), ItemType.File, IconSize.Small, ItemState.Undefined);

Implementing FolderManager and FileManager class

Below are the codes we can use to manage the icon for both folder and icon.

public static class FolderManager
{
    public static ImageSource GetImageSource(string directory, ItemState folderType)
    {
        try
        {
            return FolderManager.GetImageSource(directory, new Size(16, 16), folderType);
        }
        catch
        {
            throw;
        }
    }

    public static ImageSource GetImageSource(string directory, Size size, ItemState folderType)
    {
        try
        {
            using (var icon = ShellManager.GetIcon(directory, ItemType.Folder, IconSize.Large, folderType))
            {
                return Imaging.CreateBitmapSourceFromHIcon(icon.Handle, System.Windows.Int32Rect.Empty, BitmapSizeOptions.FromWidthAndHeight((int)size.Width, (int)size.Height));
            }
        }
        catch
        {
            throw;
        }
    }
}

public static class FileManager
{
    public static ImageSource GetImageSource(string filename)
    {
        try
        {
            return FileManager.GetImageSource(filename, new Size(16, 16));
        }
        catch
        {
            throw;
        }
    }

    public static ImageSource GetImageSource(string filename, Size size)
    {
        try
        {
            using (var icon = ShellManager.GetIcon(Path.GetExtension(filename), ItemType.File, IconSize.Small, ItemState.Undefined))
            {
                return Imaging.CreateBitmapSourceFromHIcon(icon.Handle, System.Windows.Int32Rect.Empty, BitmapSizeOptions.FromWidthAndHeight((int)size.Width, (int)size.Height));
            }
        }
        catch
        {
            throw;
        }
    }
}

You'll notice that in the
GetImageSource method, we used the ShellManager.GetIcon method and create a new ImageSource object by mimicing the existing one and do the resize. This also give us an idea how resize an image.

The code above returned the ImageSource object which you can use to the value of the Source property of the Image control.

Congratulations! You just finish the tutorial how to display system icon in your Image object.

4 comments:

  1. your code sample is missing the ItemType enum def.
    I assume it should look something like
    public enum ItemType : short
    {
    Folder,
    File
    }

    ReplyDelete
  2. Yes, that is the Enum type for ItemType. The Folder entry should be in 0 value ;)

    ReplyDelete
  3. I got this error in Icon Class:
    The type or namespace name 'Icon' could not be found (are you missing a using directive or an assembly reference?)

    Help me to solve this error please :)

    ReplyDelete
  4. I just fixed the error by (Add the reference System.Drawing).

    Thanks :)

    ReplyDelete

Place your comments and ideas