One of my biggest goals with this application is achieving a complete understanding of how the MVVM pattern can be applied and how to achieve strong code coverage through unit tests. Thus far the first portion of the goal is proceeding nicely, regrettably I am having difficultly with the second piece.
One of the nice thing I am finding is by using an Dependency Injection model for class type resolution I can greatly cut down on the amount of code on my code behinds. I was already familiar with this aspect with respect to properties and using INotifyPropertyChanged but now I am finally getting a chance to apply it to commands. For example, I defined the following interface to define the contract which a view model must follow for the Venue management screen:
1: public interface IVenuePageViewModel
2: {
3: ///
4: /// The list of stored venues from the repository
5: ///
6: ObservableCollection Venues { get; }
7:
8: ///
9: /// The ICommand instance which will handle the Saving operation
10: ///
11: ICommand SaveCommand { get; }
12:
13: ///
14: /// The ICommand instance which will handle the Add operation. Prepares the Venue instance appropriately
15: ///
16: ICommand AddNewCommand { get; }
17:
18: ///
19: /// The ICommand instance which will handle the Cancel Operation for active Save state
20: ///
21: ICommand CancelCommand { get; }
22:
23: ///
24: /// Select a Venue to be the active Venue
25: ///
26: ///
27: void SelectVenue(Venue venue);
28:
29: ///
30: /// Indicates the current active venue
31: ///
32: Venue Venue { get; }
33:
34: ///
35: /// Return a list of States that can be displayed to the user
36: ///
37: IList States { get; }
38: }
What this gives me is a way to implement multiple view models and allow them to operate on the same or different views. This achieves a very nice, clean separation between the view model code and the view itself.
ICommand
One of the neat things that WPF can allow you to do is to bind to commands. This removes the need for the view to specify an event handler and allows the event to be directly routed to the view model where it can be handled. I created two classes which implement this interface: EventCommand and ActivatedEventCommand. This is how I perform the binding in code and XAML:
1: ///
2: /// The ICommand instance which will handle the Saving operation
3: ///
4: public ICommand SaveCommand
5: {
6: get
7: {
8: return new ActivatedEventCommand(() => _canSave, SaveVenue);
9: }
10: }
11:
12: ///
13: /// The ICommand instance which will handle the Add operation. Prepares the Venue instance appropriately
14: ///
15: public ICommand AddNewCommand
16: {
17: get
18: {
19: return new EventCommand(CreateNewVenue);
20: }
21: }
1: <Button Style="{StaticResource CommandButton}" x:Name="btnAdd" HorizontalAlignment="Left" Margin="10,5,0,0"
2: Command="{Binding Path=AddNewCommand}" Grid.Column="0" Grid.Row="2" />
3:
4: <Button Style="{StaticResource CommandButton}" x:Name="btnSave" HorizontalAlignment="Left" Margin="0,0,7,0"
5: Command="{Binding Path=SaveCommand}" />
The only real difference between EventCommand and ActivatedCommand is that the activated variant will update its Enabled status based on the return value of the provided lambda. This is curcial because once the binding here it functions like any other property, with one slight twist. Whenever the property changes ICommand.CanExecute is polled. If the method returns false, then the command will be disabled.
What this means is whenever I notify that SaveCommand has changed WPF will set IsEnabled to the boolean return value of ICommand.CanExecute. This allows the interface to update the enabled state of components automatically based on the internal state of the view model. A very powerful concept, especially when you then combine it with ElementBinding.
1: <TextBox x:Name="txtVenueName" Width="200" MaxLength="70" HorizontalAlignment="Left" Text="{Binding Venue.Name, Mode=TwoWay}"
2: IsEnabled="{Binding ElementName=btnSave, Path=IsEnabled}" />
Now, not only does the Save button automatically disable itself when saving is not allowed, it is being watched by other elements within the form who will set their IsEnabled state based on whether the Save button is enabled. Through adding almost no code, I am quickly able to provide a clean intuitive interface which switches state intelligently. This is the code is my code behind:
1: public partial class ManageVenue : UserControlBase
2: {
3: public ManageVenue()
4: {
5: InitializeComponent();
6: }
7:
8: private void Venue_Loaded(object sender, System.Windows.RoutedEventArgs e)
9: {
10: LayoutRoot.DataContext = InjectionContainer.Get();
11: }
12:
13: private void dgVenue_SelectionChanged(object sender, SelectionChangedEventArgs e)
14: {
15: if (e.AddedItems.Count > 0)
16: InjectionContainer.Get().SelectVenue((Venue)e.AddedItems[0]);
17: }
18: }
Since we define each ViewModel as a Singleton, thereby allowing only one instance to event exist we don’t even need to worry about casting or accessing the LayoutRoot property here. However, I wonder what will happen if I hit my “Back” button and do not reset the internal state of the view model. Could be kind of weird when I come back to this page later on. But Ill check that out later.
Conclusion
The MVVM pattern is a very powerful pattern and requires much discipline and understanding to fully implement. This is why it is common for programmers to rely on a framework like Caliburn.Micro to do this for them. That said, I have often found that understanding what you are doing makes using a framework much easier. I can already see some of the concepts I didn’t quite grasp in Caliburn making more and more sense to me. With any luck when I do finally decide to transition this application to use Caliburn the switch will be very easy.
Command Binding is quickly becoming one of my new favorite aspects in WPF, though it does seem you can command bind everything, case in point the SelectionChanged event on the DataGrid. But, it does make things significantly cleaner. I am also certain that when I focus more on the testing aspect of this it will be easier to test with all the view logic in the View Model instead of the code behind.