Saturday, October 27, 2012

Rewriting WPF Code-Behind to MVVM

I wrote a code-behind WPF application. The app grew, and the maintainability of the app's GUI-related code became quite poor. Some data input pages used models for the DataContext, other pages had code-behind that read individual fields, and there was one page that used a 'custom model class' that was basically a view model without any logic.

After converting most of the code-behind to use MVVM instead, I found that my program was far easier to maintain than the code-behind version.

Choosing a MVVM Toolkit

The first step is to decide on a MVVM toolkit to use. I used a custom MVVM toolkit, but toolkits like MVVM Light Toolkit and Caliburn.Micro are available for use. WPF Apps With The Model-View-ViewModel Design Pattern explains much of what you need to know to write your own.

You also need to decide on how navigation occurs in your code. Magellan is one possible solution, and some MVVM toolkits also solve this problem. If you are writing your own, you should take a quick look at pre-existing toolkits to see how they handle navigation.

Converting Code-Behind to MVVM

The conversion process provides unique challenges because the structure of code-behind can be different for each page. Because each page will be slightly different for code-behind, this is an incomplete guide to converting code.

Most pages in my application used a Model as the DataContext, and the conversion process for most of those pages went like this:
  1. Create the view model (which should extend a ViewModel base class)
    1. Code-behind click handlers become custom commands (you can mostly copy code from the click handler into the custom command)
    2. Navigation calls are changed so that they don't use NavigationService
    3. Models & private fields in the code-behind become VM properties or private fields
    4. Named controls in the View become properties in the VM of an appropriate type (most likely String)
    5. If code behind changes IsEnabled or Visibility for a control, add a property of type bool (IsEnabled) or Visibility for each control changed in this way so that VM code can manipulate IsEnabled/Visibility for that control through data binding
  2. Change the page to use the view model as its data context (do not remove click handlers yet)
    1. Change the DataContext to use a view model instance (and add a private field for the model if necessary)
    2. Change all data bindings to match those of the VM 
    3. Remove model properties from the View's code-behind
    4. Replace any calls to the (now removed) model properties with calls to ViewModel properties
  3. Add bindings for IsEnabled or Visibility for controls that need them
  4. Test with an emphasis on data validation & property bindings
  5. Replace the click handlers with VM Command bindings in the Page's xaml file.
  6. Test with an emphasis on commands and navigation.