Thursday 5 April 2012

MVVM Light - Passing Params to Target ViewModel Before Navigating

Overview
I use MVVM Light for my WP7 apps, it's great! I implement the following navigation service for controlling navigation from the view model layer:


http://blog.galasoft.ch/archive/2011/01/06/navigation-in-a-wp7-application-with-mvvm-light.aspx


Part 2 Here


The main problem I've faced is that I often want to pass parameters to the target view's view model to prepare it for what it needs to do. It is possible to access properties in the target view model through the ViewModelLocator as each view model has a static property used for the view binding via the non-static properties, however this means that the view models are tightly coupled through the VML and it seems like a bit of a hack.


The example code is from an article page which requires navigation to a links page.


Messaging
It's possible to fire a message out before navigating to pass parameters to the target view model, however, if the target view has never been hit, the associated vm will not not have instantiated so will not receive the message! This can be worked around by calling the VML CreateXXX method however this is still not the elegant solution I was hoping for.


2-Way Messaging
To solve this problem, I figured that when a VM instantiates, ask if any other VM has any parameters for it then register a message pipe for parameters when it is instantiated.

The Message
I created a message which would take a parameter to pass from the source to the target or an action to request a parameter from the source, by the target:


public class LinksInitMessage : MessageBase
{
    public Article Payload { get; set; }
    public Action<Article> Callback { get; set; }

    public LinksInitMessage(Article payload)
    {
        this.Payload = payload;
    }

    public LinksInitMessage(Action<Article> callback)
    {
        this.Callback = callback;
    }
}


Source VM
First off messages need registering (at constructor stage) so that the target VM may message back one it constructs:


private void RegisterMessenger()
{
    //Register any messages
    Messenger.Default.Register<LinksInitMessage>(this, msg =>
    {
        if (this._linksInitItem != null)
        {
            msg.Callback(this._linksInitItem);
            this._linksInitItem = null;
        }
    });
}

Then when the source VM is ready to navigate , it can send out a message and keep a parameter in case the VM calls back, then navigate:

private void Link()
{
    // Send message to Links (it will get it if it's there otherwise, the VM will request when open)
    Messenger.Default.Send<LinksInitMessage>(new LinksInitMessage(this._selectedItem));

    // Store article incase Links requests it
    this._linksInitItem = this._selectedItem;

    // Nav to links
    _navService.NavigateTo(ViewModelLocator.LinksUri);


Target VM
First off when the VM constructs, it sends out a message to ask if any other VM has any a message for it, if it gets a response it can initialise itself. Second it registers a message type so once it is already instantiated, it can receive params again and re-initialise itself:

private void RegisterMessenger()
{
    //Send out a message to see if another VM has params we need
    Messenger.Default.Send<LinksInitMessage>(new LinksInitMessage(article =>
    {
        if (article != null)
        {
            this.SelectedItem = article;
        }
    }));

    //Register any message pipes
    Messenger.Default.Register<LinksInitMessage>(this, msg =>
    {
        this.ClearViewModel();
        this.SelectedItem = msg.Payload;
    });
}

Conclusion
That's it, seems like a nice loosely coupled way of initialising a view model on navigation.


9 comments:

  1. Looks good. Another way I use sometimes it to pass parameters through the query string (mypage.xaml?param1=hello&param2=world) but it also doesn't feel right. I am working on a concept allowing parameters to be passed to a viewmodel in the constructor with SimpleIoc, that would help too. The Messenger solution with callbacks is a good one because it is dynamic (and can work with already instantiated ViewModels too).

    Cheers,
    Laurent

    ReplyDelete
  2. Thanks for the feedback. I also had an idea after I wrote this that it might be good to have a message queue which the Messenger could post to if it had nobody to deliver to, then when a VM constructs it can check the queue to see if there are any messages waiting (like a PO box, just waiting for someone to collect the mail). I might take a look at this as it might be possible to simply pass params in the Navigate service method and get the service to do the messaging.

    I use an IoC container in the VML to inject services into the VMs...works really nicely.

    Cheers,

    Geoff

    ReplyDelete
  3. Thank you very much Geoff for this pattern. I always wondered how you can actually deal with view models needing non-injected parameters to live.

    I always found quite strange that every MVVM framework out there does not provide support for this classic scenario.

    ReplyDelete
  4. No problem :-) Did you try out the message sender and receiver as well?
    http://geoffwebbercross.blogspot.co.uk/2012/04/mvvm-light-passing-params-to-target_12.html

    ReplyDelete
  5. Thanks for the solution. I think there should be a link from the MVVM Light docs page to your article. That way others would not have to go to the lengths that I did to finally find a good approach that works.

    ReplyDelete
  6. Thanks Geoff, I was looking for a nice decoupled solution, this working nicely.

    ReplyDelete
  7. Blue Code on Black background is genius.. that way your reader will be pissed off immediately as they begin to read.. thanks!

    ReplyDelete
    Replies
    1. Yeah, getting free information on the internet really pisses me off too! I might spend some time next week reformatting this post which is over 2 years old. Thanks anonymous :)

      Delete