Part One: Starting with MVVM
Part Two: The MVVM solution structure and basic framework
Let’s take a look at SampleView, SampleViewModel and the base Sample class all of our models extend from so we can get a better understanding of how this works.
In the first set of samples included with the code the only controls we need are a checkbox to identify which samples should be run. As we expand the capabilities of the samples to demonstrate more functionality of the TPL we’ll need to increase that. For now, however, you’ll see that the SampleView.xaml is really as simple as can be.
<UserControl x:Class="TPLSamples.Views.SampleView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
>
<Grid>
<CheckBox IsChecked="{Binding Sample.IsEnabled, Mode=TwoWay}" Content="{Binding Sample.SampleName}" ToolTip="{Binding Sample.SampleName}" />
</Grid>
</UserControl>
We have the boilerplate WPF code with the standard references and as well as our checkbox. The code-behind is the boilerplate created code with nothing added. Important here is how the checkbox data gets populated. Remember that in the ItemsControl of the SamplerView, each ViewModel that is added to the observable list the framework will bind to a view which, conversely, has as it’s data context the view model it’s bound to. Additionally, when constructing the ViewModels, they take an instance of a sample. This is how we get our View <-> ViewModel <-> Model. Let’s take a look at our SampleViewModel so we can understand what is happening in it.
public class SampleViewModel : NotificationObject
{
private readonly Sample sample;
public Sample Sample
{
get { return sample; }
}
public SampleViewModel(Sample sample)
{
if(sample == null)
throw new ArgumentNullException("Sample is null");
this.sample = sample;
}
}
That’s it. It just exposes our sample directly. Looking back at the xaml for our view, you can see that the content of the checkbox is bound to “Sample.SampleName”. Since the view has the view model as it’s data context that means we are bind the content of the checkbox to the name of the sample model. Note that there are instances when you’re not dealing in clear-cut models that it makes sense to simply incorporate the aspects of a model into the view model. It also makes sense when you want to limit the exposure of your model that you may not expose the model directly but may wrap properties in calls to the model (for example if you need to handle multi-threaded scenarios where you have to lock on properties because there may be only one instance of the model). In this case we are dealing with very obvious models and there is no reason here to limit the exposure of the model so the model is just exposed directly for binding.
One of the critical aspects of this binding is the IsChecked property. You can see that we have a “Mode=TwoWay” parameter on the binding. This means that not only does the value of the IsChecked property get set from the model, but if the value of the IsChecked property changes it will update the model. In terms of the purest implementation of MVVM this is what is problematic with exposing the model directly. The view model is supposed to act as a controller for the interface from the view and to the model. Exposing the model directly means the the view model is no longer controlling the model but instead the view is. With our samples we could have rolled the model into the model view. To simplify what is already a fairly complex set of code for something so simple as well as showing how view models work in MVVM I created view models but exposed the model.
Finally we need to look at the Sample base model that all samples derive from.
public abstract class Sample : DomainObject
{
public bool IsEnabled { get; set; }
public string Results { get; set; }
public TimeSpan RunTime { get; set; }
public abstract string SampleName { get; }
public abstract bool ImageRequired { get; }
public abstract void Run(System.Drawing.Bitmap bmp = null, Action<string> updateLog = null);
}
The vast majority of my samples need a consistent set of data to work on so we can compare times of execution. The easiest way to do this is just take an image on the Run method. This way, if we want to test our samples under different loads we can just use a bigger or smaller image. The Results property is unused and will be removed from future sets of the code. Originally I had envisioned that as the sample needed to display something to the user it would just update the Results property. Obviously this doesn’t work as the user doesn’t get immediate feedback but instead will only get updated when the sample completes its run. I replaced this with an Action that takes a string and in my SamplerViewModel the action updates the results in the Sampler model. This way the user gets continuous feedback from the sample as it updates.
As the samples run, if they use an image they modify that image. The ImageRequired property is important because it is potentially expensive on the front-end to reload the image if a sample doesn’t need it. Especially in cases where a large image is used.
And that’s it for our Sample model. I realize that the initial set-up of MVVM can be complex. Even some of the implementation of the View through ViewModel to Model work-arounds that have be put into place to handle things that are fairly simple when working with code-behind can be downright ugly. The pay-off is that supporting long-term maintenance should be a lot easier. That’s the tough part to get across to a lot of people. You will have a lot more overhead when initally spinning up an MVVM solution but in the end you will find that it is all worth it during maintenance as you fix bugs and add features. If nothing else then because of how cleanly separate your tiers are.
Up next is a more in-depth explanation of our SamplerView, SamplerViewModel and Sampler model.
Thanks,
Brian
3 thoughts on “The Task Parallel Library Sampler – Part Three: Base Classes”