Async implementation of IValueConverter
You probably don't want to call Task.Result
, for a couple of reasons.
Firstly, as I explain in detail on my blog, you can deadlock unless your async
code is has been written using ConfigureAwait
everywhere. Secondly, you probably don't want to (synchronously) block your UI; it would be better to temporarily show a "loading..." or blank image while reading from the disk, and update when the read completes.
So, personally, I would make this part of my ViewModel, not a value converter. I have a blog post describing some databinding-friendly ways to do asynchronous initialization. That would be my first choice. It just doesn't feel right to have a value converter kicking off asynchronous background operations.
However, if you've considered your design and really think an asynchronous value converter is what you need, then you have to get a bit inventive. The problem with value converters is that they have to be synchronous: the data binding starts at the data context, evaluates the path, and then invokes a value conversion. Only the data context and path support change notifications.
So, you have to use a (synchronous) value converter in your data context to convert your original value into a databinding-friendly Task
-like object and then your property binding just uses one of the properties on the Task
-like object to get the result.
Here's an example of what I mean:
<TextBox Text="" Name="Input"/>
<TextBlock DataContext="{Binding ElementName=Input, Path=Text, Converter={local:MyAsyncValueConverter}}"
Text="{Binding Path=Result}"/>
The TextBox
is just an input box. The TextBlock
first sets its own DataContext
to the TextBox
's input text running it through an "asynchronous" converter. TextBlock.Text
is set to the Result
of that converter.
The converter is pretty simple:
public class MyAsyncValueConverter : MarkupExtension, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var val = (string)value;
var task = Task.Run(async () =>
{
await Task.Delay(5000);
return val + " done!";
});
return new TaskCompletionNotifier<string>(task);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
The converter first starts an asynchronous operation to wait 5 seconds and then add " done!" to the end of the input string. The result of the converter can't be just a plain Task
because Task
doesn't implement IPropertyNotifyChanged
, so I'm using a type that will be in the next release of my AsyncEx library. It looks something like this (simplified for this example; full source is available):
// Watches a task and raises property-changed notifications when the task completes.
public sealed class TaskCompletionNotifier<TResult> : INotifyPropertyChanged
{
public TaskCompletionNotifier(Task<TResult> task)
{
Task = task;
if (!task.IsCompleted)
{
var scheduler = (SynchronizationContext.Current == null) ? TaskScheduler.Current : TaskScheduler.FromCurrentSynchronizationContext();
task.ContinueWith(t =>
{
var propertyChanged = PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs("IsCompleted"));
if (t.IsCanceled)
{
propertyChanged(this, new PropertyChangedEventArgs("IsCanceled"));
}
else if (t.IsFaulted)
{
propertyChanged(this, new PropertyChangedEventArgs("IsFaulted"));
propertyChanged(this, new PropertyChangedEventArgs("ErrorMessage"));
}
else
{
propertyChanged(this, new PropertyChangedEventArgs("IsSuccessfullyCompleted"));
propertyChanged(this, new PropertyChangedEventArgs("Result"));
}
}
},
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
scheduler);
}
}
// Gets the task being watched. This property never changes and is never <c>null</c>.
public Task<TResult> Task { get; private set; }
Task ITaskCompletionNotifier.Task
{
get { return Task; }
}
// Gets the result of the task. Returns the default value of TResult if the task has not completed successfully.
public TResult Result { get { return (Task.Status == TaskStatus.RanToCompletion) ? Task.Result : default(TResult); } }
// Gets whether the task has completed.
public bool IsCompleted { get { return Task.IsCompleted; } }
// Gets whether the task has completed successfully.
public bool IsSuccessfullyCompleted { get { return Task.Status == TaskStatus.RanToCompletion; } }
// Gets whether the task has been canceled.
public bool IsCanceled { get { return Task.IsCanceled; } }
// Gets whether the task has faulted.
public bool IsFaulted { get { return Task.IsFaulted; } }
// Gets the error message for the original faulting exception for the task. Returns <c>null</c> if the task is not faulted.
public string ErrorMessage { get { return (InnerException == null) ? null : InnerException.Message; } }
public event PropertyChangedEventHandler PropertyChanged;
}
By putting these pieces together, we've created an asynchronous data context that is the result of a value converter. The databinding-friendly Task
wrapper will just use the default result (usually null
or 0
) until the Task
completes. So the wrapper's Result
is quite different than Task.Result
: it won't synchronously block and there's no danger of deadlock.
But to reiterate: I'd choose to put asynchronous logic into the ViewModel rather than a value converter.
How to create a converter that use async and await in Windows 10?
Use this binding
<Image DataContext="{Binding ImageUrl, Converter={StaticResource ImageConverter}}" Stretch="Uniform"
Source="{Binding Result}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
Converter class
public class ImageConverter : IValueConverter
{
StorageFolder localFolder = ApplicationData.Current.LocalFolder;
public object Convert(object value, Type targetType, object parameter, string culture)
{
if (value != null)
{
string imageUrl = value.ToString();
if (imageUrl.Contains("NoImageIcon"))
return value;
if (imageUrl.Contains(Constants.IMAGES_FOLDER_PATH))
{
var task = Task.Run(()=>( (GetImage((String)value))));
return new TaskCompletionNotifier<BitmapImage>(task);
}
if (imageUrl.Contains("mp4"))
{
return new TaskCompletionNotifier<BitmapImage>(Task.Run(() => ((GetImage("ms-appx:///Images/video.png")))));
}
if (MCSManager.Instance.isInternetConnectionAvailable)
{
return new TaskCompletionNotifier<BitmapImage>(Task.Run(() => ((GetImage(value.ToString(),true)))));
}
else
{
var task = Task.Run(() => ((GetImage("ms-appx:///Images/defaultImage.png"))));
return new TaskCompletionNotifier<BitmapImage>(task);
}
}
return new Uri("ms-appx:///Images/defaultImage.png", UriKind.RelativeOrAbsolute);
}
private async Task<BitmapImage> GetImage(string path,bool link=false)
{
BitmapImage image=null;
var dispatcher = Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher;
try
{
await dispatcher.RunAsync(CoreDispatcherPriority.Normal,async () =>
{
if (link)
{
image = new BitmapImage();
image.UriSource = new Uri(path, UriKind.RelativeOrAbsolute);
}
else
{
image = new BitmapImage();
image.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
StorageFile imagefile = await localFolder.GetFileAsync(path);
using (IRandomAccessStream fileStream = await imagefile.OpenAsync(FileAccessMode.ReadWrite))
{
image.SetSource(fileStream);
}
}
});
return image;
}
catch(Exception e)
{
return null;
}
}
public object ConvertBack(object value, Type targetType, object parameter, string culture)
{
return null;
}
private async Task<bool> FileExists(string fileName)
{
try
{
StorageFile file = await localFolder.GetFileAsync(fileName);
return true;
}
catch (FileNotFoundException ex)
{
return false;
}
}
}
public sealed class TaskCompletionNotifier<TResult> : INotifyPropertyChanged
{
public TaskCompletionNotifier(Task<TResult> task)
{
Task = task;
if (!task.IsCompleted)
{
var scheduler = (SynchronizationContext.Current == null) ? TaskScheduler.Current : TaskScheduler.FromCurrentSynchronizationContext();
task.ContinueWith(t =>
{
var propertyChanged = PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs("IsCompleted"));
if (t.IsCanceled)
{
propertyChanged(this, new PropertyChangedEventArgs("IsCanceled"));
}
else if (t.IsFaulted)
{
propertyChanged(this, new PropertyChangedEventArgs("IsFaulted"));
propertyChanged(this, new PropertyChangedEventArgs("ErrorMessage"));
}
else
{
propertyChanged(this, new PropertyChangedEventArgs("IsSuccessfullyCompleted"));
propertyChanged(this, new PropertyChangedEventArgs("Result"));
}
}
},
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
scheduler);
}
}
// Gets the task being watched. This property never changes and is never <c>null</c>.
public Task<TResult> Task { get; private set; }
// Gets the result of the task. Returns the default value of TResult if the task has not completed successfully.
public TResult Result { get { return (Task.Status == TaskStatus.RanToCompletion) ? Task.Result : default(TResult); } }
// Gets whether the task has completed.
public bool IsCompleted { get { return Task.IsCompleted; } }
// Gets whether the task has completed successfully.
public bool IsSuccessfullyCompleted { get { return Task.Status == TaskStatus.RanToCompletion; } }
// Gets whether the task has been canceled.
public bool IsCanceled { get { return Task.IsCanceled; } }
// Gets whether the task has faulted.
public bool IsFaulted { get { return Task.IsFaulted; } }
public event PropertyChangedEventHandler PropertyChanged;
}
I'm returning task from converter and setting it to DataContext
of image. Task has Result
property,that i'm binding it to Source
property
Referred from this link
Creating an async ValueConverter in MvvmCross
I found Stephen Cleary's answer to this question helpful: Async Implementation of IValueConverter
In the end I used his excellent AsyncEx library in a Windows-specific Value Converter to do async conversion of a byte array. For Android and iOS I was able to do the async conversion in MVVMCross custom bindings, without needing a Value Converter.
A little more information can be found in a blog post that I wrote: http://www.sequence.co.uk/blog/infinite-scrolling-using-mvvmcross-and-xamarin/
x:Bind converter with async method
panda is right, you really shouldn't open the file in the converter. This way, every time Convert() is called, you'll open the file and read from it, which is taking too much time and resources unnecessarily. Not to mention it hangs your UI thread and causes a deadlock. You should open the file before or while you are populating your ListView, read the list of URLs from it and bind to that (observable) collection instead.
If you want to stick with your current solution (which I strongly advise you against), please read the accepted answer here, Mr. Cleary describes the problem perfectly and in depth.
That's how I would do it: You'll need to make PlayerHistory an ObservableCollection of items, so the ListView will get notified when new items are added or old ones are removed from it. Based on your code snippet I suppose you already do it this way. The second thing you'll need is that you have to make the items in PlayerHistory (let's call them HistoryViewModel for now) implement the INotfyPropertyChanged interface, they have to have a property (let's call it PictureUri), which will you bind to in your DataTemplate, and they have to raise the PropertyChanged event in the setter of this property.
These written in code:
class HistoryViewModel : INotifyPropertyChanged
{
private Uri _pictureUri;
public Uri PictureUri
{
get
{
return _pictureUri;
}
set
{
if (value == _pictureUri)
return;
_pictureUri = value;
RaisePropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Then your DataTemplate will look like this:
<DataTemplate x:DataType="data:HistoryViewModel ">
<Grid HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Image Source="{Binding PictureUri}" /> ...
This way whenever you update the PictureUri property of the backing ViewModels, the ListView's items will automagically be updated too, thanks to data binding.
How to return a value from a method, when the underlying API to be invoked is asynchronous
As SearchAsync return type is void, I cannot use the await keyword here. So how would I be able to design the function here?
SearchAsync
returns void
because it is an EAP method. You can convert it to a TAP method which is awaitable. I prefer to make TAP wrappers like this into extension methods, as such:
public static Task<IEnumerable<Contact>> SearchTaskAsync(this Contacts contacts, string filter, FilterKind filterKind)
{
var tcs = new TaskCompletionSource<IEnumerable<Contact>>();
EventHandler<ContactsSearchEventArgs> subscription;
subscription = (_, args) =>
{
contacts.SearchComplete -= subscription;
try
{
tcs.TrySetResult(args.Results);
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
}
contacts.SearchComplete += subscription;
contacts.SearchAsync(filter, filterKind, null);
return tcs.Task;
}
Once you write a SearchTaskAsync
TAP extension method, you can call it like this:
private async Task<string> GetDisplayNameAsync(string number)
{
Contacts contactsBook = new Contacts();
var matches = await contactsBook.SearchTaskAsync(number, FilterKind.PhoneNumber);
...
return name;
}
However, this doesn't really solve the problem of how to display asynchronous data in a UI. And the answer (if you think about it) is that you can't, of course.
Instead, you have to design your UI so that it understands when it doesn't have the results yet (e.g., showing a spinner), and then update it when the search completes.
Note that this is tricky to pull off using a value converter. And that makes sense; you don't really want a value converter kicking off background operations! (You can hack it to work, but the results aren't pretty).
So I recommend that you use a separate VM property that is the display name. You can use NotifyTaskCompletion
from my AsyncEx library to make this easier. Here's one example of how this could work:
public string Number
{
get { return _number; }
set
{
_number = value;
NumberDisplayName = NotifyTaskCompletion.Create(GetDisplayNameAsync(value));
RaisePropertyChanged("Number");
RaisePropertyChanged("NumberDisplayName");
}
}
public INotifyTaskCompletion<string> NumberDisplayName
{
get; private set;
}
Then your binding could use NumberDisplayName.Result
to display the result (or a null string if the search has not completed). If you want a spinner or whatnot, you can use other paths such as NumberDisplayName.IsCompleted
; and if you want proper error handling, there are paths for that as well (e.g., NumberDisplayName.ErrorMessage
).
How to use IValueConverter implementation from another file?
Your IValueConverter
class can be anywhere you want, including in another assembly. Based on your use, you need to have a resource somewhere accessible from your MainWindow
class. An easy way is to have it in your window resources.
<MainWindow.Resources>
<my:IconExtractor x:Key="iconExtractor"/>
</MainWindow.Resources>
You can also use the resources in App.xaml
, since its resources are accessible from all XAML based classes in your project. Using App.xaml
appeals to me since you can avoid duplicating effort if more than one window in your project will use IconExtractor
.
ivalueconverter vs system.converter vs delegate
The best approach according to me:
- Let the user decide whether to use
IValueConverter
orConverter
by implementing a class that offers both possibilities. - The lambda is out of scope because a lambda expression is what is used on the user side to easily provide a delegate implementation. This means that a
ConvertingCollection
would expect a delegate as parameter, which is what aConverter
already is. - Finally, the idea to use an implicit conversion is not kept because :
- It forces the user to provide a conversion that could apply anywhere and not only in the sole context of the usage of
ConvertingCollection
, which may be problematic sometimes - In case an implicit conversion applies, is is easy to use it in a lambda expression
- It makes the implementation of the
ConvertingCollection
more complex (check if an implicit conversion exists between the 2 types A and B), and I'm too lazy for this. Ok, that may be the first reason, but the other two are still valid. :)
- It forces the user to provide a conversion that could apply anywhere and not only in the sole context of the usage of
I hope this helps.
Improved IValueConverter -- MarkupExtension or DependencyObject?
Deriving from each gives you different kind of power and flexibility:
Deriving from
MarkupExtension
enables you to use the value converter without making it a static resource, as described below:public class DoubleMe : MarkupExtension, IValueConverter
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
public object Convert(object value, /*rest of parameters*/ )
{
if ( value is int )
return (int)(value) * 2; //double it
else
return value.ToString() + value.ToString();
}
//...
}In XAML, you can directly use it without creating a StaticResource:
<TextBlock Text="{Binding Name, Converter={local:DoubleMe}}"/>
<TextBlock Text="{Binding Age, Converter={local:DoubleMe}}"/>Such code is very handy when debugging, as you can just write
local:DebugMe
and then can debug the DataContext of the control on which you use it.Deriving from
DependencyObject
enables you to configure the value converter with some preferences in a more expressive way, as described below:public class TruncateMe : DependencyObject, IValueConverter
{
public static readonly DependencyProperty MaxLengthProperty =
DependencyProperty.Register("MaxLength",
typeof(int),
typeof(TruncateMe),
new PropertyMetadata(100));
public int MaxLength
{
get { return (int) this.GetValue(MaxLengthProperty); }
set { this.SetValue(MaxLengthProperty, value); }
}
public object Convert(object value, /*rest of parameters*/ )
{
string s = value.ToString();
if ( s.Length > MaxLength)
return s.Substring(0, MaxLength) + "...";
else
return s;
}
//...
}In XAML, you can directly use it as:
<TextBlock>
<TextBlock.Text>
<Binding Path="FullDescription">
<Binding.Converter>
<local:TruncateMe MaxLength="50"/>
</Binding.Converter>
</Binding>
</TextBlock.Text>What does it do? It truncates the string
FullDescription
if it is more than50
characters!
@crazyarabian commented that:
Your statement "Deriving from DependencyObject enables you to configure the value converter with some preferences in a more expressive way" isn't exclusive to DependencyObject as you can create the same MaxLength property on a MarkupExtension resulting in
<TextBlock Text="Binding Age, Converter={local:DoubleMe, MaxLength=50}}"/>
. I would argue that a MarkupExtension is more expressive and less verbose.
That is true. But then that is not bindable; that is, when you derive from MarkupExtension
, then you cannot do :
MaxLength="{Binding TextLength}"
But if you derive your converter from DependencyObject
, then you can do the above. In that sense, it is more expressive compared to MarkupExtension
.
Note that the target property must be a DependencyProperty
for Binding
to work. MSDN says,
Each binding typically has these four components: a binding target
object, a target property, a binding
source, and a Path to the value in the
binding source to use. For example, if
you want to bind the content of a
TextBox to the Name property of an
Employee object, your target object is
the TextBox, the target property is
the Text property, the value to use is
Name, and the source object is the
Employee object.The target property must be a dependency property.
Related Topics
Resolve Type from Class Name in a Different Assembly
Split String into String Array of Single Characters
How to Prevent Flickering in Listview When Updating a Single Listviewitem's Text
Changing Project Port Number in Visual Studio 2013
How to Make JSON.Net Serializer to Call Tostring() When Serializing a Particular Type
ASP.NET Core Long Running/Background Task
Run Two Winform Windows Simultaneously
Windows 10 Scrollintoview() Is Not Scrolling to the Items in the Middle of a Listview
A Type for Date Only in C# - Why Is There No Date Type
Await Operator Can Only Be Used Within an Async Method
Store a Reference to a Value Type
Viewing PDF in Windows Forms Using C#
How to Hide Desktop Icons Programmatically
Popup's in Selenium Webdrivers