I am having some trouble getting the Folder icon, or keeping the icon persistent. The class I wrote which allows me to manage access to all icon functions for files and folders is as follows:
Complete class supplied so that you can compare the different methods used to retrieve icons and to show that it is working as intended in all other cases using a similar structure for the return data. This question focuses on a problem with using the result from GetFolderIcon
which when compared to GetFileIcons
is a similar structure for the return data. You can also use ShellFileGetInfo.IconCollections.shell32
to retrieve a list of over 300 icons, also in a similar structure which works.
public class ShellFileGetInfo
{
[DllImport("shell32.dll")]
private static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags);
[DllImport("user32.dll", EntryPoint = "DestroyIcon", SetLastError = true)]
private static extern int DestroyIcon(IntPtr hIcon);
[DllImport("shell32.dll", CharSet = CharSet.Auto)]
private static extern int ExtractIconEx(string szFileName, int nIconIndex, IntPtr[] phiconLarge, IntPtr[] phiconSmall, int nIcons);
[StructLayout(LayoutKind.Sequential)]
public struct SHFILEINFO
{
public IntPtr hIcon;
public IntPtr iIcon;
public uint dwAttributes;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szDisplayName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string szTypeName;
};
// Enum for return of the file type
public enum ShellFileType
{
FileNotFound,
Unknown,
Dos,
Windows,
Console
}
public class FolderIcons
{
public Icon open;
public Icon closed;
}
public class FileIcons
{
public List<Icon> small;
public List<Icon> large;
}
public static class IconCollections
{
private static FileIcons _explorer;
public static FileIcons explorer
{
get
{
if(_explorer == null )
{
_explorer = GetFileIcons(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "explorer.exe"));
}
return _explorer;
}
}
private static FileIcons _shell32;
public static FileIcons shell32
{
get
{
if (_shell32 == null )
{
_shell32 = GetFileIcons(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "shell32.dll"));
}
return _shell32;
}
}
}
// Apply the appropriate overlays to the file's icon. The SHGFI_ICON flag must also be set.
public const uint SHGFI_ADDOVERLAYS = 0x000000020;
// Modify SHGFI_ATTRIBUTES to indicate that the dwAttributes member of the SHFILEINFO structure at psfi contains the specific attributes that are desired. These attributes are passed to IShellFolder::GetAttributesOf. If this flag is not specified, 0xFFFFFFFF is passed to IShellFolder::GetAttributesOf, requesting all attributes. This flag cannot be specified with the SHGFI_ICON flag.
public const uint SHGFI_ATTR_SPECIFIED = 0x000020000;
// Retrieve the item attributes. The attributes are copied to the dwAttributes member of the structure specified in the psfi parameter. These are the same attributes that are obtained from IShellFolder::GetAttributesOf.
public const uint SHGFI_ATTRIBUTES = 0x000000800;
// Retrieve the display name for the file, which is the name as it appears in Windows Explorer. The name is copied to the szDisplayName member of the structure specified in psfi. The returned display name uses the long file name, if there is one, rather than the 8.3 form of the file name. Note that the display name can be affected by settings such as whether extensions are shown.
public const uint SHGFI_DISPLAYNAME = 0x000000200;
// Retrieve the type of the executable file if pszPath identifies an executable file. The information is packed into the return value. This flag cannot be specified with any other flags.
public const uint SHGFI_EXETYPE = 0x000002000;
// Retrieve the handle to the icon that represents the file and the index of the icon within the system image list. The handle is copied to the hIcon member of the structure specified by psfi, and the index is copied to the iIcon member.
public const uint SHGFI_ICON = 0x000000100;
// Retrieve the name of the file that contains the icon representing the file specified by pszPath, as returned by the IExtractIcon::GetIconLocation method of the file's icon handler. Also retrieve the icon index within that file. The name of the file containing the icon is copied to the szDisplayName member of the structure specified by psfi. The icon's index is copied to that structure's iIcon member.
public const uint SHGFI_ICONLOCATION = 0x000001000;
// Modify SHGFI_ICON, causing the function to retrieve the file's large icon. The SHGFI_ICON flag must also be set.
public const uint SHGFI_LARGEICON = 0x000000000;
// Modify SHGFI_ICON, causing the function to add the link overlay to the file's icon. The SHGFI_ICON flag must also be set.
public const uint SHGFI_LINKOVERLAY = 0x000008000;
// Modify SHGFI_ICON, causing the function to retrieve the file's open icon. Also used to modify SHGFI_SYSICONINDEX, causing the function to return the handle to the system image list that contains the file's small open icon. A container object displays an open icon to indicate that the container is open. The SHGFI_ICON and/or SHGFI_SYSICONINDEX flag must also be set.
public const uint SHGFI_OPENICON = 0x000000002;
// Version 5.0. Return the index of the overlay icon. The value of the overlay index is returned in the upper eight bits of the iIcon member of the structure specified by psfi. This flag requires that the SHGFI_ICON be set as well.
public const uint SHGFI_OVERLAYINDEX = 0x000000040;
// Indicate that pszPath is the address of an ITEMIDLIST structure rather than a path name.
public const uint SHGFI_PIDL = 0x000000008;
// Modify SHGFI_ICON, causing the function to blend the file's icon with the system highlight color. The SHGFI_ICON flag must also be set.
public const uint SHGFI_SELECTED = 0x000010000;
// Modify SHGFI_ICON, causing the function to retrieve a Shell-sized icon. If this flag is not specified the function sizes the icon according to the system metric values. The SHGFI_ICON flag must also be set.
public const uint SHGFI_SHELLICONSIZE = 0x000000004;
// Modify SHGFI_ICON, causing the function to retrieve the file's small icon. Also used to modify SHGFI_SYSICONINDEX, causing the function to return the handle to the system image list that contains small icon images. The SHGFI_ICON and/or SHGFI_SYSICONINDEX flag must also be set.
public const uint SHGFI_SMALLICON = 0x000000001;
// Retrieve the index of a system image list icon. If successful, the index is copied to the iIcon member of psfi. The return value is a handle to the system image list. Only those images whose indices are successfully copied to iIcon are valid. Attempting to access other images in the system image list will result in undefined behavior.
public const uint SHGFI_SYSICONINDEX = 0x000004000;
// Retrieve the string that describes the file's type. The string is copied to the szTypeName member of the structure specified in psfi.
public const uint SHGFI_TYPENAME = 0x000000400;
// Indicates that the function should not attempt to access the file specified by pszPath. Rather, it should act as if the file specified by pszPath exists with the file attributes passed in dwFileAttributes. This flag cannot be combined with the SHGFI_ATTRIBUTES, SHGFI_EXETYPE, or SHGFI_PIDL flags.
public const uint SHGFI_USEFILEATTRIBUTES = 0x000000010;
/// <summary>
/// Get a list of open and closed icons for the specified folder
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static FolderIcons GetFolderIcon(string path)
{
FolderIcons fi = new FolderIcons();
SHFILEINFO shInfo = new SHFILEINFO();
IntPtr ptr = new IntPtr();
try
{
ptr = SHGetFileInfo(path, 0, ref shInfo, (uint)Marshal.SizeOf(shInfo), SHGFI_ICON | SHGFI_LARGEICON | SHGFI_ADDOVERLAYS);
if (ptr != IntPtr.Zero)
{
fi.closed = Icon.FromHandle(shInfo.hIcon);
}
}
catch (Exception)
{
fi.closed = null;
} finally
{
if(shInfo.hIcon != IntPtr.Zero)
{
DestroyIcon(shInfo.hIcon);
}
}
try {
ptr = SHGetFileInfo(path, 0, ref shInfo, (uint)Marshal.SizeOf(shInfo), SHGFI_ICON | SHGFI_LARGEICON | SHGFI_ADDOVERLAYS | SHGFI_OPENICON);
if (ptr != IntPtr.Zero) {
fi.open = Icon.FromHandle(shInfo.hIcon);
}
}
catch (Exception)
{
fi.closed = null;
} finally
{
if(shInfo.hIcon != IntPtr.Zero)
{
DestroyIcon(shInfo.hIcon);
}
}
return fi;
}
/// <summary>
/// Determine the type of executable the file is
/// </summary>
/// <param name="file"></param>
/// <returns></returns>
public static ShellFileType GetFileType(string file)
{
ShellFileType type = ShellFileType.FileNotFound;
if (File.Exists(file))
{
SHFILEINFO shinfo = new SHFILEINFO();
IntPtr ptr = SHGetFileInfo(file, 128, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFI_EXETYPE);
int wparam = ptr.ToInt32();
int loWord = wparam & 0xffff;
int hiWord = wparam >> 16;
type = ShellFileType.Unknown;
if (wparam != 0)
{
if (hiWord == 0x0000 && loWord == 0x5a4d)
{
type = ShellFileType.Dos;
}
else if (hiWord == 0x0000 && loWord == 0x4550)
{
type = ShellFileType.Console;
}
else if ((hiWord != 0x0000) && (loWord == 0x454E || loWord == 0x4550 || loWord == 0x454C))
{
type = ShellFileType.Windows;
}
}
}
return type;
}
/// <summary>
/// Get a single icon from a file
/// </summary>
/// <param name="resource"></param>
/// <param name="largeIcon"></param>
/// <returns></returns>
public static Icon GetFileIcon(string resource, bool largeIcon = false)
{
SHFILEINFO shinfo = new SHFILEINFO();
Icon icon;
try
{
IntPtr ico;
if (largeIcon == true)
{
ico = SHGetFileInfo(resource, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFI_ICON | SHGFI_SMALLICON);
}
else
{
ico = SHGetFileInfo(resource, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFI_ICON | SHGFI_LARGEICON);
}
icon = (Icon)Icon.FromHandle(shinfo.hIcon);
}
catch (Exception)
{
icon = null;
}
finally
{
if (shinfo.hIcon != IntPtr.Zero)
{
DestroyIcon(shinfo.hIcon);
}
}
return icon;
}
/// <summary>
/// Get all large and small icons from a file
/// </summary>
/// <param name="file"></param>
/// <returns></returns>
public static FileIcons GetFileIcons(string file)
{
FileIcons icons = new FileIcons()
{
small = new List<Icon>(),
large = new List<Icon>()
};
IntPtr[] large = new IntPtr[999];
IntPtr[] small = new IntPtr[999];
Icon ico;
try
{
int count = ExtractIconEx(file, -1, large, small, 999);
if (count > 0)
{
large = new IntPtr[count - 1];
small = new IntPtr[count - 1];
ExtractIconEx(file, 0, large, small, count);
foreach (var x in large)
{
if (x != IntPtr.Zero)
{
ico = (Icon)Icon.FromHandle(x).Clone();
icons.large.Add(ico);
}
}
foreach (var x in small)
{
if (x != IntPtr.Zero)
{
ico = (Icon)Icon.FromHandle(x).Clone();
icons.small.Add(ico);
}
}
}
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e.Message);
}
finally
{
foreach (IntPtr ptr in large)
{
if (ptr != IntPtr.Zero)
{
DestroyIcon(ptr);
}
}
foreach (IntPtr ptr in small)
{
if (ptr != IntPtr.Zero)
{
DestroyIcon(ptr);
}
}
}
return icons;
}
}
When I try to use the GetFolderIcon
function that is responsible for obtaining the folder's open and closed icons as FolderIcons
, it returns a value, but when I try to add it to my listview, I am getting the following exception:
Exception thrown: 'System.ObjectDisposedException' in System.Drawing.dll
The code I am using to use the function and add the icon to the listview's image collection is as follows:
// Initialize the imagelist object
listView1.LargeImageList = new ImageList() { ColorDepth = ColorDepth.Depth32Bit, ImageSize = new Size(32, 32) };
// Create a new listview item
ListViewItem lvi = new ListViewItem()
{
Text = "My Computer",
ImageIndex = viewer1.LargeImageList.Images.Count,
StateImageIndex = viewer1.LargeImageList.Images.Count
};
// Retrieve the open/closed icons into a class 'x' (i tried as a struct with the same result)
var x = ShellFileGetInfo.GetFolderIcon(
Environment.GetFolderPath(
Environment.SpecialFolder.Desktop
)
);
// Try adding the icon to the listview's image collection
listView1.LargeImageList.Images.Add(x.closed);
// Add the listview item to the listview
listView1.Items.Add(lvi);
It is important to note that I do not have this problem when using the GetFileIcon
or GetFileIcons
methods which return their results in a similar structure after calling DestroyIcon()
where required.
I have looked into several other Stack Overflow articles which claim to retrieve a folder icon using SHGetFileInfo, however none of those methods appear to work. My class above is a hybrid of the best of the methods found, as well as implementation from the MSDN articles on this. I have also added the MSDN notes inline to each constant describing its purpose and use.
Some of the Stack Overflow links I have gone through:
- How do I fetch the folder icon on Windows 7 using Shell32.SHGetFileInfo
- How to get the system icon for any file/folder
- How to get the icon associated with a specific folder?
- Getting icons of Drives and Directories: Icon.ExtractAssociatedIcon(filePath) doesn't work?
- Icon.FromHandle: should I Dispose it, or call DestroyIcon?
Some other references used that are not a part of Stack Overflow:
- https://support.microsoft.com/en-us/kb/319350
- https://msdn.microsoft.com/en-us/library/windows/desktop/bb762179(v=vs.85).aspx
- http://www.pinvoke.net/default.aspx/shell32.SHGetFileInfo
- http://www.codeproject.com/Articles/2532/Obtaining-and-managing-file-and-folder-icons-using
- http://forums.codeguru.com/printthread.php?t=319455
- https://bytes.com/topic/c-sharp/answers/812461-get-system-icon-any-file-folder
There were several others I viewed, but nothing that is fully functional. My method, I wish to add the overlay icon from the system to the icon as per the MSDN documentation which describes using the SHGFI_ADDOVERLAYS
constant will automatically merge the overlay if any is required with the icon and return the combined result. Essentially, my GetFolderIcon
method is designed to be intelligent enough to handle the return of both the open and closed state icons as well as with any overlays already applied as required.