Note: This is part of a series, you can find the related posts here…
Displaying and manipulating data on the client – the one and only purpose of any LOB application – includes two things if it comes to Silverlight: a) The asynchronous server calls and b) the client architecture. I will be going over them in a cursory fashion and come back later to address each one in more depth. This time the client architecture.
View Model basics
The client code is largely guided by the employment of the Model-View-ViewModel (M-V-VM, or MVVM) approach, which is the predominant architectural approach used with SL and WPF. The reason probably being that it is a natural counterpart to the WPF and Silverlight data binding features, the two work extremely well together.
Cook book style:
- For every page (the view) there is a respective class (the view model) that manages the data (the model).
- For each control on the view that shall be populated dynamically with data, the view model has a corresponding property providing the model.
- For each control state, such as enabled or visible, that shall be controlled by the logic, the view model has a corresponding property, probably boolean.
- For each action triggered by the UI the view model has a respective method.
The view model is very closely associated with its view, so there is not much reuse here, but then, it largely consists of properties and forwards to service calls. Or so the theory says. (Subsequent posts will gave deal with the shortcomings….)
Please note that it is debatable whether the data provided by the view model actually is the model. Another approach would be to expose a view related data model, namely view entities. The view model would have to map them to the data model (the model) the lower layer exposes, probably some service proxy.
Both approaches have pros and cons, however you’ll mostly see the former approach, since it’s well supported by the tools (service proxy generation, etc.). Still, it has some cons…
Anyway, the only technical demand for view models in SL is due to the intended use for data binding: Classes subject to fully fledged data binding need to support the INotifyPropertyChanged interface for simple properties, while collections have to support INotifyCollectionChanged. The later one comes for free if you use ObservableCollection<T> consequently. (Please note that data binding works with conventional properties, but only to a limited degree.)
For INotifyPropertyChanged a little base class comes in handy:
Note the generic overload. That’s a little trick to avoid typos in the property name argument.
With this class as base a property implementation usually follows this idiom:
(Without that trick one would have to pass the property name as string. A source of errors due to typos, and a pitfall during refactoring.)
BookFilter is a data class that, again, follows the same pattern, i.e. it supports INotifyPropertyChanged.
Hint: This begs for a code snippet!🙂
The View Model
Any book shelf has a collection of books, so does my application. The book list page should provide a means to filter the book list (two text boxes), a button to trigger the search, and to show the result (a data grid):
Not especially nice and the grid is a little degenerated for now, but that will change. In XAML:
Thus the first view model implementation may look like this (including some simple test data, the next post will deal with the server call):
For the actual binding I need to wire that up with the page. The usually presented manual way looks like this:
The view model class is created in the c’tor, and assigned to the DataContext. A property provides a more convenient access to it. This is already used in the button event handler that triggers the book search.
However, I recommend against this way. Rather I did the data binding in Blend…
Opening the page, selecting the first textbox, finding the Text property in the properties and clicking the text box (or that tiny little dot to the right) brings up the context menu.
Then I choose data binding, the Data Field tab, and the +CLR Object button:
That left finding the view model class and selecting it:
This way I could add various “data sources”, yet I only want one for now, and according to M-V-VM, for ever. Afterwards the dialog lets me browse the class structure and select the property to bind against:
On second thought, I selected the StackPanel which contains the filter textboxes and bound it against the BookFilter property, in order to narrow the available context. Afterwards the textboxes could be bound via the Explicit Data Context tab:
I had to expand the lower area to set the binding to TwoWay.
But I didn’t have to go through the dialog armada for every field. Blend also provides the data tab that lets me browse through the available data sources. Drag’n’drop of field simply works and generally uses the last settings from the dialog, i.e. it includes the TwoWay setting. Also it binds by against the default property, but if I hold the shift key down it lets me choose the property. And some other stuff I leave to you to explore. Really nice.
Anyway, this is what Blend just created for us in markup speak (just the relevant part):
It registered the namespace to locate the view model class, created the view model as resource, set the DataContext of the LayoutRoot element to this resource, and it added the usual binding to the subsequent controls.
Changing the generated prefix to viewModel was all I did. Otherwise I could live very well with that, given that is achieves all I need and it allows me to do my data binding much more efficient and less error prone in Blend. The manual way was opaque to Blend, thus it couldn’t assist me in any way.
The only thing left was removing the manual instantiation from the c’tor and changing the ViewModel property to use the LayoutRoot control. I may have moved the binding to the page instead, but I prefer to work with the tools, not against them.
After running the application and clicking on the button, it shows the respective test data:
The next post will deal with the actual server call.
That’s all for now folks,