MVVM Light - what's the Messenger?
Join the DZone community and get the full member experience.
Join For FreeMVVM Light offers a class specific to every ViewModel instance that is used in the context of an application - Messenger. One might ask - what exactly is it and why it is needed? The answer is simple - in a more complex application, ViewModel instances are not standalone entities. Chances are you would like to pass specific data from one model to another, and Messenger is facilitating this process.
NOTE: I am using a Windows Phone 7 Silverlight application for the demo.
Let's look at specific source code to illustrate its activity. Initially, I have a standard test view model (named TestViewModel):
public class TestViewModel : ViewModelBase { public TestViewModel() { } private string tS = "tS"; public string TestString { get { return tS; } set { tS = value; RaisePropertyChanged("TestString"); } } }
Nothing super useful here - just a dummy string property with an associated field that has a default value. Let's say I have another ViewModel instance that contains additional data (not necessarily related to the ViewModel above):
public class MainViewModel : ViewModelBase { public MainViewModel() { } private string pA = "pA"; public string PropertyA { get { return pA; } set { RaisePropertyChanged("PropertyA"); } } }
Both models are accessed via a ViewModelLocator instance (in case you are not sure what it is, take a look here). This very instance is declared in the App.xaml file as a resource that is globally accessible:
<Application.Resources> <vm:ViewModelLocator x:Key="Locator"/> </Application.Resources>
Now in my main page, I have to make sure that I have the correct DataContext set, so that it points to the correct ViewModel in the correct ViewModelLocator instance:
DataContext="{Binding Main, Source={StaticResource Locator}}
To show that the model is bound I created a TextBlock on the page itself, as well as a Button (its purpose will be discussed later).
<StackPanel x:Name="LayoutRoot"> <Button Height="100" Content="Test" Click="Button_Click"></Button> <TextBlock Text="{Binding Path=PropertyA}"></TextBlock> </StackPanel>
Notice that the TextBlock has its Text property bound to PropertyA. Given the DataContext, it will be located in the MainViewModel. There you have it - the very basic skeleton. Run the application and make sure that the binding works correctly. You should see something similar to this:
pA in the TextBlock means that it was successfully bound to PropertyA.
Now I will add a new Page and set its DataContext to the TestViewModel. That way I have two separate pages that pull data via the same ViewModelLocator but from different ViewModel instances:
<phone:PhoneApplicationPage x:Class="MVVMLight.SecondaryPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" SupportedOrientations="Portrait" Orientation="Portrait" mc:Ignorable="d" d:DesignHeight="800" d:DesignWidth="480" DataContext="{Binding Test, Source={StaticResource Locator}}"> <TextBlock Text="{Binding TestString}"></TextBlock> </phone:PhoneApplicationPage>
I tried to keep it as simple as possible here, so that's why you just see a TextBlock bound to the TestString property.
Now it's time to modify MainViewModel, and that's exactly where I am starting the whole Messenger story. As you know, when a property changes, in order to re-bind it in MVVM Light you have to invoke RaisePropertyChanged("PROPERTY_NAME"); There is an overload to this method (that is inherited from ViewModelBase) that is able to broadcast (inform other ViewModel instances) that a property has changed.
Here is what I changed in MainViewModel.cs:
private string pA = "pA"; public string PropertyA { get { return pA; } set { string oldValue = pA; pA = value; RaisePropertyChanged("PropertyA", oldValue,value,true); } }
Notice that I am passing both the old value and the new one as a part of the broadcast. The transmitted data is pushed inside a PropertyChangedMessage.
So let's see what happens when I modify my property. To do this, I need to access the MainViewModel instance in the ViewModelLocator I am using and set the PropertyA value. That's where I am using the button I put on the page above:
private void Button_Click(object sender, System.Windows.RoutedEventArgs e) { ViewModel.ViewModelLocator.MainStatic.PropertyA = "Hello, this changed!"; }
Launch the application and see what happens when you press the button. The property is successfully changed, but that's about it. What happened to RaisePropertyChanged that was supposed to broadcast the message to other ViewModel instances?
If you take a look at the code above, you will notice that the message is broadcasted, but there is not a single place to receive it. Therefore, no action is taken because no ViewModel knows about the message. To fix this, in the TestViewModel I need to register a listener that will get messages of a specific type and perform a specific action based on the data received.
In the TestViewModel constructor I put this line:
Messenger.Default.Register<PropertyChangedMessage<string>>(this, (s) => MessageBox.Show(s.NewValue));
Here it is - the long awaited Messenger. This class is able to both send and receive messages - here it is set to its receiver state. There is a default Messenger instance for every ViewModel, and if you take a look at the source for ViewModelBase, you will see what I am talking about:
This is a classic messaging service that allows easy communication between initialized ViewModel instances.And by default, in your ViewModelLocator the registered ViewModel entities are automatically instantiated at startup (unless a different configuration is used).
As you've seen from the snippet above (before the Reflector snapshot), I am basically registering a listener for a specific type of messages. In this case, I am looking for a PropertyChangedMessage instance that carries a string. this represents the recipient - obviously, I can set it to another ViewModel instance, but at this point this is not necessary.
Last but not least, there is the Action parameter and I am using an anonymous method to display a MessageBox with the information.
NOTE: Inside the anonymous method I have access to properties available in the recipient.
The s parameter is not a string - it is PropertyChangedMessage<string>. And as I said before, I have access to the old value of the property (that was changed) as well as the new one:
Launch the application now and see what happens. The moment you click the button and change the PropertyA value, you will see a MessageBox pop up with the new value. I can pass whatever object I want to be passed in the context of PropertyChangedMessage, as long as the sender property has access to it.
If I want to change the local property of the ViewModel that hosts the Messenger listener, I can do that in the same anonymous method I was passing before. Taking the TestViewModel as the "guinea pig", I am going to change the TestString property.
Messenger.Default.Register<PropertyChangedMessage<string>>(ViewModel.ViewModelLocator.MainStatic, (s) => TestString = s.NewValue);
Since I have the secondary page tied to a ViewModel instance (TestViewModel) I can show it once the value is modified, so in the same Button_Click event handler that triggered the property change I am going to add the navigation line:
NavigationService.Navigate(new Uri("/SecondaryPage.xaml", UriKind.Relative));
Once I navigate there, I should see a page like this:
The property was correctly updated and the second page proved it. The interesting thing is that the Messenger class doesn't handle solely messages that come from changed properties. There are other types of messages that can be manually sent:
- NotificationMessage - used to deliver pure string messages.
- DialogMessage - used to request a MessageBox to be shown. The above sample with the MessageBox in the callback can easily be modified and take advantage of DialogMessage.
- GenericMessage<T> - used to pass any object.
- NotificationMessageWithCallback - same as NotificationMessage, but with a callback that will be triggered after accessing the passed notification.
Sending is as simple as calling the Send method for the default instance. For example, I created a simple DialogMessage to be passed:
DialogMessage d = new DialogMessage("Sample dialog",(m) => { Debug.WriteLine(m.ToString()); }); d.Button = MessageBoxButton.OKCancel; d.Caption = "Test";
Then all I have to do is call Send:
Messenger.Default.Send<DialogMessage>(d);
I also need to modify the listener, so that it expects the correct message format:
Messenger.Default.Register<DialogMessage>(this, (s) => {
MessageBoxResult m = MessageBox.Show(s.Content, s.Caption, s.Button);
s.ProcessCallback(m);
});
As you can see, the DialogMessage won't show the dialog but will be kind enough to pass the required information and let you pass the result to the callback that later on will be handled by the application.
One last thing to tell you is that messages can be identified by tokens. So if I want to broadcast a message to a specific ViewModel only, I can do so by passing an object that will be verified before taking an action on handling it.
For example, the above call to send a message can be modifed like this to send a token along:
Messenger.Default.Send<DialogMessage>(d,"TEST_TOKEN");
On the receiving side, I would request that token only:
Messenger.Default.Register<DialogMessage>(this, "TEST_TOKEN", (s) => { MessageBoxResult m = MessageBox.Show(s.Content, s.Caption, s.Button); s.ProcessCallback(m); });Also, there is an important comment from Laurent Bugnion (the creator of MVVM Light) that should be mentioned here (you can see it in the comment section below too):
- While using Messenger.Default is common, it is also possible to instantiate a different instance of the Messenger, to do "private broadcasts". It is also possible to pass an instance of the Messenger to a ViewModel class (in the constructor) should you want to use that instance instead of the Default.
- You can send really any type, from simple values (string etc) to more complex objects (like the PropertyChangedMessage, or for instance a SelectedCustomer etc).
- Messenger is useful not just for ViewModels, but really any time you need two loosely coupled objects to communicate. I know that some people use the Messenger in a WinForms application for instance.
- And finally, everything shown here is also valid in WPF and in Silverlight (3 and 4) :)
Opinions expressed by DZone contributors are their own.
Trending
-
Essential Architecture Framework: In the World of Overengineering, Being Essential Is the Answer
-
How to LINQ Between Java and SQL With JPAStreamer
-
Structured Logging
-
Application Architecture Design Principles
Comments