The source code for this lesson is at the bottom of the page. Also, note in this Lesson we're changing the name of our framework. The framework is based off of Sacha Barber's Cinch framework. However, I took what he came up with and added it to my own in order to modify it when needed as you'll see when we start our lesson below. The new name is WPFMvvmPrism
Additionally, if you haven't downloaded the modified Northwind database or the SharedAssemblies, you will need to download them at the bottom of the Lesson intropage. (Click me)
(Click on the link and the DB is at the bottom of the lesson.)
Please be sure to modify the app.config file to point to the machine you which you setup restore the database too.
All the assemblies used for the demo are contained in the SharedAssemblies folder so you may need to set your reference paths accordingly.
As with all the lessons, I would like to thank the following:
Telerik for making wonderful RAD controls which make our development lives a lot easier. The Demo versions of 2010 Q1 WPF controls were used and can be found at the Telerik website.
http://www.telerik.com/account/free-...l.aspx?pid=601
A big thanks to Sacha Barber for Cinch, NSVMVVM is a slightly modified version of Sacha's Cinch Framework. His DataWrapper related code is used extensively in this Lesson.
Josh Smith for all his work posted on the internet. Finally to the others who have taken the time to post helpful work on the internet.
Areas covered in this Lesson.
PopupRegion
Inheritance with Views & ViewModels
EventAggregators
Controllers
The requirements for this lesson
1. Wire up the New Toolbar buttons for Employees & Customers. When clicked, they should display a popup window to allow a user to enter new info and create a new customer in the database.
2. The New editor window should have a Save&New button which will create a new record and clear it's fields so the user can create keep creating new records.
3. The popup should have a Close button which closes the Editor and triggers a refresh of the combo box that contains Employees or customers.
Popup Region
First we need a way to display a popup region and display a view in it. To do this we turn to Prism. It contains the code we need to accomplish. There are 5 files we take from the Prism sample DialogActivationBehavior, IWindow, RegionPopupBehaviors, WindowDialogActivationBehavior & WindowWrapper. We create a folder named Behaviors and add the files to our WPFMvvmPrism (Name changed from NSVMVVM) framework.
With the new classes in place, we go to our ShellView in our main app and modify our xaml to add the following declarations.
Code:
xmlns:WPFMVVMPrism="clr-namespace:WPFMVVMPrism;assembly=WPFMVVMPrism"
WPFMVVMPrism:RegionPopupBehaviors.CreatePopupRegionWithName="{x:Static infr:RegionNames.PopupRegion}"
WPFMVVMPrism:RegionPopupBehaviors.ContainerWindowStyle="{StaticResource WindowRegionStyle}"
Also, since we may want to use this Popup region from any of our modules. We've decided to create a PopupController in our Infrastructure assembly. We define an interface as follows:
Code:
public interface IPopupController
{
void ClosePopup();
void ShowPopup(IView view);
}
Code:
public void ShowPopup(IView view)
{
// Clear any Views in the PopupRegion
object _currentView = _popupRegion.GetView(RegionNames.PopupRegion);
if (_currentView != null)
_popupRegion.Remove(_currentView);
_popupRegion.Add(view, RegionNames.PopupRegion);
_popupRegion.Activate(view);
}
Then we add the view passed in and call Activate() which is cause the Popup window to show up.
In our ClosePopup() method, we get the view in the Popup Region, Deactivate it which causes the window to close. Then we remove it from the Popup Region.
Inheritance with Views & ViewModels
Looking further at our requirements, we realize that our Editor's will have the same UI Elements as our existing Customer & Employee Views.
Thus it would be advantageous to come up with a way where we can utilize inheritance and prevent code duplication.
To do this, we'll work in the Customer Module. The same changes were made to the EmployeeModule but won't be covered in the lesson.
When working with MVVM, you'll hear some talk about ViewModel first and then some that use View first design approaches. I tend to use the ViewModel first approach. This means that I start with building the ViewModel first and then the View. However, in other cases such as this I use the View first approach.
Originally, all our UI Elements were put in the CustomerView. However, now since our requirements indicate we need a popup window to add new customers we realize that most of the UI Elements in the CustomerView can be used in our new editor view.
Thus we decide to create a BaseCustomerModelView and move all the appropriate UI Elements to it. Then we will create a CustomerModelView which will mimic our existing functionality. Then we'll create a CustomerEditView which will be used in our Popup window. Note, in a normal situation, this would have been done from the start. However, the point of these lessons are to try and simulate the change we experience in the real world and for demonstration purposes, this will suffice.
We also create an IBaseCustomerModelView view which our BaseCustomerModelView inherits from. Next we take and move all of our UI Elements that are used to represent the fields of our CustomerModel object into the BaseCustomerModelView user control.
Then in our CustomerView, we add a new Region called CustomerDataRegion. Since we're adding regions in the module, I like to create a RegionName class for the Module and have it inherit from the RegionNames from the Infrastructure assembly.
The following is our xaml to create our new region.
Code:
<ContentControl Grid.Row="1" Grid.Column="2" Grid.RowSpan="2" Name="CustomerDataRegion" cal:RegionManager.RegionName="{x:Static local:CustomerRegionNames.CustomerDataRegion}" />
Also, while working on this I decided to create a BaseCustomerModel object which will consist of an Id, Name & Phone. This way we're not pulling back all customer info on page load to fill the combo box. However, we do add extra database calls to populate the Customer data fields on each SelectedItem change for the customer combo. The main reason I'm doing so is that after we close the Edit window for adding a new customer, we make a call to refresh all the customers to include the new one.
Note: We could design it so that the new customer gets added to the CustomerBaseModels collection but if you're new to Linq. If you look at the GetAllBaseCustomers() you can learn how to generate a query to only return certain fields.
Also, we need a way to set the IsEnabled property of our QuickAccessToolBar (referred to as QATB for the remainder of the lesson) buttons so we create a QATBIsEnabled property and wire it up with Notification so any elements bound to it will update automatically.
Code:
static PropertyChangedEventArgs _qATBIsEnabledChangeArgs =
ObservableHelper.CreateArgs<CustomerViewModel>(x => x.QATBIsEnabled);
public bool QATBIsEnabled
{
get { return _qATBIsEnabled; }
set
{
_qATBIsEnabled = value;
NotifyPropertyChanged(_qATBIsEnabledChangeArgs);
}
}
Code:
IsEnabled="{Binding QATBIsEnabled, Converter={StaticResource InverseBoolConverter}}"
We'll pick back up with Inheritance a little later. I'll label the section Inheritance II.
EventAggregators
With our new architecture, our Rad Combo which lists our customers and our RibbonBar QATB buttons are in separate Regions and thus views.
However, we need a way to communicate between them without tightly coupling them with direct references. The Prism framework includes this functionality in the way of the EventAggregator.
The Prism help explains it as follows. " The Composite Application Library provides an event mechanism that enables communications between loosely coupled components in the application. This mechanism, based on the event aggregator service, allows publishers and subscribers to communicate through events and still do not have a direct reference to each other."
In most cases when there is a need to use an EventAggregator, the events need to be available to any module in your application. Thus, any events we create for the EventAggregator are defined in our Infrastructure assembly.
For customers, we need Clear, Edit, SelectedCustomerChanged as well as UpdatedCustomer events.
The following is code for creating our ClearCustomerEvent:
Code:
public class ClearCustomerEvent : CompositePresentationEvent<object>
{
}
We do the same for the other events we need to for our QATB buttons.
With all of our Events defined, we go back to our private command functions in our CustomerViewModel. Here we want to publish our events so that any subscriber in our application will be notified.
Code:
ClearCustomerEvent _evt = _eventAggregator.GetEvent<ClearCustomerEvent>(); _evt.Publish(null);
Now we have our CustomerView & CustomerViewModels reconfigured.
Next we create the CustomerModelViewModel.
We'll set it to inherit from our BaseCustomerModelViewModel class and we'll also create an interface ICustomerModelViewModel that inherits from IBaseCustomerModelViewModel.
In our CustomerModelViewModel, we'll first start with our constructor.
Code:
public CustomerModelViewModel(IRegionManager regionManager, IEventAggregator eventAggregator, ICustomerModelView view, ICustomerService employeeService)
: base(regionManager, eventAggregator, view, employeeService)
In the CustomerModelViewModel we need to subscribe to the events that are published from our main RibbonBar's QATB buttons. In the following code we subscribe to the ClearCustomerEvent.
Code:
ClearCustomerEvent _clearCustomerEvent = _eventAggregator.GetEvent<ClearCustomerEvent>();
_clearCustomerEvent.Subscribe((m) => { this.ClearCustomer(m); });
After we do this, we're ready to create a Controller for the Customer Module.
Controllers
When reading about pure MVVM, the simplistic samples out there seldom seem to talk about controllers and how to use them best. I formed my opinions and how I would wanted to use them from looking at the stock trader sample that comes with the Oct 09 Prism library.
To me a Controller is used when you need to control intercommunication between objects in a module. Also, it can be used to handle request from other modules to display Views. For example, our Order Module has an OrderController which inherits from an IOrderController interface defined in our Infrastructure assembly. This way any module that needs the order view has a way of getting displaying it.
In our Customer and Employee modules we have a need for displaying a popup window for adding new records. Thus I decided to create the Customer and Employee Controllers in their respective modules.
Also, since I wanted a central way to "control" our popup region, I also created a PopupController in our Infrastructure assembly to handle this. The interface which it inherits from defines two methods, ShowPopup & ClosePopup which must be implemented.
In our ShowPopup() method:
Code:
public void ShowPopup(IView view)
{
// Clear any Views in the PopupRegion
object _currentView = _popupRegion.GetView(RegionNames.PopupRegion);
if (_currentView != null)
_popupRegion.Remove(_currentView);
_popupRegion.Add(view, RegionNames.PopupRegion);
_popupRegion.Activate(view);
}
In our ClosePopup() method, we get a View named PopupRegion if it exists. We deactivate it which will cause the Popup window to close. Then we remove the view from the popup view from the Popup region. A little bit repetitive I know but I like to be safe.
Now it's time to create our CustomerController for our Customer Module. We create the ICustomerController interface for to inherit from and define anything we need implemented.
Also, I moved the code that loads views into the CustomerController.
Now our CustomerModule will just register our objects in the container and then it will initialize our controller which will do the rest of our work.
There are several ways to use a controller. You could create them and rely on messaging for your communications. For now since it's a part of my module, I can't think of a good enough reason for me to resort to events. Global events are handy when you need them. However, like everything else I don't feel they're a for every situation and if overused can be hard to follow and troubleshoot.
So I use Interfaces to define the available controller functionality and utilize DI from the UnityContainer to allow my ViewModels to communicate with a controller as needed.
With that, in my ViewModels, I define the controller objects it will need. In my module controller's constructor, I set it to any ViewModels that need a reference to it specifically.
However, if a ViewModel needs to implement a central controller as is with the case with the OrderController, then I handle setting a reference in the ViewModels constructor. Again, I'm not saying it's perfect or even the best convention but it works for me.
In our CustomerController Constructor you will see the CustomerController gets assigned to the CustomerController Properties of our CustomerEditViewModel & CustomerViewModel.
Also, we populate our CustomerRegion & CustomerDataRegion with the appropriate Views.
Inheritance II
Now as promised we work our way back to our inheritance model for reconfiguring our Customer module.
We need the basic functionality of showing the data from our CustomerModel as we original had. If you recall, we moved the UI Elements we were using to do this from our CustomerView to a BaseCustomerModelView.
Now we will create a CustomerModelView and implement our BaseCustomerModelView like so:
Code:
xmlns:local="clr-namespace:NW.Modules.Customer" <Grid> <local:BaseCustomerModelView x:Name="CustomerModelViewUC"></local:BaseCustomerModelView> </Grid>
We'll register CustomerModelView & CustomerModelViewModel in the CustomerModule and we'll add the CustomerModelView to our CustomerRegion in the CustomerController.
We discussed previously of creating the CustomerModelViewModel.
Now, it's time to create a view that will be used in our Popup window to add new customers. We'll call this view CustomerEditView.
Again, we will implement BaseCustomerModelView inside of this UserControl just as we did with the CustomerModelView. But we'll also add 2 buttons:
Code:
<Button x:Name="BtnSaveNew" Content="Save & New" Grid.Row="1" Grid.Column="0" HorizontalAlignment="Right" Margin="0,10,5,0"
Command="{Binding SaveNewCustomerCommand}"></Button>
<Button x:Name="BtnClose" Content="Close" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left" Margin="0,10,0,0"
Command="{Binding CloseCustomerEditor}"
></Button>
Next we go to our CustomerEditViewModel and create the commands needed for the buttons we just created.
Also, here's where I'd like to point out the power the DataWrapper's in our framework give us. Our Save & New button, we need a way to take the info a user has entered, pass it to our Data provider to create a new customer record and then we need to clear all the UI Elements to allow them to create a new user.
An old convention would be to create a function that looped through and empty the values of our elements. However, when our Editor window loads, we call beginEdit in our CustomerModel. It caches all of our DataWrapper properties empty values.
In our SaveNewCustomer(), we make a call to our CustomerService to create the Customer. Then we can make the following call:
CurrentCustomerModel.CancelEdit();
This will set all our DataWrapper properties back to their empty values. We need to make one more call to BeginEdit
CurrentCustomerModel.BeginEdit();
This sets all or our elements back to Edit mode so the user can create the next new customer. Pretty cool stuff.
If you recall, all the initialization for our modules will now take place in their controllers.
Another thing I'd like to point out about DataWrappers. Our BaseCustomerModelView didn't contain a UI Element for our CompanyId. In order to create a new Customer, a user must enter a valid CustomerId.
All we have to do is a TextBox and bind it to our CurrentCustomer.Id.DataValue. Now when we need to edit it, we don't have to do anything else. Setting to edit mode, takes care of this for all DataWrappers in our CustomerModel.
Additionally, since CustomerId can't be null and must be less than 5 chars, we can go to our CustomerModel and add a new rule to take care of this no matter where is bound from.
There is an issue with the way the database is designed. If you allow the user to edit the customer Id, a new customer will get created. But for our purposes, that shouldn't matter.
Now, let's briefly go back to the Initialize() method of the CustomerModule. These should be the only lines of code.
Code:
public void Initialize()
{
RegisterViewsAndServices();
ICustomerController _controller = _container.Resolve<ICustomerController>();
}
That concludes Lesson 8. We covered a lot of ground in this Lesson as concise as possible. If you have any questions, suggestions or comments, please post them.







Section Widget
Categories Widget (top-down)

