Reputation: 827
I have this scenario, in my windows phone App.
I have a function which needs to return the DisplayName of a contact, when provided with the PhoneNumber.
public string GetDisplayName(string number)
{
Contacts contactsBook = new Contacts();
contactsBook.SearchCompleted += contactsBook_SearchCompleted;
contactsBook.SearchAsync(number, FilterKind.PhoneNumber, null);
//return .....; How will I be able to return the display name here?
}
As SearchAsync return type is void, I cannot use the await keyword here. So how would I be able to design the function here?
Is it recommended pattern to use some thread signaling method here? (I would wait on an event after SearchAsync method and I will fire the event in SearchCompleted event)
The reason, I would want such a function is because, I get a set of numbers from my server and I would display the corresponding name in the UI.
<phone:LongListSelector ItemsSource="{Binding PhoneNumberCollection}">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding PhoneNumber, Converter={StaticResource PhoneNumberToNameConverter}}" />
</Grid>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
and I would have the IValueConverter converter as below
public class PhoneNumberToNameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ContactsManager.Instance.GetDisplayName((string)value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
Upvotes: 1
Views: 184
Reputation: 457127
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
).
Upvotes: 3