wpf - MVVM and collections of VMs -
a common senario: model collection of item models.
e.g house collection of people.
how structure correctly mvvm - particulary regard updating model , viewmodel collections additions , deletes?
model house
contains collection of model people
(normally list<people>
).
view model housevm
contains house object wraps , observablecollection of view model peoplevm
(observablecollection<peoplevm>
). note end here housevm holding 2 collections (that require syncing):
1. housevm.house.list<people>
2. housevm.observablecollection<peoplevm>
when house updated new people (add) or people leave (remove) event must handled in both collections model house people collection and the vm housevm peoplevm observablecollection.
is structure correct mvvm?
there anyway avoid having double update adds , removes?
your general approach fine mvvm, having viewmodel exposing collection of other viewmodels common scenario, use on place. not recommend exposing items directly in viewmodel, nicodemus13 said, end view binding models without viewmodels in between collection's items. so, answer first question is: yes, valid mvvm.
the problem addressing in second question synchronization between list of people models in house model , list of people viewmodels in house viewmodel. have manually. so, no there no way avoid this.
what can do: implement custom observablecollection<t>
, viewmodelcollection<t>
, pushes it's changes underlying collection. 2 way synching, make model's collection observablecollection<> , register collectionchanged
event in viewmodelcollection.
this implementation. uses viewmodelfactory service , on, have @ general principal. hope helps...
/// <summary> /// observable collection of viewmodels pushes changes related collection of models /// </summary> /// <typeparam name="tviewmodel">type of viewmodels in collection</typeparam> /// <typeparam name="tmodel">type of models in underlying collection</typeparam> public class vmcollection<tviewmodel, tmodel> : observablecollection<tviewmodel> tviewmodel : class, iviewmodel tmodel : class { private readonly object _context; private readonly icollection<tmodel> _models; private bool _synchdisabled; private readonly iviewmodelprovider _viewmodelprovider; /// <summary> /// constructor /// </summary> /// <param name="models">list of models synch with</param> /// <param name="viewmodelprovider"></param> /// <param name="context"></param> /// <param name="autofetch"> /// determines whether collection of viewmodels should /// fetched model collection on construction /// </param> public vmcollection(icollection<tmodel> models, iviewmodelprovider viewmodelprovider, object context = null, bool autofetch = true) { _models = models; _context = context; _viewmodelprovider = viewmodelprovider; // register change handling synchronization // viewmodels models collectionchanged += viewmodelcollectionchanged; // if model collection observable register change // handling synchronization models viewmodels if (models observablecollection<tmodel>) { var observablemodels = models observablecollection<tmodel>; observablemodels.collectionchanged += modelcollectionchanged; } // fecth viewmodels if (autofetch) fetchfrommodels(); } /// <summary> /// collectionchanged event of viewmodelcollection /// </summary> public override sealed event notifycollectionchangedeventhandler collectionchanged { add { base.collectionchanged += value; } remove { base.collectionchanged -= value; } } /// <summary> /// load vm collection model collection /// </summary> public void fetchfrommodels() { // deactivate change pushing _synchdisabled = true; // clear collection clear(); // create , add new vm each model foreach (var model in _models) addformodel(model); // reactivate change pushing _synchdisabled = false; } private void viewmodelcollectionchanged(object sender, notifycollectionchangedeventargs e) { // return if synchronization internally disabled if (_synchdisabled) return; // disable synchronization _synchdisabled = true; switch (e.action) { case notifycollectionchangedaction.add: foreach (var m in e.newitems.oftype<iviewmodel>().select(v => v.model).oftype<tmodel>()) _models.add(m); break; case notifycollectionchangedaction.remove: foreach (var m in e.olditems.oftype<iviewmodel>().select(v => v.model).oftype<tmodel>()) _models.remove(m); break; case notifycollectionchangedaction.reset: _models.clear(); foreach (var m in e.newitems.oftype<iviewmodel>().select(v => v.model).oftype<tmodel>()) _models.add(m); break; } //enable synchronization _synchdisabled = false; } private void modelcollectionchanged(object sender, notifycollectionchangedeventargs e) { if (_synchdisabled) return; _synchdisabled = true; switch (e.action) { case notifycollectionchangedaction.add: foreach (var m in e.newitems.oftype<tmodel>()) this.addifnotnull(createviewmodel(m)); break; case notifycollectionchangedaction.remove: foreach (var m in e.olditems.oftype<tmodel>()) this.removeifcontains(getviewmodelofmodel(m)); break; case notifycollectionchangedaction.reset: clear(); fetchfrommodels(); break; } _synchdisabled = false; } private tviewmodel createviewmodel(tmodel model) { return _viewmodelprovider.getfor<tviewmodel>(model, _context); } private tviewmodel getviewmodelofmodel(tmodel model) { return items.oftype<iviewmodel<tmodel>>().firstordefault(v => v.isviewmodelof(model)) tviewmodel; } /// <summary> /// adds new viewmodel specified model instance /// </summary> /// <param name="model">model create viewmodel for</param> public void addformodel(tmodel model) { add(createviewmodel(model)); } /// <summary> /// adds new viewmodel new model instance of specified type, /// modeltype or derived model type /// </summary> /// <typeparam name="tspecificmodel">type of model add viewmodel for</typeparam> public void addnew<tspecificmodel>() tspecificmodel : tmodel, new() { var m = new tspecificmodel(); add(createviewmodel(m)); } }
Comments
Post a Comment