Wizard implementation

Sep 1, 2010 at 9:50 AM

Hi,

I am about to port my application from PRISM to nRoute and would like to know what the best approach is for implementing a wizard. The wizard will have one view model and multiple views. I am going to use the future desktop as a base. The wizard will not be in a popup, it will appear in the main content area of the future desktop. Also I would potentially like to have the next previous finish wizard navigation buttons appear on the bar that you have the add workspace, refresh favourite button on the future desktop example. Finally if possible I would like to have a general wizard control which I can pass views/viewmodel and then the navigation buttons for the wizard should appear and operate correclty when a wizard is active within the main container/content area.

Thanks in advance for any help.

Coordinator
Sep 1, 2010 at 11:04 AM

Now, there are many ways to do this, some options are: 

1. One parent View and VM, along with multiple child Views:

Start with one set of View and VM, in the View just put a NavigationContainer, and then within the container you can have your Wizard Pages/Views flow. Importantly, the Wizard Pages/Views shouldn't specify a VM, this way they would inherit the parent VM. So what you get is one ViewModel paired with Multiple Wizard Pages/Views. 

2. One VM, and Multiple Mapped Views

Basically, just map one or more Views to different Urls, and then have the Views map to a single VM, something like:

[DefineNavigationContent("Pages/Wizard/Page1/", typeof(Page1))]
[DefineNavigationContent("Pages/Wizard/Page2/", typeof(Page2))]
[DefineNavigationContent("Pages/Wizard/Page3/", typeof(Page3))]
[MapViewModel(typeof(Page1))]
[MapViewModel(typeof(Page2))]
[MapViewModel(typeof(Page3))]
public class WizardViewModel
{
   // do the do
}

3. One View and VM, with a Url Token

If possible I'd prefer this, rather than having multiple views just have a parametrized Url. This way you can change the View per the value of Url parameter/token:

[DefineNavigationContent("Pages/Wizard/{StepIndex}/", typeof(WizardPage))]
[MapViewModel(typeof(WizardPage))]
public class WizardViewModel : NavigationViewModelBase
{
   // get the value StepIndex by overriding OnInitialize method 
}

 

As you can tell, all the above options aren't standardized like a Wizard control, which unfortunately I can't provide out of box with nRoute. However, you can create one yourself, the NavigationContainer model is very flexible and can provide you with all the required infrastructure, you just need to layer the Wizard-specific functionality onto it.

Hope this helps,
Rishi 

PS: I'd recommend use of StatefulBrowsingNavigationContainer, read more about it here http://www.orktane.com/Blog/post/2010/04/13/nRoute-Now-More-Wholesome.aspx

Sep 2, 2010 at 6:30 AM

Thank you. I will give it a try.

Sep 5, 2010 at 10:50 PM

Hi Orktane, sorry but I am struggling to know how to start (this is the very first part of my app that I am porting over to nRoute). Would you be able to explain option 1 in more detail please?

How do I add the wizard pages to the container? To start I would like to have one viewmodel and one view. The View will have an area for displaying the current wizard step/view, the wizard navigation buttons (Previous, Next, Finish and Cancel) and ideally tabs for each wizard step with the current step highlighted/selected. Do containers support tabs, can you add a number of views toa container and then make it a tabbed container? If not can I add all the pages to the container and then somehow say which one should be visible based on the current navigation step/index?

Also how can I access the navigation container from within the view model when I look at the static methods for NavigationService I only see a method for getting the default container. Is there some way of accessing container by name such as "WizardContainer" and then I could set which view within the container is active/displayed? Also you mention that if a view is added to the container and the view doesn't have a view model sepcified then it will be given the parent view model, will this happen automatically?

 

Coordinator
Sep 6, 2010 at 11:47 AM
Edited Sep 6, 2010 at 11:55 AM

Let me try answer your questions, point-by-point:

Q. How do I add the wizard pages to the container?

A. Well, you don't have to add them all at once, that's what you use navigation for. So like navigate from one page to another just use the NavigateAction behavior.

Q.  Do containers support tabs?

A. Well, you must understand that a "navigation container" is something that can handle a navigation request/response (see the INavigationHandler interface). So tabs natively are not navigation containers, though minimally by either implementing INavigationHandler by inheriting it or by creating a Navigation Adapter for it you can make it handle navigation (see http://www.orktane.com/Blog/post/2010/06/30/Creating-a-Netflix-App-using-nRoute-A-Step-by-Step-Guide.aspx for an example on how to create adapters)

Q. About using Tabs?

A. Personally, I wouldn't use tabs for creating a Wizard control but you could. The problem with the tabs control is that tends to re-load pages when switched on/off a tab. You can create a similar effect/solution by just using the provided containers - as all you'll be doing is to show one view at a time.

Q. Also how can I access the navigation container from within the view model?

A. You don't - in VMs you keep away the View related stuff. Also if you know how containers work (just like web browsers), when you navigate (like when you click a hyperlink) within a container it just picks up the container itself by walking up the visual tree, unless you specify another container.

Q. Is there some way of accessing container by name?

A. Yes, see the "Globally Named Navigation Containers" section http://www.orktane.com/Blog/post/2010/04/13/nRoute-Now-More-Wholesome.aspx

Q. Also you mention that if a view is added to the container and the view doesn't have a view model sepcified then it will be given the parent view model, will this happen automatically?

A. Yes. And that's the setup I talked about in option 1. So you parent view will look something like:

<UserControl ...>
	<i:Interaction.Behaviors>
		<n:BridgeViewModel />
	</i:Interaction.Behaviors>

	<n:NavigationContainer Url='Pages/MyWizard/Page1'  />
</UserControl>

As you can see above we've initialized the container to start with the url "Pages/MyWizard/Page1" - that Url maps to a UserControl (say Page1.xaml) which has been earmarked with the MapNavigationContent attribute:

[MapNavigationContent("Pages/MyWizard/Page1")]
public partial class Page1 : UserControl
{
	//..
}

Now, importantly, this page doesn't have it's own VM - so it would inherit the parent's VM. So Page1.xaml could navigate to Page2.xaml which would be the second screen in your Wizard, and Page2 would be mapped with the Url like "Pages/MyWizard/Page2". So basically all you need to do is navigate from one Url to another, and you should get your wizard going.

Hope you got the basic idea?
Rishi 

Sep 6, 2010 at 12:59 PM

Hi Rishi,

Thanks for the reply but I still don't see how the navigation would work between the different wizard pages. Are you suggesting that each wizard step/view has its own navigation buttons rather than having the navigation buttons within the general wizard control? What I am unsure of is when the user is on Page 1 how do they get to Page 2, where is the url for page 2 defined? I would like to avoid having the to add wizard buttons to every view that is going to be a wizard step.

In my current prism implementation I have a general wizard control view/view model. I then add each wizard step/view to a Pages Collection (each page has the same viewModel).  The wizard view has the navigation buttons and one region for displaying the current wizard step. within the view model I then add the current wizard step to the region. The Next, Previous, Finish and Cancel buttons are bound to commands within the wizard viewmodel. I then need to know what buttons should be enabled based on the current wizard step. so previous wouldnt be enabled when on the first step etc.

Here is an example below of the same general wizard being used in 2 different instances. The idea being that the CustomerViewModel and customer views and WidgetConfigurationViewModel and widget configuration views know nothing about the wizard.

 Is it possible that I am trying to use nRoute in a way that it has not been designed for?

//Customer wizard
WizardViewModel wizardViewModel = new WizardViewModel();
wizardViewModel.Pages.Add(NewCustomerStep1View);
wizardViewModel.Pages.Add(NewCustomerStep2View);
wizardViewModel.Pages.Add(NewCustomerStep3View);
//Widget configuration wizard
WizardViewModel wizardViewModel = new WizardViewModel();
wizardViewModel.Pages.Add(WidgetConfigurationStep1View);
wizardViewModel.Pages.Add(WidgetConfigurationStep2View);
wizardViewModel.Pages.Add(WidgetConfigurationStep3View);

All Classes are:

  • WizardViewModel
  • WizardView

 

  • NewCustomerViewModel
  • NewCustomerStep1View
  • NewCustomerStep2View
  • NewCustomerStep3View

 

  • WidgetConfigurationViewModel
  • WidgetConfigurationStep1View
  • WidgetConfigurationStep2View
  • WidgetConfigurationStep3View

 

 

Coordinator
Sep 7, 2010 at 12:23 PM

Let me do a sample for you, will post it after work.

Cheers,
Rishi 

Sep 9, 2010 at 6:46 PM

Sorry to pester you but is there any update on the sample?

Coordinator
Sep 11, 2010 at 9:02 PM

Well, sorry got really caught up in work during the week - anyway, so rather than one I've done two types of wizards. One with a single backing VM, and another with multiple VMs. Download the code from http://cid-587cbdf035b4a11d.office.live.com/self.aspx/.Public/nRouteWizard.zip

Let me know if it works for ya.

Cheers,
Rishi 

Sep 13, 2010 at 10:11 PM

Hi Rishi, thanks for the samples they have helped me port over the existing wizards.

Sep 17, 2010 at 9:42 AM

I have implemented the wizard and its working great. However one final question on the subject, I would like to move the Navigation buttons next, previous etc out of the wizard general view into the application shell view (to save space). So my question is can I associate two VMs with one view so the shell View would have the Shell VM and Wizard VM associated with it when the wizard is active so that I can bind the Wizard view model navigation ActionCommands to the buttons. I think the answer to this is no, but would like to double check.

If this isn’t possible do you think an appropriate solution would be to use the eventAggregator/pub sub mechanism: When the wizard is active it publishes a message to say that it is active and the shell view model subscribes to this and displays the navigation buttons. When a navigation button is then clicked the Shell VM publishes a message to say that "Next" button etc has been clicked and the Wizard VM subscribes to these messages and acts appropriately?

Coordinator
Sep 17, 2010 at 11:55 AM

@Ultramods, did you have a look at the Widgets wizard (not the customers one) - it used nRoute's event aggregrator/pub-sub mechanism (called Channels) to publish the responses.  And the next/back/forward buttons are separate from each step View. Also, you can't have two VMs for a View, at least, not in the normal way we interpret VMs, and even if you did have two VMs you'll need to choose one to apply at runtime. However, you can reach into another VM via a relay or something. 

Rishi 

Sep 20, 2010 at 2:41 PM

Ok thanks, I will use that approach.