The SHGetFileInfo function sometimes does not give the appropriate icon when called from by a BackgroundWorker. As far as I have been able to ascertain you get the correct Icon back as long as the Icon is static for a file type. That is, the Icons for .txt, .doc, etc. are the same no matter which file it is. But the Icon for files/shortcuts with the extension .url or .sln (if you have VS 2005 installed) will have Icons which depend on file and for these files SHGetFileInfo returns does not return the appropriate Icon instead it returns the default Icon for a file (the icon that is used when Windows does not know the extension type).
To simulate this create a new C# Windows Application and place two Buttons, one PictureBox and one BackgroundWorker on the form. Create the click events for both the Buttons and the DoWork event for the BackgroundWorker. Copy the code below. When Button 1 is clicked the correct Icon is displayed but when the same code is called by the BackgroundWorker a wrong Icon is displayed. To test it with some other extension simply change ".url" in button1_Click to whichever new extension you want.
Any help would be greatly appreciated.
Thank you in advance.
rv
using
System;using
System.ComponentModel;using
System.Drawing;using
System.Windows.Forms;using
System.Runtime.InteropServices;namespace
WindowsApplication1{
public partial class Form1 : Form{
public Form1(){
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e){
Icon icon = Class1.GetIcon(".url");pictureBox1.Image = icon.ToBitmap();
}
private void button2_Click(object sender, EventArgs e){
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e){
button1_Click(
null, null);}
}
class Class1{
[
StructLayout(LayoutKind.Sequential)] private 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;};
[
DllImport("shell32.dll")] private static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, out SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags);[
DllImport("User32.dll")] private static extern int DestroyIcon(IntPtr hIcon); public const uint FILE_ATTRIBUTE_NORMAL = 0x80; private const uint SHGFI_USEFILEATTRIBUTES = 0x10; private const uint SHGFI_ICON = 0x100; private const uint SHGFI_LARGEICON = 0x0; private const uint SHGFI_SMALLICON = 0x1; public static Icon GetIcon(string ext){
SHFILEINFO shfi; IntPtr hImgSmall = SHGetFileInfo(ext,0,
out shfi,(
uint)Marshal.SizeOf(typeof(SHFILEINFO)),SHGFI_USEFILEATTRIBUTES | SHGFI_ICON | SHGFI_SMALLICON);
Icon icon = Icon.FromHandle(shfi.hIcon); // Create a new managed Icon using the unmanaged Icon. Icon managedIcon = (Icon)icon.Clone(); // Destroy the unmanaged Icon.DestroyIcon(icon.Handle);
// Return the new managed Icon. return managedIcon;}
}
}

BackgroundWorker and SHGetFileInfo
Matt Walter
The way you do it now sounds good, what are the bottlenecks and how much icons do you have to load
Rob Washo
BeginInvoke will be your solution i geus, meybe you need to lock some things to avoid race conditions and avoid that the ThreadPool becomes full. But you need to test it first. I am looking forward to your reply!
SammyF
Check if invoke is required to avoid cross-thread problems:
private void button1_Click(object sender, EventArgs e)
{
if( InvokeRequired )
{
object[] args = new object[]
{
sender, e
};
EventHandler method = new EventHandler(button1_Click);
Invoke( method, args );
return;
}
Icon icon = Class1.GetIcon(".url");
pictureBox1.Image = icon.ToBitmap();
}
Keith D
Why don't you lookup all icons, and instead of put then in the user interface, first put them in a queue. When you are finished, just process all images in the queue.
Rick Strahl
BeginInvoke does not work for my application as I have to update some UI. For my final solution I get the Icons using a normal call in BackgroundWorker DoWork event, while getting Icons I keep track of files for which Icon is same as the default Icon. In the WorkCompleted I use an Invoke to get Icons for those files that SHGetFileInfo returned the default Icon. I do not use any timer.
Sande and Sjogren thank you for all your help.
rv
Ashwin Purohit
FYI using Timer to do an Invoke works fine. Since SHGetFileInfo returns the correct Icons for most the files, hence the time required for do the Invoke is very less. Furthermore I get the correct Icons for only 4-5 files at a time so the UI does not freeze. It's a hack but it works. Now to find a less messy solution.
Will keep you posted.
rv
buragohain
If you mean use an internal list that does not always result in a call to SHGetFileInfo, then I am already doing that. My stopgap solution is that I call my GetIcon function from the BackgroundWorker DoWork event, if I already have the Icon in the list then I don't call SHGetFileInfo, otherwise I call SHGetFileInfo. If the icon returned by SHGetFileInfo is the same as the default icon (for this I use the iIcon field of the SHFILEINFO structure) then I put it in another list (incomplete). Once the RunWorkerCompleted event is fired I then call a function using Invoke that gets the correct Icons for those in the incomplete list. I am hoping for a better solution than what I am doing right now. Any takers from anybody@microsoft.com
Sande, once again thank you for your reply.
rv
Paul Cronk
Hi,
Thank you for your reply. At first glance it does seem to solve the problem. The correct Icons are displayed in the test app. But in my app where I use the BackgroundWorker to do to a lot of processing, Invoke is not an alternative. This is because despite a lot processing being done in DoWork event, as it is in another thread, the UI does not hang. If I use Invoke the procedure is executed in Form's thread and this cause the UI to freeze until the processing is done.
What I cannot understand is that if the System Image List is shared between all the threads of a process how can a different value be returned for different threads
TIA
rv
Kevinbe
There is no telling how many icons there are in the system image list and reading all is worse than reading just what I need.
I was using BackgroundWorker to pass control to the user ASAP. The problem is that no matter when I do the processing the UI is affected. What is basically boils down to is that I need SHGetFileInfo to work correctly no matter which thread call it. Otherwise I have to use hacks (Timers, partial list and Invoke).
I'll try BeginInvoke and post a reply.
rv
dnenadd
I believe SHGetFileInfo requires running in an STA. Have you tried using it from a background STA thread instead of using the BackgroundWorker
Chackowsky
Why don't you queue the images and then with a interval or defined size you process the queue
nuno_donato
Mattias could you please give me a little more info on how to do this. I have tried things like ThreadStart, ParameterizedThreadStart with STA but the application crashes on all of them.
You can assume that I have a class CTest with a sub GetIcons that takes no parameters.
rv
DesiGUY
About 20-100 icons. I don't know how many for sure as it will depend on the kind of files the user has. My problem is that I have to call Invoke after I have given user the control (i.e. the application has already stared). I think it is better to have a long startup time than to wrest control back from user, from the users perspective for no apparent reason.
Another messy approach that I am looking was using a Timer. Once the WorkCompleted event is fired, I enable a timer. In this Timer I use Invoke but at a time I process only few of the entries in the incomplete list (i.e. make only a few calls to SHGetFileInfo). This will give the appearance that user has control but actually they don't.
rv
Josh Christie
Meybe you want to implement your own BeginIvoke pattern, i have found a blogpost when helping a other user that is a good read for you to. The blog post that covers it all writen by Justin Rogers: Asynchronous Regular Expressions using the ThreadPool and a cancellation model.