Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>Your general approach is perfectly fine MVVM, having a ViewModel exposing a collection of other ViewModels is a very common scenario, which I use all over the place. I would not recommend exposing items directly in a ViewModel, like nicodemus13 said, as you end up with your view binding to models without ViewModels in between for your collection's items. So, the answer to your first question is: Yes, this is valid MVVM.</p> <p>The problem you are addressing in your second question is the synchronization between the list of people models in your house model and the list of people ViewModels in your house ViewModel. You have to do this manually. So, no there is no way to avoid this.</p> <p><img src="https://i.stack.imgur.com/29Z8S.png" alt="enter image description here"></p> <p>What you can do: Implement a custom <code>ObservableCollection&lt;T&gt;</code>, <code>ViewModelCollection&lt;T&gt;</code>, which pushes it's changes to an underlying collection. To get two way synching, make the model's collection an ObservableCollection&lt;> too and register to the <code>CollectionChanged</code> event in your ViewModelCollection.</p> <p>This is my implementation. It uses a ViewModelFactory service and so on, but just have a look at the general principal. I hope it helps... </p> <pre class="lang-cs prettyprint-override"><code>/// &lt;summary&gt; /// Observable collection of ViewModels that pushes changes to a related collection of models /// &lt;/summary&gt; /// &lt;typeparam name="TViewModel"&gt;Type of ViewModels in collection&lt;/typeparam&gt; /// &lt;typeparam name="TModel"&gt;Type of models in underlying collection&lt;/typeparam&gt; public class VmCollection&lt;TViewModel, TModel&gt; : ObservableCollection&lt;TViewModel&gt; where TViewModel : class, IViewModel where TModel : class { private readonly object _context; private readonly ICollection&lt;TModel&gt; _models; private bool _synchDisabled; private readonly IViewModelProvider _viewModelProvider; /// &lt;summary&gt; /// Constructor /// &lt;/summary&gt; /// &lt;param name="models"&gt;List of models to synch with&lt;/param&gt; /// &lt;param name="viewModelProvider"&gt;&lt;/param&gt; /// &lt;param name="context"&gt;&lt;/param&gt; /// &lt;param name="autoFetch"&gt; /// Determines whether the collection of ViewModels should be /// fetched from the model collection on construction /// &lt;/param&gt; public VmCollection(ICollection&lt;TModel&gt; models, IViewModelProvider viewModelProvider, object context = null, bool autoFetch = true) { _models = models; _context = context; _viewModelProvider = viewModelProvider; // Register change handling for synchronization // from ViewModels to Models CollectionChanged += ViewModelCollectionChanged; // If model collection is observable register change // handling for synchronization from Models to ViewModels if (models is ObservableCollection&lt;TModel&gt;) { var observableModels = models as ObservableCollection&lt;TModel&gt;; observableModels.CollectionChanged += ModelCollectionChanged; } // Fecth ViewModels if (autoFetch) FetchFromModels(); } /// &lt;summary&gt; /// CollectionChanged event of the ViewModelCollection /// &lt;/summary&gt; public override sealed event NotifyCollectionChangedEventHandler CollectionChanged { add { base.CollectionChanged += value; } remove { base.CollectionChanged -= value; } } /// &lt;summary&gt; /// Load VM collection from model collection /// &lt;/summary&gt; public void FetchFromModels() { // Deactivate change pushing _synchDisabled = true; // Clear collection Clear(); // Create and add new VM for each model foreach (var model in _models) AddForModel(model); // Reactivate change pushing _synchDisabled = false; } private void ViewModelCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { // Return if synchronization is internally disabled if (_synchDisabled) return; // Disable synchronization _synchDisabled = true; switch (e.Action) { case NotifyCollectionChangedAction.Add: foreach (var m in e.NewItems.OfType&lt;IViewModel&gt;().Select(v =&gt; v.Model).OfType&lt;TModel&gt;()) _models.Add(m); break; case NotifyCollectionChangedAction.Remove: foreach (var m in e.OldItems.OfType&lt;IViewModel&gt;().Select(v =&gt; v.Model).OfType&lt;TModel&gt;()) _models.Remove(m); break; case NotifyCollectionChangedAction.Reset: _models.Clear(); foreach (var m in e.NewItems.OfType&lt;IViewModel&gt;().Select(v =&gt; v.Model).OfType&lt;TModel&gt;()) _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&lt;TModel&gt;()) this.AddIfNotNull(CreateViewModel(m)); break; case NotifyCollectionChangedAction.Remove: foreach (var m in e.OldItems.OfType&lt;TModel&gt;()) this.RemoveIfContains(GetViewModelOfModel(m)); break; case NotifyCollectionChangedAction.Reset: Clear(); FetchFromModels(); break; } _synchDisabled = false; } private TViewModel CreateViewModel(TModel model) { return _viewModelProvider.GetFor&lt;TViewModel&gt;(model, _context); } private TViewModel GetViewModelOfModel(TModel model) { return Items.OfType&lt;IViewModel&lt;TModel&gt;&gt;().FirstOrDefault(v =&gt; v.IsViewModelOf(model)) as TViewModel; } /// &lt;summary&gt; /// Adds a new ViewModel for the specified Model instance /// &lt;/summary&gt; /// &lt;param name="model"&gt;Model to create ViewModel for&lt;/param&gt; public void AddForModel(TModel model) { Add(CreateViewModel(model)); } /// &lt;summary&gt; /// Adds a new ViewModel with a new model instance of the specified type, /// which is the ModelType or derived from the Model type /// &lt;/summary&gt; /// &lt;typeparam name="TSpecificModel"&gt;Type of Model to add ViewModel for&lt;/typeparam&gt; public void AddNew&lt;TSpecificModel&gt;() where TSpecificModel : TModel, new() { var m = new TSpecificModel(); Add(CreateViewModel(m)); } } </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload