Do you know that disappointing feeling you get, when you learn a new technology, you start using it, just to discover that all the examples of the new technology across the internet don't amount to a practical real-life application?
The problem with conventional WPF examplesI am far from an expert in UI, especially windows forms UI, which makes me the perfect victim for bad examples. This is the feeling that I am getting with WPF right now. Where are the best practices? Its a very impressive technology, and it makes for some very entertaining parlor tricks. We see new sample interfaces that look like video games. We see a plethora of code samples for new tricks, but few of them form to any semblance of a practical windows application.
New Technology Causes new ConfusionThe other thing we get with WPF is a very dangerous period of conceptual oblivion as we try to figure out where we should define the bindings, where should we define behavior, where to draw the line between presentation and logic etc.
- Should I use a ContentTemplate? Maybe a DataTemplate?
- Should I define my bindings declaratively, or should I do it manually in the codebehind? If I do it in the XAML, should I use the XAML notation, or the odd new binding syntax with the curly braces?
The questions go on and on... OK I have X-teen ways to do the same thing, which way do I go?
One thing I am discovering (the hard way) is that if you just want to write a normal program, a lot of the classic design patterns are still very relevant with WPF.
The Danger of "Direct" DataBindingDataBinding is where I got burnt.
Think about it: everywhere you look, you see examples of a WPF UI directly bound to a business object. Direct binding between the UI and a business object has some dangerous implications:
- As I type/click etc... I am causing immediate changes to the business object. How do I undo them?
- Since the binding is two-way and immediate, does this mean I no longer need an "Apply" button anymore?
- What if I want to cancel what I was just doing?
I was writing a program where the UI was directly bound to my business objects. I had a textbox bound to the "Name" property of an object. My object throws an exception if its passed a name that is empty, or too big.
What happens when I type beyond this limit? As a default behavior, WPF swallows the exception! It turns out you need to write a custom binding object to display the exception to the user in a messagebox.
What is the recommended solution for databinding to objects? Your objects need to implement
IDataErrorInfo. The gist of implementing IDataErrorInfo is this:
public virtual string this[string propertyName] {
get {
//find the property that matches the string "propertyName"
//check the current state of the property, is it valid?
//if its valid, return string.Empty
//if its invalid, return a formatted error
}
This code gives me a migraine headache. Why should my business objects EVER exist in an invalid state?
The recommended solution seems like an antipattern to me. Imagine that, all of your business objects implementing INotifyPropertyChanged, INotiftyCollectionChanged, IDataErrorInfo just so they can work with WPF?
But still, databinding is a very cool feature in WPF. How to leverage this without violating my model? I made a mistake, I was so focused on using new WPF tricks that I forgot how to build a good testable application.
My (current) SolutionMVC is a design pattern intended to solve the problem between separating a UI and the logic. These were the qualities I was looking to achieve:
- "smart" business objects, they throw exceptions if you try to pass them anything invalid, or something goes wrong in an operation
- "dumb" WPF UI that is bound in a dynamic, readonly fashion to the objects
- The UI catches exceptions and shows them to the user in a messagebox
- A "controller" that receives gestures from the UI and performs logic on the model
- The controller should be very testable
- The UI should be very mockable
- The UI has no direct write access to anything in the business objects
The Sample Program: the cliche Blogger Application ala WPFThe sample program is
here. It is a very simple, practical windows program for adding/editing/removing blog posts. The main attraction is the architecture.
The Models
The model is simple. I have a Blog that contains a collection of BlogEntries, and they are both "Persistable" meaning they have an ID and a name.
The Blog class exposes a list of its BlogEntries, but it has specialized Add/Remove methods for modifying this list.
The Views
Since I needed the view to be mockable, I set up for an interface, such as IView.
Any view must have an associated controller, and be able to update its display with the latest data. I create a view for every aspect of the program. In this case, I have a view for BlogEntries, and a view for Blogs. These views generate events that are handled by the controllers. They use BlogEntryEventArgs as arguments for the events.
The Controllers
The controllers exist to handle generated events from the views, and this is where the real business logic begins. As you can see, there is an event handler for every event in the view. One noteworthy thing here is that the controller does not need to talk back to the view for updates because the view is directly bound (in a readonly fashion) to the model. That little feature is thanks to the WPF databinding power!
Finally, The UIThe UI is two windows, one for selecting blog entries and one for editing them. Each window implements a view. Upon window construction, each window registers a controller. Button callbacks from the UI generate events sent to the controller. The windows catch and display any exceptions, and the UI is automatically updated by the direct databinding to the model.
What about tests?How to test UI interaction without a UI? Make a "mock" of the views, and test the controller. Since our views are dumb, we can effectively test the bulk of the application using a dummy view. For example:
private class MockBlogEntryView:IBlogEntryView{...}
///
/// isolate the controller for testing
///
[Test]
public void TestCreate()
{
Blog _blog = new DummyBlogDAO().GetByID(0);
MockBlogEntryView _mockBlogEntryView = new MockBlogEntryView();
BlogEntryController _blogEntryController = new BlogEntryController(_mockBlogEntryView);
BlogEntryEventArgs _blogEntryEventArgs = new BlogEntryEventArgs(null, "blahblahblah", "New post", _blog);
_mockBlogEntryView.ThrowCreateEvent(_blogEntryEventArgs);
}
At this point, feel free to replace MockBlogEntryView with something from your favorite mocking API such as Rhino Mocks or TypeMock.NET if you are too lazy to define a dummy view.
Other ObservationsAdmittedly, this is yet but another simple example for a tricky concept. Does this architecture scale nicely as the complexity grows? Your mileage may indeed vary.
In my example, I have a view-per-model design, but
- There is no real correlation at all between the number of views you have and the number of "model" objects.
- There is no real correlation between the number of controllers you have and the number of views either. (In retrospect, perhaps I only needed one controller)
There is never a single and straight path of correctness for building any complex app; there are many right ways. I'll post again when I see how this architecture holds up to the test of time and complexity.
Download the Source Code hereLabels: C#, WPF