Monday, 14 February 2011

Swapping WPF controls using MVVMC aka MVVM+Controller

We have an application that uses WPF User Controls that we would like to dynamically change using a combobox within a pluggable architecture. The solution is based on MVVMC sometimes called MVVM+Controller.

We did this by using a SpecificViewContentProvider which as the name suggests provides a view model for a ContentPresenter that has been bound to a view model with a user control as an observable property. In our case the application is very specific therefore we did not mind coupling the View coupled to the controller. If we wanted to break this dependency we would need to use MVVMLite or Prism. Again in this case this would be an overkill, therefore we decided to leave this dependency.

We where thinking of using MVVMLite in this application and there are some traces left, for this example it is not necessary. But frameworks like Prism and MVVMLite are very useful in simplifying applications that have complex controls. The idea is that the controls produce relay events that broadcast to anyone who is interested in there status. For example a complex tree view to complex controls to view different aspects of the application

We looked at how Research would like to build the individual controls. For reasons described in day 3 we cannot inherit xaml code, therefore if we wanted to have a base control we would have to only use code behind. In windows forms this was possible with visual inheritance. Since this is not possible within xaml the visual befit of inheritance is lost. Therefore we decided to take a different approach:

Most models different sets of controls. We decided a better approach would be to setup some custom controls like CreditDecductible, ZonalDeductibles etc and compose these within the control that is to be exported into the RunModelWizard.

Here is how we achieved this:

1. We create a user control within ArchitectureExample.PresentationLayer.WPF.Common We make some binding eg {Binding Deductible, Mode= TwoWay}

2. Next we make our first injectable control ModelSettingsForEarthquakeAlaskaView.xaml within the Views/ModelSettings directory of ArchitectureExample.PresentationLayer.WPF.Client The interesting line is <my:CreditDeductibleView Name="creditDeductibleView1" DataContext="{Binding CreditDeductible}" />

The interesting lines are the binding that is made to the SelectedModelSetting <ContentPresenter x:Name="SpecificModelSetting" DataContext="{Binding SelectedModelSetting}" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"/>

This is set with the combobox control <ComboBox x:Name="cmbModel" Grid.Column="1" Grid.Row="0" ItemsSource="{Binding ModelSettings}" SelectedItem="{Binding SelectedModelSetting, Mode=TwoWay}" DisplayMemberPath="Value.Name"/>

The OK button is bound to the Command SaveModelSettingsCommand. By the way, if we where programming in Silverlight we would need MVVMLite or Prism to do this because Silverlight does not support ICommand. Actually the code needed is quite small so you could role your own, but the easiest way is to take a widely used third party library like MVVMLight <Button x:Name="btnOk" Content="OK" Margin="0 0 5 0" Command="{Binding SaveModelSettingsCommand}" CommandParameter="{Binding SelectedModelSetting.Value}"/>

This meta data is needed to identify the control we want to import. Within the function GetView we use a linq expression to find the modelview that we are interested in. Since this is an enumeration of type lazy we need to use .value to instantiate the view model.

private void UpdateSpecificModelSettingsView() { if (this.viewModel.SelectedModelSetting != null) { //==Here is where the Imported Enumeration of Lazy IModelSettingView are used var specificView = viewFactory.GetView(this.viewModel.SelectedModelSetting.Metadata.ModelType) as UserControl; //Here we have to set the data context because we are changing the control specificView.DataContext = this.viewModel.SelectedModelSetting.Value; //Here we populate the place holder with our view (tightly coupled) this.view.SpecificModelSetting.Content = specificView;

} }

} }

10. The View model belonging to ModelSettingsView.xaml is ModelSettingsViewModel.cs Inside this we expose Lazy Observable collections and selected values. Here we are using MVVMLite and the ViewModelBase. Actually we don't need to do this because WPF has an implementation of ICommand.

The interesting parts of the above are public RelayCommand<ModelSettingsBase> SaveModelSettingsCommand Notice that in the can Execute Func we first chack if Ms !=null. Without this check first the function will throw an exception , (ms) => { // CanSave return ms !=null && ms.Validate(); });

The ms.Validate part is invoking an implementation of an abstract class:

Notice that we decided to use a boolean to return validation. The alternative would have been to use an exception. We choose boolean because exceptions are slow because they need to serialize the stack trace.

There is a validation enterprise building block that uses attributes to set validation rules. We will look into using this because it provides a tidy way to associate validation rules to properties and will remove the untidy code in the Validate() function

We tested this with 2 imported controls with 1 and 2 of these user controls. The OK button of the main control was properly enabled and disabled according to the validation rules. Also as we changed the combo box the values from one custom control to another where correctly persisted...