INotifyTaskCompletion - Best Practices

Nov 18, 2013 at 11:41 PM
Hi, I really appreciate your time for making this great work available to the public.

My question is regarding best practices when binding a control to a viewmodel. In this case, I am binding a List to a DataGrid:
ItemsSource="{Binding Challenges.Result}"
In my view model, it's bound as such:
        #region Challenges
        public const string ChallengesPropertyName = "Challenges";
        private INotifyTaskCompletion<List<Challenge>> _challenges;
        public INotifyTaskCompletion<List<Challenge>> Challenges
        {
            get { return _challenges; }
            private set
            {
                if (_challenges != value)
                {
                    _challenges = value;
                    OnPropertyChanged(ChallengesPropertyName);
                }
            }
        }
        #endregion
My issue is that I would normally bind a list of things to an ObservableCollection, instead of the Result of this async operation. I am just wondering the best way to:
  1. get the behavior from ObservableCollection - should I simply chage List to ObservableCollection
  2. Manipulate the backing list, i.e. _challenges without calling .Result all the time. It seems fragile, but maybe it's just me.
Thanks in advance.

-Ryan
Coordinator
Nov 21, 2013 at 3:40 PM
Ryan,

It sounds like you want to asynchronously load an ObservableCollection that you can then modify from code. There are a few different approaches.

You could have your async loader method return ObservableCollection and introduce a property that exposes the result directly:
private INotifyTaskCompletion<ObservableCollection<Challenge>> _challenges;
public INotifyTaskCompletion<ObservableCollection<Challenge>> Challenges { ... }
private ObservableCollection<Challenge> ChallengesCollection { get { return Challenges.Result; } }
If you want to avoid Result outside the VM, that's trickier. You have to listen to the INotifyTaskCompletion.OnPropertyChanged so that data binding to the bare ObservableCollection property is updated correctly when the task completes. E.g.,
public const string ChallengesPropertyName = "Challenges";
public const string ChallengesCollectionPropertyName = "ChallengesCollection";
private PropertyChangedEventHandler _challengesListener;
public ObservableCollection<Challenge> ChallengesCollection { get { return Challenges.Result; } }
private INotifyTaskCompletion<ObservableCollection<Challenge>> _challenges;
public INotifyTaskCompletion<ObservableCollection<Challenge>> Challenges
{
    get { return _challenges; }
    private set
    {
        if (_challenges != value)
        {
            if (_challengesListener != null)
            {
                _challenges.PropertyChanged -= _challengesListener;
                _challengesListener = null;
            }
            _challenges = value;
            if (_challenges != null)
            {
                _challengesListener = (_, e) =>
                {
                    if (e.PropertyName == "Result")
                        OnPropertyChanged(ChallengesCollectionPropertyName);
                };
                _challenges.PropertyChanged -= _challengesListener;
            }
            OnPropertyChanged(ChallengesPropertyName);
            OnPropertyChanged(ChallengesCollectionPropertyName);
        }
    }
}
Note that if you don't need to make Challenges a writable property, the code can be simplified dramatically. For example, if _challenges is started by the constructor (a very common pattern), the last code example simplifies to this:
public const string ChallengesPropertyName = "Challenges";
public const string ChallengesCollectionPropertyName = "ChallengesCollection";
public ObservableCollection<Challenge> ChallengesCollection { get { return _challenges.Result; } }
private readonly INotifyTaskCompletion<ObservableCollection<Challenge>> _challenges;
public INotifyTaskCompletion<ObservableCollection<Challenge>> Challenges { get { return _challenges; } }
public MyVMConstructor()
{
    _challenges = NotifyTaskCompletion.Create(async () =>
    {
        List<Challenge> challengeList = await ChallengesProvider.GetChallengesAsync();
        return new ObservableCollection<Challenge>(challengeList);
    });
    _challenges.PropertyChanged += (_, e) =>
    {
        if (e.PropertName == "Result")
            OnPropertyChanged(ChallengesCollectionPropertyName);
    };
}
However, that's an interesting question, and I'm considering ways to make this easier.

-Steve
Mar 5, 2015 at 9:52 PM
Is the first solution still the preferred way of binding an asynchronous ObservableCollection?
Coordinator
Mar 6, 2015 at 8:39 AM
If by "asynchronous ObservableCollection" you mean "a collection that may asynchronously have items added to it", then you can just use ObservableCollection directly. It handles its own change notifications.

If by "asynchronous ObservableCollection" you mean "a collection that doesn't exist until an asynchronous method completes", then you can use INotifyTaskCompletion.

-Steve