Prism 5.0 WPF Navigation Guide
Prism 5.0 WPF Navigation Guide
Navigation in Prism
Navigation is defined as the process by which the application coordinates changes
to its UI as a result of the user's interaction with the application or internal
application state changes.
UI updates can be accomplished by adding or removing elements from the
application's visual tree, or by applying state changes to existing elements within
the visual tree. WPF is a very flexible platform, and it is often possible to implement
a particular navigation scenario using this approach. However, the approach that
will be most appropriate for your application depends on multiple factors.
Prism differentiates between the two styles of navigation described earlier.
Navigation accomplished via state changes to existing controls in the visual tree is
referred to as state-based navigation. Navigation accomplished via the addition or
removal of elements from the visual tree is referred to as view-based navigation.
Prism provides guidance on implementing both styles of navigation, focusing on the
case where the application is using the Model-View-ViewModel (MVVM) pattern to
separate the UI (encapsulated in the view) from the presentation logic and data
(encapsulated in the view model).
State-Based Navigation
In state-based navigation, the view that represents the UI is updated either through
state changes in the view model or through the user's interaction within the view
itself. In this style of navigation, instead of replacing the view with another view, the
view's state is changed. Depending on how the view's state is changed, the updated
UI may feel to the user like navigation.
This style of navigation is suitable in the following situations:
The view needs to display the same data or functionality in different styles or
formats.
The view needs to change its layout or style based on the underlying state of the
view model.
The view needs to initiate limited modal or non-modal interaction with the user
within the context of the view.
This style of navigation is not suitable for situations in which the UI has to present
different data to the user or when the user has to perform a different task. In these
situations, it is better to implement separate views (and view models) to represent
the data or task, and then to navigate between them using view-based navigation,
as described later on in this topic. Similarly, this style of navigation is not suitable if
the number of UI state changes required to implement the navigation are overly
complex because the view's definition can become large and difficult to maintain. In
this case, it is better to implement the navigation across separate views by using
view-based navigation.
The following sections describe the typical situations in which state-based
navigation can be used. Each of these sections refers to the State-Based Navigation
QuickStart, which implements an instant messagingstyle application that allows
users to manage and chat with their contacts.
Displaying Data in Different Formats or Styles
Your application may often need to present the same data to the user, but in
different formats or styles. In this case, you can use a state-based navigation within
the view to switch between the different styles, potentially using an animated
transition between them. For example, the State-Based Navigation QuickStart allows
users to choose how their contacts are displayedeither as a simple text list or as
avatars (icons). Users can switch between these visual representations by clicking
the List button or the Avatars button. The view provides an animated transition
between the two representations, as shown in the following illustration.
As the user clicks the Contacts or Avatar radio buttons, the visual state is toggled
between the ShowAsList visual state and the ShowAsIcons visual state. The flip
transition animation between these states is also defined using the visual state
manager.
Another example of this style of navigation is shown by the State-Based Navigation
QuickStart application when the user switches to the details views for the currently
selected contact. The following illustration shows an example of this.
the user's connection status changes, the view is informed (via a property change
notification event) allowing the view to visually represent the current connection
state appropriately, as shown in the following illustration.
Note that the connection state can be changed by the user via the UI or by the
application according to some internal logic or event. For example, the application
may move to an "unavailable" state if the user does not interact with the view
within a certain time period or when the user's calendar indicates that he or she is
in a meeting. The State-Based Navigation QuickStart simulates this scenario by
switching the connection status randomly using a timer. When the connection status
is changed, the property on the view model is updated, and the view is informed via
a property changed event. The UI is then updated to reflect the current connection
status.
All the preceding examples involve defining visual states in the view and switching
between them as a result of the user's interaction with the view or via changes in
properties defined by the view model. This approach allows the UI designer to
implemenent navigation-like visual behavior in the view without requiring the view
to be replaced or requiring any code changes to the application's code. This
approach is suitable when the view is required to render the same data in different
styles or layouts. It is not suitable for situations in which the user is to be presented
with different data or application functionality or when navigating to a different part
of the application.
Interacting With the User
Frequently, an application will need to interact with the user in a limited way. In
these situations, it is often more appropriate to interact with the user within the
context of the current view, instead of navigating to a new view. For example, in the
State-Based Navigation QuickStart, the user is able to send a message to a contact
by clicking the Send Message button. The view then displays a pop-up window
that allows the user to type the message, as shown in the following illustration.
Because this interaction with the user is limited and logically takes place within the
context of the parent view, it can be easily implemented as state-based navigation.
The following code example shows how the view in the State-Based Navigation
QuickStart application responds to the SendMessageRequest interaction request
object provided by the view model. When the request event is received, the
SendMessageChildWindow is displayed as a popup window.
XAML
<prism:InteractionRequestTrigger SourceObject="{Binding SendMessageRequest}">
<prism:PopupWindowAction IsModal="True">
<prism:PopupWindowAction.WindowContent>
<vs:SendMessagePopupView />
</prism:PopupWindowAction.WindowContent>
</prism:PopupWindowAction>
</prism:InteractionRequestTrigger>
View-Based Navigation
Although state-based navigation can be useful for the scenarios outlined earlier,
navigation within an application will most often be accomplished by replacing one
view within the application's UI with another. In Prism, this style of navigation is
referred to as view-based navigation.
Depending on the requirements of the application, this process can be fairly
complex and require careful coordination. The following are common challenges
that often have to be addressed when implementing view-based navigation:
The target of the navigationthe container or host control of the views to be
added or removedmay handle navigation differently as views are added or
removed from it, or they may visually represent navigation in different ways. In
many cases, the navigation target will be a simple Frame or ContentControl,
and navigated views will simply be displayed within these controls. However, there
are many scenarios where the target for the navigation operation is a different
type of container control, such as a TabControl or a ListBox control. In these
cases, navigation may require the activation or selection of an existing view or the
addition of new view is a specific way.
The application will also often have to define how the view to be navigated to is
identified. For example, in a web application, the page to be navigated to is often
directly identified by a Uniform Resource Identifier (URI). In a client application, the
view can be identified by type name, resource location, or in a variety of different
ways. Furthermore, in a composite application, which is composed from loosely
coupled modules, the views will often be defined in separate modules. Individual
views will need to be identified in a way that does not introduce tight coupling and
dependencies between modules.
After the view is identified, the process by which the new view is instantiated and
initialized has to be carefully coordinated. This can be particularly important when
using the MVVM pattern. In this case, the view and view model may need to be
instantiated and associated with each other via the view's data context during the
For each control specified as a region, Prism creates a Region object to represent
the region and a RegionAdapter object, which manages the placement and
activation of views into the specified control. The Prism Library provides
RegionAdapter implementations for most of the common WPF controls. You can
create a custom RegionAdapter to support additional controls or when you need
to define a custom behavior. The RegionManager class provides access to the
Region objects within the application.
In many cases, the region control will be a simple control, such as a
ContentControl, that can display one view at a time. In other cases, the Region
control will be a control that is able to display multiple views at the same time, such
as a TabControl or a ListBox control.
The region adapter manages a list of views within the associated region. One or
more of these views will be displayed in the region control according to its defined
layout strategy. Views can be assigned a name that can be used to retrieve that
view later on. The region adapter manages the active state of the views within the
region. The active view is the view that is the selected or top-most viewfor
example, in a TabControl, the active view is the one displayed in the selected tab;
in a ContentControl, the active view is the view that is currently displayed as the
control's content.
Note: The active state of a view is important to consider during navigation.
Frequently, you will want the active view to participate in navigation so that
it can save data before the user navigates away from it, or so that it can
confirm or cancel the navigation operation.
Previous versions of Prism allowed views to be displayed in a region in two ways.
The first, called view injection, allows views to be programmatically displayed in a
region. This approach is useful for dynamic content, where the view to be displayed
in the region changes frequently, according to the application's presentation logic.
View injection is supported through the Add method on the Region class. The
follow code example shows how you can obtain a reference to a Region object via
the RegionManager class and programmatically add a view to it. In this example,
the view is created using a dependency injection container.
C#
IRegionManager regionManager = ...;
IRegion mainRegion = regionManager.Regions["MainRegion"];
InboxView view = this.container.Resolve<InboxView>();
mainRegion.Add(view);
The second method, called view discovery, allows a module to register a view type
against a region name. Whenever a region with the specified name is displayed, an
instance of the specified view will be automatically created and displayed in the
region. This approach is useful for relatively static content, where the view to be
displayed in a region does not change.
View discovery is supported through the RegisterViewWithRegion method on the
RegionManager class. This method allows you to specify a callback method that
will be called when the named region is shown. The following code example shows
how you can create a view (via the dependency injection container) when the main
region is first shown.
C#
IRegionManager regionManager = ...;
regionManager.RegisterViewWithRegion("MainRegion", () =>
container.Resolve<InboxView>());
For a detailed overview of Prisms region support and information about how to
leverage regions to compose the application's UI using view injection and discovery,
see Composing the User Interface. The rest of this topic describes how regions have
been extended to support view-based navigation, and how this addresses the
various challenges described earlier.
Basic Region Navigation
Both view injection and view discovery can be considered to be limited forms of
navigationview injection is a form of explicit, programmatic navigation, and view
discovery is a form of implicit or deferred navigation. However, in Prism 4.0, regions
have been extended to support a more general notion of navigation, based on URIs
and an extensible navigation mechanism.
Navigation within a region means that a new view is to be displayed within that
region. The view to be displayed is identified via a URI, which, by default, refers to
the name of the view to be created. You can programmatically initiate navigation
using the RequestNavigate method defined by the INavigateAsync interface.
Note: Despite its name, the INavigateAsync interface does not represent
asynchronous navigation that's carried out on a separate background
thread. Instead, the INavigateAsync interface represents the ability to
perform pseudo-asynchronous navigation. The RequestNavigate method
You can also call the RequestNavigate method on the RegionManager, which
allows you to specify the name of the region to be navigated. This convenient
method obtains a reference to the specified region and then calls the
RequestNavigate method, as shown in the preceding code example.
C#
IRegionManager regionManager = ...;
regionManager.RequestNavigate("MainRegion",
new Uri("InboxView", UriKind.Relative));
By default, the navigation URI specifies the name of a view that is registered in the
container.
Using MEF, you can simply export the view type with the specified name.
C#
[Export("InboxView")]
public partial class InboxView : UserControl
During navigation, the specified view is instantiated, via the container or MEF, along
with its corresponding view model and other dependent services and components.
After the view is instantiated, it is then added to the specified region and activated
(activation is described in more detail later in this topic).
Note: The preceding description illustrates view-first navigation, where the
URI refers to the name of the view type, as it is exported or registered with
the container. With view-first navigation, the dependent view model is
created as a dependency of the view. An alternative approach is to use view
modelfirst navigation, where the navigation URI refers to the name of the
view model type, as it is exported or registered with the container. View
modelfirst navigation is useful when the view is defined as a data template,
or when you want your navigation scheme to be defined independently of
the views.
The NavigationResult class defines properties that provide information about the
navigation operation. The Result property indicates whether or not navigation
succeeded. If navigation failed, the Error property provides a reference to any
exception that was thrown during navigation. The Context property provides
access to the navigation URI and any parameters it contains, and a reference to the
navigation service that coordinated the navigation operation.
View and View Model Participation in Navigation
Frequently, the views and view models in your application will want to participate in
navigation. The INavigationAware interface enables this. You can implement this
interface on the view or (more commonly) the view model. By implementing this
interface, your view or view model can opt-in to participate in the navigation
process.
Note: In the description that follows, although a reference is made to calls
to this interface during navigation between views, it should be noted that
the INavigationAware interface will be called during navigation whether it
is implemented by the view or by the view model.
During navigation, Prism checks to see whether the view implements the
INavigationAware interface; if it does, it calls the required methods during
navigation. Prism also checks to see whether the object set as the view's
DataContext implements this interface; if it does, it calls the required
methods during navigation.
This interface allows the view or view model to participate in a navigation operation.
The INavigationAware interface defines three methods.
C#
public interface INavigationAware
{
bool IsNavigationTarget(NavigationContext navigationContext);
void OnNavigatedTo(NavigationContext navigationContext);
You can retrieve the navigation parameters using the Parameters property on the
NavigationContext object. This property returns an instance of the
NavigationParameters class, which provides an indexer property to allow easy
access to individual parameters, independently of them being passed through the
query or through the RequestNavigate method.
C#
public void OnNavigatedTo(NavigationContext navigationContext)
{
string id = navigationContext.Parameters["ID"];
ObjectParameter myParameter =
navigationContext.Parameters["myObjectParameter"];
}
Prism supports the two scenarios described earlier via the IsNavigationTarget
method on the INavigationAware interface. This method is called during
navigation on all views in a region that are of the same type as the target view. In
the preceding examples, the target type of the view is the EditCustomer view, so
the IsNavigationTarget method will be called on all existing EditCustomer view
instances currently in the region. Prism determines the target type from the view
URI, which it assumes is the short type name of the target type.
Note: For Prism to determine the type of the target view, the view's name
in the navigation URI should be the same as the actual target type's short
type name. For example, if your view is implemented by the
MyApp.Views.EmployeeDetailsView class, the view name specified in
the navigation URI should be EmployeeDetailsView. This is the default
behavior provided by Prism. You can customize this behavior by
implementing a custom content loader class; you can do this by
implementing the IRegionNavigationContentLoader interface or by
deriving from the RegionNavigationContentLoader class.
The implementation of the IsNavigationTarget method can use the
NavigationContext parameter to determine whether it can handle the navigation
request. The NavigationContext object provides access to the navigation URI and
the navigation parameters. In the preceding examples, the implementation of this
method in the EditCustomer view model compares the current customer ID to the
ID specified in the navigation request, and it returns true if they match.
C#
public bool IsNavigationTarget(NavigationContext navigationContext)
{
string id = navigationContext.Parameters["ID"];
return _currentCustomer.Id.Equals(id);
}
<UserControl.Resources>
<DataTemplate x:Key="ConfirmExitDialogTemplate">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
Text="{Binding}"/>
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<ei:Interaction.Triggers>
<prism:InteractionRequestTrigger SourceObject="{Binding
ConfirmExitInteractionRequest}">
<prism:PopupWindowAction IsModal="True"
CenterOverAssociatedObject="True"/>
</prism:InteractionRequestTrigger>
</ei:Interaction.Triggers>
...
The callback for the interaction request is called when the user clicks the buttons in
the confirmation pop-up window to confirm or cancel the operation. This callback
simply calls the continuation callback, passing in the value of the Confirmed flag,
and causing the navigation to continue or be canceled.
Note: It should be noted that after the interaction request event is raised,
the ConfirmNavigationRequest method immediately returns so that the
user can continue to interact with the UI of the application. When the user
clicks the OK or Cancel buttons on the pop-up window, the callback method
of the interaction request is made, which in turn calls the continuation
callback to complete the navigation operation. All the methods are called on
the UI thread. Using this technique, no background threads are required.
Using this mechanism, you can control if the navigation request is carried out
immediately or is deferred, pending an interaction with the user or some other
asynchronous interaction (for example, as a result of a web service request). To
enable navigation to proceed, you can simply call the continuation callback method,
passing true to indicate that it can continue. Similarly, you can pass false to
indicate that the navigation should be canceled.
C#
void IConfirmNavigationRequest.ConfirmNavigationRequest(
NavigationContext navigationContext, Action<bool> continuationCallback)
{
continuationCallback(true);
}
If you want to defer navigation, you can store a reference to the continuation
callback you can then call when the interaction with the user (or web service)
completes. The navigation operation will be pending until you call the continuation
callback.
If the user initiates another navigation operation in the meantime, the navigation
request then becomes canceled. In this case, calling the continuation callback has
no effect because the navigation operation to which it relates is no longer current.
Similarly, if you decide not to call the continuation callback, the navigation
operation will be pending until it is replaced with a new navigation operation.
Using the Navigation Journal
The NavigationContext class provides access to the region navigation service,
which is responsible for coordinating the sequence of operations during navigation
within a region. It provides access to the region in which navigation is taking place,
and to the navigation journal associated with that region. The region navigation
service implements the IRegionNavigationService, which is defined as follows.
C#
public interface IRegionNavigationService : INavigateAsync
{
IRegion Region {get; set;}
IRegionNavigationJournal Journal {get;}
event EventHandler<RegionNavigationEventArgs> Navigating;
event EventHandler<RegionNavigationEventArgs> Navigated;
event EventHandler<RegionNavigationFailedEventArgs> NavigationFailed;
}
You can obtain and store a reference to the region navigation service within a view
during navigation via the OnNavigatedTo method call. By default, Prism provides a
simple stack-based journal that allows you to navigate forward or backward within a
region.
You can use the navigation journal to allow the user to navigate from within the view
itself. In the following example, the view model implements a GoBack command,
which uses the navigation journal within the host region. Therefore, the view can
display a Back button that allows the user to easily navigate back to the previous
view within the region. Similarly, you can implement a GoForward command to
implement a wizard style workflow.
C#
public class EmployeeDetailsViewModel : INavigationAware
{
...
private IRegionNavigationService navigationService;
public void OnNavigatedTo(NavigationContext navigationContext)
{
navigationService = navigationContext.NavigationService;
}
public DelegateCommand<object> GoBackCommand { get; private set; }
private void GoBack(object commandArg)
{
if (navigationService.Journal.CanGoBack)
{
navigationService.Journal.GoBack();
}
}
private bool CanGoBack(object commandArg)
{
return navigationService.Journal.CanGoBack;
}
You can implement a custom journal for a region if you need to implement a specific
workflow pattern within that region.
Note: The navigation journal can only be used for region-based navigation
operations that are coordinated by the region navigation service. If you use
view discovery or view injection to implement navigation within a region,
the navigation journal will not be updated during navigation and cannot be
used to navigate forward or backward within that region.
Using the WPF Navigation Framework
Prism region navigation was designed to address a wide range of common scenarios
and challenges that you may face when implementing navigation in a looselycoupled, modular application that uses the MVVM pattern and a dependency
injection container, such as Unity, or the Managed Extensibility Framework (MEF). It
also was designed to support navigation confirmation and cancelation, navigation to
existing views, navigation parameters and navigation journaling.
By supporting navigation within Prism regions, it also supports navigation within a
wide range of layout controls and supports the ability to change the layout of the
application's UI without affecting its navigation structure. It also supports pseudosynchronous navigation, which allows for rich user interaction during navigation.
However, the Prism region navigation was not designed to replace WPF's navigation
framework. Instead, Prism region navigation was designed to be used side-by-side
with the WPF navigation framework.
The WPF navigation framework is difficult to use to support the MVVM pattern and
dependency injection. It is also based on a Frame control that provides similar
functionality in terms of journaling and navigation UI. You can use the WPF
navigation framework alongside Prism region navigation, though it may be easier
and more flexible to implement navigation using only Prism regions.
The Region Navigation Sequence
The following illustration provides an overview of the sequence of operations during
a navigation operation. It is provided for reference so that you can see how the
various elements of the Prism region navigation work together during a navigation
request.
More Information
For more information about Prism regions, see Composing the User Interface.
For more information about the MVVM pattern and Interaction Request pattern, see
Implementing the MVVM Pattern and Advanced MVVM Scenarios.
For more information about the Interaction Request object, see Using Interaction
Request Objects in Advanced MVVM Scenarios.
For more information about the Visual State Manager, see VisualStateManager Class
on MSDN.
For more information about using Microsoft Blend behaviors, see Working with builtin behaviors on MSDN.
For more information about creating custom behaviors with Microsoft Blend, see
Creating Custom Behaviors on MSDN.