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:
_friendsName = new ObservableCollection<string>(myBusinessObject.FriendsName);
_friendsName.CollectionChanged += new NotifyCollectionChangedEventHandler
(_friendsName_CollectionChanged);
...
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;
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) :
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.
MVMCollectionSyncher _friendsName = new MVMCollectionSyncher
<string>(myBusinessObject.FriendsName);
...
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:
CodeProject