(X) Hide this
    • Login
    • Join
      • Say No Bots Generate New Image
        By clicking 'Register' you accept the terms of use .

Working with Prism 4 Part 2: MVVM Basics and Commands

(6 votes)
Brian Noyes
>
Brian Noyes
Joined Jun 10, 2010
Articles:   19
Comments:   117
More Articles
17 comments   /   posted on Nov 21, 2011
Tags:   prism , brian-noyes

This is Part 2 in the series Working with Prism 4.

Introduction

In the first part of this series, I introduced you to the concepts of Prism 4 – what the toolkit is for, as well as what the top level features are. Additionally, I stepped through the basic setup of getting a Prism application up and running – creating the shell project, a module, declaring a region in the shell, and plugging a view into that region from the module.

In this article, I will flesh out that application a little more and show you some of the basic support for the Model-View-ViewModel (MVVM) pattern that Prism has, as well as how to use the DelegateCommand type that Prism provides, which fits well with MVVM scenarios. An important thing to understand is that Prism is not first-and-foremost an MVVM framework. It is a toolkit for building composite applications, which may or may not choose to use the MVVM pattern. As a result, there is not a ton of framework infrastructure provided in Prism that is specifically focused on MVVM. There are other good toolkits out there, such as the MVVM Light or Caliburn Micro toolkits that can be used in conjunction with Prism or instead of Prism if your primary goal is just to put together an MVVM application. However, I have found the combination of features of Prism for composition and MVVM to be more than sufficient for many big and small real world applications, so I usually just use Prism on its own without mixing in other toolkits, and I will keep the focus just on Prism in these articles.

This article will build on the sample application developed in Part 1. You can download the starting point code from Part 1 here. You can download the finished sample for this article here.

MVVM Basics

I don’t intend this article to be a starting point for learning the MVVM pattern. There are many other good resources out there for that, including the excellent article by Josh Smith and the two chapters we wrote in the Prism book which is also available online in various electronic forms. But just so you don’t have to go brush up on MVVM too much to follow along with what I will show in this article, here are the key essentials.

MVVM is a UI separation pattern for keeping the structural aspects of the UI (the XAML elements that you see on the screen) separated from the state and logic that supports that view. The view is just the structure of what you see on the screen. It may contain some dynamic elements such as animations, but it has no logic defining the behavior of the application from a user interaction perspective. Ideally in an MVVM application, there is no code in the code-behind class of a XAML view, just the constructor with its InitializeComponent method call. The state (data) that is presented in the view is provided and manipulated by the view model, and interactions from the user such as selections, button presses, etc. are handled in the view model. The data that the user interacts with is stored in the model, which may not structure the data exactly as it is shown on the screen. The model data structures should be chosen based on the needs of the application as a whole. Another responsibility of the view model is to transform, as necessary, the data from the way the model wants to store and manipulate it to the shape that the view wants to see the data in.

The basic structure of MVVM is that the view’s DataContext property at the root of the view will be set to an instance of the view model (View.DataContext = ViewModel). There is typically a 1:1 relationship of the class defined for the view and the class defined for the view model – for example a CustomerListView will have a CustomerListViewModel. But there are situations where one view model definition might support more than one view or vice versa. But at runtime, the view’s DataContext gets set to a reference to some view model instance that will provide its data and interaction logic. There are many ways the DataContext of the view can get set to an instance of a view model. In this article I will just focus on a couple of those, specifically hooking them up statically from the XAML of the view or using DataTemplates. But you can find many other variants out there in samples of ways to get the two hooked up to one another.

To hook up a view to its view model statically from the XAML, you simply create an instance of it in the XAML like the following snippet:

   1: <UserControl x:Class="Prism101.Modules.Core.CustomerListView"
   2:              ...
   3:              xmlns:local="clr-namespace:Prism101.Modules.Core" ...>
   4:     <UserControl.DataContext>
   5:         <local:CustomerListViewModel />
   6:     </UserControl.DataContext>

To use a DataTemplate to get them hooked up, you bind something in a parent view (typically a ContentControl or ItemsControl) to an instance of the view model whose view you want to render. Then you define a data template in the resources of the parent view that ties the view and view model together, and through the way DataTemplates work, sets the DataContext of the rendered view to the bound instance of the view model.

   1: <UserControl x:Class="Prism101.Modules.Core.ParentView"
   2:              ...
   3:              xmlns:local="clr-namespace:Prism101.Modules.Core"
   4:              ...>
   5:     <UserControl.Resources>
   6:         <DataTemplate DataType="local:CustomerListViewModel">
   7:             <local:CustomerListView />
   8:         </DataTemplate>
   9:     </UserControl.Resources>
  10:     <Grid x:Name="LayoutRoot" Background="White">
  11:         <ContentControl Content="{Binding ChildViewModel}" />
  12: ...

Commanding Basics

Commands in WPF and Silverlight are based on the Gang of Four Command design pattern. With commands you have an invoker and a receiver and some infrastructure in between to keep those two parties decoupled from one another as much as possible. The invoker is typically a UI element of some sort like a button, menu, or possibly a ComboBox selection. The receiver is the handling code that takes some action based on the command being invoked. With traditional code-behind event handling of UI control events, the event handling code represented that receiver code, but it was tightly coupled to the UI element event and had to be co-located in the code behind of the view to work. With commands, particularly with the support of bindings in WPF and Silverlight, and with dependency injection support in Prism, you can have the command invoker and receiver be very loosely coupled from one another – often defined in separate modules that have no references to one another or knowledge of each other’s presence.

Prism has two command types I’ll be covering in this series: the DelegateCommand type which is perfect for hooking up interaction logic between an MVVM view and its view model, and the CompositeCommand, which supports more loosely coupled, cross-module kinds of communication. Both are implementations of the ICommand interface defined in WPF and Silverlight, which defines three members: an Execute method, a CanExecute method, and a CanExecuteChanged event. The Execute method is the crux of the ICommand interface, it is what will be invoked whenever the invoker element has the triggering action happen to it (i.e. a button is clicked that has a command hooked up to it). The CanExecute method can be optionally hooked up to conditionally say whether the associated invoker should be enabled or not – for example if a document is not dirty, the Save command should be disabled. The associated CanExecuteChanged event is a way for the supporting logic that determines whether the command should be enabled to notify the invoker that it should re-evaluate the CanExecute state of the command and update the UI appropriately as things are changing behind the scenes (i.e. the document just went dirty because the user input some text or some other formatting command was invoked on the document that the Save command relates to).

Step 1: Getting Some Data Into the Application

Message boxes and Hello World prompts can only keep your attention for so long. So the first thing I am going to do is to use some Northwind data to add a little more real world structure to the application that I will be expanding on in this article. The sample code for this article includes a SQL script for creating the Northwind DB on your machine (assumes you have SQL Server on your machine in one of its forms). Additionally, the sample code has a WCF RIA Services domain service added to the host Web project to make it easy to retrieve and update the Northwind data from the Silverlight sample application. I am not going to go into any detail on the structure of how to do that since I have covered it in detail in my article series on WCF RIA Services here.

The steps I followed for the Silverlight sample application here is as follows:

  • Ran the SQL script to create a clean copy of the Northwind database on my machine.
  • Added an ADO.NET Entity Data Model to the Prism101.Web project from the Data project item templates in the Add New Item dialog, using the Database-first model approach to generate an entity framework model including the Customers, Orders, and Order Details tables from the Northwind database.
  • Build the project so that the next step will see the EF model.
  • Add a Domain Service Class to the Prism101.Web project from the Web project item templates in the Add New Item dialog, naming it CustomersDomainService. In the wizard, I selected those same three tables from the entity model.
  • Build the project so that it code generates the client side support for consuming the domain service and the client side entity types.

Step 2: Creating a CustomerListView

To get started with some MVVM views, I will replace the WelcomeView view that was plugged in from the Core module in the last article with a CustomerListView with a supporting view model.

In the Prism101.Modules.Core project, add a new Silverlight User Control to the project and name it CustomerListView. With the designer showing in the editor, bring up the Data Sources window (Data menu, Show Data Sources). This allows you to code generate user interface controls and have the bindings for them set up (at least partially) from data types in your application. I wrote about these features in details in my book Data Binding with Windows Forms 2.0 a long time ago, but you can find a more recent coverage in this Hands on Lab from Microsoft for WPF.

After a moment, if you have done the steps in Step 1, you should see the Data Sources window populate with the Customer, Order, and Order_Detail types.

Figure1

If you drag and drop the Customer type from this window into the Grid in the designer, it will code generate a DataGrid with the appropriate columns based on the properties of the Customer entity. Because these entities are generated by RIA Services, it will also add some additional stuff into the XAML that I stripped out to transform it into an MVVM structure, specifically a DomainDataSource. I did some other clean up in the sample code to reorder the columns from the order they were generated in. But after the cleanup, the XAML looks like that shown in the following code listing.

   1: <UserControl x:Class="Prism101.Modules.Core.CustomerListView"
   2:              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   5:              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   6:              xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
   7:              mc:Ignorable="d"
   8:              d:DesignHeight="300"
   9:              d:DesignWidth="400">
  10:     <Grid x:Name="LayoutRoot" Background="White">
  11:         <sdk:DataGrid ItemsSource="{Binding Customers}" 
  12:                       AutoGenerateColumns="False"
  13:                       Name="customerDataGrid"
  14:                       RowDetailsVisibilityMode="VisibleWhenSelected">
  15:             <sdk:DataGrid.Columns>
  16:                 <sdk:DataGridTextColumn x:Name="customerIDColumn"
  17:                                         Binding="{Binding Path=CustomerID, Mode=OneWay}"
  18:                                         Header="Customer ID"
  19:                                         IsReadOnly="True"
  20:                                         Width="SizeToHeader" />
  21:                 <sdk:DataGridTextColumn x:Name="companyNameColumn"
  22:                                         Binding="{Binding Path=CompanyName}"
  23:                                         Header="Company Name"
  24:                                         Width="SizeToHeader" />
  25:                 <sdk:DataGridTextColumn x:Name="contactNameColumn"
  26:                                         Binding="{Binding Path=ContactName}"
  27:                                         Header="Contact Name"
  28:                                         Width="SizeToHeader" />
  29:                 <sdk:DataGridTextColumn x:Name="contactTitleColumn"
  30:                                         Binding="{Binding Path=ContactTitle}"
  31:                                         Header="Contact Title"
  32:                                         Width="SizeToHeader" />
  33:                 <sdk:DataGridTextColumn x:Name="phoneColumn"
  34:                                         Binding="{Binding Path=Phone}"
  35:                                         Header="Phone"
  36:                                         Width="SizeToHeader" />
  37:                 <sdk:DataGridTextColumn x:Name="faxColumn"
  38:                                         Binding="{Binding Path=Fax}"
  39:                                         Header="Fax"
  40:                                         Width="SizeToHeader" />
  41:                 <sdk:DataGridTextColumn x:Name="addressColumn"
  42:                                         Binding="{Binding Path=Address}"
  43:                                         Header="Address"
  44:                                         Width="SizeToHeader" />
  45:                 <sdk:DataGridTextColumn x:Name="cityColumn"
  46:                                         Binding="{Binding Path=City}"
  47:                                         Header="City"
  48:                                         Width="SizeToHeader" />
  49:                 <sdk:DataGridTextColumn x:Name="countryColumn"
  50:                                         Binding="{Binding Path=Country}"
  51:                                         Header="Country"
  52:                                         Width="SizeToHeader" />
  53:                 <sdk:DataGridTextColumn x:Name="postalCodeColumn"
  54:                                         Binding="{Binding Path=PostalCode}"
  55:                                         Header="Postal Code"
  56:                                         Width="SizeToHeader" />
  57:             </sdk:DataGrid.Columns>
  58:         </sdk:DataGrid>
  59:     </Grid>
  60: </UserControl>

You can see that the code just contains a DataGrid and its columns for the properties of a Customer. I’ve modified it to assume that the DataContext of the view has a property called Customers that the DataGrid.ItemsSource property is bound to. There is also a Loaded event handler for the DomainDataSource that is added into the code behind of the view from the Data Sources window drag/drop operation that I stripped out after deleting the DomainDataSource.

Step 3: Replace WelcomeView with CustomerListView

Open the CoreModule class in the Prism101.Modules.Core project and replace the creation of the WelcomeView with the CustomerListView.

   1: public void Initialize()
   2: {
   3:     var view = new CustomerListView();
   4:     RegionManager.AddToRegion("MainContent", view);
   5: }

You can now delete the WelcomeView.xaml from that project. You should be able to build and run at this point and see that the grid shows up in the MainContent region in the shell now instead of the Hello Prism of the WelcomeView. This helps to emphasize that when using regions in Prism, the hosting view that contains a region does not need to be touched to completely alter its content, you just change what is being plugged into that container.

Step 4: Add a View Model

Add a class named CustomerListViewModel to the Prism101.Modules.Core project. Add the following code to that class.

   1: public class CustomerListViewModel : NotificationObject
   2: {
   3:     CustomersDomainContext _Context = new CustomersDomainContext();
   4:     IEnumerable<Customer> _Customers;
   5:  
   6:     public CustomerListViewModel()
   7:     {
   8:         if (!DesignerProperties.IsInDesignTool)
   9:         {
  10:             _Context.Load(_Context.GetCustomersQuery(), OnLoadComplete, null);
  11:         }
  12:     }
  13:  
  14:     public IEnumerable<Customer> Customers
  15:     {
  16:         get { return _Customers; }
  17:         set
  18:         {
  19:             if (_Customers != value)
  20:             {
  21:                 _Customers = value;
  22:                 RaisePropertyChanged(() => Customers);
  23:             }
  24:         }
  25:     }
  26:  
  27:     private void OnLoadComplete(LoadOperation<Customer> loadOp)
  28:     {
  29:         if (loadOp.HasError)
  30:         {
  31:             MessageBox.Show(loadOp.Error.Message);
  32:             loadOp.MarkErrorAsHandled();
  33:             return;
  34:         }
  35:         Customers = _Context.Customers;
  36:     }
  37: }
The view model provides the Customers collection of data that the view wants to bind to. It does that by having WCF RIA Services make a call to the back end and load the customer data using the CustomersDomainContext that is a member of the view model, then exposes the resulting customer data through a property on the view model that the view will bind to. The CustomersDomainContext is part of the code generated client code for RIA Services that acts as a proxy to the server side domain service. You can see that the Load call is an asynchronous service calls with a callback hooked up to the OnLoadComplete method to handle errors and to populate the Customers collection property once the call is complete.
 
There are a couple of important things to point out about this code. One is that in the next step I will be hooking this view model up to the view statically. That means it will get created in the designer as well. The WCF RIA Service calls to load data are not going to work in the designer. Often you will have things in your view model initialization or construction that will break the designer. To guard against this, you can use the DesignerProperties class to avoid executing that code if you are in the designer. So the call to the CustomerDomainContext.Load method is only done at runtime, not at design time.
 
Another is to notice the base class that I added to the view model class – NotificationObject. This is a base class that Prism provides that encapsulates the implementation of the INotifyPropertyChanged interface, which is important for your view models to support for the properties they expose. The base class exposes several overloaded RaisePropertyChanged methods you can call from your property set{} blocks to make sure that bindings in the view are refreshed when the underlying properties change. The call to RaisePropertyChanged in the Customers property set{} block shows that the Prism base class supporting using strongly typed lambda expressions to point to the property (Customers) itself, instead of using a string for the name of the property as is normally done when raising the PropertyChanged event directly. This makes the code more maintainable because refactoring the property name will update the RaisePropertyChanged calls as well. If declared as a string, it may not be picked up depending on what refactoring tools you use.
 

Step 5: Hook the view up to the view model

Add a XAML namespace to the CustomerListView so it has access to the types in its own assembly:
 
   1: xmlns:local="clr-namespace:Prism101.Modules.Core"

Then set the DataContext of the view in the XAML to an instance of the view model as shown earlier:

   1: <UserControl.DataContext>
   2:     <local:CustomerListViewModel />
   3: </UserControl.DataContext>
You should now be able to run and see the data pop into the view after the asynchronous call completes.
 
Figure2
 
This structuring of view and view model is often referred to as “view-first”, because the view is constructed, and then it constructs and wires up its own view model (either from XAML as done here or from code behind). View-first has advantages of simplicity as well as the fact that you can leverage the design time data support of Visual Studio and Expression Blend.
 

Step 6: Adding an Edit Command

To demonstrate using DelegateCommands to hook up communication between our view and view model for some interaction logic as well as navigating to another view using regions, I will modify the CustomerListView slightly to add an Edit button to the top of the form.
 
The XAML for the button looks like this.
   1: <Button Content="Edit"
   2:         Command="{Binding EditCustomerCommand}"
   3:         Height="23"
   4:         HorizontalAlignment="Left"
   5:         VerticalAlignment="Top"
   6:         Width="75" />
Notice the binding on the Command property assumes there is a property on the DataContext (the view model) named EditCustomerCommand. Buttons in Silverlight and WPF have a Command property that can point to an instance of an ICommand object. If that is hooked up, the button will be enabled or disabled based on calls to the CanExecute method of the ICommand interface as described earlier. Additionally, when the button is clicked, it will call the Execute method of the ICommand object.
 
Rather than implementing ICommand directly on an object, a common approach is to use an intermediary class that implements ICommand to dispatch the calls to target methods on some other object, which in this case will be our view model. The first command type that Prism provides is the DelegateCommand class. To add this support, first we declare the property that the button Command binding points to in the CustomerListViewModel class, passing the target Execute and CanExecute handling methods through delegate inference to the constructor.
 
   1: public DelegateCommand EditCustomerCommand { get; private set; }
Next we populate that property in the constructor of the view model class:
 
   1: public CustomerListViewModel()
   2: {
   3:     EditCustomerCommand = new DelegateCommand(OnEditCustomer, CanEditCustomer);
   4:     ...
   5: }

Then we define the handling method that those point to.

   1: private bool CanEditCustomer()
   2: {
   3:    return true;
   4: }
   5:  
   6: private void OnEditCustomer()
   7: {
   8: }

At this point you should be able to build and run, set a breakpoint in the OnEditCustomer method and see that your command handler is being called. Because right now the CanEditCustomer method returns true, the command will always be enabled. I will remedy that shortly.

Step 7: Pushing selection state into the view model

An important aspect of user interaction is selection in the view. But as mentioned before, in an MVVM design, all state and state manipulation should happen in the view model, not in the view. So when the user selects something in the view, the view model needs to know about it so it can take any appropriate action at that point, as well as making that selection available to command handling logic like our edit command.

Add the following property to the CustomerListViewModel.

   1: Customer _SelectedCustomer;
   2: public Customer SelectedCustomer
   3: {
   4:     get { return _SelectedCustomer; }
   5:     set
   6:     {
   7:         if (_SelectedCustomer != value)
   8:         {
   9:             _SelectedCustomer = value;
  10:             RaisePropertyChanged(() => SelectedCustomer);
  11:             EditCustomerCommand.RaiseCanExecuteChanged();
  12:         }
  13:     }
  14: }

Notice that in addition to doing raising the PropertyChanged event through the base class RaisePropertyChanged method, whenever the selected customer changes, the code makes a call to the EditCustomerCommand.RaiseCanExecuteChanged method. This causes the CanExecuteChanged event of the DelegateCommand’s implementation of ICommand to fire, which causes any command invoker such as our Edit button to update its enabled state by calling CanExecute again.

Change the CanEditCustomer method to the following.

   1: private bool CanEditCustomer()
   2: {
   3:     if (SelectedCustomer == null) return false;
   4:     else return true;
   5: }

Finally, on the DataGrid in the view, bind the SelectedItem property to the SelectedCustomer property in the view model.

   1: <sdk:DataGrid ItemsSource="{Binding Customers}" 
   2:               SelectedItem="{Binding Path=SelectedCustomer, Mode=TwoWay}" .../>

As a result of this change, the Edit button will now be disabled if there is no selection in the DataGrid. One the user selects a row in the DataGrid, the SelectedItem binding will push a reference to that Customer bound object into the SelectedCustomer property in the view model. That will fire the CanExecuteChanged event on the command, which will cause the button to requery CanExecute, and it will enable the button since the SelectedCustomer will no longer be null at that point.

Step 8: Define an edit view to switch to

As you have already seen with the MainContent region in Part 1, regions in Prism allow you to plug a view into a parent view’s region for presentation. This can be used to logically navigate the user from view to view. What I want to do when the edit command is invoked is to swap out the main view from presenting a list of customers to one that presents an edit form for the selected customer. To do this, I can just plug another view into the MainContent region, replacing the list view as the active view.

A region is a logical container for a collection of views that can be presented within that region. You can add multiple views into the region, and depending on what kind of control the region is, it can present those views either one at a time or all at once. So far I am using a ContentControl in the MainPage.xaml shell view as the MainContent region. A ContentControl can only have a single child element as its Content at one time. But the Prism IRegion abstraction can keep track of a collection of views and set the appropriate view as the Content for that control when you tell it to. This concept is called activating a view in a region in Prism. The thing that actually maps the abstract region to a concrete container control is called a region adapters. There are built in region adapters supporting ContentControl, ItemsControl, and Selector controls in Prism.

To switch to the edit view for a customer, we first have to have one. I won’t go through all the details of defining the CustomerEditView, that is Silverlight basics and is similar to what I described earlier for creating the CustomerListView. Instead of dragging and dropping the Customer type from the Data Sources window with the default settings, you drop down the selection on the Customer type in that window and select Details. Then when you do the drag and drop onto the designer, it generates a data form for a single instance of the type instead of a data grid for a collection of the type. I again trimmed out the DomainDataSource that is added in that operation and patched up the bindings to expect a Customer property exposed from the view’s DataContext (view model) once it is hooked up.

Notice the Button at the bottom of the listing that is bound to a SaveCommand property it also expects to be exposed from the view model. I will hook that up once I get to defining the view model for this view in the next step. Also notice that in this case I did not wire up the view model as the DataContext from the XAML. I will do that programmatically for this sample when the Edit command is invoked in the listing view.

   1: <UserControl x:Class="Prism101.Modules.Core.CustomerEditView"
   2:              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   5:              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   6:              xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
   7:              mc:Ignorable="d"
   8:              d:DesignHeight="212"
   9:              d:DesignWidth="301">
  10:         <Grid x:Name="LayoutRoot"
  11:           Background="White">
  12:  
  13:         <Grid DataContext="{Binding Customer}"
  14:               HorizontalAlignment="Left"
  15:               Margin="12,12,0,0"
  16:               Name="grid1"
  17:               VerticalAlignment="Top"
  18:               Width="272">
  19:             <Grid.ColumnDefinitions>
  20:                 <ColumnDefinition Width="Auto" />
  21:                 <ColumnDefinition Width="Auto" />
  22:                 <ColumnDefinition Width="5*" />
  23:             </Grid.ColumnDefinitions>
  24:             <Grid.RowDefinitions>
  25:                 <RowDefinition Height="Auto" />
  26:                 <RowDefinition Height="Auto" />
  27:                 <RowDefinition Height="Auto" />
  28:                 <RowDefinition Height="Auto" />
  29:             </Grid.RowDefinitions>
  30:             <sdk:Label Content="Company Name:"
  31:                        Grid.Column="0"
  32:                        Grid.Row="0"
  33:                        HorizontalAlignment="Left"
  34:                        Margin="3"
  35:                        VerticalAlignment="Center" />
  36:             <TextBox Grid.Column="1"
  37:                      Height="23"
  38:                      HorizontalAlignment="Left"
  39:                      Margin="3,3,0,3"
  40:                      Name="companyNameTextBox"
  41:                      Text="{Binding Path=CompanyName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, TargetNullValue=''}"
  42:                      VerticalAlignment="Center"
  43:                      Width="164" />
  44:             <sdk:Label Content="Contact Name:"
  45:                        Grid.Column="0"
  46:                        Grid.Row="1"
  47:                        HorizontalAlignment="Left"
  48:                        Margin="3"
  49:                        VerticalAlignment="Center" />
  50:             <TextBox Grid.Column="1"
  51:                      Grid.Row="1"
  52:                      Height="23"
  53:                      HorizontalAlignment="Left"
  54:                      Margin="3,3,0,3"
  55:                      Name="contactNameTextBox"
  56:                      Text="{Binding Path=ContactName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, TargetNullValue=''}"
  57:                      VerticalAlignment="Center"
  58:                      Width="164" />
  59:             <sdk:Label Content="Contact Title:"
  60:                        Grid.Column="0"
  61:                        Grid.Row="2"
  62:                        HorizontalAlignment="Left"
  63:                        Margin="3"
  64:                        VerticalAlignment="Center" />
  65:             <TextBox Grid.Column="1"
  66:                      Grid.Row="2"
  67:                      Height="23"
  68:                      HorizontalAlignment="Left"
  69:                      Margin="3,3,0,3"
  70:                      Name="contactTitleTextBox"
  71:                      Text="{Binding Path=ContactTitle, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, TargetNullValue=''}"
  72:                      VerticalAlignment="Center"
  73:                      Width="164" />
  74:             <sdk:Label Content="Phone:"
  75:                        Grid.Column="0"
  76:                        Grid.Row="3"
  77:                        HorizontalAlignment="Left"
  78:                        Margin="3"
  79:                        VerticalAlignment="Center" />
  80:             <TextBox Grid.Column="1"
  81:                      Grid.Row="3"
  82:                      Height="23"
  83:                      HorizontalAlignment="Left"
  84:                      Margin="3,3,0,3"
  85:                      Name="phoneTextBox"
  86:                      Text="{Binding Path=Phone, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, TargetNullValue=''}"
  87:                      VerticalAlignment="Center"
  88:                      Width="164" />
  89:         </Grid>
  90:         <Button Content="Save"
  91:                 Command="{Binding SaveCommand}"
  92:                 Height="23"
  93:                 HorizontalAlignment="Left"
  94:                 Margin="162,134,0,0"
  95:                 VerticalAlignment="Top"
  96:                 Width="75" />
  97:     </Grid>
  98: </UserControl>

Step 9: Define the CustomerEditViewModel

When you define a view like the edit view, you quickly identify what properties are needed from your view model to support the view, in this case a Customer property and a SaveCommand property. Add a CustomerEditViewModel class to the project and add the following contents to it.

   1: public class CustomerEditViewModel : NotificationObject
   2: {
   3:     public CustomerEditViewModel()
   4:     {
   5:         SaveCommand = new DelegateCommand(OnSave);
   6:     }
   7:  
   8:     public DelegateCommand SaveCommand { get; private set; }
   9:  
  10:     Customer _Customer;
  11:     public Customer Customer
  12:     {
  13:         get { return _Customer; }
  14:         set
  15:         {
  16:             if (_Customer != value)
  17:             {
  18:                 _Customer = value;
  19:                 RaisePropertyChanged(() => Customer);
  20:             }
  21:         }
  22:     }
  23:  
  24:     private void OnSave()
  25:     {
  26:     }
  27: }

Step 10: Switch to the edit view when the Edit command executes

As you learned in Part 1, the RegionManager service provides the mechanism for plugging views into regions. That means if you are going to switch to the edit view from the list view model, you will need access to that service there. To gain access, you can leverage dependency injection through MEF in your view model. Add the following property declaration in the CustomerListViewModel.

   1: [Import]
   2: public IRegionManager RegionManager { get; set; }

This view model gets constructed from the XAML of the view, so the container is not involved and can’t do the dependency injection automatically. But thanks to the CompositionInitializer class, you can ask the container to do the injection as the view model gets constructed explicitly. Add line 7 in the the following code to the constructor of the CustomerListViewModel.

   1: public CustomerListViewModel()
   2: {
   3:     EditCustomerCommand = new DelegateCommand(OnEditCustomer, CanEditCustomer);
   4:     if (!DesignerProperties.IsInDesignTool)
   5:     {
   6:         _Context.Load(_Context.GetCustomersQuery(), OnLoadComplete, null);
   7:         CompositionInitializer.SatisfyImports(this);
   8:     }
   9: }

That will get the IRegionManager reference injected (the set block of the property will be called to provide the reference) by the container. However, to make sure there is a single container used by both Prism and the CompositionInitializer class, you need to add one more overload to the Bootstrapper class in the Prism101 shell project.

   1: protected override CompositionContainer CreateContainer()
   2: {
   3:     var container = base.CreateContainer();
   4:     CompositionHost.Initialize(container);
   5:     return container;
   6: }

This makes it so the container that Prism sets up is the one that is used by the CompositionHost that sits underneath the CompositionInitializer class.

Now you are ready to actually do the view switching with the region manager. Modify the OnEditCustomer code with the following.

   1: private void OnEditCustomer()
   2: {
   3:     IRegion mainContentRegion = RegionManager.Regions["MainContent"];
   4:     bool alreadyExists = false;
   5:     foreach (var view in mainContentRegion.Views)
   6:     {
   7:         if (view is CustomerEditView)
   8:         {
   9:             CustomerEditViewModel viewModel = ((FrameworkElement)view).DataContext as CustomerEditViewModel;
  10:             viewModel.Customer = SelectedCustomer;
  11:             mainContentRegion.Activate(view);
  12:             alreadyExists = true;
  13:         }
  14:     }
  15:     if (!alreadyExists)
  16:     {
  17:         CustomerEditView editView = new CustomerEditView();
  18:         CustomerEditViewModel viewModel = new CustomerEditViewModel { Customer = SelectedCustomer };
  19:         editView.DataContext = viewModel;
  20:         mainContentRegion.Add(editView);
  21:         mainContentRegion.Activate(editView);
  22:     }
  23: }

If you look at what this code is doing, first it obtains a reference to the IRegion reference for the MainContent region. Then it loops through the Views collection exposed on the region to see if it contains an instance of a CustomerEditView. That would be the case if the user had already switched to the edit view at least once before. If so, it obtains a reference to the view model for that view by casting the DataContext of the view. Remember that in MVVM, View.DataContext = ViewModel. Once it has the view model reference, it can push the reference to the selected customer into the Customer property of the edit view model. Finally in that case it calls Activate on the region to get that as the currently presented view.

If the view was not in the Views collection, it simply constructs a new instance and goes through a similar process, first calling Add before Activate. This chunk of code should only execute the first time the edit view is visited, because the region will cache the reference to the edit view in its Views collection.

One thing to point out here is that I am kind of violating the principles of MVVM by explicitly constructing the CustomerEditView type in the list view model. Typically if you were going to do it this way you would factor this code out to a Controller that managed multiple views and their view models and use a CompositeCommand to hook things up. I’ll show that kind of structure in the next article, and in a later one I’ll highlight the newer navigation features of Prism regions that were added in Prism 4 as an even more decoupled way to switch views. But for now I wanted to show the basic view switching mechanisms of Prism regions and not have too many moving parts, so I am sacrificing a little bit of view model cleanliness for compactness for writing purposes here.

Once you have this code in place, you should be able to build, fire up the application, select a Customer in the grid, press the Edit button and have the main view’s content switch to the edit form.

Figure3

The last piece for this article is to hook up the Save button in the edit form so that it can at least switch back to the main form. That means you need access to the region manager from that view model as well. The code there is very similar:

  • Add an Import property for the IRegionManager
  • Call CompositionInitializer.SatifyImports from the constructor in a guard clause checking to see if you are in the designer
  • Use the region manager to activate the list view in the region

The following code shows those changes to the CustomerEditViewModel.

   1: [Import]
   2: public IRegionManager RegionManager { get; set; }
   3:  
   4: public CustomerEditViewModel()
   5: {
   6:     SaveCommand = new DelegateCommand(OnSave);
   7:     if (!DesignerProperties.IsInDesignTool)
   8:     {
   9:         CompositionInitializer.SatisfyImports(this);
  10:     }
  11: }
  12:  
  13: private void OnSave()
  14: {
  15:     IRegion mainContentRegion = RegionManager.Regions["MainContent"];
  16:     foreach (var view in mainContentRegion.Views)
  17:     {
  18:         if (view is CustomerListView)
  19:         {
  20:             mainContentRegion.Activate(view);
  21:         }
  22:     }
  23: }
At this point you have a simple but functioning example of an MVVM Prism application that uses a bit of view model base class infrastructure that Prism provides in the NotificationObject base class, DelegateCommands to communicate from the view to the view model, and regions to do the switching of views in the main window.

Summary

You can see that it is not a lot of code, nor a lot of complexity that Prism provides  with respect to MVVM and commanding, but it provides the common pieces you need for a clean MVVM application. There is no built in implementation of the ICommand interface in Silverlight, so it provides the DelegateCommand to step in nicely for hooking up views to view model logic. There are other discrete pieces of functionality related to MVVM that Prism supports. I’ll touch on some of those when we get to the navigation services in Prism in a later article. There are also features like the DataTemplateSelector capabilities, which are not as necessary in Silverlight 5 with the addition of implicit data templates, but can still come in handy in other scenarios. Another piece is the interaction request capabilities that allow you to be more decoupled in your view model and not directly present pop ups but leave it up to the view to decide what the appearance of the notification to a user looks like.

In the next article I will expand on the communication by looking at CompositeCommands and loosely coupled events in Prism. You can download the completed code for this article here.

About the Author

Brian Noyes is Chief Architect of IDesign, a Microsoft Regional Director, and Silverlight MVP. He is a frequent top rated speaker at conferences worldwide including Microsoft TechEd, DevConnections, VSLive!, DevTeach, and others. Brian worked directly on the Prism team with Microsoft patterns and practices and co-authored the book Developers Guide to Microsoft Prism 4. He is also the author of Developing Applications with Windows Workflow Foundation, Smart Client Deployment with ClickOnce, and Data Binding in Windows Forms 2.0. Brian got started programming as a hobby while flying F-14 Tomcats in the U.S. Navy, later turning his passion for code into his current career. You can contact Brian through his blog at http://briannoyes.net/ or on Twitter @briannoyes.


Subscribe

Comments

  • salim

    Re: Working with Prism 4 Part 2: MVVM Basics and Commands


    posted by salim on Nov 23, 2011 05:16
    download project does not work
  • brian.noyes

    Re: Working with Prism 4 Part 2: MVVM Basics and Commands


    posted by brian.noyes on Nov 23, 2011 05:28

    Salim, The download is working fine for me from multiple machines. You have to login first, did you do that?

    Thanks, Brian

  • salim

    Re: Working with Prism 4 Part 2: MVVM Basics and Commands


    posted by salim on Nov 23, 2011 07:45
    i'm sorry @ brian.noyes thanks yes it works..
  • bobby

    Re: Working with Prism 4 Part 2: MVVM Basics and Commands


    posted by bobby on Dec 28, 2011 17:57

    Hi Brian,

     Are you also planning to do an article on Silverlight, Prism similar to WCF RIA Services Part 7 - Authentication and Authorization?

    Cheers

    Bobby

  • brian.noyes

    Re: Working with Prism 4 Part 2: MVVM Basics and Commands


    posted by brian.noyes on Dec 28, 2011 18:02

    Hi Bobby,

    Since Prism doesn't have any security specific features or capabilities, I'm not sure what you have in mind.

    Thanks, Brian

  • bobby

    Re: Working with Prism 4 Part 2: MVVM Basics and Commands


    posted by bobby on Dec 29, 2011 10:56
    Hi Brian,

     Using FormsAuthentication, WCF RIA Services (using WCF RIA Services Class Library pattern) and Prism.

    Thanks, Bobby
  • brian.noyes

    Re: Working with Prism 4 Part 2: MVVM Basics and Commands


    posted by brian.noyes on Dec 29, 2011 17:08

    Hi Bobby,

    While I don't disagree that a sample application with all of them used in one application would be a handy reference application, since there are no Prism specific features for security, don't think covering that would fit in this article series, and there would be so much going on that it wouldn't fit in an article. Almost more the topic of a whole book. One I've often thought about writing but just can't find the time.

    Brian

  • casterle

    Re: Working with Prism 4 Part 2: MVVM Basics and Commands


    posted by casterle on Jan 19, 2012 19:54

    Hi Brian,

    Do you have an estimated date for part III?

    Thanks,
    Leroy

  • brian.noyes

    Re: Working with Prism 4 Part 2: MVVM Basics and Commands


    posted by brian.noyes on Jan 19, 2012 21:07

    Almost done, got busy over the holidays, out within the next 5 days.

  • casterle

    Re: Working with Prism 4 Part 2: MVVM Basics and Commands


    posted by casterle on Jan 19, 2012 21:24
    Excellent, thank you!
  • jawahar

    Re: Working with Prism 4 Part 2: MVVM Basics and Commands


    posted by jawahar on Mar 08, 2012 07:07

    Hello Brian

    When I  do step 2 as you have layed out I cannot see the three tables in the DataSources window. I have followed the step in step 1 you layed out. I check the final solution that you have for download and inthat  to the DataSources window is empty.

    What could I be missing

    Jawahar

  • brian.noyes

    Re: Working with Prism 4 Part 2: MVVM Basics and Commands


    posted by brian.noyes on Mar 08, 2012 14:58

    Hi Jawahar,

    Most likely you are missing the RIA Services link between the client and server project. First thing to check is if there is a GeneratedCode folder under the Prism101.Modules.Core project. It won't show up in Solution Explorer unless you select the Show All Files button at the top of Solution Explorer. If that is not there, it means your RIA Services link is not set up. Go to the project properties for that project all the way to the bottom of the first tab (application) and you will see a drop down for a RIA Services link - that should be selected to the hosting web project. If that is not it, let me know and I can brainstorm some more what might be going on there.

    Brian


  • jawahar

    Re: Working with Prism 4 Part 2: MVVM Basics and Commands


    posted by jawahar on Mar 08, 2012 19:59

    Thank you Brian for your quick response, It was the RIA Service Link that was not set. Thanks again

    Jawahar

  • jawahar

    Re: Working with Prism 4 Part 2: MVVM Basics and Commands


    posted by jawahar on Mar 08, 2012 20:26

    Thank you Brian for your quick response, It was the RIA Service Link that was not set. Thanks again

    Jawahar

  • jawahar

    Re: Working with Prism 4 Part 2: MVVM Basics and Commands


    posted by jawahar on Mar 09, 2012 03:47

    Hello Brian

    When I  do step 2 as you have layed out I cannot see the three tables in the DataSources window. I have followed the step in step 1 you layed out. I check the final solution that you have for download and inthat  to the DataSources window is empty.

    What could I be missing

    Jawahar

  • Nuitari

    Re: Working with Prism 4 Part 2: MVVM Basics and Commands


    posted by Nuitari on Jul 23, 2012 15:37

    Thanks for the tutorial!

    I had the same issue as Jawahar, the RIA Service Link had to be set manually after following the steps in Step1. (Visual Studio 2010, Silverlight 5, SQL Server Express 2012 with Northwind).

  • jetr

    Re: Working with Prism 4 Part 2: MVVM Basics and Commands


    posted by jetr on Nov 07, 2012 19:40
    Excellent article, Congratulations a thanks too much Brian

Add Comment

Login to comment:
  *      *       

From this series