EnumerableCollection

Imagine the following scenario. You use MVVM, you have two classes on your model:

public class Child
{
}


public class Parent
{
    private ObservableCollection<Child> _children;
    public ObservableCollection<Child> Children
    {
        get { return _children ?? (_children = new ObservableCollection<Child>()); }
    }
}


You have two more classes on your ViewModel:


public class ChildViewModel
{
    public ChildViewModel(Child child)
    {
        Child = child;
    }

    private Child Child { get; set; }
}


public class ParentViewModel 
{
    public ParentViewModel(Parent parent = null)
    {
        Model = parent ?? new Parent();
    }


    private Parent Model { get; set; }


    private ObservableCollection<ChildViewModel> _children;
    public ObservableCollection<ChildViewModel> Children
    {
        get { return _children ?? (_children = new ObservableCollection<ChildViewModel>(Model.Children.Select(c => new ChildViewModel(c)))); }
    }
}


Now, how to fix the the synchronization problem between the two collections?


I think that, in this case, the ViewModel should not be a collection but only an IEnumerable.


So now, you can write the following code:


public class ParentViewModel : INotifyPropertyChanged
{
    public ParentViewModel(Parent parent = null)
    {
        Model = parent ?? new Parent();
        Model.Children.CollectionChanged += delegate { RaisePropertyChanged(() => Children); };
    }


    private Parent Model { get; set; }


    public IEnumerable<ChildViewModel> Children
    {
        get { return Model.Children.Select(c => new ChildViewModel(c)); }
    }


    #region INotifyPropertyChanged Members
    public void RaisePropertyChanged<T>(Expression<Func<T>> exp)
    {
        var memberExpression = exp.Body as MemberExpression;
        if (memberExpression == null)
            return;
        string propertyName = memberExpression.Member.Name;
        if (propertyName != null && PropertyChanged != null)       
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
    public event PropertyChangedEventHandler PropertyChanged;
    #endregion
}


It works. However, if you add or remove a child to Parent.Children collection, all ViewModel Children are recreated which can be a big problem with binding.


So in fact, I want an IEnumerable<T> which also implements INotifyCollectionChanged.


So I propose a new class: EnumerableCollection


public interface IEnumerableCollection<out T> : IEnumerable<T>, INotifyCollectionChanged
{
    void RaiseCollectionChanged(NotifyCollectionChangedEventArgs e);
}





public class EnumerableCollection<T> : EnumerableCollection<T, T>
{
    public EnumerableCollection(ObservableCollection<T> observableCollection)
        : base(observableCollection, null)
    {
    }

    public EnumerableCollection(IEnumerable<ObservableCollection<T>> observableCollections)
        : base(observableCollections, null)
    {
    }

    public EnumerableCollection(params ObservableCollection<T>[] observableCollections)
        : this((IEnumerable<ObservableCollection<T>>)observableCollections)
    {
    }

    public EnumerableCollection(IEnumerable<IEnumerableCollection<T>> enumerableCollections)
        : base(enumerableCollections, null)
    {
    }

    public EnumerableCollection(params IEnumerableCollection<T>[] observableCollections)
        : this((IEnumerable<IEnumerableCollection<T>>)observableCollections)
    {
    }

    public EnumerableCollection(IEnumerable<T> enumerable)
        : base(enumerable, null)
    {
    }
}

public class EnumerableCollection<TSource, TResult> : IEnumerableCollection<TResult>, IEnumerable<TResult>, INotifyCollectionChanged
{
    private IEnumerable<TResult> _enumerable;

    #region ctors
    public EnumerableCollection(ObservableCollection<TSource> observableCollection, Func<TSource, TResult> selector)
    {
        observableCollection.CollectionChanged += (sender, e) => RaiseCollectionChanged(e);
        _enumerable = Convert(observableCollection, selector);
    }

    public EnumerableCollection(IEnumerable<ObservableCollection<TSource>> observableCollections, Func<TSource, TResult> selector)
    {
        foreach (ObservableCollection<TSource> observableCollection in observableCollections)
            observableCollection.CollectionChanged += (sender, e) => RaiseCollectionChanged(e);
        _enumerable = Convert(observableCollections.SelectMany(item => item), selector);
    }

    public EnumerableCollection(IEnumerable<IEnumerableCollection<TSource>> enumerableCollections, Func<TSource, TResult> selector)
    {
        foreach (EnumerableCollection<TSource> enumerableCollection in enumerableCollections)
            enumerableCollection.CollectionChanged += (sender, e) => RaiseCollectionChanged(e);
        _enumerable = Convert(enumerableCollections.SelectMany(item => item), selector);
    }

    public EnumerableCollection(IEnumerable<TSource> enumerable, Func<TSource, TResult> selector)
    {
        _enumerable = Convert(enumerable, selector);
    }
    #endregion ctors

    private IEnumerable<TResult> Convert(IEnumerable<TSource> source, Func<TSource, TResult> selector)
    {
        if (selector != null)
            return source.Select(selector);
        if (typeof(TSource) == typeof(TResult))
            return (IEnumerable<TResult>)source;
        throw new InvalidOperationException();
    }

    public void OrderBy<K>(Func<TResult, K> keySelector)
    {
        if (_enumerable == null)
            throw new InvalidOperationException();
        _enumerable = _enumerable.OrderBy(keySelector);
    }

    public void RaiseCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (CollectionChanged != null)
            CollectionChanged(this, e);
    }

    #region IEnumerable<T> Members
    public IEnumerator<TResult> GetEnumerator()
    {
        return _enumerable.GetEnumerator();
    }

    #endregion

    #region IEnumerable Members
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    #endregion

    #region INotifyCollectionChanged Members
    public event NotifyCollectionChangedEventHandler CollectionChanged;
    #endregion
}


With this, my ParentViewModel code becomes this:


public class ParentViewModel
{
    public ParentViewModel(Parent parent = null)
    {
        Model = parent ?? new Parent();
    }

    private Parent Model { get; set; }

    private EnumerableCollection<Child, ChildViewModel> _children;
    public EnumerableCollection<Child, ChildViewModel> Children
    {
        get { return _children ?? (_children = new EnumerableCollection<Child, ChildViewModel>(Model.Children, c => new ChildViewModel(c))); }
    }
}


Hope that helps
This entry was posted in 13461, 7671, 7672, 8708. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>