IMultiValueConverter converting file path to icon in different thread

296 Views Asked by At

I am trying to extract icon in another thread with IMultiValueConverter and even though this works in another project it does not work here. IsAsync=True makes start the converter without FullPath value, and IsAsync=False blocks the UI especially when extracting icons from .lnk files.

<Image>
  <Image.Source>
   <MultiBinding Converter="{StaticResource Fullpath2Icon}"   >
    <MultiBinding.Bindings>
      <Binding RelativeSource="{RelativeSource Self}"  />
      <Binding Path="FullPath" IsAsync="True"  />
    </MultiBinding.Bindings>
  </MultiBinding>
 </Image.Source>
</Image>


public class Fullpath2IconConverter : IMultiValueConverter
{
  public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  {
    System.Windows.Controls.Image image = values[0] as System.Windows.Controls.Image;
    if (values[1] == null) return null;
    string mypath = values[1] as string;

    ThreadPool.QueueUserWorkItem((WaitCallback)delegate
    {
      image.Dispatcher.Invoke(DispatcherPriority.Normal,
            (ThreadStart)delegate {
                image.Source = System.Drawing.Icon.ExtractAssociatedIcon(mypath).IconToImageSource();

Can you explain me the reason for this or suggest any way of improving this?

EDIT: I created a class extending image and now it looks like this:

<local:cIconImage Path="{Binding FullPath}"></local:cIconImage>

public class cIconImage : System.Windows.Controls.Image
    {
        public cIconImage()
        {
            //this.Loaded += new RoutedEventHandler(cIconImage_Loaded);
            Dispatcher.BeginInvoke(DispatcherPriority.Input, new ThreadStart(() =>
            {
                Source = GetImage();
            }));
        }

It is much better as now the list populates, and each icon appears as it is being extracted, but during some long .lnk extraction UI still locks up.

1

There are 1 best solutions below

4
On

Setting the Image control's Source property inside the Convert method doesn't make any sense. Not to mention calling Dispatcher.Invoke immediately in a separate thread.

Use a simple binding on the Source property like this:

<Image Source="{Binding FullPath, Converter={StaticResource Fullpath2Icon}}">

and a single value converter that simply returns the result of the conversion:

public class Fullpath2IconConverter : IValueConverter
{
    public object Convert(
        object value, Type targetType, object parameter, CultureInfo culture)
    {
        var mypath = value as string;
        return System.Drawing.Icon.ExtractAssociatedIcon(mypath).IconToImageSource();
    }

    ...
}

Update: In order to asynchronously load a number of icon images, you should bind to an ObservableCollection<ImageSource> in your view model (without a converter) and fill that collection from a background thread. Note that the images are created in the background thread and must therefore be frozen.

public class ViewModel
{
    public ViewModel()
    {
        Icons = new ObservableCollection<ImageSource>();
    }

    public ObservableCollection<ImageSource> Icons { get; set; }

    public void LoadIconsAsync()
    {
        ThreadPool.QueueUserWorkItem(o =>
        {
            foreach (var path in paths)
            {
                var image = System.Drawing.Icon.ExtractAssociatedIcon(mypath).IconToImageSource();
                image.Freeze();

                Application.Current.Dispatcher.Invoke(
                    new Action(() => Icons.Add(image)));
            }
        });
    }
}