Click here to Skip to main content
15,849,678 members
Articles / Programming Languages / C#

M-V-VM: How to keep collections of ViewModel and Model in sync

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
2 Mar 2010Ms-PL1 min read 23.2K   10   2
As pointed out in this post, collections of the ViewModels and the models are not in sync.

As pointed out in this post, collections of the ViewModels and the models are not in sync. This is because we do not directly access the model but an ObservableCollection (in the viewModel) which contains the object of the original collection (in the model) and these two collections are not the same...

As pointed out in the comments on CodeProject, there is a workaround. Here, I try to present two of them!

A First Solution: Register to the Wrapping Collection Changes

The first solution is to register to the events of the ObservableCollection in your ViewModel and to translate the changes to the wrapped collection.
It is very straighforward, but it becomes very fastidious if you have a lot of collections to deal with.

Here is the code:

C#
//Wrap the business object collection
_friendsName = new ObservableCollection<string>(myBusinessObject.FriendsName);
//Register to the wrapping CollectionChanged event
_friendsName.CollectionChanged += new NotifyCollectionChangedEventHandler
	(_friendsName_CollectionChanged);
 
...
 
//Translate the changes to the underlying collection
void _friendsName_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
 switch (e.Action)
 {
  case NotifyCollectionChangedAction.Add:
   _myBusinessObject.FriendsName.AddRange(
    e.NewItems.OfType<String>()
    );
   break;
  case NotifyCollectionChangedAction.Remove:
   _myBusinessObject.FriendsName.RemoveAll(
    friendName => e.OldItems.Contains(friendName)
    );
   break;
  //Reset = Clear
  case NotifyCollectionChangedAction.Reset:
   _myBusinessObject.FriendsName.Clear();
   break;
 }
}

Another Solution: Create a Proxy

You also can create a class which will act as a Proxy to the businessObject. Its only function will be to leverage the INotifyCollectionChanged events when necessary. I called it MVMCollectionSyncher for ModelViewModelCollectionSyncher and here is the code (which is very straightforward) :

C#
public class MVMCollectionSyncher<T> : ICollection<T>, 
	IDisposable, INotifyCollectionChanged
{
 #region fields
 private ICollection<T> _wrappedCollection;
 #endregion
 
 public MVMCollectionSyncher(ICollection<T> wrappedCollection)
 {
  if (wrappedCollection == null)
   throw new ArgumentNullException(
    "wrappedCollection",
    "wrappedCollection must not be null.");
  _wrappedCollection = wrappedCollection;
 }
 
 #region ICollection<T> Members
 public void Add(T item)
 {
  _wrappedCollection.Add(item);
  FireCollectionChanged(
   new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
 }
 
 public void Clear()
 {
  FireCollectionChanged(
   new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
  _wrappedCollection.Clear();
 }
 
 public bool Contains(T item)
 {
  return _wrappedCollection.Contains(item);
 }
 
 public void CopyTo(T[] array, int arrayIndex)
 {
  _wrappedCollection.CopyTo(array, arrayIndex);
 }
 
 public int Count
 {
  get { return _wrappedCollection.Count; }
 }
 
 public bool IsReadOnly
 {
  get { return _wrappedCollection.IsReadOnly; }
 }
 
 public bool Remove(T item)
 {
  if (_wrappedCollection.Remove(item)) {
   FireCollectionChanged(
     new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
   return true;
  }
  return false;
 }
 
 #endregion
 
 #region IEnumerable<T> Members
 public IEnumerator<T> GetEnumerator()
 {
  return _wrappedCollection.GetEnumerator();
 }
 #endregion
 
 #region IEnumerable Members
 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
 {
  return _wrappedCollection.GetEnumerator();
 }
 #endregion
 
 #region INotifyCollectionChanged Members
 public event NotifyCollectionChangedEventHandler CollectionChanged;
 private void FireCollectionChanged(NotifyCollectionChangedEventArgs eventArg)
 {
  NotifyCollectionChangedEventHandler handler = CollectionChanged;
  if (handler != null) handler.Invoke(this, eventArg);
 }
 #endregion
 
 #region IDisposable Members
 public void Dispose() { _wrappedCollection = null; }
 #endregion
}

Then in your ViewModel, instead of presenting an ObservableCollection<>, you offer an MVMCollectionSyncher with this code.

C#
//Creation
MVMCollectionSyncher _friendsName = new MVMCollectionSyncher
				<string>(myBusinessObject.FriendsName);
...
//Property
public MVMCollectionSyncher<String> FriendsName
{
 get { return _friendsName; }
 set
 {
  if (value != null){
   _friendsName.Dispose();
   _friendsName = value;
   FirePropertyChanged("FriendsName");
  }
 }
}

Here are some links dealing with the same subject:

Shout it kick it on DotNetKicks.com
This article was originally posted at http://blog.lexique-du-net.com/index.php

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
Software Developer http://wpf-france.fr
France (Metropolitan) France (Metropolitan)
Jonathan creates software, mostly with C#,WPF and XAML.

He really likes to works on every Natural User Interfaces(NUI : multitouch, touchless, etc...) issues.



He is awarded Microsoft MVP in the "Client Application Development" section since 2011.


You can check out his WPF/C#/NUI/3D blog http://www.jonathanantoine.com.

He is also the creator of the WPF French community web site : http://wpf-france.fr.

Here is some videos of the projects he has already work on :

Comments and Discussions

 
QuestionCan you provide an example? Pin
ziza8427-Aug-10 4:07
ziza8427-Aug-10 4:07 
AnswerRe: Can you provide an example? Pin
Lance Contreras26-Mar-13 4:02
Lance Contreras26-Mar-13 4:02 
In the class where your observablecollection is a member, you can register to the collection changed event. Here's a dirty example to give you an idea.

<pre lang="c#">
public class MainVM: ViewModelBase
{
ContactCollection modelCollection;
ObservableCollection<ContactViewModel> contacts;

public MainVM(ContactCollection modelCollection)
{
if (Contacts == null)
Contacts = new ObservableCollection<ContactViewModel>();

foreach (Contact contact in modelCollection)
{
Contacts.Add(new ContactViewModel(contact));
}

Contacts.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Contacts_CollectionChanged);
}

void Contacts_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
foreach (ContactViewModel con in e.NewItems)
modelCollection.Add(con.Model as Contact);
}
else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
{
foreach (ContactViewModel con in e.NewItems)
modelCollection.Remove(con.Model as Contact);
}
}

public ObservableCollection<ContactViewModel> Contacts
{
get { return contacts; }
set
{
contacts = value;
RaisePropertyChanged("Contacts");
}
}
}
</pre>

Lance Contreras
Software Developer

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.