WPF List of ViewModels bound to a list of model objects
In the model I have:
public ObservableCollection<Item> Items { get; private set; }
In the ViewModel, I have a corresponding list of ItemViewModels. I would like this list to be anchored to the list of models from two sides:
public ObservableCollection<ItemViewModel> ItemViewModels ...
In XAML, I'll bind (in this case, a TreeView) to the ItemViewModels property.
My question is, what's going on in the "..." in the ViewModel shown above? I hope that a line or two of code will link to these two ObservableCollections (providing a ViewModel type for each model object). However, I'm afraid you have to put together a bunch of code to handle the Items.CollectionChanged event and manually update the ItemViewModels, building ViewModels as needed, and the corresponding opposite that will update the Items collection based on changes in the ItemViewModels.
Thanks!
Eric
a source to share
You can use the following class:
public class BoundObservableCollection<T, TSource> : ObservableCollection<T>
{
private ObservableCollection<TSource> _source;
private Func<TSource, T> _converter;
private Func<T, TSource, bool> _isSameSource;
public BoundObservableCollection(
ObservableCollection<TSource> source,
Func<TSource, T> converter,
Func<T, TSource, bool> isSameSource)
: base()
{
_source = source;
_converter = converter;
_isSameSource = isSameSource;
// Copy items
AddItems(_source);
// Subscribe to the source CollectionChanged event
_source.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(_source_CollectionChanged);
}
private void AddItems(IEnumerable<TSource> items)
{
foreach (var sourceItem in items)
{
Add(_converter(sourceItem));
}
}
void _source_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
AddItems(e.NewItems.Cast<TSource>());
break;
case NotifyCollectionChangedAction.Move:
// Not sure what to do here...
break;
case NotifyCollectionChangedAction.Remove:
foreach (var sourceItem in e.OldItems.Cast<TSource>())
{
var toRemove = this.First(item => _isSameSource(item, sourceItem));
this.Remove(toRemove);
}
break;
case NotifyCollectionChangedAction.Replace:
for (int i = e.NewStartingIndex; i < e.NewItems.Count; i++)
{
this[i] = _converter((TSource)e.NewItems[i]);
}
break;
case NotifyCollectionChangedAction.Reset:
this.Clear();
this.AddItems(_source);
break;
default:
break;
}
}
}
Use it like this:
var models = new ObservableCollection<Model>();
var viewModels =
new BoundObservableCollection<ViewModel, Model>(
models,
m => new ViewModel(m), // creates a ViewModel from a Model
(vm, m) => vm.Model.Equals(m)); // checks if the ViewModel corresponds to the specified model
BoundObservableCollection
will update when ObservableCollection
changed, but not vice versa (you will have to override several methods for this)
a source to share
Yes, your fears are correct, you will have to wrap all the functionality ObservableCollection
.
My return question is why you want the model wrapper around to look like a good model already? The View Model is useful if your data model is based on some incompatible business logic. Typically, this business / data layer has one or two ways to retrieve data and notify external observers of changes, which are easily handled by the view model and converted into changes in ObservableCollection
. In fact in .NET 3.5 it ObservableCollection
was part of WindowsBase.dll, so it was usually not used in data models in the first place.
My suggestion is that either the logic that fills / modifies ObservableCollection
should be carried over from your data model to the view model, or you just need to bind directly to the layer you now call the data model and just call it what it is. View model.
You can obviously write a helper class that will sync the two collections using some kind of lambdas converter ( Item
to ItemViewModel
and from) and use it all over the place (make sure you handle element uniqueness correctly), however IMHO this approach spawns excessive number of wrapper classes, and each layer reduces functionality and complicates. Which is exactly the opposite of MVVM goals.
a source to share