⬆️ ⬇️

Prism Developer Guide - Part 8.1, View-Based Navigation

Table of contents

  1. Introduction
  2. Initializing Prism Applications
  3. Manage dependencies between components
  4. Modular Application Development
  5. Implementation of the MVVM pattern
  6. Advanced MVVM scripts
  7. Creating user interface

    1. User Interface Design Recommendations
  8. Navigation
    1. View-Based Navigation (View-Based Navigation)
  9. The interaction between loosely coupled components


View-Based Navigation ( View-Based Navigation )



Although state-based navigation may be useful in the scenarios described earlier, however, navigation in an application often requires replacing one view with another. In Prism, this type of navigation is called " view-based navigation ".



Depending on the application requirements, the navigation process can be quite complex and requires careful coordination. The following are some of the difficulties you may encounter when implementing navigation based on views:





Prism provides guidance on how to solve these problems, expanding the mechanism of regions to support navigation. The following sections provide a brief summary of the Prism regions and how they were extended to support view-based navigation.



Region overview Prism



Prism regions were designed to support the development of composite applications (applications consisting of several modules), allowing the user interface to be designed in a loosely coupled fashion. Regions allow you to display the views defined in the modules in the application UI, and the modules do not need to know about the full structure of the user interface. This makes it easy to change the UI markup of the application, without having to make changes to the modules themselves.

')

Regions of Prism, for the most part, are named placeholders that display views. Any control can be declared as a region, by simply adding the attached RegionName property, as shown below.



 <ContentControl prism:RegionManager.RegionName="MainRegion" ... /> 


For each control defined as a region, Prism creates a Region object, representing the region itself, and a RegionAdapter object (region adapter), whose task is to control the location and activation of views in a given control element. The Prism Library provides a RegionAdapter implementation for most Silverlight and WPF controls. You can create your own RegionAdapter to support additional controls, or to implement special behavior. The RegionManager class (region manager) provides access to the Region application objects.



In many cases, a region can be a simple control, such as ContentControl , which can display only one view at a time. In other cases, it can be a control that allows you to display multiple views at once, such as TabControl , or ListBox .



The region adapter manages the list of views associated with that region. One or more of these views can be displayed in the region's control, in accordance with its content display strategy. Views can be assigned names that can be used to search for them in the future. The region adapter also controls which presentation is active in the region. Active, is the view that is highlighted at the moment, or is the top one. For example, in TabControl , the view that is displayed on the highlighted tab is active, in ContentControl , the one that is currently displayed on the screen.



The note.

Determining which presentation is active is especially important in the navigation process. Often, it may be necessary for an active view to take part in navigation, for example, to save data before the user leaves it, or to request cancellation, or to confirm the operation.



Previous versions of Prism made it possible to display views in regions in two different ways. The first method, called view injection , allowed programmatically display views in a region. This approach is useful when displaying dynamic content, when the view that needs to be displayed in a region often changes to reflect the logic of the application.



The implementation of views is supported by providing the Add method in the Region class. The following code shows how you can get a reference to a Region object through the RegionManager class, and programmatically add a view to it. In the example, the view is created using the DI container.



 IRegionManager regionManager = ...; IRegion mainRegion = regionManager.Regions["MainRegion"]; InboxView view = this.container.Resolve<InboxView>(); mainRegion.Add(view); 


The following method, called view discovery , allows modules to register a mapping between view types and region names. At the moment when the region with the specified name is displayed on the screen, an instance of the corresponding view will be automatically created and displayed in this region. This trip is useful for displaying relatively static content when the view displayed in the region does not change.



View detection is supported through the RegisterViewWithRegion method in the RegionManager class. This method allows you to specify a callback method that will be called when displaying a region with the specified name. The following example shows how you can create a view (using a dependency injection container) when displaying a region named “MainRegion” .



 IRegionManager regionManager = ...; regionManager.RegisterViewWithRegion("MainRegion", () => container.Resolve<InboxView>()); 


For a more detailed overview of the regions of Prism, see the chapter " Creating a User Interface ". The rest of the article will look at how the regions were expanded to support navigation, and how to overcome the problems described earlier.



Basic navigation in the regions



Both of the above-described methods for displaying views in regions can be regarded as some limited form of navigation — the introduction of a view is an explicit, programmatic navigation, and the detection of a view is implicit or deferred navigation. However, in Prism 4.0, regions were expanded to support more general concepts of navigation, based on URIs and an extensible navigation mechanism.



Navigating within a region means displaying a new view in it. The displayed view is identified by a URI, which, by default, refers to the name of the view being navigated to. You can initiate navigation programmatically using the RequestNavigate method defined in the INavigateAsync interface.



The note.

Despite the name, the INavigateAsync interface INavigateAsync not imply asynchronous navigation performed in a separate thread. On the contrary, INavigateAsync implies pseudo-asynchronous navigation. The RequestNavigate method may end synchronously after the end of navigation, or it may end before the end of navigation, for example, when the user needs to confirm the navigation. By allowing you to set a callback method during navigation, Prism enables support for such scenarios without the difficulty of handling background threads.



The INavigateAsync interface implements the Region class, allowing you to initiate navigation in that region.



 IRegion mainRegion = ...; mainRegion.RequestNavigate(new Uri("InboxView", UriKind.Relative)); 


You can also call the RequestNavigate method on the RegionManager object, specifying the name of the region on which you are navigating. This method finds a link to the corresponding region and calls the RequestNavigate method on it. This is shown in the example below.



 IRegionManager regionManager = ...; regionManager.RequestNavigate("MainRegion", new Uri("InboxView", UriKind.Relative)); 


By default, the navigation URI specifies the name of the view by which it is registered in the container. The code below illustrates the relationship between the view registration name in a Unity container and the use of that name during navigation.



 container.RegisterType<object, InboxView>("InboxView"); regionManager.Regions[Constants.MainRegion].RequestNavigate(new Uri("InboxView", UriKind.Relative)); 


The note.

When creating a view by a navigation service, it requests an object of type Object from a container, with the name provided in the navigation URI. Different containers use different registration mechanisms to support this. For example, in Unity, you need to register the views by associating the Object type with this view, and providing the registration name that matches the name in the navigation URI. In MEF, you only need to specify the name of the contract in the ExportAttribute attribute.



Example. When using Unity to register a view:



Do not use:

 container.RegisterType<InboxView>("InboxView"); 


Use instead:

 container.RegisterType<object,InboxView>("InboxView"); 


The note.

The name used for registration and navigation does not have to be associated with the name of the view type, any string will do. For example, instead of a string, you can explicitly use the full name of the type: typeof(InboxView).FullName ;



In MEF, you can simply export the view with the desired name.



 [Export("InboxView")] public partial class InboxView : UserControl { ... } 


During navigation, the specified view is requested from the container along with the corresponding view model and other dependencies. After that, it is added to the specified region and activated (details about activation will be later in the article).



The note.

The preceding description illustrates view-first navigation when a URI refers to the name of the view with which it was exported or registered in a container. With view-first navigation, the dependent view model is created as a view dependency. An alternative approach is to use the view model – first navigation when the navigation URI refers to the name of the view model with which it was registered in the container. This approach can be useful when views are defined as data patterns, or when you want the navigation pattern to be defined independently of the views.



The RequestNavigate method also allows you to specify a callback method that will be called when the navigation is completed.



 private void SelectedEmployeeChanged(object sender, EventArgs e) { ... regionManager.RequestNavigate(RegionNames.TabRegion, "EmployeeDetails", NavigationCompleted); } private void NavigationCompleted(NavigationResult result) { ... } 


The NavigationResult class has properties through which you can get information about the navigation operation. The Result property indicates whether navigation has completed. If the navigation ended with an error, then the Error property will contain a reference to any exceptions thrown during the navigation. Through the Context property, you can access the navigation URI and any parameters it contains, as well as a link to the service that coordinates navigation operations.



View participation and view models in navigation



It often happens that both the view and the view model in your application will want to participate in the navigation. This allows the interface INavigationAware . You can implement this interface in a view, or (more often) in a view model. By implementing it, your view, or view model, can be directly involved in the navigation process.



The note.

In the following description, it is assumed that navigation occurs between views. But it should be noted that the INavigationAware interface during navigation will be called regardless of whether it is implemented in the view, or in the view model. During navigation, Prism checks whether the INavigationAware view INavigationAware , if so, the necessary methods are called on it. Also, Prism makes a check on the implementation of this interface on the object in which the View DataContext property is set, and, if successful, calls the necessary methods.



This interface allows the view, or view model, to participate in a navigation operation. Three methods are defined in the INavigationAware interface:



 public interface INavigationAware { bool IsNavigationTarget(NavigationContext navigationContext); void OnNavigatedTo(NavigationContext navigationContext); void OnNavigatedFrom(NavigationContext navigationContext); } 


The IsNavigationTarget method allows an existing representation in the region, or a representation model, to show whether it can process a navigation request. This can be useful in cases where you want to reuse an already existing view for navigation processing, or you want to navigate to an already existing view. For example, a view that displays customer information can be updated by selecting different customers. For more information, see the “Navigating to Existing Views” section later in this article.



The OnNavigatedFrom and OnNavigatedTo are called during a navigation operation. If the current active view in a region (or its view model) implements this interface, the OnNavigatedFrom method OnNavigatedFrom called before navigation begins. The OnNavigatedFrom method allows the previous view to save its state, or prepare for deactivation or deletion from the user interface. For example, a view can save changes made by a user to a database, or send them to a web service.



If the newly created view (or view model) implements this interface, its OnNavigatedTo method OnNavigatedTo called after the navigation is completed. The OnNavigatedTo method allows the view that was just displayed to be initialized, possibly using parameters passed along with the navigation URI. For more information, see the next section, “Passing Parameters During Navigation.”



After creating, initializing and adding a new view to the target region, it becomes active, and the previous view is deactivated. Sometimes, it may be necessary to remove a deactivated view from a region. In Prism, there is an IRegionMemberLifetime interface that allows you to control the lifetime of views in regions, asking whether you should immediately remove deactivated views from a region, or simply mark them as deactivated.



 public class EmployeeDetailsViewModel : IRegionMemberLifetime { public bool KeepAlive { get { return true; } } } 


The IRegionMemberLifetime interface defines the only KeepAlive property that is read-only. If it is set to false , the view will be removed from the region when it is deactivated. Since the region then stops storing the link to the view, it will be accessible to the garbage collector (unless, of course, other parts of your application reference it). You can implement this interface in both the view and the view model. Although the IRegionMemberLifetime interface IRegionMemberLifetime intended, for the most part, to control the lifetime of views in regions during activation and deactivation, the KeepAlive property is also used during navigation after creating and activating a view in the target region.



The note.

, , ItemsControl , TabControl , , . , , .





To implement the necessary behavior during navigation, you may often need to specify additional data that is transmitted during the navigation request, in addition to the name of the target view. The object NavigationContextprovides access to the navigation URI and to all parameters that were passed along with it. You can get access to NavigationContextin the way IsNavigationTarget, OnNavigatedFromand OnNavigatedTo.



To help set and get navigation options, there is a class in Prism UriQuery. You can use it if necessary to add navigation parameters to the URI before starting navigation and to access these parameters during navigation. UriQuerycreates a list with name-value pairs for each parameter.



The following code example shows how to add parameters toUriQuery and attach them to the navigation URI.



 Employee employee = Employees.CurrentItem as Employee; if (employee != null) { UriQuery query = new UriQuery(); query.Add("ID", employee.Id); _regionManager.RequestNavigate(RegionNames.TabRegion, new Uri("EmployeeDetailsView" + query.ToString(), UriKind.Relative)); } 


You can get navigation parameters using the Parametersobject property NavigationContext. This property returns an instance of a class UriQuerythat has an indexing property to simplify access to individual parameters.



 public void OnNavigatedTo(NavigationContext navigationContext) { string id = navigationContext.Parameters["ID"]; } 


Navigation to existing views



Quite often, reusing a view, updating it, or activating during navigation, is a preferred behavior than replacing it with a new view. This is a typical case when you are navigating to a view of the same type, but in which you want to display other data, or when the required view is already available, but requires activation (to highlight or move up).



An example of the first scenario is when the application allows the user to change information about the client using the view EditCustomer, and the user already uses this view to edit the client with ID 123. If the user decides to edit the client record with ID 456, he can simply navigate to submissionEditCustomerand enter a new ID. After that, the view EditCustomerwill request information about the new client and update its UI accordingly.



For an example of using the second scenario, let's say that the application allows you to edit information about several clients simultaneously. In this case, the application displays several views EditCustomerin TabControl, for example, clients with ID 123 and ID 456. When you navigate to EditCustomerand enter ID 456, the corresponding view will be activated (that is, the corresponding tab will be highlighted). If the user navigates to the view EditCustomerand enters ID 789, then a new instance will be created and displayed in the UI.



The ability to navigate to already existing views is useful for a variety of reasons. It is often more efficient to update an existing view, rather than replace it with a new one of the same type. Similarly, activating an existing view instead of creating a new one makes the interface more consistent. In addition, the ability to handle such scripts without the need to write additional code simplifies the development and support of the application.



Prism supports both scripts described above through a method INavigationAware.IsNavigationTarget. This method is called during navigation on views in the region that have the same type as the target view. In the previous example, the target representation was of type EditCustomer, so the method IsNavigationTargetwill be called on all existing representations of typeEditCustomerlocated in the region. Prism determines the target presentation type using a navigation URI, assuming that it is the short name of the target representation type.



The note.

In order for Prism to determine the type of the target view, the name of the view in the navigation URI must match the short name of its type. For example, if a view has a class MyApp.Views.EmployeeDetailsView, then the name of the view specified in the navigation URI should be EmployeeDetailsView . This is the standard behavior of Prism. You can change it by creating your content loader class. You can do this by implementing an interface IRegionNavigationContentLoader, or by inheriting your class from RegionNavigationContentLoader.



The method implementation IsNavigationTargetcan use a parameter NavigationContextto determine if the view can process the navigation request. NavigationContextgives access to the navigation URI and navigation parameters. In the previous example, the implementation of this method in the view model EditCustomercompared the current client ID with the ID specified in the navigation query, returning trueif it matches.



 public bool IsNavigationTarget(NavigationContext navigationContext) { string id = navigationContext.Parameters["ID"]; return _currentCustomer.Id.Equals(id); } 


If the method IsNavigationTargetalways returns true, regardless of the navigation parameters, this view will always be reused. Such an approach can guarantee that in a certain region there will be only one representation of a certain type.



Confirmation or cancellation of navigation



It may often be necessary to interact with the user during navigation, allowing the user to confirm or cancel it. In many applications, the user, for example, can start navigation while entering or editing data. In such situations, you may need to ask the user if he wants to save, or to cancel the changes, before leaving the page, or if he does not want to cancel the navigation at all. Prism supports such scripts through the interface IConfirmNavigationRequest.



The interface IConfirmNavigationRequestinherits from the interface INavigationAwareand adds a methodConfirmNavigationRequest. By implementing this interface in your view, or view model, you allow them to participate in the navigation process, allowing you to interact with the user so that he can cancel or confirm the navigation. You may also need to use the Interaction Request object , which was discussed in Part 6, MVVM Advanced Scenarios , to display a confirmation popup window.



The note.

The method ConfirmNavigationRequestis called on the active view (or view model), similar to the method OnNavigatedFromdescribed earlier.



The method ConfirmNavigationRequesttakes two parameters, a reference to the current navigation context described above, and a delegate that must be called to continue navigation. For this reason, this delegate is often called the continuation callback . You can save a reference to the delegate-continuation, to call it after the end of user interaction. If an application interacts with the user through Interaction Request objects , you can use this delegate as the callback method of the interaction request. The following diagram illustrates the complete process.



Confirmation of navigation using the interaction request object



The following steps describe the process of confirming navigation when using an object InteractionRequest:



  1. Navigation is initiated through a method call RequestNavigate.
  2. , IConfirmNavigation , ConfirmNavigationRequest .
  3. ( interaction request event ).
  4. .
  5. , .
  6. - , .
  7. , .




To see an illustration of this, see View-Switching Navigation Quick Start . This application allows the user to create emails using classes ComposeEmailView, and ComposeEmailViewModel. The view model class implements the interface IConfirmNavigation. If the user navigates, for example, by pressing the Calendar button , at the time of creating the message, a method will be invoked ConfirmNavigationRequestso that the view model can request confirmation from the user. To support this, the view model sets the interaction request object, as shown in the example below.



 public class ComposeEmailViewModel : NotificationObject, IConfirmNavigationRequest { private readonly InteractionRequest<Confirmation> confirmExitInteractionRequest; public ComposeEmailViewModel(IEmailService emailService) { this.confirmExitInteractionRequest = new InteractionRequest<Confirmation>(); } public IInteractionRequest ConfirmExitInteractionRequest { get { return this.confirmExitInteractionRequest; } } } 


The class has a ComposeEmailVewtrigger InteractionRequestTriggerattached to ConfirmExitInteractionRequestthe view model property . When an interaction is requested, a simple popup window is shown to the user.



 <UserControl.Resources> <DataTemplate x:Name="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:PopupChildWindowAction ContentTemplate="{StaticResource ConfirmExitDialogTemplate}"/> </prism:InteractionRequestTrigger> </ei:Interaction.Triggers> ... 


The ConfirmNavigationRequestclass method ComposeEmailVewModeis invoked when a user attempts to navigate while writing a message. The implementation of this method triggers the interaction request defined earlier, so that the user can confirm or change the navigation operation.



 void IConfirmNavigationRequest.ConfirmNavigationRequest( NavigationContext navigationContext, Action<bool> continuationCallback) { this.confirmExitInteractionRequest.Raise( new Confirmation {Content = "...", Title = "..."}, c => {continuationCallback(c.Confirmed);}); } 


The callback method for requesting interaction is invoked when the user clicks a button on the confirmation popup. It simply invokes the delegate continuation, passing in the flag's value Confirmed, which forces it to confirm or cancel the navigation.



.

, Rise , ConfirmNavigationRequest , UI . OK , Cancel , , , , - . UI. .



Using this mechanism, you can control whether the navigation request is executed immediately, or with a delay, waiting for a user response, or other asynchronous operation, such as a request to a web server. To continue and confirm the navigation, you just need to call the delegate-continuation, passing in it true, to cancel the navigation, you must pass false.



 void IConfirmNavigationRequest.ConfirmNavigationRequest( NavigationContext navigationContext, Action<bool> continuationCallback) { continuationCallback(true); } 


If you want to postpone navigation, you can save a link to the delegate continuation, which can be called after the end of user interaction, or an asynchronous request (for example, to a web server). The navigation operation will be in a pending state until the delegate continues.



If the user starts another navigation at this time, the navigation request will be canceled. In this case, calling the delegate-continuation will not cause any effect. Similarly, if you decide not to call the delegate continuation, the navigation operation will be in a pending state until it is replaced with a new navigation operation.



Using the navigation history ( the Navigation Journal )



The class NavigationContextprovides access to the region’s navigation service, which is responsible for coordinating the sequence of operations during navigation in the region. It provides access to the region in which you are navigating, and to the navigation log associated with that region. The navigation service implements the interface IRegionNavigationServiceshown below.



 public interface IRegionNavigationService : INavigateAsync { IRegion Region { get; set; } IRegionNavigationJournal Journal { get; } event EventHandler<RegionNavigationEventArgs> Navigating; event EventHandler<RegionNavigationEventArgs> Navigated; event EventHandler<RegionNavigationFailedEventArgs> NavigationFailed; } 


Since this interface is inherited from the interface INavigateAsync, you can initiate navigation in the parent region by calling the method RequestNavigate. The event Navigatingis triggered when a navigation operation is initiated. The event Navigatedis triggered when navigation is completed in the region. NavigationFailedcalled when an error occurs during navigation.



The property Journalgives access to the navigation log associated with the region. The navigation log implements the interface IRegionNavigationJournalshown below.



 public interface IRegionNavigationJournal { bool CanGoBack { get; } bool CanGoForward { get; } IRegionNavigationJournalEntry CurrentEntry { get; } INavigateAsync NavigationTarget { get; set; } void Clear(); void GoBack(); void GoForward(); void RecordNavigation(IRegionNavigationJournalEntry entry); } 


You can get and save a link to the region’s navigation service in a view while navigating through a method call OnNavigatedTo. By default, Prism provides a simple stack log that allows you to navigate back and forth within a region.



You can use the navigation log to allow the user to navigate within the view itself. In the following example, the view model implements a command GoBackthat uses the navigation log of the parent region. Consequently, the view can display the Back button , which allows the user to move to the previous view in the region. Similarly, you can implement a command GoForwardto create a wizard-style workflow (wizard style workflow ).



 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 create your own journal for a region to implement a specific workflow for that region.



The note.

The navigation log can be used only in navigation operations in the region, which are coordinated by the navigation service of the region. If you use a detection or deployment technique to implement navigation in a region, the navigation log will not be updated during navigation and cannot be used to navigate backward or forward in a region.



Using WPF and Silverlight Navigation Frameworks



Region-based navigation in Prism has been designed to support a wide range of scenarios and problems that you may encounter when implementing navigation in loosely coupled modular applications that use the MVVM pattern and dependency injection container, such as Unity, or MEF. It was also designed to support the confirmation and cancellation of navigation, navigation to existing views, transfer of parameters during navigation, and navigation logging.



By supporting navigation for regions, Prism provides the ability to perform navigation within a variety of controls, and allows you to change the layout of the user interface of the application without disturbing the navigation structure. Pseudo-synchronous navigation is also supported, which allows for enhanced user interaction during navigation.



However, Prism navigation was not designed to replace the Silverlight navigation framework (introduced in Silverlight 3.0), or the WPF's navigation framework . Instead, navigation in the regions of Prism was designed to work with Silverlight and the WPF navigation framework .



Silverlight navigation frameworkprovides deep link support, browser integration, and navigation projection URIs. Navigation is possible inside the control Frame. Framemay, if necessary, display an address bar that allows the user to navigate backward or forward between the views displayed in Frame. The usual approach is to use the Silverlight navigation framework to implement high-level navigation in the application shell and use the Prism navigation for all other parts of the application. In this case, your application will support deep links and will be integrated with the browser’s browser and its address bar, and will also enjoy all the benefits of navigation in the Prism regions.



WPF navigation frameworknot as extensible as in Silverlight. Accordingly, support for dependency injection and the MVVM pattern is significantly hampered. It is also based on a control Framethat has similar functionality in terms of logging and navigation UI. You can use the WPF navigation framework with Prism navigation, although implementing navigation using only Prism regions may be a simpler and more flexible solution.



The sequence of navigation in the region



The following illustration gives an overview of the sequence of operations during navigation. It is provided as a reference so you can see how the various elements of the Prism navigation interact with each other during the navigation request.



The sequence of navigation in the region



Additional Information



For more information about the Visual State Manager, see the "VisualStateManager Class" on MSDN: http://msdn.microsoft.com/en-us/library/cc626338 ( v = VS.95 ).aspx .



For more information about using Microsoft Expression Blend behaviors, see "Working with built-in behaviors" on MSDN: http://msdn.microsoft.com/en-us/library/ff724013(v=Expression.40).aspx .



For more information about creating Microsoft Expression Blend behaviors, see "Creating Custom Behaviors" on MSDN: http://msdn.microsoft.com/en-us/library/ff724708(v=Expression.40).aspx .



For more information on the Silverlight Navigation Framework, see “Navigation Overview” on MSDN: http://msdn.microsoft.com/en-us/library/cc838245(VS.95).aspx .



For more information on integrating Silverlight's Navigation Framework with Prism, see “Integrating Prism v4 Region Navigation with Silverlight Frame Navigation” on Karl Schifflett's blog: http://blogs.msdn.com/b/kashiffl/archive/2010/10/05 /integrating-prism-v4-region-navigation-with-silverlight-frame-navigation.aspx .

Source: https://habr.com/ru/post/182052/



All Articles