An ObservableCollection or BindingList of View Models

An ObservableCollection<T> will notify a view of insertions, deletions, and replacements. Conversely, a BindingList<T> will accept insertions from a view. To be most useful, these collection types should be data bound to a view. In MVVM, that means that these should be properties of a view model. Moreover, it means that these should be collections of view models.

View models are projections of model objects. If an Order has a collection of OrderLines, then an OrderViewModel should have a collection of OrderLineViewModels. In this situation, you need to keep a collection of view models synchronized with a collection of models.

Here are a couple of helper classes that do just that: MappedObservableCollection and MappedBindingList.

MappedObservableCollection takes a mapping function and a source collection. The map creates a view model for each model object. It will produce an observable collection of view models. If the source is observable (i.e. implements INotifyCollectionChanged), then it will mirror any changes in the source collection to the target collection.

public class MappedObservableCollection<TSource, TTarget>
{
    private Func<TSource, TTarget> _map;
    private IEnumerable<TSource> _sourceCollection;
    private ObservableCollection<TTarget> _targetCollection = new ObservableCollection<TTarget>();

    public MappedObservableCollection(Func<TSource, TTarget> map, IEnumerable<TSource> sourceCollection)
    {
        _map = map;
        _sourceCollection = sourceCollection;

        var notifyCollectionChanged = sourceCollection as INotifyCollectionChanged;
        if (notifyCollectionChanged != null)
            notifyCollectionChanged.CollectionChanged += SourceCollectionChanged;
        PopulateTargetCollection();
    }

    public ObservableCollection<TTarget> TargetCollection
    {
        get { return _targetCollection; }
    }

    private void SourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.Action == NotifyCollectionChangedAction.Add)
        {
            // Add the corresponding targets.
            int index = e.NewStartingIndex;
            foreach (TSource item in e.NewItems)
            {
                _targetCollection.Insert(index, _map(item));
                ++index;
            }
        }
        else if (e.Action == NotifyCollectionChangedAction.Remove)
        {
            // Delete the corresponding targets.
            for (int i = 0; i < e.OldItems.Count; i++)
            {
                _targetCollection.RemoveAt(e.OldStartingIndex);
            }
        }
        else if (e.Action == NotifyCollectionChangedAction.Replace)
        {
            // Replace the corresponding targets.
            for (int i = 0; i < e.OldItems.Count; i++)
            {
                _targetCollection[i + e.OldStartingIndex] = _map((TSource)e.NewItems[i + e.NewStartingIndex]);
            }
        }
        else
        {
            // Just give up and start over.
            _targetCollection.Clear();
            PopulateTargetCollection();
        }
    }

    private void PopulateTargetCollection()
    {
        foreach (TSource item in _sourceCollection)
        {
            _targetCollection.Add(_map(item));
        }
    }
}

MappedBindingList performs a similar function, but it generates a BindingList<T>. BindingList<T> is useful for DataGrid, which allows the user to add new rows. When the user creates a new row, MappedBindingList calls a factory to get a new model object. Then it maps it into the target collection as a view model.

public class MappedBindingList<TSource, TTarget>
{
    private Func<TSource, TTarget> _map;
    private IEnumerable<TSource> _sourceCollection;
    private BindingList<TTarget> _targetCollection = new BindingList<TTarget>();
    private Func<TSource> _factory;
    
    public MappedBindingList(IEnumerable<TSource> sourceCollection, Func<TSource, TTarget> map, Func<TSource> factory)
    {
        _map = map;
        _sourceCollection = sourceCollection;
        _factory = factory;

        _targetCollection.AllowNew = true;
        _targetCollection.AllowEdit = true;
        _targetCollection.AllowRemove = true;
        _targetCollection.AddingNew += TargetCollection_AddingNew;

        var notifyCollectionChanged = sourceCollection as INotifyCollectionChanged;
        if (notifyCollectionChanged != null)
            notifyCollectionChanged.CollectionChanged += SourceCollectionChanged;
        PopulateTargetCollection();
    }

    private void TargetCollection_AddingNew(object sender, AddingNewEventArgs e)
    {
        e.NewObject = _map(_factory());
    }

    public BindingList<TTarget> TargetCollection
    {
        get { return _targetCollection; }
    }

    private void SourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.Action == NotifyCollectionChangedAction.Add)
        {
            // Add the corresponding targets.
            int index = e.NewStartingIndex;
            foreach (TSource item in e.NewItems)
            {
                _targetCollection.Insert(index, _map(item));
                ++index;
            }
        }
        else if (e.Action == NotifyCollectionChangedAction.Remove)
        {
            // Delete the corresponding targets.
            for (int i = 0; i < e.OldItems.Count; i++)
            {
                _targetCollection.RemoveAt(e.OldStartingIndex);
            }
        }
        else if (e.Action == NotifyCollectionChangedAction.Replace)
        {
            // Replace the corresponding targets.
            for (int i = 0; i < e.OldItems.Count; i++)
            {
                _targetCollection[i + e.OldStartingIndex] = _map((TSource)e.NewItems[i + e.NewStartingIndex]);
            }
        }
        else
        {
            // Just give up and start over.
            _targetCollection.Clear();
            PopulateTargetCollection();
        }
    }

    private void PopulateTargetCollection()
    {
        foreach (TSource item in _sourceCollection)
        {
            _targetCollection.Add(_map(item));
        }
    }
}

These mapped collections are useful when using a framework like MVVM Light. But if you are using Update Controls, you’ll just want to use linq. It keeps your view model code cleaner, and also updates your collection when filter or sort properties change.

Comments

Very nice

Thanks for sharing, very useful.

Example Application

Nice but usage is not clear to me. A small example which use those helper classes would be great.
Thanks.

How to know if a property is up to date now or not?

Can I know whether a property is out of date or not by myself ?
I want to do the following code:

private int _y;
public int Y
{
get
{
if ( IsOutOfDate == true )
_y = _x*_x + 2;
return _y;
}
set
{
_y = value;
( notify Y changed here )
}
}

private int _x;
public int X
{
get
{
if ( IsOutOfDate == true )
_x = sqrt(_y - 2);
return _x;
}
set
{
_x = value;
( notify X changed here )
}
}

so that _x and _y can be inter-dependant. Whenever _x changes, Y will recalculate its value, otherwise, it will just return its own field, and vice versa. This is especially useful when the "get method" involved heavy calculation like linq or something.

I believe with updatecontrols, there will be no need for the "Multibinding" or "bindingconverter" of wpf.

Inter-dependent properties

The simple way to model that is to have one Independent and one Dependent:

private Independent<int> _x = new Independent<int>();
private Dependent<int> _y;
 
public MyClass()
{
  _y = new Dependent<int>(() => _x.Value*_x.Value + 2);
}
 
public int X
{
  get { return _x.Value; }
  set { _x.Value = value; }
}
 
public int Y
{
  get { return _y.Value; }
  set { _x.Value = Math.Sqrt(value-2); }
}

This will calculate the inverse immediately, and then calculate Y only when it is out-of-date. Then it will cache the value for the next get.

There is a more complex way to model that situation if you anticipate that the inverse will be costly to calculate and rarely used. You could have an Independent switch.

private Independent<bool> _userHasSpecifiedX = new Independent<bool>();
private Independent<int> _usersX = new Independent<bool>();
private Independent<int> _usersY = new Independent<bool>();
 
private Dependent<int> _x;
private Dependent<int> _y;
 
public MyClass()
{
  _x = new Dependent<int>(() => _userHasSpecifiedX
    ? _usersX.Value
    : Math.Sqrt(_usersY.Value-2));

  _y = new Dependent<int>(() => _userHasSpecifiedX
    ? _usersX.Value*_usersX.Value + 2
    : _usersY.Value);
}
 
public int X
{
  get { return _x.Value; }
  set { _usersX.Value = value; _userHasSpecifiedX.Value = true; }
}
 
public int Y
{
  get { return _y.Value; }
  set { _usersY.Value = value; _userHasSpecifiedX.Value = false; }
}

When the switch is true, then _y depends upon _usersX. When it is false, _x depends upon _usersY. Again, the calculation is only performed once when the values are out-of-date, and then cached until the values change again.

Installer can not be installed

I have a problem installing updatecontrols.msi, it says that "the installer was interrupted before update controls could be installed"
I've retried so many times and still it didn't work.

Could anyone tell me how to solve this problem?

Same Problem here

Latest installer 2.2.1.1 aborts every installation attempt.
Version 2.2.1.0 is installing fine.
Also installation of 2.2.1.1 through nuget is working.

Please try NuGet

Thank you for your question. I’m sorry that the MSI installer isn’t working for you. I’ll see if I can reproduce the problem.

In the meantime, can you try the NuGet package? You’ll need to install the NuGet Visual Studio extension, if you haven’t already. Then you can add the UpdateControls package to your project directly from Visual Studio.

ANOTHER DUMB ***** PASTE AND COPY POSTING

AS USUAL ANOTHER DUMB **** **** **** GOAT **** ***** POST CRAPOLA PASTE AND COPY ARTICLE WITH NOW SOURCE CODE AND DEMO PROJECT FOR DOWNLOAD
hey MICHEAL *** **** YOU

Re: Please post the source code

Thank you for your thoughtful and grammatically creative request to post the source code. I've taken the liberty of redacting language that I'd prefer not to subject my readers to.

Please feel free to create a github repository for these two classes. Choose whatever namespace you like, so long as it does not include UpdateControls. These two classes are not necessary when using this library, so that would be a poor choice of name.

Also, please be sure to select an appropriate open source license. I prefer MIT or three-clause BSD.

Finally, compile your assembly, package it, and post it to NuGet. This will allow other people to use these two classes without the inconvenience of looking at their source code or understanding how they work.

Once you've done that, please feel free to post the links here. I'll be glad to share.

A benchmark to test performance ?

Hi,

I like "binding without InotifyPropertyChanged" but there are a benchmark to evaluate the performance?

Your framework is comaptible with WP7 ?
thanks

Performance

I have measured performance in terms of both time and memory usage. Some of those measurements are encoded in the unit tests that are part of the source code on Codeplex. Using these measurements, I was recently able to decrease memory consumption by 60%, and increase speed significantly.

So I have compared Update Controls performance version to version, but I have not compared it against other solutions.

And, yes, Update Controls works with Windows Phone. I am actively using it in three Windows Phone apps myself.