Messenger Tokens and RaisePropertyChanged

Aug 31, 2010 at 2:54 PM
Edited Aug 31, 2010 at 2:55 PM

I was just writing my umpteenth method to receive PropertyChangedMessageBase messages which then compares the type of the message's sender and a property on that sender when I realized that I could be utilizing tokens. I took at look at the RaisePropertyChanged and Broadcast methods in ViewModelBase and tried to add the ability to specify a Token along with the RaisePropertyChanged, and realized that the IMessenger contract doesn't include Messenger.cs's Send overload that can take a Token.

Here are the modifications I made to allow a RaisePropertyChanged overload to send a Token.

 

public interface IMessenger
{
    /* clipped out unchanged code */

    /// <summary>
    /// Sends a message to registered recipients. The message will
    /// reach only recipients that registered for this message type
    /// using one of the Register methods, and that are
    /// of the targetType.
    /// </summary>
    /// <typeparam name="TMessage">The type of message that will be sent.</typeparam>
    /// <param name="message">The message to send to registered recipients.</param>
    /// <param name="token">A token for a messaging channel. If a recipient registers
    /// using a token, and a sender sends a message using the same token, then this
    /// message will be delivered to the recipient. Other recipients who did not
    /// use a token when registering (or who used a different token) will not
    /// get the message. Similarly, messages sent without any token, or with a different
    /// token, will not be delivered to that recipient.</param>
    void Send<TMessage>(TMessage message, object token);

    /* clipped out unchanged code */
}

public abstract class ViewModelBase : INotifyPropertyChanged, ICleanup, IDisposable
{
    /* clipped out unchanged code */

    /// <summary>
    /// Broadcasts a PropertyChangedMessage using either the instance of
    /// the Messenger that was passed to this class (if available)
    /// or the Messenger's default instance.
    /// </summary>
    /// <typeparam name="T">The type of the property that
    /// changed.</typeparam>
    /// <param name="oldValue">The value of the property before it
    /// changed.</param>
    /// <param name="newValue">The value of the property after it
    /// changed.</param>
    /// <param name="propertyName">The name of the property that
    /// changed.</param>
    /// <param name="token">The token value to send along with the PropertyChangedMessage.</param>
    protected virtual void Broadcast<T>(T oldValue, T newValue, string propertyName, object token)
    {
        var message = new PropertyChangedMessage<T>(this, oldValue, newValue, propertyName);

        if (MessengerInstance != null)
        {
            MessengerInstance.Send(message, token);
        }
        else
        {
            Messenger.Default.Send(message, token);
        }
    }

    /* clipped out unchanged code */

    /// <summary>
    /// Raises the PropertyChanged event if needed, and broadcasts a
    /// PropertyChangedMessage using the Messenger instance (or the
    /// static default instance if no Messenger instance is available).
    /// </summary>
    /// <typeparam name="T">The type of the property that
    /// changed.</typeparam>
    /// <param name="propertyName">The name of the property that
    /// changed.</param>
    /// <param name="oldValue">The property's value before the change
    /// occurred.</param>
    /// <param name="newValue">The property's value after the change
    /// occurred.</param>
    /// <param name="broadcast">If true, a PropertyChangedMessage will
    /// be broadcasted. If false, only the event will be raised.</param>
    /// <param name="token">If not null, and if a PropertyChangedMessage
    /// is broadcasted, the message is sent with the specified token.
    /// If null, the PropertyChangedMessage is sent without a token.</param>
    [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate",
        Justification = "This cannot be an event")]
    protected virtual void RaisePropertyChanged<T>(string propertyName, T oldValue, T newValue, 
bool
broadcast, object token = null) { RaisePropertyChanged(propertyName); if (broadcast) { Broadcast(oldValue, newValue, propertyName, token); } } /* clipped out unchanged code */ }

 

Laurent, do you think this might be a good addition to the next release of MVVM Light Toolkit?

Coordinator
Aug 31, 2010 at 3:28 PM

Hey Matt,

 Point 1 was on my backlog, I noticed that the other day when I wanted to mock a messenger using tokens ;)

 Point 2 sounds very reasonable, I will add it in V4.

Thanks for the message,

Cheers,

Laurent

Sep 13, 2010 at 5:58 PM

Laurent,

In addition to the

void Send<TMessage>(TMessage message, object token);

signature missing from IMessenger, the following two Register overloads are missing as well.

void Register<TMessage>(object recipient, object token, Action<TMessage> action);
void Register<TMessage>(object recipient, object token, bool receiveDerivedMessagesToo, Action<TMessage> action);

I discovered the Send missing when I was trying to mock a messenger, but then when I was implementing some overloads in a Messenger class noticed that those two Registers were missing as well.

 

Just out of curiosity, why doesn't Messenger.OverrideDefault take an IMessenger instead of Messenger?  When I setup my mock test I had to create basically a shell Messenger that overrode all the method and called my mocked instance. 


Sep 15, 2010 at 8:00 PM

Laurent,

for point 1 I would suggest to create an explicit method along the lines "RaiseAllPropertiesChanged" to make it explicit. Personally, I think that using null or empty string to highlight such an important issue is not appopriate.

Regards, Axel

Nov 27, 2010 at 3:25 AM
Edited Nov 27, 2010 at 3:27 AM

I am trying to update another VM when a property changes (it's an aggregated property bases on other VM properties). So I am listening for an INT but from what I can see it means that every int property that is broadcasting would be received - I need to specify a token like I do in the SEND method so I can match senders to receivers - is this what this is doing? If so, any chance it could be released soon ;)

Just wanted to specify my use case:

I have a total based on the sum of 2 figures, and then the sum or 2 more figures multiplied by a factor. Hence if any one of these changes (ie. all different types; ints, decimals) I need to receive the UpdatePrize message. Hence I would like to specify the "UpdatePrize" token instead of doing it by type.

 

Thanks

Coordinator
Nov 29, 2010 at 8:36 AM

@PokerDIY: If you register for a message using the overload of Register that has a "token" parameter, then the recipient will only receive messages that are sent with the same token (using the corresponding overload of the "Send" method.

For your scenario, you can use a normal RaisePropertyChanged method in your property setter, and then Send the message manually.

In V4, I am introducing a RaisePropertyChanged(..., object token, ...) method that will do this automatically.

Cheers,

Laurent

Coordinator
Nov 29, 2010 at 8:40 AM

@ThunderEagle, sorry for the delay. The reason why OverrideDefault takes a Messenger is historical, it was implemented in a time where IMessenger was missing. I will try to find a way to do the same using IMessenger in V4.

Cheers,

Laurent

Jun 3, 2011 at 1:46 PM

Did the token argument get added in V4?  Can't see it.  I have the very same need to broadcast the PropertyChanged to interested parties.  I guess the RelayCommand with callback actually solves my particular problem but generally I use the tokens to  direct the messages to only those who care...

Thanks.

 

 

 

 

Coordinator
Jun 5, 2011 at 12:33 PM

Sorry, it was not added yet. In the meantime you can get the same effect by using the first overload of RaisePropertyChanged(string) and right after, send a PropertyChangedMessage with Messenger.Default.

Cheers,

Laurent

Jun 18, 2012 at 5:22 PM
Edited Jun 18, 2012 at 5:23 PM

Hi Laurent,

I am also looking for this feature to broadcast RaisePropertyChanged with a token.  I'll use your above method, will work fine.  I think some of the earlier property code snipits used this method with the broadcast commented out by default.

As a side note, to help with the WP7 UI updates, it would be nice if broadcast RaisePropertyChanged could be async on a background thread - but I understand why you don't do this.  Actually, thinking more about your suggestion, I can wrap the 2nd Messenger.Default PropertyChangedMessage in a Dispatcher.BeginInvoke to essentially get the same effect, allowing the UI to update prior to the broadcast being sent out.  Yes?

Thanks,

Mike