SHGetFileInfo从后台线程返回默认图标,但从UI线程中更正图标

时间:2014-02-28 18:18:57

标签: c# wpf multithreading pinvoke

我有图像控件,可以在不同的线程中加载图标

它通常可以工作但是对于某些文件扩展名(xml,html ...),如果在后台线程中检索它,它总是返回默认图标,但在UI线程上检索时是正确的图标。

为什么会这样?

        void cIconImage_Loaded(object sender, RoutedEventArgs e)
        {       
            var source = GetImage(mypath); 
            this.Source = source;       
        }

        private ImageSource GetImage(string mypath)
        {
            ImageSource imgSource = null;   
            icon = ShellIcon.GetSmallIcon(mypath);
            imgSource = icon.ToImageSource();
            imgSource.Freeze();
            return imgSource;   
        }

        private static Icon GetIcon(string fileName, SHGFI flags, bool isFolder = false)
        {
            SHFILEINFO shinfo = new SHFILEINFO();

    //////For some extensions (xml, html,...) returns default icon
    var task = Task.Factory.StartNew(() => 
Win32.SHGetFileInfo(fileName, isFolder ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL, ref shinfo, (uint)Marshal.SizeOf(shinfo), (uint)(SHGFI.Icon | flags))
                );
    task.Wait(); //temporary 

    //////Everything OK but not in background thread
IntPtr hImgSmall = Win32.SHGetFileInfo(fileName, isFolder ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL, ref shinfo, (uint)Marshal.SizeOf(shinfo), (uint)(SHGFI.Icon | flags));       
    //////

            Icon icon = (Icon)System.Drawing.Icon.FromHandle(shinfo.hIcon).Clone();
            Win32.DestroyIcon(shinfo.hIcon);
            return icon;
        }

GetIcon是此课程https://gist.github.com/madd0/1433330的一部分 但我尝试过这个类的许多不同变体,结果相同。

2 个答案:

答案 0 :(得分:0)

这似乎有用,并显示所有图标

void cIconImage_Loaded(object sender, RoutedEventArgs e)
{
    var thread = new System.Threading.Thread(() =>
    {
        CoInitialize((IntPtr)0);
        var source = GetImage(mypath);
        this.Dispatcher.BeginInvoke(new Action(() =>
        {
                this.Source = source;
        }), System.Windows.Threading.DispatcherPriority.Background);
        CoUninitialize();
    });
    thread.IsBackground = true;
    thread.SetApartmentState(System.Threading.ApartmentState.STA);
    thread.Start();
}

其他一切都是一样的(除了GetIcon不使用Task.Factory)

答案 1 :(得分:0)

您需要指定该线程是单线程单元。您可以在线程启动之前调用SetApartmentState(System.Threading.ApartmentState.STA)来执行此操作。请注意,您不应该致电CoInitializeCoInitializeEx,因为这将自动为您完成。有关详细信息,请参阅此优秀问题:Do i need to call CoInitialize before interacting with COM in .NET?

要做的另一点是SHGetFileInfo不是线程安全的。如果您有多个线程调用SHGetFileInfo,则需要将这些调用序列化为SHGetFileInfo