This problem is starting to haunt me!!
My google-fu may not be strong in the force, however what I have been able to come up with so far still results in an expensive operation.
I am trying to obtain the icons from folders, but ONLY folders that have an icon other than default assigned to them. What it has come down to is checking for the existence of a desktop.ini
file within the folder, then getting the icon using SHGetFileInfo
for the folder. The problem with this method, is windows seems to enjoy placing desktop.ini
files in places where they really have no impact on anything such as c:\windows\assembly
. So then I adjusted my code to not only check for the existence of desktop.ini
, but to read it's contents to check for the string IconFile
.
This is still painfully slow as looking at 10 directories takes approximately 7-10 seconds. The purpose of this is to dynamically add the icons to a treeview control on-the-fly since there doesn't appear to be any other way to determine or obtain the icons. So I decided to try an make the OnBeforeExpand() into an async
/await
by fudging around with Task.Run()
and Invoke()
the node changes as required. The resulting code is as follows :
protected async override void OnBeforeExpand(TreeViewCancelEventArgs e)
{
await Task.Run(() =>
{
if (!_expandedCache.Contains(e.Node.FullPath))
{
ShellFileGetInfo.FolderIcons fi;
_expandedCache.Add(e.Node.FullPath);
string curPath;
foreach (TreeNode n in e.Node.Nodes)
{
curPath = Path.Combine((string)Tag, n.FullPath.Replace('/', Path.DirectorySeparatorChar));
if (File.Exists(Path.Combine(curPath, "desktop.ini")) == true)
{
if (File.ReadAllText(Path.Combine(curPath, "desktop.ini")).Contains("IconFile"))
{
fi = ShellFileGetInfo.GetFolderIcon(curPath, false);
if (fi.closed != null || fi.open != null)
{
if (InvokeRequired)
{
Invoke((MethodInvoker)(() =>
{
BeginUpdate();
ImageList.Images.Add(fi.closed);
ImageList.Images.Add(fi.open);
n.SelectedImageIndex = ImageList.Images.Count - 1;
n.ImageIndex = ImageList.Images.Count - 2;
EndUpdate();
}
));
}
else
{
BeginUpdate();
ImageList.Images.Add(fi.closed);
ImageList.Images.Add(fi.open);
n.SelectedImageIndex = ImageList.Images.Count - 1;
n.ImageIndex = ImageList.Images.Count - 2;
EndUpdate();
}
}
}
}
//EndUpdate();
}
}
});
base.OnBeforeExpand(e);
}
What can I do about this to make this perform so there is little or no noticeable lag when expanding the tree, as 1 second (approximately) per sub-folder scan is absolutely insane.
Is there another place I can scan for the icon information to read it before hand or am I stuck parsing/checking for Desktop.ini files ?? I believe there must be as I can delete my desktop.ini file from many locations and still have folders with assigned icons. I am unable to find any information on where Windows is hiding this information. My best guess is somewhere in the registry (which would still be faster than accessing the file system and parsing desktop.ini files).
Paste of the GetFolderIcons()
method which returns a struct of two Icon
types. - You will notice options to have any overlay embedded as part of the icon if required, as well as large or small icons. For the purpose of the above code, I am requesting small icons with automatic overlays embedded.
/// <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, bool largeIcon = true, bool autoOverlay = true)
{
FolderIcons fi = new FolderIcons();
SHFILEINFO shInfo = new SHFILEINFO();
IntPtr ptr = new IntPtr();
uint flags = SHGFI_ICON | SHGFI_SMALLICON | SHGFI_ADDOVERLAYS;
uint flags_open = SHGFI_ICON | SHGFI_SMALLICON | SHGFI_ADDOVERLAYS;
if (autoOverlay == false && largeIcon == false)
{
flags = SHGFI_ICON | SHGFI_SMALLICON;
flags_open = SHGFI_ICON | SHGFI_SMALLICON | SHGFI_OPENICON;
} else if(autoOverlay == false && largeIcon == true)
{
flags = SHGFI_ICON | SHGFI_LARGEICON;
flags_open = SHGFI_ICON | SHGFI_LARGEICON | SHGFI_OPENICON;
}
else if(autoOverlay == true && largeIcon == false)
{
flags = SHGFI_ICON | SHGFI_SMALLICON | SHGFI_ADDOVERLAYS;
flags_open = SHGFI_ICON | SHGFI_SMALLICON | SHGFI_ADDOVERLAYS | SHGFI_OPENICON;
}
else if(autoOverlay == true && largeIcon == true)
{
flags = SHGFI_ICON | SHGFI_LARGEICON | SHGFI_ADDOVERLAYS;
flags_open = SHGFI_ICON | SHGFI_LARGEICON | SHGFI_ADDOVERLAYS | SHGFI_OPENICON;
}
try
{
ptr = SHGetFileInfo(path, 0x00000010, ref shInfo, (uint)Marshal.SizeOf(shInfo), flags);
if (ptr != IntPtr.Zero)
{
fi.closed = (Icon)Icon.FromHandle(shInfo.hIcon).Clone();
}
}
catch (Exception)
{
fi.closed = null;
} finally
{
if(shInfo.hIcon != IntPtr.Zero)
{
DestroyIcon(shInfo.hIcon);
}
}
try {
ptr = SHGetFileInfo(path, 0x00000010, ref shInfo, (uint)Marshal.SizeOf(shInfo), flags_open);
if (ptr != IntPtr.Zero) {
fi.open = (Icon)Icon.FromHandle(shInfo.hIcon).Clone();
}
}
catch (Exception)
{
fi.closed = null;
} finally
{
if(shInfo.hIcon != IntPtr.Zero)
{
DestroyIcon(shInfo.hIcon);
}
}
return fi;
}
I have since rewritten my method using Dictionary ( OnBeforeExpand async/await using Dictionary ), but it still lags, and worse, it skips icons. The behavior is slightly different, the Tree expands first, then lags, where before it would lag, then expand the tree.