MVVM detail ponderings

So, I am refactoring some of the ViewModel help code I have here at work, and some things bubbled up to consider.

A common scene is to have an ObservableCollection<T> in the viewmodel which is databound to a DataGrid in the View. T in this case is my entities from EF which are implementing INotifyPropertyChanged.

ObservableCollection<T> is very nifty, implementing ICollectionChanged it tells the view when an item is added or removed from the collection. Since my entities implement INotifyPropertyChanged, when an item in the collection has one of it’s properties edited, the view will be notified.

But, what about values that are dependent on values in the collection but is not directly notified via INotifyPropertyChanged?

Consider the following

public class Customer : INotifyPropertyChanged
{
    public double Balance { get; set; }
}

public class CustomerEditViewModel : INotifyPropertyChanged
{
    public CustomerEditViewModel(IEnumerable<Customer> customerList)
    {
        CustomerList = new ObservableCollection<Customer>(customerList);
    }

    public ObservableCollection<Customer> CustomerList { get; set; }

    public double CustomerAverageBalance
    {
        get
        {
            return CustomerList.Average(cust => cust.Balance);
        }
    }
}

Now the CustomerAverageBalance property will be updated when the collection is modified.
But what about if an item in the collection is edited? Quick solution is to loop through the items in the list and subscribe to their PropertyChanged event.

CustomerList.ForEach(cust => cust.PropertyChanged += (s, e) =>
{
    if(e.PropertyName == "Balance")
    {
        OnPropertyChanged("CustomerAverageBalance"));
    }
};

Will do the trick.

Now we are notifying the view every time an item in the collection is changed.</p>
This works as a start, but it is not very elegant in my opinon. Also, when an item is added to the collection, the view will not be updated of it’s PropertyChanged.

So what I’d like to do, is wrap these two events (CollectionChanged and PropertyChanged) together in a (hopefully) more elegant and reusable way. The idea is to have “anything that affects the outcome
This is my helper class which takes care of this.

public class ListCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
    #region Private fields
    private ICollectionView view;
    #endregion

    #region Public constructors
    public ListCollection() { Setup(); }
    public ListCollection(List<T> list) : base(list) { Setup(); }
    public ListCollection(IEnumerable<T> collection) : base(collection) { Setup(); }
    #endregion

    private void Setup()
    {
        CollectionChanged += (s, e) =>
        {
            if (e.Action == NotifyCollectionChangedAction.Add | e.Action == NotifyCollectionChangedAction.Replace)
            {
                foreach (T item in e.NewItems)
                {
                    item.PropertyChanged += (sender, args) => OnItemPropertyChanged(item, args);
                }
            }
            if (e.Action == NotifyCollectionChangedAction.Remove)
            {
                foreach (T item in e.OldItems)
                {
                    item.PropertyChanged -= (sender, args) => OnItemPropertyChanged(item, args);
                }
            }
        };
    }

    private void OnItemPropertyChanged(T item, PropertyChangedEventArgs e)
    {
        ItemPropertyChangedHandler handler = ItemPropertyChanged;
        if (handler != null)
        {
            handler((T)item, e);
        }
    }

    public event ItemPropertyChangedHandler ItemPropertyChanged;

    public delegate void ItemPropertyChangedHandler(T item, PropertyChangedEventArgs e);
}

It’s a class which inherits ObservableCollection<T> and after initialization it sets up that when the collection is changed, it adds or removes an event handler on the added/removed items. The class exposes an event “ItemPropertyChanged” which is fired every time an item in the collection fires PropertyChanged.
Usage can look like this:

CustomerList = new ListCollection<Customer>(customerList);
CustomerList.ItemPropertyChanged += (s, e) =>
{
    if (e.PropertyName == "Balance")
    {
        OnPropertyChanged("CustomerAverageBalance");
    }
};

Paul Stovell has written about similar problem (Introducing Observal), which could probably solve the above problem also in a very elegant, but for my purposes, too complex way.

Leave a Reply

You must be logged in to post a comment.