When you develop an application using MVVM one of the most frequent scenarios is the communication between different view models: for example, you need to exchange data between two view models or you need that, as a consequence of an action in a page, something should happen in another page. In MVVM application the answer is messaging! All the MVVM toolkits offer a messenger class: you can think of it like a postman, that is able to dispatch and to process messages that are exchanged between view models.
UPDATE: since many of you have asked, I’ve updated the post to explain how to manage communications also between a View and a ViewModel
The message is represented by a simple class: it can have one or more properties, where you can store the data that you need to pass between the view models. Using messaging, usually, means to apply the following flow:
- A view model prepares a message, by creating a new instance of the class that represents it and then send it using the messenger.
- One or more view models can subscribe to receive that type of message: in that case, they are notified every time another view model sends that type of message and they can act, to process it and to do some operations.
Let’s see how this is implemented in Caliburn Micro. The scenario we’re going to use for our sample is the following: we have two views, each one with its view model. The second page has a button to send a message, that contains a name: when the button is pressed, the message with the name is passed to the first view model, so that it can be displayed in the first page.
The first step is to setup the project: please follow the steps described in all the previous tutorials to create the bootstrapper, the two pages and the two view models and to connect all of them together.
Before taking care of the messaging stuff, let’s prepare the needed infrastructure to make the app working. The first thing is to add, in the first page, a TextBox and a Button: the first one will be used to show the information passed by the other page, the second one will be used to navigate to the second page. Open the main page (usually the MainPage.xaml file) and add the following XAML in the Grid labeled ContentPanel:
<StackPanel> <TextBox x:Name="Name" /> <Button Content="Go to page 2" x:Name="GoToPage2" /> </StackPanel>
Again, we are using the standard Caliburn naming conventions: the TextBox control will be bound with a property called Name in the ViewModel, while the button, when clicked, will execute the GoToPage2 method defined in the ViewModel.
And here’s our ViewModel:
public class MainPageViewModel: Screen { private string name; public string Name { get { return name; } set { name = value; NotifyOfPropertyChange(() => Name); } } public MainPageViewModel(INavigationService navigationService) { this.navigationService = navigationService; } public void GoToPage2() { navigationService.UriFor<SecondPageViewModel>().Navigate(); } }
There isn’t too much to say about it: it has a Name property, which type is string, and it holds a reference to the Caliburn’s navigation service, which is used in the GoToPage2 method to redirect the user to the second page of the application. Now let’s see the XAML inside the ContentPanel of the second page, that is very simple too:
<StackPanel> <Button Content="Send message" x:Name="SendMessage" /> </StackPanel>
The page contains a button, that is linked to the method SendMessage that is declared in the ViewModel. Let’s take a look at the ViewModel of the second page, that is much more interesting because we introduce the classes needed to exchange messages:
public class SecondPageViewModel: Screen { private readonly IEventAggregator eventAggregator; public SecondPageViewModel(IEventAggregator eventAggregator) { this.eventAggregator = eventAggregator; } public void SendMessage() { eventAggregator.Publish(new SampleMessage("Matteo")); } }
The class that is used to manage all the messaging stuff in Caliburn is called EventAggregator and, as for the NavigationService, it’s built in in the framework: this means that it’s enough to add a parameter in the constructor of your ViewModel which type is IEventAggregator to automatically have a reference to the object. Sending a message is really simple: you call the Publish method passing, as a parameter, the object that represents the message you want to send. As I’ve already told you before, the message is a simple class, that can hold one or more properties. Here is an example of the SampleMessage class:
public class SampleMessage { public string Name { get; set; } public SampleMessage(string name) { Name = name; } }
Nothing special to say about: it’s a class with a property called Name. In the constructor, we allow the developer to set a value for this property simply by passing it as a parameter. The result is that, in the previous sample, we use the EventAggregator to publish a message that contains the value Matteo in the Name property.
Ok, the EventAggregator, that is our postman, has sent the message. How can the ViewModel of the first page receive it? We need to do some changes:
public class MainPageViewModel: Screen, IHandle<SampleMessage> { private readonly IEventAggregator eventAggregator; private readonly INavigationService navigationService; private string name; public string Name { get { return name; } set { name = value; NotifyOfPropertyChange(() => Name); } } public MainPageViewModel(IEventAggregator eventAggregator, INavigationService navigationService) { this.eventAggregator = eventAggregator; this.navigationService = navigationService; eventAggregator.Subscribe(this); } public void GoToPage2() { navigationService.UriFor<SecondPageViewModel>().Navigate(); } public void Handle(SampleMessage message) { Name = message.Name; } }
The first step is to inherit our ViewModel from the IHandle<T> class, that is part of the Caliburn toolkit and that it’s been created specifically for messaging purposes. When a ViewModel inherits from IHandle<T>, we say that the ViewModel will be able to receive messages which type is T. In our sample, since the SecondPageViewModel class sends a message which type is SampleMessage, we make the MainPageViewModel class to inherit from IHandle<SampleMessage>.
When we inherit from this interface, we are forced to implement in the ViewModel the Handle method, that contains as parameter the message that has been received. As you can see, dealing with messages is really simple: we simply need to include in the Handle method the code that we want to execute when the message is received. In this sample, we set the Name property of the ViewModel with the value of the Name property that comes with the message.
There’s another important thing to do to get messages working: we need to tell to the ViewModel that we want to subscribe to receive messages. For this reason, we need a reference to the IEventAggregator class also in the view model that will receive the message: we do it in the constructor, as usual, and, once we have it, we call the Subscribe method passing a reference to the ViewModel itself (we do this by passing the value this).
And we’re done! Now if we launch the application, we go to the second page, we press the Send message button and we go back to the first page, we’ll see that in the TextBox the string Matteo will be displayed.
Communications between the View and the ViewModel
In some cases you may need to exchange data between the view and the view model, for example to manage animations or events that can’t be subscribed using binding. The approach to do that is exactly the same we’ve just seen: the only difference is that we need a workaround to get access to the EventAggregator, since we can’t put a parameter which type is IEventAggregator in the constructor of the page: Caliburn Micro won’t be able to resolve it and will raise an exception. First we need to apply a change to the bootstrapper, to make the PhoneContainer object (that is the used to register and resolve classes in the application) a public property, so that it can be accessed also from other classes:
public class Bootstrapper : PhoneBootstrapper { public PhoneContainer container { get; set; } protected override void Configure() { container = new PhoneContainer(); container.RegisterPhoneServices(RootFrame); container.PerRequest<MainPageViewModel>(); AddCustomConventions(); } static void AddCustomConventions() { //ellided } protected override object GetInstance(Type service, string key) { return container.GetInstance(service, key); } protected override IEnumerable<object> GetAllInstances(Type service) { return container.GetAllInstances(service); } protected override void BuildUp(object instance) { container.BuildUp(instance); } }
Then we need, in the code behind of the page, to get a reference to the container:
private IEventAggregator eventAggregator; // Constructor public MainPage() { InitializeComponent(); Bootstrapper bootstrapper = Application.Current.Resources["bootstrapper"] as Bootstrapper; IEventAggregator eventAggregator = bootstrapper.container.GetAllInstances(typeof (IEventAggregator)).FirstOrDefault() as IEventAggregator; this.eventAggregator = eventAggregator; eventAggregator.Subscribe(this); }
First we get a reference to the bootstrapper: since it’s declared as a global resource, we can access to it with its key by using the Application.Current.Resources collection. Then, thanks to the modify we did before in the bootstrapper, we are able to access to the container and, using the GetAllInstances method, we get a reference to the EventAggregator class that has been automatically registered by Caliburn Micro at startup. We need to specify, as parameter of the method, which is the type of the class we want to get a reference, in this case typeof(IEventAggregator). Since there’s just one EventAggregator registered in the application, this collection will always contain just one element: we take it using the FirstOrDefault() LINQ operation.
Now that we have a reference to the EventAggregator class, we can use the same approach we’ve seen before for view models. If we want to receive a message, we have to call the Subscribe() method and we need to inherit our page from the IHandle<T> class, where T is the message we need to manage. By implementing this interface we’ll have, also in the view, to implement the Handle method, that receives the message as parameter. Here is an example:
public partial class MainPage : PhoneApplicationPage, IHandle<SampleMessage> { private IEventAggregator eventAggregator; // Constructor public MainPage() { InitializeComponent(); Bootstrapper bootstrapper = Application.Current.Resources["bootstrapper"] as Bootstrapper; IEventAggregator eventAggregator = bootstrapper.container.GetAllInstances(typeof (IEventAggregator)).FirstOrDefault() as IEventAggregator; this.eventAggregator = eventAggregator; eventAggregator.Subscribe(this); } public void Handle(SampleMessage message) { MessageBox.Show(message.Name); } }
In case, instead, we want to send a message we call the Publish method, passing the object that represents the message to send:
public partial class MainPage : PhoneApplicationPage { private IEventAggregator eventAggregator; // Constructor public MainPage() { InitializeComponent(); Bootstrapper bootstrapper = Application.Current.Resources["bootstrapper"] as Bootstrapper; IEventAggregator eventAggregator = bootstrapper.container.GetAllInstances(typeof (IEventAggregator)).FirstOrDefault() as IEventAggregator; this.eventAggregator = eventAggregator; eventAggregator.Subscribe(this); } private void OnSendOtherMessageClicked(object sender, RoutedEventArgs e) { eventAggregator.Publish(new SampleMessage("Matteo")); } }
In this sample we’ve created an event handler to manage the Click event of a button: when the user taps on it the message is sent directly from the View. Obviously, this is just a sample, in a real scenario is not useful: it would be better to create a method in the ViewModel and connect it to the button using the Caliburn naming convention. But there are some events that can’t be connected to a command using binding: in this case, using messages is a good alternative.
Keep visiting this blog because we’re not done yet More posts about Caliburn Micro and Windows Phone are coming! In the meantime, you can play with a sample project that implements what we’ve seen in this post.
The Caliburn Micro posts series
Hi,
Great article again 🙂
Can we send a message to Views? It might be useful to start/stop animations, set a scroll position based on certain actions etc…
Thank you
ArchieCoder
Sure, I’ve just updated the post to explain this scenario
Congratulations again for a great post.
Would you recommend to use also EventAggregator to communicate ViewModel –> View for a very specific event? E.g.: change VisualState when a condition is raised in ViewModel. If not, how would you proceed with Caliburn.Micro?
Yes, it’s a good way, I’ve updated the post to explain this scenario
Hi,
Great, thanks.
I have a question. What’s the difference between message and query? We can use query instead of message , right ?
Hi, what do you exactly mean with “query” in this context?