Table of Contents
Since my last post on building ChumChase, I've had a couple of projects that have changed my thinking just a bit about how to architect a WPF application. When I reviewed the code for ChumChase (in order to continue this series) I found it needlessly complicated in more than a few places. So I decided to fix it.
A good portion of what I wrote in the first three posts is still true, but I did make changes in two major areas.
Too Much Focus on Presenters
I have had a habit of naively creating presenter classes for every "screen" within an application. I think this is a consequence of my experiences as a Web developer using MVC. With MVC, you really need a controller to coordinate any action with the the view. Not so with a technology like WPF.
What's sad, is that I've known this intellectually for some time. Posts by Dan Crevier and John Gossman explained patterns like DataModel-View-ViewModel a couple of years back.
If that's the case, what did I change?
First, I created a model that represented what is going to be visualized. This is the "ViewModel" mentioned above, although I tend to use the term "Presentation Model". This view or presentation model often mirrors the actual domain model (or data model) of the application, but it has an entirely different set of concerns.
For example, my company worked on the recently released (to beta) MapDotNet Studio UX. It's an application for designing maps to be rendered with MapDotNet Server. There's a domain model for representing the maps and the various layers of data that might be represented on them (e.g., parks, property parcels, postal codes, etc). In this application, we display a list of maps that can be edited. Underlying the application is a domain model that represents the maps and business of rendering them on a server. However, in presenting this model in the UI we need to do things like: keep track of the currently selected map, know wether or not a map is open for editing, and so on. These are presentation concerns and have no business in the domain model. Thus, we create a presentation model.
In many cases, this presentation model is a wrapper around the domain model and adds the presentation concerns. However, the presentation model might stand alone,similar to data transfer objects. In that case, there would need to be some sort of translation between the domain model and the presentation model. All that is to say that there is more than one way to do it.
In some cases, presenters make a lot more sense that a presentation model. The scenario we frequently encounter is a form or wizard-based UI. Presenters are much better suited for these types of UI's.
Of course, there is no reason why an application might not have presenters and presentation model.
Where do you put stuff?
I'll reference our work on Studio UX again; a user loads a map up from a server, styles the map, and then saves or publishes the map back to the server. A user might initiate publishing a map from any number of places in the UI: right-clicking on a map in the explorer pane, Ctrl+S when a map is open, and so on. So where should the code that actually publishing the map live? These different portions of the UI might be backed by different presenters, or by a class in the presentation model. How do we handle the save?
The missing link is using the Command pattern.
Of course, the Command pattern is built into WPF. It's all other place and part of the WPF Way. Take a look at ApplicationCommands.Save, it's practically a stub for what I just described.
Even though I live and breathe WPF, I was not bringing these concepts into my solutions.
The act of saving the map can be expressed as a command. It might be class named something like the PublishMapCommand. This hypothetical class PublishMapCommand is a first class member of my presentation model. (I'm simplifying the scenario from what the real app dealt with). It has some prerequisites of course, for example it needs a map to operate on, and it might need access to a repository, but it's not tied to a specific presenter or even to a specific instance of a map. The map is provide by the context (the node I'm clicking on the explorer, or the tab that has focus when I press Ctrl+S). My IoC container can handle injecting the repository. And now, I can bind disparate parts of the UI to the same command.
The command is also highly testable because it so focused, it does just one thing. As a side note, thinking about your domain in terms of Verbs is really useful. I would argue much more useful that the traditional approach of thinking in Nouns. Verbs == Behavior, Nouns == Data.
Summing It Up
My basic approach for the UI is this: first, I am thinking in terms of a presentation model. An important part of this model is the verbs or commands. Secondly, where it makes since I use presenters. If necessary, I'll introduce an application controller (or master presenter) to help orchestrate the entire application. Again, if necessary.
The lesson for myself was this: don't get so caught up in the design, that you lose sight of what works. Keep it clear, keep it simple.
I've already committed the changes for ChumChase that reflect these ideas. There aren't any commands in the model yet, but I did away with the HomeFeedPresenter. Instead, I now have the beginning of a presentation model that models the data I want visualized in the application. Right now, that is merely the home feed.
You can get the latest for ChumChase here. The application actually works now , well the minimum base feature works. You'll be prompted for your user name (or nickname) and your remote key. The remote key is different from your FriendFeed password, and you can find it here (assuming you are logged into FriendFeed.) If you are lazy, you can use the default credentials for the dummy account I set up.
Oh, and I started playing with the new 2D on 3D stuff, that's what the screen shot is, but I'll blog about that later.
Oh yeah, and there are some wicked cool new WPF binding tests via Caliburn in there as well.
Update: Karl Shifflett posted recently on the M-V-VM pattern, and also directs some attention (rightly so) to Josh Smith's site.
More to come...
11-07-2008 10:06 PM