DM-V-VM part 6: Revisiting the data model

DM-V-VM part 6: Revisiting the data model

Sorry it's taking me so long to get the posts out. The series turned out to be a little longer than I anticipated :-) I got a lot of good feedback on the Data Model stuff.

First off, I want to mention layering. The DataModel typically is a layer on top of some other lower level data model that's not optimized to WPF. This might be something specific to the database technology you are using. Or, it could wrap some native object that's accessed via interop (I've run into a couple of examples of people doing this recently).

Also, I made some simplifications in the first post that made them much less interesting. The big simplification was that the models only fetched their data once and weren't live. Things get a lot more interesting when the models keep their data up to date.

Once you make them live, you run into a life time issue. If you have a large set of items, you only want to keep the visible items live. We'll do this by giving models Activate and Deactivate functions that control when it is live. Let's start with the DataModel changes. I'm not going to list the full class here, just the modifications. I'll post the entire sample soon when I finish up the series. If you have any question about how to apply these changes, let me know.

First, an IsActive property, which is implemented much like the State property. We could make it settable to activate and deactivate the model, but I like to think of those as methods rather than a property change:

Note: We could have used a System.Threading.Timer to do the updates where we'd be called on a background thread directly, but then we couldn't set the model state to fetching. We'd have to send that back to the UI thread.

Ok, now we've made it so you can activate it and deactivate it, but when do we do so? Let's say we've got thousands of our models in a ListBox. It's only going to only show a few of them on the screen at a time and we only want the ones on screen to be active. We'll use the attached property trick to do this without having to write custom code each time we want to activate and deactivate models. The basic idea is that we're going to display our model in a DataTemplate and we want to activate the model when FrameworkElements in the UI are loaded, and deactivate the model when they are unloaded. With the attached property trick, our DataTemplate Xaml just has to be:

We've registered a PropertyChangedCallback on the property, so any time it is changed (including when it's initially set), OnModelInvalidated is going to be called. In OnModelInvalidated, we're going to register for Loaded and Unloaded events on the the FrameworkElement we are attached to. We also have to do a bit of bookkeeping to clean up if we were previously pointing to a different model.

Pretty neat trick, isn't it? This means it's really easy for us to activate models when they are visible in the UI by simply adding the ActivateModel.Model property to the UI element. Since we know all FrameworkElements will get unloaded when they go away, we know we won't have to worry about leaking anything. It doesn't require any custom activate/deactivate code per view!

We get a form of data virtualization out of this trick. If the data is very expensive, the models can act as a relatively cheap shell. When you go to view a a large collection of items, you just need to provide a collection of the data models instead of the full items. The expensive data can then be accessed only when the UI for the data is visible on the screen and then thrown out when the data goes offscreen.

I'll confess, I've made another simplification from what we've done in Max. The lifetime management of a lot of our models is slightly more complex. And, as much as we hated to do so, we ended up adding a reference counting to our models. So we keep track of multiple levels of activation and a model is live as long as that count is greater than zero. To get something a bit like smart pointers, we have Activate return an IDisposable which we call an "activation cookie". Disposing of the activation cookie decrements the activation count. The cookie is the only way to decrement the count, there's no public method on the model. It's smart enough to not let you decrement multiple times. And, in debug builds, we have a finalizer on the cookie that asserts if Dispose wasn't called, to help catch leaks. We were quite happy to leaving reference counting when moving to managed code, so it hurt a bit to bring it back :-)