Welcome to WindowsClient.net | Sign in | Join
Friday, June 26, 2009 6:37 PM joeyw

Model-View-ViewModel – Real Time Models

The View Model is essentially a projection out of the model to make data-binding easier.  It’s one of those WPF things that makes life so much easier you cannot understand why a ViewModel class wasn’t created into the File|New WPF Project default template.  Before the ViewModel, spending a few months creating converters, wondering how many objects should go into a multi-bound converter and then keeping resource references up to date and named correctly, you really start to question this whole ‘declarative’ only model.

One problem that is still carried over from the world of 100 converters is synchronizing the GUI with model changes; that is making sure the GUI is hooked up to those INotifyPropertyChanged and CollectionChanged events coming out of your model.  This is especially important when you have data changing in your model in real-time.  This was tricky with Converters because you needed to make sure that the root bound object supported notifications and not the object being passed into the converter.  To get round this I’ve actually created a public “This” property on my model class just so the converter could use any property it liked from the object being bound and the view was correctly synchronized.

In the ViewModel world the trick is to make sure that INotifyPropertyChanged events get propagated back from the model through the ViewModel and into the View.  This means making sure the ViewModel subscribes to all its dependencies and re-raises the event for the View.  This can be quite tricky for ViewModel properties that are aggregate functions (like .Count or .Sum) and even worse if the ViewModel is a projection based on joins from multiple model collections.

I use a couple of ViewModel helper classes that do some of this event propagation.  There is nothing clever and there are better MVVM libraries out on the web, this is just to illustrate the point.  For a ViewModel base object I use the following generic class that is used to project from the UnderlyingModelType.

  1.     public class ViewModelObject<UnderlyingModelType> : ObservableObject, IDisposable where UnderlyingModelType : class, INotifyPropertyChanged
  2.     {
  3.         private UnderlyingModelType modelObject;
  4.         public ViewModelObject()
  5.         {
  6.         }
  7.         public ViewModelObject(UnderlyingModelType modelObject)
  8.             : base()
  9.         {
  10.             this.ModelObject = modelObject;
  11.         }
  12.         protected bool IsModelObjectNull
  13.         {
  14.             get { return (this.modelObject == null); }
  15.         }
  16.         virtual protected void OnModelObjectChanged(object sender, PropertyChangedEventArgs e)
  17.         {
  18.             UpdateViews();
  19.         }
  20.         public UnderlyingModelType ModelObject
  21.         {
  22.             get { return this.modelObject; }
  23.             set
  24.             {
  25.                 if (this.modelObject != null) OnUnsetModelObject(this.modelObject);
  26.                 this.modelObject = value;
  27.                 if (this.modelObject != null) OnSetModelObject(this.modelObject);
  28.             }
  29.         }
  30.         virtual protected void OnUnsetModelObject(UnderlyingModelType modelObject)
  31.         {
  32.             this.modelObject.PropertyChanged -= new PropertyChangedEventHandler(OnModelObjectChanged);
  33.         }
  34.         virtual protected void OnSetModelObject(UnderlyingModelType modelObject)
  35.         {
  36.             this.modelObject.PropertyChanged += new PropertyChangedEventHandler(OnModelObjectChanged);
  37.         }
  38.         {// on dispose disconnect from the model}
  39.         public virtual void Dispose()
  40.         {
  41.             this.ModelObject = null;
  42.         }
  43.     }

This class simply propagates NotifyPropertyChanged events from the source model object out to the View.  The underlying model object is accessed through the ModelObject property.

The fun starts with collections.  For this I need a ViewModel collection class that wraps a model collection and propagates INotifyCollectionChanged events.  The ViewModel collection needs to be a collection of ViewModel objects – not model objects.  The class below automatically creates these ViewModel objects for each new item in the underlying model collection – it essentially acts as a ViewModel factory based on the contents of an underlying model collection. This is effectively the same as a SQL View, where the row in the view is different to the underlying table but the contents are changing in real-time.

  1.     public class ViewModelCollection<T,TWrapped> : ObservableCollection<T>
  2.         where TWrapped : ObservableObject
  3.         where T: ViewModelObject<TWrapped>, new()
  4.     {
  5.         public IEnumerable<TWrapped> ModelCollection;
  6.         public ViewModelCollection(IEnumerable<TWrapped> collection)
  7.         {
  8.             this.ModelCollection = collection;
  9.             var notifyCollection = collection as INotifyCollectionChanged;
  10.             if (notifyCollection == null) throw new ArgumentException({"Must implement INotifyCollectionChanged"}, {"collection"});
  11.             notifyCollection.CollectionChanged += new NotifyCollectionChangedEventHandler(collection_CollectionChanged);
  12.             foreach (TWrapped item in collection)
  13.             {
  14.                 this.Add(new T() { ModelObject = item });
  15.             }
  16.         }
  17.         void collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
  18.         {
  19.             if (e.Action == NotifyCollectionChangedAction.Add)
  20.             {
  21.                 foreach (TWrapped device in e.NewItems)
  22.                 {
  23.                     this.Add(new T() { ModelObject = device });
  24.                 }
  25.             }
  26.             else if (e.Action == NotifyCollectionChangedAction.Remove)
  27.             {
  28.                 foreach (TWrapped ob in e.OldItems)
  29.                 {
  30.                     var oldWrapper = this.FirstOrDefault(o => o.ModelObject == ob);
  31.                     if (oldWrapper == null) continue;
  32.                     this.Remove(oldWrapper);
  33.                 }
  34.             }
  35.             else
  36.             {
  37.                 this.Clear();
  38.                 foreach (var ob in this.ModelCollection)
  39.                 {
  40.                     this.Add(new T() { ModelObject = ob });
  41.                 }
  42.             }
  43.         }
  44.     }

This class is incomplete, it doesn’t efficiently deal with the different types of INotifyCollectionChanged events and it should really recycle ViewModel objects.  It does serve the basic function for being used in something like an ItemsControl on a Model collection where you really want to introduce a ViewModel object instead.

I could extend these classes to support some LINQ type queries on the underlying model.  You could imagine these projections becoming results from LINQ queries.  In fact, this is exactly what has been done in libraries like CLINQ already.  CLINQ offers the ability to create a declarative view-model based on a LINQ query.  If you’re looking at using a ViewModel on live data in a non-trivial model I would recommend you take a look at CLINQ on CodePlex.

Filed under: , , ,

Comments

# re: Model-View-ViewModel – Real Time Models

Tuesday, June 30, 2009 5:58 PM by Erem

Good stuff

# re: Model-View-ViewModel – Real Time Models

Wednesday, July 08, 2009 5:23 PM by Martin Konicek

Nice, but where is the implementation of UpdateViews()? I would like to know how to propagate OnPropertyChanged when ViewModel's property has different name than Model's property. Thanks

# re: Model-View-ViewModel – Real Time Models

Sunday, July 12, 2009 12:20 AM by joeyw

Konicek - I tend to send a generic update out once the underlying model object has completed its changes. An OnPropertyChanged with a null property name tells the GUI that all properties have changed. This is a less chatty and therefore better performant way of updating the views (which in most cases will be updated if any property changes). As I mentioned, CLINQ supports filtering updates based on property changes. This was added in version 2.

# re: Model-View-ViewModel – Real Time Models

Thursday, November 19, 2009 5:29 PM by Kwess

I’d recollect the department of to ordeal that too!

Leave a Comment

(required) 
(required) 
(optional)
(required) 
 
Page view counter