问题
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 two-way bound to the model's list:
public ObservableCollection<ItemViewModel> ItemViewModels ...
In the XAML, I will bind (in this case a TreeView) to the ItemViewModels property.
My question is, what goes in the "..." in the ViewModel shown above? I am hoping for a line or two of code to binds these two ObservableCollections (providing the type of the ViewModel to construct for each model object). However, what I'm fearing is necessary is a bunch of code to handle the Items.CollectionChanged event and manually updates the ItemViewModels list by constructing ViewModels as necessary, and the corresponding opposite that will update the Items collection based on changes to ItemViewModels.
Thanks!
Eric
回答1:
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's 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 as follows :
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
The BoundObservableCollection
will be updated when the ObservableCollection
will change, but not the other way around (you would have to override a few methods to do that)
回答2:
Yes, your fears are true, you'd have to wrap all ObservableCollection
functionality.
My return question is though, why would you want to have view-model wrapper around already what seems to be nice model? View model is useful if your data model is based on some unbindable business logic. Normally this business/data layer has one or two ways of retrieving data and notifying external observers of its changes which are easily handled by view model and converted into changes to ObservableCollection
. In fact in .NET 3.5 ObservableCollection
was part of WindowsBase.dll, so normally it wouldn't be used in data models in the first place.
My suggestion is either the logic which populates/modifies ObservableCollection
should be moved from your data model into view model, or you should just bind directly to the layer you currently call data model and just call it what it is. A view model.
You can obviously write a helper class which will be syncing two collections using some converter lambdas (from Item
to ItemViewModel
and backward) and use it all over places like this (make sure you handle item uniqueness properly though), however IMHO this approach spawns redundant amount of wrapper classes, and each layer reduces functionality and adds complexity. Which is exactly the opposite of MVVM goals.
来源:https://stackoverflow.com/questions/2853276/wpf-list-of-viewmodels-bound-to-list-of-model-objects