I spoke at the Dallas XAML User Group on Tuesday about building line-of-business applications in WPF and Silverlight. This was a hands-on event, so the participants followed along with their own laptops. Please download the source code and try it yourself.
The idea behind the talk was to exercise some of the most common features of XAML, Visual Studio, and Blend that are used in a line-of-business application. There are more features in XAML than one person can master. This is the subset that will give you the best return on your learning investment.
Unit test your view models
One of the benefits of separation patterns like MVVM is that you can unit test more of your code. Take advantage of it.
You can only unit test code that is in a class library. So it follows that view models should be in class libraries.
View models need to call services to access data and make changes. At run time, these can be WCF, RIA Services, OData feeds, or any other kind of service. But at unit test time, these have to be mocks. There are two reasons for mocking a service in a unit test:
- Unit tests should run in isolation; they should not pass or fail depending upon the environment.
- You need to test that the service gets called appropriately.
To mock a service at unit test time, we inject our dependencies. For example:
public interface ICatalogService
{
List<Product> LoadProducts();
}
public class CatalogViewModel
{
private ICatalogService _catalogService;
public CatalogViewModel(ICatalogService catalogService)
{
_catalogService = catalogService;
}
}
By injecting the service into the constructor via an interface, we can provide a real implementation at run time, and a mock implementation at unit test time.
public class MockCatalogService : ICatalogService
{
public List<Product> LoadProducts()
{
return new List<Product>()
{
new Product() { Name = "Widget" },
new Product() { Name = "Gadget" }
};
}
}
[TestClass]
public class CatalogViewModelTest
{
private CatalogViewModel _catalogViewModel;
[TestInitialize]
public void Initialize()
{
MockCatalogService mockService = new MockCatalogService();
_catalogViewModel = new CatalogViewModel(
mockService);
_catalogViewModel.Load();
}
[TestMethod]
public void CanGetProductListFromService()
{
var products = _catalogViewModel.Products;
Assert.AreEqual(2, products.Count());
}
}
Put all views in UserControls
Don’t use the MainWindow as a view. Don’t drop controls directly into it. Instead, create a new UserControl and build your view in there. Doing so will have several benefits:
- You can reuse and compose UserControls, but not Windows.
- You can switch from one UserControl to another within a Window for simple navigation.
- UserControls can be used within a DataTemplate to polymorphically select the view based on the view model.
- The container can inject the DataContext into a UserControl, rather than relying upon the view model locator pattern.
That last point requires further explanation. We’ve already uses dependency injection to get the service into the view model. We could have used a pattern called “service locator” instead to let the view model find the service itself. Service locator is well documented to have disadvantages when compared to dependency injection.
The view model locator pattern is just another service locator. As such, it suffers from many of the same drawbacks. For example, when the user navigates to a view, they have already selected a specific object. The view model locator must somehow locate that selected object. We end up with a Hail Mary pass, where the selection is stored in one view and retrieved in the locator for another view. If the object was instead injected into the view, it becomes much easier to manage.
To inject the first view model at run time, set the DataContext inside of the main window’s Loaded event.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
CatalogViewModel viewModel = new CatalogViewModel(
new RealCatalogService());
viewModel.Load();
this.DataContext = viewModel;
}
From that point forward, you can use properties of the main view model to declaratively inject child view models. DataTemplates come in handy for this.
Design time data
There are two ways to use design-time data in Blend with the MVVM pattern. The easy way is to create sample data from a class.
Select the view model class. Then layout your controls and drag properties onto them to databind.
The second way is to create an object data source.
First create a class with one property: the view model. Initialize this view model with a mock service. This mock data will appear at design time.
public class CatalogDesignerData
{
public CatalogViewModel ViewModel
{
get
{
CatalogViewModel viewModel = new CatalogViewModel(
new MockCatalogService());
viewModel.Load();
return viewModel;
}
}
}
Drag and drop to create list boxes
If you create sample data from a class, you will be able to drag and drop collections onto the art board. This will automatically generate a ListBox and give it an ItemTemplate. Then you can customize this ItemTemplate using Edit Additional Templates: Edit Generated Items: Edit Current.
By default, all properties are added to a StackPanel. If you want a horizontal layout:
- Set the StackPanel’s Orientation property to Horizontal.
- Right-click the StackPanel in the Objects and Timeline view and select Change Layout Type: Grid.
- Click the grid ruler to create columns.
- Right-click each property and select Auto Size: Fill.
- Set all but the last column to Pixel sized. Set the last column to Auto sized.
If you want a vertical layout, adjust the Margin of the lower properties to indent them and add spacing between items. Also, adjust the colors and fonts to enhance the contrast of the first property.
If you use the object data source method, Blend will not create a ListBox when you drag and drop. I recommend starting with the simple sample data, then creating an object data source later if you find you need to.
Use ValueConverters for application-specific view logic
You might need to represent data using different styles based on its value. For example, positive numbers appear black while negative numbers appear red. Create ValueConverters for these cases.
public class RedWhenNegativeValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (targetType != typeof(Brush))
throw new ApplicationException("You must bind to a brush.");
decimal decimalValue = (decimal)value;
if (decimalValue < 0.00m)
return new SolidColorBrush(Colors.Black);
else
return new SolidColorBrush(Colors.Red);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Data bind the element’s color to the value. Apply value converters in Blend using the advanced properties pane in the binding dialog. Click the ellipsis to create a new instance of the value converter class.
These are the techniques that I find myself using most frequently when building line-of-business applications in WPF and Silverlight. I don’t know everything there is to know about XAML, but by focusing on the features that I find most useful, I’m able to build apps quickly.