Binding in View Models, wire up all the things!
Wire up those models
I recently worked with a client on a databinding issue in a WPF UI that was implementing the MVVM (Model View View Model) pattern. MVVM is amazing when everything is wired up properly, and that can be the challenge. There are a lot of moving parts in implementing the pattern and it is easy to miss something. If you do, things just won't work right. I have been fortunate to implement it in many systems, build libraries that help others use it, and to write articles and guidance. When I was the PM at Microsoft for patterns & practices Prism, MVVM was a central theme.
In this post I'll give a quick summary of MVVM and then give an example to illustrate how to use it in practice. The example is inspired by the issue I was working through with the client.
Why MVVM
The main reason for using this pattern is for implementing a UI that has complex logic in a maintainable way. It helps you to separate out UI concerns from the UI-centric business logic. With MVVM your code will be easier to test, and your UIs will be less brittle, at least from a standpoint of changes in the logic not breaking the UI or vice-versa.
Implementing MVVM
When you implement the pattern which is also know as Presentation Model, there are a number of core concepts. There also will be specific nuances and libraries that you will have to work with depending on the stack. I'll be focusing on how to do it in WPF for most of this post.
Concepts
View - The user interface. In the case of WPF it could be the window, frame, or other kind of component.
Model - The underlying data model that contains the information which will be displayed. Models may be just light data containers (anaemic) or have business methods. There is a lot of religious debate on this, which I'll avoid.
View Model - A UI-centric data model that the UI directly binds to. This contains members / logic that is specific to the UI context. Do not mix the View Model and the Model, that will just cause you problems. The view model's responsibilty is to offer up data that will surface in the view. It will talk to the underlying data, and format it in the way that makes sense for the UI. It will contain logic that responds to UI events. As an example, a view model may have a Refresh() method which is invoked by the UI to refesh the data. The view model is also providing encapsulation. It prevents the UI from having any intimate details or coupling to the underlying infrastructure and prevents UI details from bleeding down.
Data Binding - This is the glue that allows the view to connect to the view model in WPF. Other platforms like React have their own state and binding mechanisms. Same concept though it may play out differently.
Interfaces and Types
In WPF there a few different interfaces and types you need to know about to implement MVVM.
INotifyPropertyChanged - This interface (shorthand INPC) is implemented by a view model in order to notify the UI component that property has a changed. It contains a PropertyChanged event which is fired. If the prop value is a collection or a complex object, then the event is fired when the prop is updated with a new instance.
INotifyCollectionChanged - This interface (shorthand INCC) is implemented by a view model in order to tell the UI component that a collection property has had items added or removed from the list. The CollectionChanged event fires as part of this.
ObservableCollection - This collection implements INotifyCollectionChanged and will fire the event when it changes. This collection is very handly as it can wrap an existing collection. You will use it often when building UIs. Essentially it is a View Model.
Using it in practice, building a Contact Management app
Let's imagine a contact management system (for anyone who knows me from my ASP.NET Web API PM days they'll be laughing now). It has a screen that displays a list of contacts. You can filter contacts based on a contact name. It lets you add, delete, etc. You can also display the individual details of a contact.
Let's talk about the components. I'll focus on the view models specifically and leave it up to the reader to implement the View.
Below is a diagram illustrating the different parts. Disclaimer: I am a coder not a designer. 🤣
View
ContactListView
This displays the list of contacts. It has a text control for specifying the filter. The list displays first name, last name, and phone for each contact. It also has a "Save" and "Undo" buttons.
ViewModel - We'll have 2 corresponding models.
ContactViewModel
This view model is for supporting the display of an individual contact and ensuring changes are bubbled up.
public class ContactViewModel : INotifyPropertyChanged {
private Contact _contact
public ContactViewModel(Contact contact) {
_contact = contact;
}
public string OnPropertyChanged(string property) {...}
public First {
get { return _contact.First; }
set {
_contact.First = value;
OnPropertyChanged("First");
}
}
// Last and Phone should be implemented similar to First. ID does not need to be displayed.
}
ContactListViewModel
The view model is for the UI business logic of the contact list view. The List and "Save" and "Undo" buttons are bound to the this. Notice how it calls to the data service to retrieve the contact info.
public class ContactListViewModel : INotifyPropertyChanged {
private IContactData _data;
public ContactListViewModel(IContactService data) {
_data = data;
}
public string OnPropertyChanged(string property) {...}
private ObservableCollection<ContactViewModel> _contacts;
public ObservableCollection<ContactViewModel> Contacts {
get {
return _contacts;
}
set {
_contactList = value;
OnPropertyChanged("Contacts");
}
}
private string _filter;
public string Filter {
get {
return _filter;
}
set {
_filter = value;
// Needed in case this is updated programatically
OnPropertyChanged("Filter");
//Update the list with the filtered data. Construct a ContactViewModel to wrap each contact.
Contacts = _data.GetContacts(value).select((x)=>new ContactViewModel(x));
}
}
public void Save() {
_data.Save();
}
// No need to support undo at the data layer, just reload
public void Undo() {
if (_filter == "") {
Contacts = Contacts = _data.GetContacts(value).select((x)=>new ContactViewModel(x));
}
else {
Contacts = Contacts = _data.GetContacts().select((x)=>new ContactViewModel(x));
}
}
// Load the initital set of data
public void Load() {
Contacts = _data.GetContacts().select((x)=>new ContactViewModel(x));
}
}
Model
The model is a very simple data container.
public class Contact {
public int ID {get;set;}
public string First {get;set;}
public string Last {get;set;}
public string Phone {get;set;}
}
Data
The ContactService is responsible for retrieivng the contacts. It implements an interface to allow it to be easily mocked / injected by an IoC container.
public interface IContactService {
IQueryable<Contact> GetContacts()
IQueryable<Contact> GetContacts(string filter)
}
// Talks to some data source to return a Queryable<Contact>. Also handles persisting the data.
public class ContactService : IContactService {
IQueryable<Contact> GetContacts() {
...
}
IQueryable<Contact> GetContacts(string filter) {
...
}
// Write the data
public void Save() {
...
}
}
How it all works
Now that we've described all the components and everything is correctly wired, let's talk about how it all works.
Loading data
- The View is instantiated.
- The
ContactListViewModel
is constructed with theContactService
injected - The
Load()
method is invoked to populate the data. It calls to theContactService
- The resulting data is wrapped in an
ObservableCollection(ContactViewModel)
using aSelect()
to create view models. - The
PropertyChanged
event is fired to tell the View that theContacts
property has changed. - UI displays the updated contacts and starts tracking
CollectionChanged
events. The UI also binds to eachContactViewModel
instance in the collection to track `PropertyChanged' events for each contact.
Filtering data
- The filter property is updated in the UI.
- Databinding updates the
Filter
property on theContactListViewModel
- The view model invokes the
ContactService.GetContacts
method passing in the filter. - The result is wrapped similarly as during
Load()
.
Saving data
ContactService.Save()
is invoked. There's a lot of handwaviness here as I have left the implementation up to the reader such as using Entity Framwork.
Undo
- The
ContactService
is invoked to reload the data.
Tips
Back to the opener of this post, there are a number of things that have to be wired up properly. Here's a few things to remember which may help you in your implementation.
- The View should always invoke the ViewModel for logic. It should never call to any of the underlying classes.
- If you are exposing a collection of objects (like the Contact List) you should seriously consider wrapping them in a View Model so that the UI will detect changes.
- View Model setters will often be used for triggering logic. In the ContactManager case we used a setter on the Filter property to force the data to reload.
INotifyPropertyChanged
is used for notifying the UI when individual properties are updated. Having anOnPropertyChanged
method is a common convention for firing the event. This was called within the setter for Filter and Contacts.INotifyCollectionChanged
is used for notifying the UI when items are added or deleted to a collection. If the collection instance itself is changed, then thePropertyChanged
event must be fired for the UI to detect it.- Breakpoints and the watch window are your friend. When trying to debug MVVM, put breakpoints in your MVVM code methods and properties and you can see exactly what is going on.
The MVVM Toolkit
There's a lot of boilerplate like INPC that you have to implement as part of the MVVM pattern. Years ago I was a big fan of the famed MVVM Light Toolkit written by my friend Laurent Bugnion. Although that is no longer maintained, Microsoft has released the MVVM toolkit. It contains helper classes like ObserableObject<T>
which implement INPC for you and get ride of some of the boilerplate.
Summary
The MVVM pattern is a fantastic pattern for building UI applications and it has many benefits including making your code easy to maintain and test. Implementing MVVM can be easy to mess up. Hopefully this post will help you as you delve deeper into using it.
If you are struggling using MVVM, I am here, reach out!