Prism v4 Region Navigation Pipeline

Updated on 10/14/2010

This blog post was updated on 11/8/2010 and requires Prism v4 Drop 10 or later.

The code download was updated, the Region Navigation diagram was updated, along with adding the below Region.NavigationService.NavigationFailed Event section.

Introduction

Navigation guidance has been added to Prism v4. In addition to chapter on Navigation there are two Navigation Quick Starts along with the two Reference Implementations that demonstrate various techniques for application navigation.

This article will focus on the new Prism v4 Region Navigation guidance and Prism Library API's that provide navigation within a region.

Prerequisites

A general knowledge of Prism regions, modules and MEF is required to understand this article and demo application.

Region Navigation

The below image is a diagram of the Region Navigation Pipeline. (Click image to see full size image)

NewPrismNavigationPipeline

Region Navigation Pipeline Learning Assets

In addition to the above flow diagram this article's download includes the, WPF Prism v4 Navigation Timing Cycle application. This application traces method calls, events and object construction during a navigation request. The application illustrates straightforward navigation requests, navigating backwards, closing forms as well as throwing exceptions at different locations during the navigation request. As an added bonus the application also demonstrates how to retrieve metadata from objects in a region to display a list of the objects and a navigation link to display them.

In the below image pictures the application after two navigation requests have completed. The red rows in the DataGrid are the first entry for each navigation request. The DataGrid automatically scrolls to the last entry each time data is added to the DataGrid. Filtering is accomplished by clicking the desired filter RadioButton above the DataGrid.

Each object visible or not, in the MainContentRegion, has a corresponding blue Hyperlink control that is displayed on the left. The Hyperlink renders the Uri used to navigate to the object. Hyperlinks also have rich ToolTips the display the contents of the bound metadata object.

You can watch the tutorial video here which covers using the included demo application.

alt

Overview

Prism v4 adds a rich Region Navigation API to the Prism Library. The Navigation API uses an opt-in model so that objects can elect to participate in the navigation pipeline as desired. To opt-into the various Navigation API calls, objects implement the required Interface(s). The Interface names are indicated in the above navigation pipeline flow diagram.

The Navigation API interacts with both the currently active and navigation destination objects within a region. The above navigation pipeline flow diagram indicates which object is the target of each method call; Active object or destination object.

The Navigation API supports navigating to:

  • View (View first)
  • View with ViewModel (View first)
  • ViewModel (ViewModel first, view created by DataTemplate resource lookup)

If you look at the navigation pipeline diagram, you'll notice that most of the boxes indicate that the Navigation API interacts with the View and ViewModel. It does this by casting the object to the Interface that is indicated on the diagram. If the object implements the Interface, the specified method will be called.

The Navigation API is Model-View-ViewModel (M-V-VM) friendly. During navigation request processing, the Navigation API first casts the object to the Interface then calls the method. It then casts the object's DataContext to the Interface and calls the method. This allows both a View and ViewModel to participate in the navigation pipeline.

Your application View and ViewModels do not have to implement all of the navigation Interfaces. Views in your M-V-VM applications are not required to have any knowledge that a Navigation API even exists. You can put all your navigation logic in your ViewModels. The demo application included with this article only displays View navigation information for purposes of instruction only.

Once you have mastered Region Navigation you'll quickly discover that you can create one or two ViewModel base classes that can handle most of the Navigation API Interface methods and events. Then your concrete ViewModels only implement the methods the application requires and allow the base class to handle the rest of the method calls.

Motivation

Some who have never used Prism Regions before or those that currently use Regions may be asking, "What benefits does the Region Navigation API provide?"

In addition to the benefits of using Prism Regions, the Navigation API provides these features: 

  • Interacts with the container to handle object construction (MEF, Unity, other containers)
  • Is the same API for WPF, Silverlight and integration with Silverlight Frame Navigation API
    • Note: Silverlight Frame Navigation integration is not supported out of the box. An example with supporting code will be posted on this blog soon.
  • Navigation journal that enables navigating back
  • Rich set of navigation methods and events to navigate, monitor and veto requests
  • Objects can opt-in to be removed from the Region when navigated away from
  • Navigation Request Callback provides a simple method for verifying a navigation request success
  • With most or all navigation related code in your ViewModel, you can easily test your code  

This granular walk-through will step you through the navigation flow diagram and included application code. Steps that are optional will be marked as such.

INavigateAsync.RequestNavigate

 RequestNavigate

 _regionManager.RequestNavigate(RegionNames.MainContentRegion, ViewNames.ApplicationMenuView, Callback);

 

 <ContentControl 
    Grid.Column="1" Grid.Row="2" Margin="6,0"
    prism:RegionManager.RegionName="{x:Static constants:RegionNames.MainContentRegion}" />

Use the RegionManager's RequestNavigate method to initiate navigation.

The first argument is the region name that was assigned by the RegionManager.RegionName attached property.

The second argument can either be string or Uri object. The string or object of the Uri must be the same as the name used to register the object in the application container. When using MEF, objects can be registered using the Export attribute and supplying a string as done below. When using an IOC container, ensure that objects added to the container are named with the same information provided to the Export attribute.

Notice that the above RequestNavigate method second argument matches the value in the below Export attribute.

 [Export(ViewNames.ApplicationMenuView)]
[PartCreationPolicy(CreationPolicy.Shared)]
public partial class ApplicationMenuView : UserControl, IPartImportsSatisfiedNotification, IConfirmNavigationRequest {

The take-way here is that the string in the Export attribute, must match the string or object of the Uri. Valid examples:

  • ItemView (this application uses the short type name)
  • WPFPrismv4Navigation.UI.ItemView (you could also use the full type name)

The third optional argument is the Action<NavigationResult> Callback. This delegate will be called at the end of navigation request processing, passing the result of the navigation request. This delegate is where unsuccessful navigation requests are handled.

The Callback delegate is a good candidate for being implemented in a ViewModel base class. You could also inject the Callback handler into the ViewModel during construction. This way, any requests that do not process as expected can be handled in a central location.

Remember

The string in the Export attribute or IOC object registration, must match the string or object of the Uri.

Optional: IConfimNavigationRequest.ConfirmNavigationRequest

 IConfirm

 void IConfirmNavigationRequest.ConfirmNavigationRequest(
    NavigationContext navigationContext, Action<Boolean> continuationCallback) {
            
    //perform application logic and return a Boolean in the continuationCallback
    continuationCallback(true);
}

After calling this method, the navigation pipeline pauses the navigation request processing. Views or ViewModels that implement this Interface must call the provided Callback otherwise navigation will not precede. If a subsequent navigation request is issued and this Callback has not been called, the original request will be discarded.

The driving force behind this Callback being asynchronous is the fact that Silverlight does not have a blocking dialog box capability. Silverlight developers can display a ChildWindow object and then use that response to reply to the Callback. WPF developers can use a blocking modal dialog or a non-blocking implementation similar to Silverlight.

Use this interface if you want to prevent the user from navigating away from a view before the view is completed. Since the Navigation APIs' allow you to navigate away and then return to a form, you may find that you don't need to prevent users from navigating away from a form in progress, since they can easily navigate back.

Remember

The asynchronous Callback stops the navigation request until the Callback is called.

Optional: INavigationAware.OnNavigatedFrom

 OnNavigatedFrom

 void INavigationAware.OnNavigatedFrom(NavigationContext navigationContext) {

}

Implementers can use this method to save state of the currently active view. For example, I like to record which UI control has focus so that when the view is navigated back to, I can restore focus to the UI control that previously had focus.

Remember

Use this method to save state of your View or ViewModel if required.

Optional: INavigationAware.IsNavigationTarget

While implementing this Interface is optional, this method is critical to managing multiple instances of the same objects. For example, this method enables a region to contain multiple instances of a CustomerView, each editing a different customer record. This method provides the required API to enable this scenario.

 IsNavigationTarget

 Boolean INavigationAware.IsNavigationTarget(NavigationContext navigationContext) {

    String item = null;

    if(navigationContext.Parameters != null) {
        item = navigationContext.Parameters[Constants.Global.Item];
    }

    Boolean result = this.CurrentItem == item;
    return result;
}

Implementers use this method to inform the Navigation API that they are the target of the current navigation request.

The Navigation API queries each object in the region that matches the type being navigated to. Individual objects then use this method to communicate to the Navigation API that they are in-fact the destination object.

Implementers use the Parameters passed as part of the NavigationContext to determine if it matches the current request. In the above code snippet, the previously stored Item value is compared to the Item value in the query string parameter. The method then returns the Boolean value of that comparison.

Remember

This method is critical to managing multiple instances of the same object that are differentiated by the record key they are editing.

Parse the NavigationContext's Parameters, compare to the current state of your object and return a value indicating if the object is the target of the request or not.

Object Construction and or Activation

LocatedCantLocate

If the destination object is located, the NavigationService will activate it if it implements IActiveAware.

If the destination object is not located, the NavigationService will use the container to create the object, add it to the region and then active it if it implements IActiveAware.

Remember

The string in the Export attribute or IOC object registration, must match the string or object of the Uri otherwise the container will not be able to create it.

Optional: IRegionMemberLifeTime.KeepAlive

 KeepAlive

 Boolean IRegionMemberLifetime.KeepAlive {
    get {
        return _keepAliveState;
    }
}

By default, the RegionManager does not destroy an object when a different object becomes the active object in the region.

Developers can use this method to tell the Navigation API to remove the object after navigation. Since the object is no longer referenced by any object, it becomes available for garbage collection.

The below code snippet demonstrates how the included application has its objects removed when the Close button is pressed.

The below method sets _keepAliveState to false and then initiates a navigation.  During this navigation request the above KeepAlive property will be checked by the Navigation API, false will be returned and the object will be removed from the region and garbage collected.

 void CloseExecute() {
    if(_navigationJournal.CanGoBack) {
        _keepAliveState = false;
        _navigationJournal.GoBack();
    }
}

Remember

Implement this method to have objects removed from a region and destroyed when they are navigated away from.

Optional: IRegionNavigationService.Navigating

Navigating

 void NavigationService_Navigating(object sender, RegionNavigationEventArgs e) {
    this.Logger.Log(Global.RegionNavigatingTo + e.Uri);
}

This event is raised by the Region Navigation Service each time the region is navigating.

Remember

Use this top level event to perform an action each time a region is navigating.

Optional: INavigationAware.OnNavigatedTo

 OnNavigatedTo

 void INavigationAware.OnNavigatedTo(NavigationContext navigationContext) {

    if(_navigationJournal == null) {
        _navigationJournal = navigationContext.NavigationService.Journal;
    }
    if(_originalTargetUriString == null) {
        _originalTargetUriString = navigationContext.Uri.ToString();
    }
    
    if(navigationContext.Parameters != null && this.CurrentItem == null) {
        this.CurrentItem = navigationContext.Parameters[Constants.Global.Item];
    }
}

Implementers use this method to be notified by the Navigation API that they are they have been navigated to. This allows objects to execute code each time they are navigated to.

Implementers can easily determine if this is the initial or subsequent navigation to the object. In the above code snippet you can see that the CurrentItem is only set if it is null. In the above code, if the CurrentItem is null, this indicates that this is the initial navigation to this object. If the CurrentItem is not null, this indicates the object is being navigated back to.

I use this method to initiate loading of data and to restore focus to the UI Control that had focus when the object was navigated away from.

You should not allow unhandled exceptions thrown in this code to bubble back up to the Navigation API because the object has already been navigated to at this point. If an exception does get thrown, catch it and handle it just like any other exception that occurs when the forum is being used. If you allow the exception to be bubbled back up to the Navigation API's, the navigation pipeline will stop and not complete; however the object has been navigated to; creating a disconnect.

Please read the notes in the ItemViewModel OnNavigatedTo method. These comments describe how to properly handle exceptions that are thrown by code executing in this method.

Remember

Use this method to initiate code the View or ViewModel needs run each time it is brought into view.

Do not allow unhandled exceptions in this method to bubble back up through the Navigation API. Instead, handle the exception.

INavigateAsync.NavigationRequest Callback

 Callback

 void Callback(NavigationResult result) {
    _logger.Log(Constants.Global.NavigationRequestResultsCallBack + NavigationResultParser.Parse(result));
}

This Callback is an optional argument in the original RequestNavigate method call.

If supplied, the Callback is called with a NavigationResult object that is a rich set of properties allowing the original caller to evaluate the results of the navigation request and take required action.

For example, if the Callback handler detects that the navigation request failed, it can display a dialog informing the user of the problem.

In the above code snippet, the demo application is logging the method call with the NavigationResult information.

Developers should consider putting a Callback handler in a ViewModel or View base class. Doing this will limit the repeated boiler maker code to handle navigation errors.

Remember

Use the Callback to display errors from the navigation request.

Region.NavigationService.Navigated Event

 Navigated

 void NavigationService_Navigated(object sender, RegionNavigationEventArgs e) {
    if(dg.Items.Count > 0) {
        var border = VisualTreeHelper.GetChild(dg, 0) as Decorator;
        if(border != null) {
            var scroll = border.Child as ScrollViewer;
            if(scroll != null) {
                scroll.ScrollToEnd();
            }
        }
    }
}

The above code snippet is from the demo application ShellView.  This event handler scrolls the DataGrid to the bottom each time the region is navigated.

The ShellViewModel in demo application uses this event to reload the metadata from each object in the region, each time the region is navigated. 

Remember

Use this top level event to perform an action each time a region is navigated.

Region.NavigationService.NavigationFailed Event

Error

 /// <summary>
/// Handles the NavigationFailed event of the NavigationService.
/// 
/// Developers you can use this event handler as a central place to 
/// display a navigation failed message.  This handler can come in handy
/// when initiating navigation from XAML where no call back is available.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="Microsoft.Practices.Prism.Regions.RegionNavigationFailedEventArgs"/> instance containing the event data.</param>
void NavigationService_NavigationFailed(object sender, RegionNavigationFailedEventArgs e) {
    this.Logger.Log(String.Format(Constants.Global.NavigationFailedMessage_FormatString, e.Uri.ToString(), e.Error.Message));

    // demo only. In a real-world application, use a dialog service or interaction request to display messages.
    MessageBox.Show(String.Format(Constants.Global.NavigationFailedMessage_FormatString, e.Uri.ToString(), e.Error.Message), 
        Constants.Global.NavigationFailed, MessageBoxButton.OK, MessageBoxImage.Error);
}

The above code snippet is from the demo application ShellView.  This event handler provides a central handler for NavigationFailed event.  When initiating a navigation request from XAML, there is no opportunity to capture the callback.  This handler provides that capability and allows you to display the error message to the user.

Remember

Use this top level event to perform an action each time a region navigation fails.

Non-Linear Navigation

You do not need to implement the Non-Linear Navigation interfaces if you don't want to. The Prism v4 Navigation API does not have any dependencies on this code.

Background

The term Non-Linear Navigation was coined by Billy Hollis in one of his PDC presentations. He also did a dnrTV video. The application he demonstrated was a product he wrote for a customer. He spoke about the ability to hyperlink to any form or data record from anywhere in the application.

A while back I wrote WPF Non-Linear Navigation API and presented it in the BBQ Shack blog post. Additionally, I did a proof-of-concept Non-Linear Navigation in Silverlight 4 blog post.

After spending the last few weeks on the Prism Team, I've come to deeply respect the team members, their commitment to excellence and customer satisfaction.

If you are looking for a complete Navigation API for your projects, take the time to read and study the guidance and example code in the Prism v4 Guidance and Library. You'll be glad you did.

I will be updating my own blogs posts to use this API because it is better and supported.

Motivation

In a modern line of business application, it is important to provide your end users the ability to multi-task. For example they could be working on a Customer record; someone comes to their desk and needs them to view history in another module. Then the phone rings and now they need to do another task, but don't want to cancel out of the current task.

The application should allow them to quickly navigate to another task, and then return to the original task. This workflow should be easy and simple.

The application should also make it easy for a user to visually see the number of active views that are hidden and easily get back to them.

Included Demo Application

Implementing Non-Linear Navigation is actually very simple. It starts with a container like a region that contains multiple objects. The objects can all be visible or one can be visible and the others hidden until they are navigated to.

This application uses a collection of metadata to surface the objects in region.

alt

The above Hyperlink Controls are created using the metadata sourced from the region objects.

The ShellViewModel NavigationService_Navigated event handler is used to iterate over each object in the region that implements the INonLinearNavigationObject Interface.

This Interface provides a common way for objects to communicate metadata. The below class diagram shows the metadata properties. In a future blog post I'll show this metadata being used in a real-world scenario. Please see the detailed comments in this Interface's source code.

alt

Video

The demo application video can be viewed here.

Note: You can view the high resolution version of the .wvm video file by:

  • Clicking on the video link
  • Log into Vimeo

After logging in, you’ll be given access to the high resolution video download.  There is no cost associated with this.

Download

The link to download the demo application is located at the bottom of this article.

Requirements: you must download Prism v4 and run the RegisterPrismBinaries.bat batch file.  The Prism v4 Readme covers this file in detail.  If you do not want to run this batch file, you’ll need to remove and re-add the references to the Prism assemblies.

Comments

Microsoft values your opinion about our products, guidance, documentation and samples.

Thank you for your feedback and have a great day,

Karl Shifflett

Patterns & Practices Prism Team

WPFPrismv4NavigationUpdated11-8-2010.zip