Mobile Zone is brought to you in partnership with:

Den is a DZone Zone Leader and has posted 460 posts at DZone. You can read more from them at their website. View Full User Profile

MVVM Light - what's the Messenger?

03.30.2011
| 27428 views |
  • submit to reddit

MVVM 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) :)

Comments

Laurent Bugnion replied on Wed, 2011/03/30 - 1:53am

Hi,

This is a good article. I just want to point out a few things

  • 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) :)

Thanks!

Laurent

Den D. replied on Wed, 2011/03/30 - 1:59am

Thanks a lot for the clarification, Laurent! Really appreciated! I put it in the article, so it gets more visibility.

Simon Jackson replied on Wed, 2011/03/30 - 2:24am

Fantastic article, clean concise and steps you through piece by piece. Great work and keep it coming (makes me want to look into MVVM Light)

Keith Nicholson replied on Wed, 2013/06/12 - 8:09am

Great article.  Couple of question however.

If I'm setting up two view models to communicate using Messenger, do they both need to use the same base?  I've got a number of view models to update, but I need to get an iteration completed prior to updating everything to using MVVM light. I like it better than the model Telerik provided and was used by my predecessor.    If Messenger can be used simply between the two, then I'm struggling with my implementation.  My sender has:

I have the following as a backing field in each view model:

     private string employeeId

My sending View Model has within a method activated by a button command:

     Messenger.Default.Send(employeeId)

My receiver has the following in it's constructor:

     Messenger.Default.Register<string>(this, param => employeeId = param);

Sadly nothing gets returned to my receiver and employeeId is left null.  Am I missing something simple?

Also, it appears the <code> tags are not working?

Best Wishes,
Keith 

Fei Xu replied on Tue, 2014/08/12 - 1:09am

Where can I download the whole solution source code? by your code snippet, I can't understand whether your message is registered in View, or your message is sent out from ViewModel?

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.