Tuesday, October 23, 2007

Model View Presenter Data Binding Part 2: The Model

In my Model-View-Presenter application, the Model is fairly simple, but can be extended to be more complicated (I guess that's really true for just about anything). I won't go into details on persistence, I'm going to concentrate on how I do the data binding. For the Model, I follow the same pattern for properties that I did on the View:
int _nThingy;
public int Thingy
{
   get
   {
       return _nThingy;
   }
   set
   {
       bool bChanged = (this.Thingy != value);
       _nThingy = value;
       if (bChanged) FireDataChanged("Thingy");
   }
}
The FireDataChanged(string) method will fire a PropertyChanged event for the specified property. When I'm working with collections, I create a class that inherits from BindingListBase (my custom .NET 1.1 class that implements IBindingList) or from System.ComponentModel.BindingList (when using .NET 2.0). Both IBindingList implementations will look for changes to each of the elements in the list and translate them to ListChanged events. This will make life really easy when binding to grids and lists. Oh yea, I almost forgot, there's one more piece that needs to be in place. The data object needs to implement IEditableObject. The MSDN documentation has an example on how to implement it. While this is deceptively simple, it does have some interesting consequences. Now, I can make changes to properties (with sheer impunity) and I can notify everyone else of all the changes that happen on the data objects. Here's an example: I have a program that lets the user define a set of tubular equipment that is laid end to end. Each piece of equipment has the following properties: Inner Diameter, Outer Diameter, Top Depth, Bottom Depth, Length. For the sake of simplicity, lets say they are all integers. For each equipment, Bottom Depth = Top Depth + Length. The equipment has to enforce this rule all the time. Also, when making adjustments, it should favor keeping the Length property constant. Here's how the properties end up looking:
public int TopDepth
{
   get { return _nTopDepth; }
   set
   {
       bool bChanged = (this.TopDepth != value);
       _nTopDepth = value;
       if (bChanged)
       {
           this.BottomDepth = this.TopDepth + this.Length;
           FireDataChanged("TopDepth");
       }
   }
}

public int BottomDepth
{
   get { return _nBottomDepth; }
   set
   {
       bool bChanged = (this.BottomDepth != value);
       _nBottomDepth = value;
       if (bChanged)
       {
           this.Length = this.BottomDepth - this.TopDepth;
           FireDataChanged("BottomDepth");
       }
   }
}

public int Length
{
   get { return _nLength; }
   set
   {
       bool bChanged = (this.Length != value);
       _nLength = value;
       if (bChanged)
       {
           this.BottomLength = this.TopDepth + this.Length;
           FireDataChanged("Length");
       }
   }
}
If I set any of these three properties, the others will be synced up until the system stabilizes. Also, setting any one property will not only make adjustments to another property, it will fire both notifications automatically. This is OK for a single piece of equipment, but we need to have some more interaction between the equipment. When the BottomDepth of a piece of equipment changes, the next equipment's TopDepth needs to change. When the TopDepth of a piece of equipment changes, the previous equipment's BottomDepth needs to change. To make this work, we hook into the ListChanged event on the BindingList and look at the ListChangedEventArgs.PropertyDescriptor property. If the change was BottomDepth or TopDepth, we make a change to the appropriate item. We have to take care of all the different types of ListChanged events: ItemAdded, ItemChanged, ItemDeleted, ItemMoved, and Reset. In each case, we make sure the adjacent TopDepth/BottomDepth pairs match up on the changed equipment. Once all the pieces are in place, I just need to create a list, add some items to it, then set the DataSource property of a DataGridView, sit back, and watch the magic. If you edit the TopDepth, Length, or BottomDepth columns, then the model will make the adjustments and the DataGridView will be automatically updated. Also, you can assign the same list instance to another DataGridView, then the two grids will be synchronized automagically. This is the basis for the Model layer of the Model View Presenter architecture. Using these techniques, we can bind any collection of data fearlessly to any UI component as well as binding properties with each other.

10 comments:

Steve said...

Hey Garo,

I saw your post over on Jeremy Miller's blog (which I read religiously), and I was like...Garo Yeriazarian....I know that name.

Click boom pic - Garo! Yes, our NSCS webmaster. Couldn't find an email, so I'm posting off topic.

Hope things are going well. I'm actually moving back to Houston next month, I've been in VA for the last 4.5 years.

I'm going to go peruse through your older posts and see if I can get MVP to stick a little better. I've tried some different things after reading Jeremy's Build a CAB series and I can't quite figure out which pattern to use in my situations. Of course I've got an MVC background so I think I'm fighting my bias at times. That and I live in the land of asynchronous device driving 50% of the time and esoteric network environments the other 50% so I don't get as much gui work as I used to.

Garo Yeriazarian said...

Hey Steve! (which Steve?) My email is garoyeri@yahoo.com.

Anonymous said...

a thought. Does databinding break MVP? I'm currently on a super huge winforms project utitizing MVP for our presentation layer. There is almost a religous battle between the developers on this point. I'm on the yes it does break MVP side of things and here is my argument. Databinding Model objects directly to the view, essentially removes the work of the presenter, and makes the view much smarter then it should be. I noticed on almost all pages that our developers used databinding, slowly but surely, presenter logic was leaking into the view. Without a view adapter of some sort it seams impossible to avoid this.

Garo Yeriazarian said...

I don't think that Data Binding breaks MVP because the View doesn't care what it binds to in the Model as long as the types are compatible. The responsibility of setting up the Data Binding is with the Presenter (the Model and the View are oblivious). They just have these endpoints set up to accept certain criteria.

If you look at my more recent posts, I have a more complete example that shows how I'm using the MVP pattern. In my simple example, the models have interfaces that the WinForms data binding mechanism can use to generate a binding source. You make assumptions on the structure of the data (i.e. what you're looking for), but you don't know where the data is coming from or how to get it. It is handed to the View by the Presenter.

Anonymous said...

so you are suggesting an intermediate vernacular between the presenter and view, and not the model objects interface? From that point of view you then have a presentation based interface that represents the views needs? I've seen a few developers in my group do this. The part that seems to pickle the whole thing is that databinding has some real function tied to it in .net. The currency manager etc. As all of that abstracted functionality lives on the view, it has essentially hijacked it from the presenter. From our point of view, being able to unit test the view is super importnant, and databinding makes that very difficult. For example, of my grid sorts on it's own, if my grid is updating the bound object on its own, etc. How and when my business objects change is the job of my presenter so that i can test it yes?

Garo Yeriazarian said...

I recommend having a data contract on the View side using interfaces just the same as having a contract on the Model side using interfaces. The Presenter can then be agnostic in how it transforms the data from one to the other.

The currency manager and the sorting of rows in a grid are all functions of the View that only affect the presentation of data, not the data itself. The only thing that affects the data is when the cells of the grid data are modified. In this case, the reference to the data bound object exists on both the View and the Presenter. The Presenter can look for changes (events, INotifyPropertyChanged, etc) and map the changes to the model objects.

In the applications I've developed so far, whenever I deal with a grid, it usually maps directly to a collection in the Model. This way, I just pass a collection (the View expects a certain interface) and all the data changes generate notifications.

If the View decides it wants to be a single-record view instead of a grid view, I can pass the same collection and the View can use a currency manager to select the current record.

To Unit-Test the View, you just have to automate the actions on the View and make sure the data is changed properly on the Presenter side.

Anonymous said...

so really you are advocating for mvpvp. I'm still not sold on sorting being the concern of the view. It is busisness logic in my mind how two business objects are compared. As soon as you get a way from simple string comparison, and need complex sorting, it is no longer confused as a view concern. Rather than mixing some here some there, i've always opted that sorting is business logic.

Craig Susen said...

when using the mvpvp model, where you are creating a contract between the view and presenter, how are you converting your model objects? Is your contract simply an adapter or are you flatting the object. I'm asking all of this as i am writing a paper on this subject and am trying to garner lots of different persepectives. Your's is obviously a valid and well used one, please dont mistake my questions as anything other than picking your brain. --Craig

Garo Yeriazarian said...

MVPVP: interesting way of putting it, although I'd have to read more into what you mean by that.

The implementation of the View could end up using some thing like a mini MVP to make it work, but any other code is uses for any complex logic should be provided by the Presenter somehow.

Jeremy Miller talks about Micro-Controllers here as an alternative to data binding. Some of the logic in these Micro-Controllers could be provided by the Presenter as well.

Regarding sorting:
In my applications, sorting a grid has always been a user preference. My Views, however, are usually quite specialized. If I have a generalized grid view, then I provide some more properties on the View's contract to pass in helpful information like sort order, nicer column titles, units of measure, etc.

In some cases, the model has "PositionIndex" field that I can tell the grid to look for and don't bother sorting by anything else. The Application Model is usually what sets this index, I can tell the grid to use this as the only sorting column and whenever the Application Model decides to shuffle items around, it just needs to modify these indices.

These are the two cases I see: (1) the program doesn't allow the user to resort the rows, (2) the program does allow the user to resort the rows. In (1), the sort order is business logic, in (2) the sort order is view logic (except for providing the default sort order).

Regarding the MVPVP Questions:
The Presenter-View contract is decided by the interface supported by the View. (It IS the interface on the View). I look at what information I feel the View should need and the View can provide events that the Presenter can act on (or forward to another Controller). If you haven't already, take a look at my posts from 2008 regarding the StockSample. I've got links to get the source code as well.

In my applications, the Application Model is essentially the View Model. A particular View may only use some parts of the Application Model for individual field display, but then it will use the collections as they are for grids (and just show / hide the columns that it doesn't care about). I haven't run into a situation yet where I needed to transform a collection. Usually I'll have several Application Model objects that have various bits of information that the View is interested in and I bind those specifically. For collections I bind the whole collection to a collection on the View contract.



Craig, I'm not mistaking your questions for anything but shop-talk (which I thoroughly enjoy). ',;-)

Craig Susen said...

Garo,

Thought i'd follow up to this discussion. We are not alone in hashing out the ideologies of MVP. Martin Fowler officially retired it and broke it into two patterns, passive view (my ideology) and supervising controller (your ideology). Here's the link. http://martinfowler.com/eaaDev/ModelViewPresenter.html