Reputation: 435
I have a WPF listbox, which will bind a lot of images. Each image may will from local disk or get icon from Exe itself.
I put all those parse codes in MultiValueConverter. But it now seems block the UI. How to make that async?
Code Sample: https://github.com/qianlifeng/Wox/blob/master/Wox/Converters/ImagePathConverter.cs#L53
Upvotes: 0
Views: 6833
Reputation: 7249
First you should provide some sample code so it easy to reproduce for answerers.
IsAsync
will not help
Using IsAsync
binding will not help because of below reasons:
IsAsync
propertyIsAsync
will not always help as one problem case mentioned in the question here where OP is trying to load image from web but wpf is resolving DNS on main thread so again application hangs for whileSolution is Use Attached Binding Property in WPF
Details:
Source
in XAML<Image my:ImageAsyncHelper.SourceUri="{Binding Author.IconUrl}" />
Attached Property class
public class ImageAsyncHelper : DependencyObject
{
public static Uri GetSourceUri(DependencyObject obj) { return (Uri)obj.GetValue(SourceUriProperty); }
public static void SetSourceUri(DependencyObject obj, Uri value) { obj.SetValue(SourceUriProperty, value); }
public static readonly DependencyProperty SourceUriProperty = DependencyProperty.RegisterAttached("SourceUri", typeof(Uri), typeof(ImageAsyncHelper), new PropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
((Image)obj).SetBinding(Image.SourceProperty,
new Binding("VerifiedUri")
{
Source = new ImageAsyncHelper { GivenUri = (Uri)e.NewValue },
IsAsync = true,
});
}
});
Uri GivenUri;
public Uri VerifiedUri
{
get
{
try
{
Dns.GetHostEntry(GivenUri.DnsSafeHost);
return GivenUri;
}
catch(Exception)
{
return null;
}
}
}
}
And if you need to use IMultiValueConverter
with attached property defined above then it should go like below xaml code:
Attached Property with IMultiValueConverter
<Image>
<my:ImageAsyncHelper.SourceUri>
<MultiBinding Converter="{StaticResource MyImageMultiValueConverter}">
<Binding Source="Author" Path="IconUrl"/> <!-- Binding Parameters -->
<Binding Path="ImageType"/> <!-- Binding Parameters -->
<Binding Path="MyParameterToConverter"/> <!-- Binding Parameters -->
</MultiBinding>
</my:ImageAsyncHelper.SourceUri>
</Image>
Reference links
Upvotes: -2
Reputation: 13669
You can leverage IsAsync
property of Binding
From MSDN:
Use the IsAsync property when the get accessor of your binding source property might take a long time. One example is an image property with a get accessor that downloads from the Web. Setting IsAsync to true avoids blocking the UI while the download occurs.
example
<Image Source="{Binding MyImage,IsAsync=True, Converter={StaticResource MyConverter}}" />
more on Binding.IsAsync
Async Converter
I managed to create a async converter
namespace CSharpWPF
{
class AsyncConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new AsyncTask(() =>
{
Thread.Sleep(4000); //long running job eg. download image.
return "success";
});
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
public class AsyncTask : INotifyPropertyChanged
{
public AsyncTask(Func<object> valueFunc)
{
AsyncValue = "loading async value"; //temp value for demo
LoadValue(valueFunc);
}
private async Task LoadValue(Func<object> valueFunc)
{
AsyncValue = await Task<object>.Run(()=>
{
return valueFunc();
});
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("AsyncValue"));
}
public event PropertyChangedEventHandler PropertyChanged;
public object AsyncValue { get; set; }
}
}
}
this converter will return an instance of AsyncTask
which will encapsulate the long running job within
class AsyncTask
will execute the task asynchronously and will set the result to AsyncValue
as it also implements INotifyPropertyChanged
hence using the notification to update the UI
usage
<Grid xmlns:l="clr-namespace:CSharpWPF">
<Grid.Resources>
<l:AsyncConverter x:Key="AsyncConverter" />
</Grid.Resources>
<TextBlock DataContext="{Binding MyProperty,Converter={StaticResource AsyncConverter}}"
Text="{Binding AsyncValue}" />
</Grid>
Idea is to bind the DataContext
of the element to the converter and the desired property to the AsyncValue of the new data context
above example is using Text property of a text block for easy demo
exmaple is made for a IValueConverter
same approach can be used for IMultiValueConverter
too.
Upvotes: 7