In this part, I cover Prism Region Navigation, which allows changing a view displayed or active within a Prism Region. (For region definition and samples look at the first part of this tutorial).

Region navigation is essential for properly using the "real estate" of the application by changing the views displayed at different locations within the application screen depending on the current state of the application.

Region Navigation functionality within Prism allows loading a view within a region based on the view's class name, passing navigation parameters, making decisions whether the view should be displayed or not, cancelling the navigation if needed and recording the navigation operations.

On top of the prerequisites listed for "Part 1" (C#, MEF and Silverlight), one also needs to understand the MVVM pattern for some of the samples in "Part 2".

Region Navigation Overview and Samples

Simple Region Navigation Sample

This sample's code is located under SimpleRegionNavigation.sln solution. It demonstrates basic navigation functionality. Module1 of the sample application has two views: Module1View1 and Module1View2. Both views are registered with "MyRegion1" region within the Module1Impl.Initialize method (view discovery is used to associate these views with the region):

Both views display a message (a TextBlock specifying the name of the view) and a button to switch to the other view. The foreground of Module1View1 is red, while the foreground of Module1View2 is green.

There are two ways to navigate to a view within a region:

By using RegionManager's RequestNavigate method.

By using Region's RequestNavigate method.

These two methods are very similar, except that RegionManager.RequestNavigate function requires region name as its first parameter, while Region's method does not require it.

To demonstrate both methods, Module1View1 navigates to Module1View2 using RegionManager.RequestNavigate function:

One can see that both methods require a relative Uri argument, whose string matches the name of the view we want to navigate to.

Both methods also need a post-navigation callback as their last argument. This is because some navigation implementations might be asynchronous so, if you want some code for sure to be executed after the end of the navigation you put it within the post-navigation callback. In our case we want to display a modular MessageBox stating whether the navigation was successful or not:

Many times, however, the post-navigation callback is not needed, and then the following simple lambda can be placed in its stead:

a => { }

Up to now we've covered the code behind for Module1View1 view located within Module1View1.xaml.cs file. Module1View2 functionality, however, is more complex. We can see, that Module1View2 class implements IConfirmNavigationRequest interface. IConfirmNavigationRequest extends INavigationAware interface.

IsNavigationTarget method allows the navigation target to declare that it does not want to be navigated to, by returning "false". This method is most useful with injected views as will be shown below.

OnNavigatedFrom method is called before one navigates away from a view. It allows doing any pre-navigation processing within the view that has been navigated from. In case of Module1View2 we show a message box:

OnNavigatedTo method is called after one navigates to the view. It allows doing any post-navigation processing within the view that has been navigated to. In case of Module1View2 a message box is shown:

The above methods IsNavigationTarget, OnNavigatedFrom and OnNavigatedTo are part of INavigationAware interface (which IConfirmNavigationRequest extends) and can be used if the view class implements INavigationAware only. The method ConfirmNavigationRequest, however, is only part of IConfirmNavigationRequest and in order to use it one needs to implement IConfirmNavigationRequest interface.

ConfirmNavigationRequest method is called before one navigates from the current view. It gives you a last chance to cancel the navigation. Its second argument is a delegate called "continuationCallback" that takes a Boolean value as its argument. Calling continuationCallback(true) will allow the navigation to continue, while calling continuationCallback(false) will cancel it.

Within Module1View2 we show a message box, asking whether the user wants to continue navigation or cancel it and the argument to "continuationCallback" is determined by whether the user pressed OK or Cancel button:

Even though we use a modular window that blocks the control flow until the user clicks a button, non-modular control can also be used to continue or cancel the navigation - we simply need to pass the "continuationCallback" parameter to it and based on the user action it should either call continuationCallback(true) or continuationCallback(false).

This is how the application looks when it is started:

Once "Navigate to Next View" button is clicked, we get a popup indicating that we are inside Module1View2.OnNavigatedTo function. After OK is clicked, we are getting the post-navigate callback message indicating that the navigation has been successful. Once OK is clicked, the new view is shown on the screen:

If we click "Navigate to Next View" button now, we'll hit the Module1View2.ConfirmNavigationRequest method first, which will show us a popup window asking whether to continue navigation or cancel it. If we press ok, navigation to Module1View1 continues successfully, otherwise it fails and we stay at Module1View2. (In case of successful navigation we get two more modular popups - one indicating that we are inside Module1View2.OnNavigatedFrom function and one informing us that the navigation was successful, while in case of a navigation failure we are informed of it by a message popup)

Exercise: create a similar demo with the two views located in different modules instead of them being within the same module. (Since several people had difficulty with this exercise, I added another sample illustrating navigation between two views located in different modules: NavigationBetweenTwoDifferentModules.zip).

Simple Navigation via the View Model

In the above example, Module1View2 view implements IConfirmNavigationRequest interface and because of that its functions IsNavigationTarget, OnNavigatedFrom, OnNavigatedTo and ConfirmNavigationRequest participate in the navigation process. A better way, however, is to make all the navigation decisions within the view model (if MVVM pattern is being followed). So, Prism provides a way for the view model to implement INavigationAware or IConfirmNavigationRequest interfaces (instead of the view).

The rule is the following: if the view implements one of INavigationAware or IConfirmNavigationRequest interfaces than the view's corresponding methods will be called during the navigation. If, however, the view does not implement it, but the view's DataContext does, then the corresponding functions of the DataContext will be involved as demonstrated by our sample under NavigationViaViewModel.sln solution.

Module1View1 is almost the same as in the previous sample (aside from the fact that it does not popup message windows).

Module1View2 does not implement IConfirmNavigationRequest. Instead, within the constructor, it sets its DataContext property to be a new object of View2Model type:

We use the view model's ShouldNavigateFromCurrentViewEvent event to inform the view that navigation has been started away from it. The view connects a handler to this event within the view's constructor:

Just like in the previous sample, View2Model.view2Model_ShouldNavigateFromCurrentViewEvent displays a modular popup window asking whether the user wants to continue or cancel the navigation. Then, depending on the user choice it returns a Boolean to the view model and the View2Model.ConfirmNavigationRequest's "continuationCallback" argument is called with the corresponding value specifying whether to continue or cancel the navigation.

Exercise: create a similar demo.

Navigation Parameters Sample

If a view or a view model implements INavigationAware or IConfirmNavigationRequest, interface, the navigation can pass parameters to these functions as part of the navigation Url. As I mentioned above, the Url should contain the name of the view class to which one wants to navigate. This name of the class can be followed by '?' character and key-value parameter pairs where the keys are separated from the values by '=' character and the pairs are separated from each other by '&' character.

NavigationParameters.sln solution contains a sample where the view Module1View1 navigates to itself but with different parameters.

Here is how the sample's window looks:

One can enter any text into the text box above, then press the button "Copy via Navigation" and the text will be copied into the text block below.

Note that the full Url will look like: "Module1View1?TheText=<text-to-copy>".

Module1View1 view implements INavigationAware interface, so, its own OnNavigatedTo function will be called at the end of the navigation process. Within this function we get the UriQuery dictionary from the navigationContext argument and from the UriQuery dictionary we get the value corresponging to "TheText" key as below:

Of course, in reality, RequestNavigate and OnNavigatedTo functions can be in different views and even in different modules, and instead of a trivial task of copying text, other much more complex tasks requiring navigation parameters might be employed.

Exercise: Create a similar demo.

Using IsNavigationTarget Method for Navigating to Injected Views

In case of the view injection there can be multiple views of the same view type within an application. Navigating based on the Url containing the View class name only, will not work in that case, since all those views have the same view class. Prism employs bool INavigationAware.IsNavigationTarget(NavigationContext) method to specify which view to navigate to in such case.

When navigating to injected views, each of the region's views of the specified type has its INavigationAware.IsNavigationTarget method called until one of them returns "true" (or until all of them return "false"). The view whose INavigationAware.IsNavigationTarget method returns "true" will be navigated to.

The corresponding sample is located under InjectedViewNavigation.sln solution. Unlike in previous examples, the region control is represented by a ListBox in the application's Shell.xaml file:

so that it displays every view connected to the region and navigating to a view makes this view selected within the list box:

The view is defined by Module1View1 class. It has ViewID property that uniquely specifies the view object. This class also has a static method CreateViews() which creates 5 views with different ViewIDs ranging from 1 to 5 and associates the created views with "MyRegion1" region:

IsNavigationTarget method compares the "ViewID" parameter received as part of the Navigation Url to the ViewID property of the view. If they are the same, it returns true (this means we navigate to this view), if they are different, it returns false (the view is not navigated to):

One can see that by pushing the view's "Navigate to Next View" button, we indeed switch to the next view. After changing NextViewID property to return every second view, we can see that every second view in the list gets selected. If we change the region control within Shell.xaml file of the application project to be a ContentControl instead of a ListBox the navigation will be displaying the navigated view instead of selecting it.

Exercise: Create a similar demo.

Undo-Redo Functionality Using Navigation Journal

Navigation functionality also includes undo-redo capabilities as shown in this sample. The sample is located under NavigationJournal.sln solution.

This sample is very similar to the previous one, only each injected view has two more buttons: "Back" button and "Forward" button. "Back" button allows undoing the last operation, while "Forward" - redoing the last undone operation:

"Back" and "Forward" buttons are enabled only when the corresponding operations are possible.

Undo-redo functionality is available from RegionNavigationJournal, which in turn can be accessed via RegionNavigationService. We can get a reference to the navigation service from the navigationContext argument within OnNavigatedTo method:

Button enabling/disabling functionality is based on _navigationService.Journal.CanGoBack and _navigationService.Journal.CanGoForward properties for "Back" and "Forward" buttons correspondingly. It seems that it is logical to add button enabling/disabling functionality to the body of OnNavigatedTo method (which is called on Undo-Redo), but unfortunately it is called before the navigation Journal state is updated, so that the CanGoBack and CanGoForward properties do not have the correct values yet. In order to get around this problem, I had to figure out which view is current based on the Uri property of the current entry within the Journal. We get the ViewID from the Uri and set IsEnabled properties on the "Back" and "Forward" buttons of the corresponding view:

Important Note: the journal undo/redo functionality works only within one region. If there are multiple regions each one of them will have their own undo/redo sequence.

Exercise: create a similar demo.

Conclusion

In part 2 of this tutorial I gave a detailed description of Prism's Navigation functionality in small and simple samples. I'd appreciate very much if you could vote for this article and leave a comment. I would love to hear your suggestions for improving and expanding the content of this tutorial.

History

Feb 15, 2011 - changed the code to use latest Prism dlls (which lead to significant changes in API: INavigationAwareWithVeto interface was replaced by IConfirmNavigationRequest, CanNavigateTo() was replaced by IsNavigationTarget() and method OnNavigatedFrom was added to INavigationAware interface. Hat tip to jh1111 for noticing that my API was out of date.

Share

About the Author

I have 15 years of experience developing enterprise software, starting from C++ and Java on UNIX and moving towards C# on Windows platforms.
I am fascinated by the new .NET technologies especially WPF, Silverlight and LINQ.
Recently I decided to make a move and start my own contracting consulting and mentoring company AWebPros.
I can be contacted via my web site awebpros.com or through my blog at nickssoftwareblog.com

Trying to do the navigate to a second view in another module (first exercise). Not sure what the Uri should be? Could you give me a hint? For example, if my module2view was going to be called from module1view, what should the Uri be in the ReuestNavigate call. Thanks.

Answer to your question - whether within the same module or across the modules the URL should be simply the name of the view class that you are navigating to.
Sorry for not answering you earlier - we had a release at work and I had to work even on Sundays so I did not even have time to think about my articles on the codeproject, let alone answer questions. I am still kind of in a recovery mode.

Regardless of being stuck on the exercise, your contributions to this subject are so useful and appreciated. I have pointed the entire team to this series because of the straightforward way of how you tackle the topic. With navigating to a view class in another module, the name of the view class alone as the URI did not work for me -- from within the same module works. I'll try again, but I wanted to reply and say thanks for doing these.

I was actually wondering the same thing. What would be the syntax to navigate from Module1.Module1View1 to Module2.Module2.View1? Or is it more involved than just a slight syntax change to the RequestNavigate method?

Excellent article and I would really love to see you cover the same tutorials/topics on the WPF side. I do have a question, how does the biding work on the module's view. The user control's x:Name is set to "TheView" and the binding of the text block is set "TheView"'s ID property so it can show the ID. But when you create the five views, the name is set to "View1", "View2", etc. So I know it works in Silverlight but will it work on WPF as well?

Thanks!
I do not quite understand your question - are you talking about injected view navigation? Can you elaborate what binding you are talking about? If it is indeed about the injected views, it is developer's responsibility to override INavigationAware.IsNavigationTarget target method of the views (or the view models) so that only the correct view's method would return true during the navigation and yes, this will work in WPF also.
I decided against writing a WPF article at this point - all the concepts described here are also applicable to WPF, except that instead of .xap files we are dealing with .dll files in case of WPF. Also the are a lot of other Prism WPF tutorials, e.g. Introduction to Composite WPF (CAL, Prism): Part 1.
Instead, I plan to re-write the samples in WPF and add them under the same articles - but it might take me some time - I am quite overwhelmed with work at this point.
Regards