Introduction
Continuing to my Prism 2 of n series, in this article I am going to talk about how a communication happens between various application components, following Prism framework.
Background
In earlier articles of this series, I already mentioned that Prism is all about loose coupling and modularity. So, in order to achieve both these aspects we divide our application into multiple modules. Now, when we are talking about modularity, first thing which strikes to our mind is communication. How will these module going to talk with each other, how they are going to communicate with each other, etc, etc. So, when we have a need of communication between modules, there are couple of approaches which we can take like Commanding, Event Aggregation, Shared Services, Region Context and probably there are many more. In this article, mainly I'll be taking these four concepts:
- Commanding
- Event Aggregation
- Region Context
- Shared Services
Now let's take one-by-one and see, how they make our application loose coupled
Commands/Commanding Overview
This is the most common method of communication in Prism application. Now again, commanding is not specific to Prism library. It is similar to what we have in WPF and Silverlight. Main purpose of commanding is to bind a UI gesture (i.e. a button click) to an action which needs to be performed. Each command has an Execute method and this method is called whenever a command is to be execute. Apart from Execute method, command also has an CanExecute method, which determines whether or not a command can be execute. An element that is bind to the command can be either enable or disable based on the result of CanExecute method.
Most common ways to create command is either to use the RoutedCommand or Custom Command. RoutedCommand delivers the command messages to UI element. On the other hand, Custom Command can be implemented by creating a custom class, which in turns inherits an ICommand interface. Using custom command needs a lot of extra work and one has to provide command handlers to hook off and then do the routing, when the command is on board.
Please note, in Prism application, command handlers doesn't have any association with any elements in the visual tree. But don't get panic. Fortunately, Prism provides us with two classes that makes commanding more easier and provides more functionality. It provides DelegateCommand and CompositeCommand.
A DelegateCommand is a command that allows you to call a delegate when the command is executed whereas CompositeCommand allows us to combine multiple commands.
DelegateCommand
A DelegateCommand is a command that allows you to supply methods as delegates and will be invoked when the command is invoked. So, this means that event handler is not at all required in the code behind. Another good thing is DelegateCommands are normally stored locally means that are created in ViewModel and the concerns of the delegate methods are within the context of that ViewModel. Now agian, Prism has something for these DelegateCommands. Prism actually provide you with two DelegateCommand as DelegateCommand andDelegateCommand<T>. The difference is the Execute and CanExecute delegate methods for DelegateCommand will not accept the parameter whereas the DelegateCommand<T> allows you to specify the type of parameter that Execute and CanExecute parameter can be. Now, let's jump into some code:
Using the code
Before getting into much depth, first I want to tell you about what I want to achieve here.
Now what I want is, whenever I click on Calculate button, interest amount should be calculated. So, let's go ahead and implement this.
We know that calculate command should be executed on button click. So, we have to add below line of code forButton as
<Button Content="Calculate" Grid.Column="0" Grid.Row="3" HorizontalAlignment="Left" Command="{Binding CalculateInterestCommand}" />
As of now this command doesn't exist in our ViewModel. So, let's go ahead and add this in our ViewModel as:
public ICommand CalculateInterestCommand { get; private set; }
Now we need to create an instance of this command. So, in the constructor add this line:
public InterestCalculatorViewModel()
{
CalculateInterestCommand = new DelegateCommand(Calculate, () => CanCalculate);
}
In the above command, first parameter will be the Execute method(in our case, it is Calculate method) which will be called when a command will execute and the next parameter will be CanExecute(in our case, it is CanCalculate), which will tell whether a command can execute.
Now go ahead and create your Execute and CanExecute parts as:
private void Calculate()
{
InterestAmount = PrincipalAmount * InterestRate / 100;
}
private bool CanCalculate
{
get
{
// you can add your condition to enable or disable the button
return true;
}
}
Run the application and you are all done. Isn't it very easy ??? Well, now quickly move on to the CompositeCommand.
CompositeCommand
These are the commands which are globally scoped and exists in the common portion(in our case it will be in Infrastructure project) of our app and contains multiple child commands. These commands can be used where we have a requirement to perform same logic on multiple views with single command (in our case, let's assume we have a tab control and on each tab I am calculating interest amount). In that case, each view will have the local command bind in the ViewModel registered with a CompositeCommand. So, whenever CalculateAll command is invoked, all the child commands will also be invoked. Similarly even if single child's CanExecute returns false, CalculateAll will not be invoked.
For the demonstration of CompositeCommand, first let's open Shell.xaml. In Shell, I have added a another region, which contains CalculateAll button. We also added a TabControl, in order to inject the multiple instances of a view. Your Shell.xaml will look like this:
Then I've modified InterestCalculatorModule.cs to create multiple instances of view as:
At this point of time, if you will launch your application and click on CalculateAll button, you need to click the button three times, as one more each view. So, to make CalculateAll work in proper manner, let's add a new class named CompositeCommands as:
public class CompositeCommands
{
public static readonly CompositeCommand CalculateAllCommand = new CompositeCommand();
}
Now go to CalculateAllModule and open CalculateAllView.xaml and add the reference of Infrastructure project as:
<UserControl x:Class="CalculateAllModule.Views.CalculateAllView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cc="clr-namespace:PrismDemo.Infrastructure;assembly=PrismDemo.Infrastructure"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Button Command="{x:Static cc:CompositeCommands.CalculateAllCommand}" Content="Calculate All"/>
</UserControl>
Now next step is to register our ViewModel for this CompositeCommand. Open the constructor of our ViewModel and add below line:CompositeCommands.CalculateAllCommand.RegisterCommand(CalculateInterestCommand);
Now launch the application and it will work as expected. Just clicking the button CalculateAll button, all the view will get their calculation done.
EventAggregation Overview
This is the one of the common pattern, which is specific to Prism. EventAggregation provides event based communication in a very loosely coupled manner. It is made up of Publishers and Subscribers. Here a publisher will execute an event and a subscriber will listen to the event. Please note, subscribers do not need have any strong references to publishers.
Prism has built-in support for EventAggregation as it provides a core service by IEventAggregator interface. EventAggregator is responsible for locating and loading the events. It is also responsible for keeping the collection of events in the system. Please remember, publisher and subscribers will need an instance of an EventAggregator and to get that event and for performing this, they need help of container. EventAggregator also provides multicast Pub/Sub functionality means there can be multiple publishers which raise the same event and multiple subscribers listening to that event.
Events created using Prism library are typed events, which means compile time checking is possible, before we launch an application. Prism provides a class named CompositePresentationEvent<T> to create such events. This class maintains the list of all the subscribers and handles everything related to dispatching events. Please note, this is a generic class, so we need to mention the payload type T. Here T is what we want to send to subsciber, when a event is published. EventAggregator provides us with few services as:
- Publication of events - by using Publish method
- Subscribing events - by using Subscribe method
- Subscribe using a strong reference - by using keepSubscriberReferenceAlive parameter on subscribe method. Please be cautious while using this because while using this you manually have to unsubscribe from the event, when closing the subscriber
- Filtering events - by using filter delegate while subscribing
- Unsubscribing events - by using UnSubscribe method
Now, let's jump back to code once again. Here our requirement is, whenever a calculation is completed, user should get a message on statusbar indicating completetion of a calculation. Now to accomplish this, let's add a new class named UpdateCalculationStatusEvent as:
public class UpdateCalculationStatusEvent:CompositePresentationEvent<String>
{ }
Now we need to write a publisher event in our ViewModel. So, add an instance of IEventAggregator and pass it to the constructor of our ViewModel as:
IEventAggregator mEventAggregator;
public InterestCalculatorViewModel(IInterestCalculatorView view,IEventAggregator eventAggregator)
{
this.mEventAggregator=eventAggregator;
}
Now when a Calculate button is clicked, we want to send a message to the statusbar. So, in order to achieve that, we need to modify our Calculate method as:
private void Calculate() {
InterestAmount = PrincipalAmount * InterestRate / 100;
mEventAggregator.GetEvent<UpdateCalculationStatusEvent>().Publish(String.Format("Interest amount is {0}", InterestAmount));
}
As per the above line of code, whenever the Calculate method is executed, result will be published as 'Interest amount is so and so'.
Now, next we need to create a subscriber for this event and here subscriber is going to be a StatusBar. So, let's open up our StatusBar ViewModel as follows:
public class StatusBarViewModel : ViewModelBase,IStatusBarViewModel
{
IEventAggregator mEventAggregator;
public StatusBarViewModel(IStatusBarViewModel view, IEventAggregator eventAggregator)
:base(view)
{
mEventAggregator = eventAggregator;
mEventAggregator.GetEvent<UpdateCalculationStatusEvent>().Subscribe(CalculationCompleted);
}
private string message;
public string Message
{
get { return message; }
set
{
message = value;
OnPropertyChanged("Message");
}
}
private void CalculationCompleted(Object obj)
{
Message = "Updated";
}
}
Now let's go ahead and run our application and you are done " />
Shared Servcies
Shared services is the another way of communication in Prism application. It is a custom class that provides functionality to other modules in a very loosely coupled way. This service is generally placed in a separate module and can be registered using a Service Locator. When we register our service, it is registered as a common interface. This allows other modules to use our service with acquiring a static reference to the module.But there is a side effect of using this common interface is that the concrete implementation don't have to be shared. Registering your service as a shared service is very easy and can be done by ContainerControlledLifetimeManager.
Region Context
Sometimes there are scenarios where we have to share contextual information between a view that is hosting a region and a view that is inside the region. For example, you may have a master detail type scenario where you have an orders view, which exposes a region to show the order detail information. To support this scenario, Prism provides Region Context. By using Region Context one can share an object between the parent view and the child view that are hosted in the region.
Please note, Prism only supports consuming the Region Context from a view inside a region only if the view is a dependency object. So, if your view is not a dependency object, then you need to create a custom region behavior. One important thing to note here is do not use datacontext here, because datacontext is mainly use to bind viewmodel to a view. So, this means, that datacontext is storing view's entire viewmodel. So, until and unless, we have very simple view, it is not recommended to use datacontext for cummunicating loosely coupled views.
Comments
Post a Comment