Pages

Wednesday, 20 May 2015

A Humble Review of Josh Smith's MVVM Article Part#3


How WorkSpaces are rendered and saving/adding new Customers:


Before getting in the the design process lets first look at some WPF XAML principles as a reminder. The following two diagrams show the basic principle of binding properties to ContentControls and ItemsControls and sourcing them via DataTemplate for Visual purposes.

 


Now regard the second diagram below, which represents an ItemsControl 

 

It can be seen immediately that the resemblance is obvious. The only difference is the ItemsControl class versus ContentControl class and the names of the properties the derived classes expose. However the point here is that both diagrams show the basic technique of binding a class property (could be a Dependency Property or a Collection type Property like ObservableCollection) and a DataTemplate. So in other words, if we have a container control, be it an ItemsControl (like a ListBox) or a ContentControl (like HeaderedContentControl or GroupBox) the principle is the same for filling up the container, namely bind it to a class property and provide the Template (i.e. how it will look visually) via the DataTemplate.



Another technique widely used in WPF design as a principle is to use a UserControl under a DataTemplate, thus wrapping a whole amount of UI Elements into a UserControl and then present it under a DataTemplate to the Container (ItemsControl or ContentControl). This approach is also encouraged, as it provides further loose coupling modularity especially in MVVM design and also by using a UserControl as a DataTemplate, you are afforded the full design flow of using Expression Blend to write the XAML for the DataTemplate.

Rendering Process:


There are two HeaderedContentControls used in the design. Their contents are bound to ObservableCollection and ReadOnlyCollection type properties at MainWindowViewModel class respectively. The template resource (for the second HeaderedContentControl of the WorkSpaces) is called  "WorkspacesTemplate" which is detailed in the XAML Resource file (MainWindowResources).


 

The diagram above shows the basic principle of what the Parser needs and how it acts in terms of obtaining the object and rendering it on the screen. When an ItemsControl or ContentControl is involved, the XAML Parser needs a definition of the object first. This means it needs to see a class object which exposes a Collection type Property (generally ObservableCollection). Also it needs further definitions for rendering, if the Collection's items are of Class type objects. In this example, we see that the Collection called "WorkSpaces" may contain 2 different class type objects, namely CustomerViewModel and AllCustomersViewModel (Please take note that we could place those two different type of classes into the same Collection as both classes are derived from base (abstract) class WorkspaceViewModel).

In this example, the path for rendering definition to the XAML Parser engine is provided as Global DataTemplate definitions at the ResourceDictionary (MainWindowResources.xaml) as shown below.

<!-- 
  This template applies an AllCustomersView to an instance 
  of the AllCustomersViewModel class shown in the main window.
  -->
  
<DataTemplate DataType="{x:Type vm:AllCustomersViewModel}">
    
<vw:AllCustomersView />
  
</DataTemplate>
 
  
<!-- 
  This template applies a CustomerView to an instance  
  of the CustomerViewModel class shown in the main window.
    -->
  
<DataTemplate DataType="{x:Type vm:CustomerViewModel}">
    
<vw:CustomerView />
  
</DataTemplate>

Here there are two DataTemplates which wrap 2 UserControls (Views). The XAML Parser will read and memorise it during the runtime setup (launch) process and associate the ViewModels to their related Views (i.e. AllCustomerViewModel class object to AllCustomerView UserControl (class) object. When the user clicks on "View all Customers" or "Create new Customer" hypertext buttons, either "CustomerViewModel" or "AllCustomersViewModel" class objects will be added to the ObservableCollection _workspaces. When this happens, the XAML Parser knowing the relationship between ViewModel and it's View (i.e. CustomerViewModel vs CustomerView) will automatically know that it has to use the View as the template for rendering for this particular ObservableCollection item. So, if "Create new Customer" is clicked, then it will render the UserControl assosiated (CustomerView) and use the CustomerViewModel class as the binding source (depending on the XAML definition on the UserControl). The diagram below shows how the workspace is styled and rendered.

<Border
       
 Grid.Column="2"
       
 Style="{StaticResource MainBorderStyle}"
       
 >
        
<HeaderedContentControl
         
 Content="{Binding Path=Workspaces}"
         
 ContentTemplate="{StaticResource WorkspacesTemplate}"
         
 Header="Workspaces"
         
 Style="{StaticResource MainHCCStyle}"
         
 />
</Border>


Saving/Adding Customers:


 


We know that the UserControl "CustomerView" is linked to "CustomerViewModel" by the XAML code above Notes on page "MvvmDemoApp Part3" . So for any commands issued on the UserControl, the Binding Source object is the CustomerViewModel class, unless set differently. Below,there is the XAML code for the "Save" button.


<!-- SAVE BUTTON -->
    
<Button
     
 Grid.Row="8" Grid.Column="2"
     
 Command="{Binding Path=SaveCommand}"
     
 Content="_Save"
     
 HorizontalAlignment="Right"
     
 Margin="4,2"
     
 MinWidth="60"
     
 />

The command property of the Button is tied to the SaveCommand property of Icommand type. 



The diagram above describes visually how the "Save" operation works, when the user clicks on the "Save" button on the new Customer workspace. During runtime at the initial setup run of the program (when the program is launched), the XAML Parser Engine reads all necessary binding information, as defined on the XAML script. From this reading XAML Parser knows that there is a command binding between the Save Button on the "CustomerView" UserControl and the CustomerViewModel (public ICommand SaveCommand). The second binding that the XAML Parser is aware of is the binding between the ObservableCollection of "AllCustomersViewModel" and the ListView element on the "AllCustomersView".

Now, when the "Save" Button is clicked on the "CustomerView" UC (which is rendered via DataTemplate on the HeaderedContentControl on the MainView), the Save method at CustomerViewModel will fire (via the Binding object). This method will create a new "RelayCommand" object instance, which will run CanSave validation process (described later in the article). If the validation passes, RelayCommand will delegate next to the Save method of the CustomerViewModel. The Save method will call the "CustomerRepository" object and will add the new customer object to the generic list<customer>. Then an event will be raised at the CustomerRepository, which is handled at "AllCustomersViewModel", where the CustomerViewModel is added into the  "AllCustomers" ObservableCollection. As mentioned above, this collection is bound to the ListView control at "AllCustomerView", thus the CustomerViewModel properties (such as email,FirstName etc.) will be rendered under the ViewList as a new element and displayed (i.e. new customer is added to the "AllCustomers" workspace). 

The next article will describe 3 processes which take place, which merit to be mentioned. These processes are:



1-Validation during the Saving process (CanSave method)

2-How CustomerRepositoy reads XML file data

3-Validation Process during new Customer entry.



Finally the last article of the series will make a summary of the MVVM design we have seen so far.

No comments:

Post a Comment