Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Building ViewModels based on nested Model Entities in WPF and MVVM Pattern

I have a problem understanding how to build view models based on the following models

(I simplified the models to be clearer)

public class Hit
{
   public bool On { get; set;}
   public Track Track { get; set; }
}
public class Track
{
   public ObservableCollection<Hit> Hits { get; set; }
   public LinearGradientBrush Color { get; set; }
   public Pattern Pattern { get; set; }
}
public class Pattern
{
   public string Name { get; set; }
   public ObservableCollection<Tracks> Tracks { get; set; }
}

Now, my problem is, how to build the ViewModels..

I need to keep the original relationships through the models, beacaus i have a Serialize() method on the Pattern that serializes it to an XML file.. (with the related Tracks and Hits)

To be able to bind the pattern to the user controls and it's nested templates I should also have a PatternViewModel with an ObservableCollection<TrackViewModel> in it, same thing for the TrackViewModel and the HitViewModel.. and i neet to have custom presentation properties on the view models that aren't part of the business object (colors and more..)

It just seem not a good thing to me to duplicate all of the relationships of the models on the view models... and keeping track of all this relations while coding the viewmodels is also much more error prone..

anyone has a better approach/solution?

like image 391
BFil Avatar asked Nov 22 '10 10:11

BFil


People also ask

Can a ViewModel have multiple models?

ViewModel is nothing but a single class that may have multiple models. It contains multiple models as a property. It should not contain any method. In the above example, we have the required View model with two properties.

What is the use of ViewModel in MVVM?

The purpose of ViewModel is to encapsulate the data for a UI controller to let the data survive configuration changes. For information about how to load, persist, and manage data across configuration changes, see Saved UI States.

What is the role of WPF in an MVVM based application?

The single most important aspect of WPF that makes MVVM a great pattern to use is the data binding infrastructure. By binding properties of a view to a ViewModel, you get loose coupling between the two and entirely remove the need for writing code in a ViewModel that directly updates a view.


2 Answers

One thing I've done, with some success, is to move the ObservableCollection out of the model. Here's my general pattern:

  • In the model objects, expose a property of type IEnumerable<TModel> that gives read-only access to the collection. Use a plain old List<TModel>, not an ObservableCollection, as the backing collection.
  • For code that needs to mutate the models' collections (add, delete, etc.), add methods to the model object. Don't have outside code directly manipulating the collection; encapsulate that inside methods on the model.
  • Add events to the model for each type of change you allow. For example, if your model only supports adding items to the end of the collection, and deleting items, then you would need an ItemAdded event and an ItemDeleted event. Create an EventArgs descendant that gives information about the item that was added. Fire these events from the mutation methods.
  • In your ViewModel, have an ObservableCollection<TNestedViewModel>.
  • Have the ViewModel hook the events on the model. Whenever the model says an item was added, instantiate a ViewModel and add it to the ViewModel's ObservableCollection. Whenever the model says an item was deleted, iterate the ObservableCollection, find the corresponding ViewModel, and remove it.
  • Apart from the event handlers, make sure all of the collection-mutation code is done via the model -- treat the ViewModel's ObservableCollection as strictly something for the view's consumption, not something you use in code.

This makes for a lot of duplicate code for each different ViewModel, but it's the best I've been able to come up with. It does at least scale based on the complexity you need -- if you have a collection that's add-only, you don't have to write much code; if you have a collection that supports arbitrary reordering, inserts, sorting, etc., it's much more work.

like image 191
Joe White Avatar answered Sep 27 '22 18:09

Joe White


I ended up using part of the solution that Joe White suggested, in a slighty differ manner

The solution was to just leave the models as they were at the beginning, and attaching to the collections an eventhandler for CollectionChanged of the inner collections, for example, the PatternViewModel would be:

public class PatternViewModel : ISerializable
{
    public Pattern Pattern { get; set; }
    public ObservableCollection<TrackViewModel> Tracks { get; set; }

    public PatternViewModel(string name)
    {
        Pattern = new Pattern(name);
        Tracks = new ObservableCollection<TrackViewModel>();
        Pattern.Tracks.CollectionChanged += new NotifyCollectionChangedEventHandler(Tracks_CollectionChanged);
    }

    void Tracks_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                foreach (Track track in e.NewItems)
                {
                    var position = Pattern.Tracks.IndexOf((Track) e.NewItems[0]);
                    Tracks.Insert(position,new TrackViewModel(track, this));
                }
                break;
            case NotifyCollectionChangedAction.Remove:
                foreach (Track track in e.OldItems)
                    Tracks.Remove(Tracks.First(t => t.Track == track));
                break;
            case NotifyCollectionChangedAction.Move:
                for (int k = 0; k < e.NewItems.Count; k++)
                {
                    var oldPosition = Tracks.IndexOf(Tracks.First(t => t.Track == e.OldItems[k]));
                    var newPosition = Pattern.Tracks.IndexOf((Track) e.NewItems[k]);
                    Tracks.Move(oldPosition, newPosition);
                }
                break;
        }
    }
}

So i can attach the new Color/Style/Command on the view models to keep my base models clean

And whenever I add/remove/move items in the base models collection, the view models collections remain in sync with each other

Luckily I don't have to manage lots of object in my application, so duplicated data and performance won't be a problem

I don't like it too much, but it works well, and it's not a huge amount of work, just an event handler for the view model that contains others view model collections (in my case, one for PatternViewModel to sync TrackViewModels and another on TrackViewModel to manage HitViewModels)

Still interested in your thoughs or better ideas =)

like image 44
BFil Avatar answered Sep 27 '22 19:09

BFil