One Viewmodel - Many Views

Sep 7, 2011 at 6:10 AM
Edited Sep 7, 2011 at 6:26 AM

Hi Tony,

I am creating a wizard like application. The last step of the application would provide user to be able to print the information user has entered in the previous steps. For this I need to maintain one viewmodel and populate the data/properties in it. 

I am using navigation framework and the last step displays a page that I would like to populate with the values entered in previous steps. I will be using this page to print out the report.

I added the datacontext of this summary page to the same viewmodel as the initial page but it would create a new instance of the viewmodel and all the data would not be available.

Any help is highly appreciated.

Thanking you in advance.

===========================

In the discussion thread: http://simplemvvmtoolkit.codeplex.com/discussions/266817 you mentioned something about maintaining one viewmodel

  • If you want one instance of a ViewModel to remain in memory, I would suggest you replace the code in the locator that was created with the mvvmlocator snippet with a standard property using the propfull code snippet.  Then every time you reference the ViewModel property on the locator, you would be getting the same instance.

I cannot get the "propfull" code snippet. Do you mean, mvvmprop? and if so, how would I use it as I have to pass the service agent as well?

Sep 7, 2011 at 3:34 PM

The challenge you will have is each time the view calls the ViewModel a new instance is created of the VM. This will reset your values to null within the new view. One approach you could take is to use the MVVM message bus to pass the values as a serialized string or tuple to the new page.

Another approach you could use is to have once view with multiple user controls. One user control could contain the form entry fields and the second usercontrol could contain the confirmation. Both user controls are loaded in a page which has the datacontext set for the page. This would allow the user controls to inherit the bindings from the page. (technically the usercontrols could be referred to a views as well so it falls in the multiple views to one VM).

In the past I have done this and you can manage the visibility of each control using IValueConverters.  However the above approach will give you full access to one VM using multiple views (with views being defined as user controls loading in one page)

Hope this helps

Cheers-

Randy

Sep 8, 2011 at 5:15 AM
Edited Sep 8, 2011 at 5:16 AM

Hi,

Thanks rlcrews for posting suggestion. The way I went about is using the message bus.

  • Registered to receive message in my report viewmodel

public ReportViewModel()
{
RegisterToReceiveMessages<KeyValuePair<string,object>>(MessageTokens.OverviewDataTransfer, SetData);
}

  • Sent a message from OverviewViewModel (This is where all the data is stored/collected) when the page unloads (using CallMethodAction)
	public void PassOverviewData()
        {
            var pageValues = new KeyValuePair<string, object>("OverviewData", this);
            SendMessage(MessageTokens.OverviewDataTransfer, new NotificationEventArgs<KeyValuePair<string, object>>(PageNames.Overview, pageValues));

        }
  • A property of type OverviewViewModel in ReportViewModel is assigned the object sent in the message. (It is OK to create a property in a viewmodel of type of another viewmodel?)
	void SetData(object sender,NotificationEventArgs<KeyValuePair<string,object>>e )
        {
            OverviewViewModel = e.Data.Value as OverviewViewModel;
        }

Binding is done using this property and sub-properties.

Is this the correct way to achieve result? I am not sure if I can create a property of type of another viewmodel. Is this tight couping two view-models?

Sep 8, 2011 at 2:51 PM

Shakyad,

Glad you got it working. Yes you can create a property viewmodel based on another viewmodel. I can't speak to whether this is a best practice but I have done seomthing similar in the past :)  It does create awareness between the viewmodels so there some introduction of dependency of one to the other but sometimes this is a needed to achieve our desired output.

-Cheers

Randy

Sep 10, 2011 at 10:55 PM

Good discussion guys.  The "container" ViewModel for wizard would be responsible for navigating to various pages by setting the Source property of a Frame control.  At this point the View (and its ViewModel) are instantiated by the navigation framework.  The challenge is to pass a value from the current ViewModel to a subsequent ViewModel that is about to be instantiated.  The best way to do this, I think, is to have an object that can be seen by all ViewModels in the application.  A natural candidate would be an application-level resource.

The MessageBus is designed to communication among ViewModels (or other kinds of classes) that are in memory at the same time.  The problem with passing values between ViewModels in a navigation framework is that the ViewModels are not in memory at the same time.  So I would advocate using a static class or app-level resource as the conduit.  That way you can keep a clean separation between the ViewModels by having them instantiated by the View to which they belong.

See this discussion for for info and a sample app using this approach.  I may need to revisit this code to clean it up a bit, but it should at least get you started. :-)

Cheers,

Tony

Sep 12, 2011 at 2:56 AM

Hi,

I guess sending message to the page directly worked in my case as the pages are initialised in the datacontext of my container.

Thanks both.

Sep 13, 2011 at 6:30 AM
Edited Sep 13, 2011 at 6:32 AM

Hi,

Just wanted to let you know that I ended up using the static class NavigationHelper. As you said this is more convenient and makes sense when you navigate to a page that has not been initialised yet.

  • I still pass the whole viewmodel to the MainPageViewmodel that is registered to listen to sent message. The callback then stores the values in the static class.

public MainPageViewModel()

 

        {
            // Register to get notified when a message arrives
            // at the MessageBus using the navigation message token
            RegisterToReceiveMessages<KeyValuePair<string, object>>(MessageTokens.StoreData, StoreData);
        }

 

  • and the callback method:

void StoreData(object sender, NotificationEventArgs<KeyValuePair<string,object>> e )

 

        {
            //Pass data to NavigationHelper static class.
            var pageValues = new Dictionary<string, object> { { e.Data.Key, e.Data.Value } };
            string pageName = e.Message;
            string propertyName = e.Data.Key;
            object data = e.Data.Value;
            if (NavigationHelper.PageValues.ContainsKey(pageName))
            {
                if (NavigationHelper.PageValues[pageName].ContainsKey(propertyName))
                    NavigationHelper.PageValues[pageName].Remove(propertyName);
                NavigationHelper.PageValues[pageName].Add(propertyName, data);
            }
            else
            {
                NavigationHelper.PageValues.Add(pageName, pageValues);
            }

        }
  • And then in the viewmodel of the page that needs this data, I created the property of the viewmodel type and assigned the value from NavigationHelper on PageLoaded event using CallMethodAction
public void GetOverviewData()
        {
            Dictionary<string, object> pageValues;
            if (NavigationHelper.PageValues.TryGetValue(PageNames.Overview, out pageValues))
            {
                object iData;
                if (pageValues.TryGetValue("OverviewData", out iData))
                {
                    var oData = iData as OverviewViewModel;
                    if (oData != null)
                    {
                        OverviewViewModel = oData;
                    }
                }
            }
        }
  • I can get the binding working in the ReportView but this report contains a reporting control designed by David Poll. 
  • For some reason I cannot get the binding to work inside that report container (CollectionPrinter). 
  • This is a whole new story I need to follow up now.
  • While on subject of printing/reporting, do you know of any reporting tools that I can use to generate a report that has features like auto pagination, header, footer, etc?
  • I tried to use Telerik's reporting but couldn't as I can't pass data to the reporting engine (dll) from silverlight project and I do not want to store the data/report in the server.

Thanks for all your help once again.