Messaging, RIA and navigation

Jul 29, 2011 at 3:45 PM

Hello!

I'm building an app with the Simple MVVM toolkit which I find very good compared to others.

But I do have a problem regarding messaging.

I need to transfer an object (SelectedSlideshow) to a (nav) page where photos related to that slideshow are displayed.

Should be simple.

Data is beeing transferred but nothing displays.

            SendMessage(MessageTokens.Slideshow, new NotificationEventArgs<SlideShowLocale>(null, SelectedSlideshow));
            SendMessage(MessageTokens.Navigation, new NotificationEventArgs(PageNames.SlideTest));

I imagine that I'm missing something.

The SimpleMVVM examples are related to displaying everyting on a single page with ViewModelDetailBase.

Regards

Kennet

Jul 29, 2011 at 4:39 PM
Edited Jul 29, 2011 at 4:54 PM

Hi kennet,

You can't exactly "transfer" an object, you can only send a reference to an object and this works just fine with Simple MVVM Toolkit, I'm doing it all the time...

That being said, it's not clear exactly what your problem is but I have a couple of questions for you:

1. In your nav page's ViewModel did you register to receive the message sent from wherever it is being sent?

For example: RegisterToReceiveMessages<SlideShowLocale>(MessageTokens.Slideshow, OnSelectedSlideshow);

2. Did you create the OnSelectedSlideshow method and if you place a break point within that method does it get hit when you send the message containing a reference to your SlideShowLocale object?

3. If you did register to receive the message and do have a method, are you sure that your nav page's ViewModel is instantiated at the time when the message is being sent?

Hope this helps,


Dean

Jul 30, 2011 at 12:08 PM

Hello Dean and thanks!

Everything is working now exept that I don't get the refrerenced object (SelectedSlideshow). 

Obviously am I missing something basic here.

In the AlbumViewModel I have

            SendMessage(MessageTokens.Slideshow, new NotificationEventArgs<SlideShowLocale>(null, SelectedSlideshow));
            SendMessage(MessageTokens.Navigation, new NotificationEventArgs(PageNames.SlideTest));

And in then SlideshowViewModel I have

        public SlideshowsViewModel(ISlideshowsServiceAgent serviceAgent)
        {
            this.serviceAgent = serviceAgent;
            RegisterToReceiveMessages(MessageTokens.Language, OnChangeLanguageRequested);
            RegisterToReceiveMessages<SlideShowLocale>(MessageTokens.Slideshow, OnChangeSlideshowRequested);
        }
        #endregion
        #region Notifications
        void OnChangeSlideshowRequested(object sender, NotificationEventArgs<SlideShowLocale> e)
        {
            SelectedSlideshow = e.Data;
        }

The data is correct and I set it to the SelectedSlidehow property that raises PropertyChangedEvent.

And in the SlideTest page in code behind I have

    public partial class SlideTest : Page
    {
        SlideshowsViewModel viewModel;
        public SlideTest()
        {
            InitializeComponent();
            viewModel = (SlideshowsViewModel)DataContext;
            viewModel.ErrorNotice += OnErrorNotice;
            viewModel.LoadData();            
        }

LoadData gets all slideshows and defaults to the first one as SelectedSlideshow which is what is showing up.

And the xaml looks like this

 <TextBlock Text="{Binding SelectedSlideshow.Title, Mode=TwoWay}" Foreground="{StaticResource WhiteColorBrush}"/>

Regards

Kennet

Jul 30, 2011 at 12:17 PM

Is this the same problem I'm having (http://simplemvvmtoolkit.codeplex.com/discussions/266519) ie that the receiving VM isn't instantiated at the time the sending VM sends its message?

Cheers - Graham

Jul 30, 2011 at 12:45 PM
Edited Jul 30, 2011 at 12:47 PM

Maybe (or partial).

The data is correct in OnChangeSlideshowRequested but I'm not able to show it.

Kennet

Coordinator
Jul 31, 2011 at 10:33 PM
Edited Jul 31, 2011 at 10:35 PM

This is the same issue that Graham brought up, namely, that there is no convenient way in Silverlight to pass an object to a page when navigating to it.  The problem is that the page being navigated to has not yet been instantiated, so it's not possible to send a message to it with the data you want to send it.  One way to address this is with a static Dictionary.  I spent some time today implementing one for the navigation sample, which you can download here: http://tonysneed.com/simplemvvm/SimpleMvvm-Navigation.zip.

If you open the project you'll find a NavigationHelper class with a property called PageValues:

 

public static class NavigationHelper
{
    public static Dictionary<string, Dictionary<string, object>> PageValues
    {
        get { return pageValues; }
        set { pageValues = value; }
    }
    private static Dictionary<string, Dictionary<string, object>> pageValues =
        new Dictionary<string,Dictionary<string,object>>();
}

 

The key is the name of the page you're navigating to, and the value is a dictionary of property names and values.  In the CustomerViewModel I modified the Save method to pass a KeyValuePair for the call to SendMessage:

 

public void Save()
{
    // Create page value to send saved customer to home page
    var pageValues = new KeyValuePair<string, object>("SavedCustomer", Customer);
    
    // Navigate to home page with selected customer
    SendMessage(MessageTokens.Navigation, new NotificationEventArgs
        <KeyValuePair<string, object>>(PageNames.Home, pageValues));
}

 

The main page adds this data to the PageValues dictionary on NavigationHelper.

 

void OnNavigationRequested(object sender, 
    NotificationEventArgs<KeyValuePair<string, object>> e)
{
    // Pass data to destination page
    var pageValues = new Dictionary<string, object> { { e.Data.Key, e.Data.Value } };
    NavigationHelper.PageValues.Add(e.Message, pageValues);

    // Navigate to the page
    Navigate(e.Message);
}

 

Lastly, the code-behind for the Home page overrides the NavigateTo method to pull out "SavedCustomer" from the dictionary and set the ViewModel's SavedCustomer property.

 

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    // Get selected customer
    Dictionary<string, object> pageValues;
    if (NavigationHelper.PageValues.TryGetValue(PageNames.Home, out pageValues))
    {
        object propertyValue;
        if (pageValues.TryGetValue("SavedCustomer", out propertyValue))
        {
            Customer savedCustomer = propertyValue as Customer;
            if (savedCustomer != null)
            {
                viewModel.SavedCustomer = savedCustomer;
            }
        }
    }
}

 

That's it.  Hope this makes sense and addresses the issue.  Please let me know if you can get this to work in your projects.

Cheers,

Tony

Aug 1, 2011 at 1:13 PM
Edited Aug 1, 2011 at 1:39 PM

Thank you very much Tony!

A big step and elegant too.

But I have an issue when calling the page the second time

Error [Argument_AddingDuplicate] in
        void OnNavigationRequested(object sender,
            NotificationEventArgs<KeyValuePair<string, object>> e)
        {
            // Pass data to destination page
            var pageValues = new Dictionary<string, object> { { e.Data.Key, e.Data.Value } };
            NavigationHelper.PageValues.Add(e.Message, pageValues);

            // Navigate to the page
            Navigate(e.Message);
        }
Seems one has to do NavigationHelper.PageValues.Clear(); or check for the key and update it since it's static
Kennet
Aug 1, 2011 at 3:44 PM

Thanks Tony,

Another great feature. This came in at the right time. I had similar problem passing an ObservableCollection to another viewmodel. The solution worked great. Wouldn't use anything else for doing MVVM.

I have moved the code from NavigatedTo into ViewModel method and then used the Trigger for Loaded to populate my ObservableCollection property in the viewmodel.

Thanks again!

Coordinator
Aug 1, 2011 at 3:53 PM
Edited Sep 10, 2011 at 9:54 PM

Forgot to remove the value after retrieving it. I fixed it and re-posted the code here: http://tonysneed.com/simplemvvm/SimpleMvvm-Navigation.zip.

Tony

Aug 1, 2011 at 4:45 PM
Edited Aug 1, 2011 at 5:19 PM

Hello again!

That didn't seem to do the trick in an navigation app.

Same error

Works when doing NavigationHelper.PageValues.Clear(); (of course)

Kennet