Prism 4 文档 ---第8章 导航

    作为同用户具有丰富的交互的客户端应用程序,它的用户界面(UI)将会持续不断的更新来反映用户工作的当前的任务和数据。用户界面可以进行一段时间相当大的变化作为用户交互的应用程序中完成各种任务。通过该应用程序协调这些用户界面的变化的过程通常被称为导航。
    经常,导航意味着某些控件将会从UI中移除,其他的控件将会被添加到UI中。在另外的情况下,导航也意味着一个或多个存在的控件的可视化状态被更新。---例如,一些控件可能被简单的隐藏或收缩而另外的一些控件被显示后展开。类似的,导航可能意味着一个控件展示的绑定的数据被更新来反映应用程序的当前状态---例如,在一个主-细 场景中,细节中展示的数据将会基于当先主视图中选中项来更新。所有的这些场景都可以认为是导航因为用户界面被更新了来翻译用户的当前的任务和应用程序的当前状态。
    应用程序中的导航可以是来自UI中的用户交互或者来自应用程序本身中内部逻辑驱动的状态变化的结果。在一些情况下,导航可能会环形非常简单的没有应用程序逻辑的UI更新。在另外的一些情况下,应用程序可能会实现复杂的逻辑来以编程的方式控制导航来却好一些确定的业务逻辑被遵循---例如,应用程序可能无法允许用户导航离开一定的形式在未确认输入的数据是否正确。
    实现在WPF或Silverlight应用程序的所需导航性能往往是相对简单的,因为它们都提供了用于导航的直接支持。然而,在一个使用了MVVM模式或者一个使用松耦合的模块组合的应用程序中实现导航将会非常的负责。Prism提供了这种情况下实现导航的指导。
Prism 中的导航
    导航被应用程序协调用户交互的结果或者应用程序内部状态的改变导致的UI的变化的过程。
    UI的更新可以通过充应用程序的可视树中添加或者移除元素来实现。或者通过应用可视树中存在的元素的状态的变化来实现。WPF和Silverlight是非常灵活的平台,并且它往往是可能实现使用下面两种方法中的特定导航方案。然而,最适合你的应用程序的方法取决于多方法的因素。
    在前面描述中Prism区分了两种导航方式的区别。通过改变可视树中已存在的控件的状态来实现导航的方式称为基于状态的导航。通过往可视树中添加或者移除元素的方式来是实现导航的方式称为基于视图的导航。Prism提供了基于两种方式实现导航的指导,注意应用程序使用MVVM模式来区分UI与展现逻辑与数据(封装在视图模型中)的情况。

基于状态导航

    在基于状态的导航中,在UI中展现更新的视图的更新方式要么是通过视图模型中状态的变化,要么是通过用户与视图本身的交互。在这种导航的方式中,不是将一个视图替换为另一个视图,而是视图的状态发生了变化。基于视图的状态是如何变化的,UI的更新使得用户感觉像是导航。
    这种方式的导航适用于以下几种情况:
  • 视图需要在不同的方式或者格式中展现相同的数据或功能
  • 视图需要根据其视图模型中的底层状态来改变布局或者样式
  • 视图需要启动用户和视图内容的模态或者非模态的交互
    这种导航方式不适用于UI还没有为用户呈现不同的数据或者用户还没有指定一个不同的任务的情况。在这些情况下,最好的实现是分离视图(视图模型)来重新呈现数据或这任务,然后在她们之间使用在本章后面将会讲到的基于视图的导航。类似地,这种导航方式不适用于要求在导航中实现一系列的过于复杂的UI状态变化因为视图的定义可能会变得很大而且维护起来很困难。在这种情况下,最好通过使用基于视图的导航来实现单独的视图之间的导航。
    接下来的几节将会描述适用基于状态的导航的典型的场景。每一个这样的场景中都在State-Based Navigation QuickStart有涉及,它实现了一个可使他们管理和交流它们的联系的即时消息方式的应用程序。

以不同的格式或者样式展示数据

    你的应用程序可能经常需要为用户展现相同的数据,但是实在不同的格式或样式中。在这种情况下,你可以使用一个基于状态的导航在视图中来在不同的样式的视图中间进行切换,可能在她们之间使用动画变换。例如,State-Based Navigation QuickStart允许用户选择他们的关系被如何展示---作为一个简单的文本列表或者作为一个图标。用户可以通过点击List按钮或者Avatarts按钮来在这些可视化的展现中切换。视图提供了两个重现之间的动画变化,如下插图所示。
    因为视图展现的是相同的数据,但是在不同的可视化展现中,所以视图模型在重现之间的导航就不要被纠缠在其中。在这种情况下,导航被视图本身整个的处理了。这种方式为UI设计者提供了很大的灵活性来设计一个引人注目的用户体验而不需要改变应用程序的代码。
    Blend 行为提供了一种很好的方式来实现视图的这种导航方式。

State-Based Navigation QuickStart应用程序使用了Blend的DataStateBehavior 数据绑定到一个复选按钮来切换使用了可视化状态管理器定义的两中可视化状态,一个按钮来显示内容为一个列表,一个按钮显示内容为图标。

XAML
<DataStateBehavior
           Binding="{Binding IsChecked, ElementName= ShowAsListButton}"
           TrueState="ShowAsList" FalseState="ShowAsIcons"/>
    当用户点击Contacts或者Acatar复选按钮是,使状态在ShowAslist可视化状态和ShowAsIcons可视化状态之间切换。这些状态之间的反转变换动画也是使用可视化状态管理器定义的。
   这种方式的导航的另一个例子被State-Based Navigation QuickStart应用程序展示在当用户切换当前选中内容项的详细视图时。下面的插图展示了这个例子。
    再次,这里可以使用Blend DataStateBehavior实现起来非常简单。然而,这次它绑定到了视图模型中使用反转变换动画切换ShowDetails和ShowContracts可视化状态的的ShowDetails属性。

反映应用程序状态

    类似地,应用程序的视图有时候需要基于应用程序的通过视图模型中的一个属性来轮换表现的内部状态的变化来改变它的布局和样式。这样的场景的的一个例子是在State-Based Navigation QuickStart中展示的在图标视图模型类中使用一个ConnectionStatus属性来呈现用户的连接状态。当用户的连接状态变化是,视图被通知后允许视图来展现当前合适的连接状态。如下图所示。
    为了实现这点,视图定义了一个DataStateBehavior数据绑定到视图模型的ConnectionStatus属性来在合适的视图状态间切换。
XAML
<DataStateBehavior Binding="{Binding ConnectionStatus}" 
            TrueState="Available" FalseState="Unavailable"/>
    注意连接状态可以被用户铜鼓UI或者被应用程序根据一些内部逻辑或者状态改变。例如,应用程序可能会在用户没有在一个确定时间内同界面进行交互或者当用户的日历显示他(她)正在开会时转到”不可用“状态。State-Based Navigation QuickStart通过在使用一个计时器随机的切换连接状态模拟了这种场景。当连接状态变化了,视图模型中的属性就被更新了,并且视图被一个属性变化事件通知。UI然后被更新来反映当前的连接状态。
    所有前面的例子中包括了在视图中定义可视化状态和作为用户同视图交互的结果或者通过在视图模型中定义的属性的变化来切换这些状态。这种方式允许UI设计者来实现导航就像在视图中实现可视化行为二部要求视图被替换或者要求任何应用程序代码的改变。这种方式适用于当视图被要求在不同的形式或布局中呈现相同的数据。它不适用于用户用于展现不同的数据或者应用程序功能或者导航到应用程序的不同部分的情况。
同用户交互
    经常,应用程序将需要同用户在一种受限的方式下进行交互。在这些情况下,经常同用户在当前视图的上下文中进行交互更合适,而不是导航到一个新的视图中。例如,在State-Based Navigation QuickStart中,用户可以通过点击SendMessage按钮来往会话中发送一条消息。视图然后显示一个允许用户将消息分类的窗口,如下图所示。由于同用户的交互是受限的并且在逻辑上发生在父视图的上下文中,它将非常容易的实现为基于状态的导航。
    为了实现这个行为,State-Based Navigation QuickStart实现了一个SendMessage命令,这个命令绑定到了SendMessage按钮。当这个命令被唤醒时,视图模型同视图进行交互展现一个弹出式的创建,这是通过使用第五章"Implementing the MVVM Pattern."中的请求交互模式达到的。
    下面的代码实例显示了

State-Based Navigation QuickStart应用程序中的视图是如何对视图模型中的SendMessageRequest交互请求对象应答的。当收到请求事件时,

SendMessageChildWindow作为一个弹出窗口显示出来。

XAML
<prism:InteractionRequestTrigger SourceObject="{Binding SendMessageRequest}">
    <prism:PopupChildWindowAction>
        <prism:PopupChildWindowAction.ChildWindow>
            <vs:SendMessageChildWindow />
        </prism:PopupChildWindowAction.ChildWindow>
    </prism:PopupChildWindowAction>
</prism:InteractionRequestTrigger>

 基于导航的视图

    尽管基于状态的导航对于前面描述的场景中比较有用,应用程序中的导航经常会发生在UI中使用一个视图替换另一个视图的情况。在Prism中,这种方式的导航被称为基于视图的导航。
    根据应用程序的需求,处理过程可能会相当的负责并且需要细心的协调。以下是实现基于视图的导航时需要解决的常见的挑战:
  • 导航的目标---容器或者可以用来添加或者移除的视图的承载控件---当往其中添加视图或者从其中移除视图时处理导航的方式将会不同,或者它们可以在不同的方式可视化的表现导航。在许多情况下,导航的目标将会是一个简单的Frame或者ContentControl,并且被导航的视图将会在这些控件中简单的展示。然而,有许多的场景是导航操作的目标是不同类型的容器控件,例如一个TabControl或者一个ListBox控件。在这些情况下,导航可能会请求激活或者选择一个已存在的视图或者通过一种指定的方式添加一个新的视图。
  • 应用程序将也经常必须去定义视图是如何被导航,以进行识别。例如,在一个Web应用程序中,要导航到的页面经常直接的通过一个URI来识别。在一个客户端应用程序中,视图可以通过类型名称,资源位置,或者许多不同的方式进行识别。此为,在一个由松耦合的模块组合起来的组合的应用程序中,视图将经常被定义在单独的模块中。单独的视图将需要在一种不需要引入模块间紧耦合和依赖关系的方式中识别。
  • 在视图被识别之后,新视图实例化和初始化的过程必须仔细的协调。这时候使用MVVM的重要性就显现出来了。在这种情况下,视图和视图模型需要被实例化并且通过导航期间视图的数据上下文来进行关联。在当应用程序使用了一个依赖注入容器的情况下,例如Unity或者MEF,视图和/或者视图模型(以及其他依赖类)需要使用以中特殊的构造机制来达到实例化。
  • MVVM模式提供了应用程序UI和它的表现及业务逻辑之间的分离。然而,应用程序的导航行为将经常跨越应用程序的UI和展现逻辑部分。用户将会经常从视图启动导航,并且作为导航的结果视图将会被更新。但是导航也经常需要发起或协调同视图模型。清晰的分离应用程序的视图和视图模型之间的导航行为的能力对于考虑的影响非常重要。
  • 应用程序也经常需要往视图中传递参数或者内容以使得视图可以初始化属性。例如,如果用户导航到一个视图来更新一个指定消费者的详细信息,消费这的ID或者数据就需要被传递到视图中以使得它可以显示正确的信息。
  • 许多应用程序也必须小心的协调导航来保证确定的业务规则被遵守。例如,用户在离开一个视图时需要被提示以使得他们可以纠正任何无效的数据或者提示提交或者放弃它们在视图中做出的改变。这个过程要求在前一个视图和新视图之间仔细的协调。
  • 最后,许多现代的应用程序允许用于可以很容易的导航到后退(或者前进)到之前展示的视图。类似地,一些应用程序通过使用一列的允许用户通过导航进行前进或者后退,随着导航的导向添加或者更新数据,在完成任务之前一次性提交所有的改的视图或者窗体来实现它们的工作流程。这些场景需要一种日志(或者历史)机制以使得导航的队列可以被存储,回放或者预定义。
    Prism提供了通过扩展Prism 区域机制来支持导航的方式为这些变化提供支持和指导。接下来的几节提供了Prism区域的简单介绍以及描述了它们是如何被扩展来只是基于视图的导航的。

Prism 区域概述

    Prism 区域被定义通过允许应用程序的整体UI在一种松耦合的方式构建来支持组合应用程序的发展(就是指,由多个模块组合而成的应用程序)。区域允许视图被定义在一个模块中,在不要求模块必须确切的指导应用程序的整体UI结构的情况下在应用程序的UI中进行展示。它们使得应用程序的UI的布局可以轻松的改变,因此,允许UI设计者来为应用程序选择最合适的UI设计和布局而不需要在模块自身进行变换。
    Prism 区域实际上命名了一个视图可以被展示的占位符。应用程序UI中的任何控件可以通过简单的添加一个RegionName附加属性来声明,如下所示。
XAML
<ContentControl prism:RegionManager.RegionName="MainRegion" ... />
    每一个作为具体区域的控件,Prism创建了一个Region对象来展现区域以及一个管理具体控件中视图的位置和激活状态的RegionAdapter对象。Prism类库提供了WPF和Silverlight中最常用的控件的关于RegionAdaper的实现。你可以创建一个自定义的Regionadaper来支持另外的控件或者当你需要定义一个自定义行为的时候。RegionManager类提供了在应用程序中访问Region对象的方法。,
    在许多情况下,区域控件是一个简单的控件,例如一个可以一次展示一个视图的ContentCongtrol。在其他的情况下,Region控件将会是一个可以同时展示多个视图的控件,例如TabControl或一个ListBox控件。
    区域适配器管理者关联到区域的视图的集合。根据区域定义的布局策略一个或者多个视图可以在区域展示。视图被分配一个名称来在后面用于检索视图。区域适配器管理者区域中视图的活动状态。激活的视图是被选中的视图或者最前面的视图---例如,在一个TabControl中,激活的视图在一个选中的tab中显示;在一个ContentControl中,激活的视图是当前展示为控件内容的视图。
    注意:
    视图的激活状态是需要在导航期间重点考虑的。经常,你讲想要激活的视图参与导航以使得它可以在用户从视图中导航离开时保存数据,或者使得它可以确认或者取消导航操作。
    Prism的之前的版本允许视图在区域中以两种方式展示。第一种,叫做视图注入,允许视图通过编程的方式展示在区域中。这种方式用于那些根据应用程序的展现逻辑,区域中展现的视图会经常的变化的动态的内容。
    视图注入在Region类中的Add方法被支持。下面的代码示例展示你如何通过RegionManager类来获取一个Region对象的引用并通过编程来往其中添加一个视图。在这个例子中,视图是通过依赖注入容器创建的。
IRegionManager regionManager = ...;
IRegion mainRegion = regionManager.Regions["MainRegion"];
InboxView view = this.container.Resolve<InboxView>();
mainRegion.Add(view);
    第二种,叫做视图发现,允许一个模块通过区域名称将视图注册到区域中。无论何时指定名称的区域展示时,指定的视图的实例将会自动的创建和在区域中展示。这种方式对于相关的展示到你个区域的视图不会改变的静态内容非常有用。
    视图发现通过RegionManager类的RegisterViewWithRegion方法被支持。这个方法允许你去指定一个在命名的区域显示的时候会调用的回调方法。下面的代码实例展示了你可以在主区域第一次显示时创建一个视图(通过依赖注入容器)。
C#
IRegionManager regionManager = ...;
regionManager.RegisterViewWithRegion("MainRegion", () =>
                   container.Resolve<InboxView>());
    关于Prism区域支持的详细内容以及区域如何使用视图注入和视图发现来组成应用程序界面的信息,请查看第七章"Composing the User Interface."。本章接下来的几节描述了区域如何被扩展来支持基于视图的导航,以及如何解决前面描述的各种挑战。

区域导航基础

    视图注入和视图发现两种形式都可以被认为是有限的形式的导航。视图注入是一种明确的,计划性的导航,视图发现是一种隐式的或者延迟的导航形式。然而,在Prism 4.0中,区域已经被扩展为支持更一般概念上的导航,基于URI和一个可以扩展的导航机制。
    区域中导航意味着在区域中展现一个新的视图。被展示的视图通过一个URI来区分。默认情况下,指视图创建时的名称。你可以使用INaviagetAsync接口中定义的RequestNavigate方法来计划性的启动导航。
    注意:
    尽管它的名称(INaviagetAsync),当时INaviagetAsync接口并没有呈现一个在后头单独的线程中执行的异步导航。而是,INaviagetAsync接口表现了伪异步执行导航的能力。RequestNavigate方法可能在导航完成后紧接着返回一个同步的操作,或者它可能分会一个导航操作尽管它仍然在挂起状态,就像用户需要确认导航的情况下。通过允许你指定在导航期间的回调以及继续,Prism 提供了在不用请求在一个复杂的后台线程的情况下在这种场景中可用的机制。
    INaviagetAsync接口被Region类实现,允许你在Region中启动导航。
C#
IRegion mainRegion = ...;
mainRegion.RequestNavigate(new Uri("InboxView", UriKind.Relative));
    你也可以调用RegionManager的RequestNavigate方法,它允许你指定需要导航到的区域的名称。这个方便的方法获取一个指定区域的引用然后调用RequestNavigate方法,如下所示。
C#
IRegionManager regionManager = ...;
regionManager.RequestNavigate("MainRegion",
                               new Uri("InboxView", UriKind.Relative));
默认情况下,导航URI指定需要在区域中创建和显示的视图的名称。这个名称用于通过一个依赖注入容器解析视图类型。例如Unity或者MEF。例如使用Unity你可以注册一个视图关联一个名称,如下所示。
C#
container.RegisterType<Object,InboxView>("InboxView");
注意:
    当使用容器创建视图时,它会解析为一个Object类型。前面的实例代码中将指定的视图类型映射为Object类型并且分配了指定的名称作为映射名称。容器然后使用映射名称来接卸目标对象,这可以导致指定视图类型被实例化。
    使用MEF,你可以使用指定名称简单的导出视图类型。
C#
[Export("InboxView")]
public partial class InboxView : UserControl
在导航期间,通过容器或者MEF指定的视图被实例化,同时它的相应的视图模型和其他的依赖服务或者组件也被实例化。在视图被实例化之后,它被添加到指定的区域中并被激活(激活将会在后面章节中详细描述)
    注意:
    前面的距离描述了视图优先导航,在那URI指视图类型的名称,作为它的导出的或者在容器中注册的。在视图优先的导航中,被依赖的视图模型被作为视图的以来向而被创建。一个可选的方式就是使用模型优先的视图导航,在那里导航到URI指视图模型的名称,作为它的导出或者在容器中注册的。模型有限的视图导航在当视图被定义为一个数据模板,或者当你想要导航到定义在视图依赖的框架的时候非常有用。
    RequestNavigate方法也允许你指定在导航结束时调用的一个回调方法或一个委托。
C#
private void SelectedEmployeeChanged(object sender, EventArgs e)
{
    ...
    regionManager.RequestNavigate(RegionNames.TabRegion,
                     "EmployeeDetails", NavigationCompleted);
}
private void NavigationCompleted(NavigationResult result)
{
    ...
}

    NavigationResult类定义了提供关于导航操作信息的属性。Result属性表明了导航是否成功。如果导航失败,Error属性提供了一个导航期间抛出的异常的引用。Context属性提供了方法导航URI和它包含的任何参数及细条导航操作的导航服务的引用的访问方式。

View和ViewModel 参与导航

    经常的,应用程序中的视图和视图模型将想要参与导航。INaviagtionAware接口使这点称为可能。你可以在视图或者(更常见)视图模型中实现这个接口。通过是实现这个接口,视图或者视图模型可以选择参与到导航过程中。
    注意:
    在接下来的描述中,虽然在导航时调用这个接口使得视图之间会建立起引用关系,应该注意INaviagtionAware接口将会被调用取决于视图或者视图模型是否是实现了这个接口。
    在导航期间,Prism检查视图是否实现了INaviagtionAware接口;如果它实现了,导航期间将会调用请求方法。Prism 也检查设置到视图的DataContext对象是否是实现了INaviagtionAware接口,如果实现了,在导航期间也会调用请求的方法。
    这个接口使得视图或者视图模型参与到导航操作中。INaviagtionAware接口定义了三个方法。
C#
public interface INavigationAware
{
    bool IsNavigationTarget(NavigationContext navigationContext);
    void OnNavigatedTo(NavigationContext navigationContext);
    void OnNavigatedFrom(NavigationContext navigationContext);
}
    IsNavigationTarget方法允许一个现存的(展示的)视图或者视图模型来表明是否它能够处理导航请求。这在重用一个现存的视图来处理导航操作或者当导航到一个已存在的视图时的情况中很有用。例如,一个展示的消费者信息可以被更新来显示不同消费者的信息。关于使用这个方法的更多信息,请看本章中后面的"Navigating to Existing Views,"章节。

OnNavigatedFromOnNavigatedTo方法在导航期间被调用。如果区域中当前活动的视图(或者视图模型)实现了这个接口,

    在导航发生之前OnNavigatedFrom方法将被调用。OnNavigatedFrom方法允许前一个视图保存任何状态或者为它被置为非活动状态或者从UI中移除做准备,例如,保存用户对Web 服务或者数据多的改变。
    如果新创建的视图(或者视图模型)实现了这个接口,它的OnNavigatedTo方法将会在导航完成之后被调用。OnNavigatedTo方法方法允许新创建的视图初始化自身,可能使用在导航URI中穿点的任何参数。更多信息,请参考下一节"Passing Parameters During Navigation."
    在新视图被创建后,初始化,以及添加到了目标区域,他然后将会称为活动的视图,前一个视图将会变为非活动状态。有些时候你将想要把非活动的视图从区域中移除。Prism提供了IRegionMemberLifetime接口,这个接口允许你通过你指定这些非活跃的视图是否需要从区域中移除还是仅仅是标记物非活动状态来控制视图的生命周期。
public class EmployeeDetailsViewModel : IRegionMemberLifetime
{
    public bool KeepAlive
    {
        get { return true; }
    }
}
    IRegionMemberLifetime接口只定义了一个只读属性,KeepAlive。如果这个属性返回false,视图将在它变为非活动状态时从区域中被移除。由于区域不在保持一个对这个视图的引用,它可以被垃圾回收机制回收(除非应用程序中其他的组合还维护这对它的引用)。你可以在你的视图或者视图模型中实现这个接口。虽然IRegionMemberLifetime接口主要的义务是允许你在区域中视图的活动和非活动之间管理视图的声明周期,KeepAlive属性仍然在新视图在目标区域中激活之后被考虑。
    注意:
    可以展现多个视图的区域,例如这些使用ItemsControl或者TabControl的区域,将会显示非活动和活动的视图。将非活动的视图从这些区域中移除将会导致这些视图从UI上被移除。
导航期间传递参数
    为了在应用程序中实现要求的导航行为,你将经常需要在导航请求期间去指定额外的数据而不仅仅是目标视图的名称。

NavigationContext 对象提供了获取导航URI的方法,以及获取任何在其中指定的参数。你可以在IsNavigationTargetOnNavigatedFrom, 和OnNavigatedTo 方法中访问.NavigationContext 。

    Prism 提供了UriQuery 类来帮助指定和检索导航参数。你可以使用这个类在启动导航之前网导航URI中添加导航参数,也可以在导航期间获取单独的参数。UriQuery 类维护这一个代表这每一个参数的名称-值的集合。

    下面的示例代码展示如果往UriQuery 实例中添加单独的参数以使得它可以被追加到导航URI中。

 

C#
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));
}

    你可以使用NavigationContext 对象的parameters属性来检索导航参数。这个属性返回了一个UriQuery 类的实例,这个实例提供了一个索引属性来使得很容易的获取不同的参数

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

 导航到存在的Views

    经常地,在导航中复用,更新,激活应用程序视图更合适,而不是替换为一个新的视图。这在你正导航到相同类型的视图但是需要为用户展示不同的信息或者状态,或者当已经在UI中已经可用的合适的视图需要被激活(即,选中或者置顶)时是非常常见的情况。
    第一种场景的一个例子,想像一下你的应用程序允许用户使用EditCustomer视图编辑客户记录,并且用户正在使用这个视图编辑ID为123的客户信息,如果客户决定去编辑ID为456的客户信息。用户可以见到的导航到EditerCustomer视图并传入新的客户ID。然后EditerCustomer视图可以检索新客户的数据并更新想要的UI。
    第二种场景的例子,应用程序允许用户能在同一时间编辑多个客户的信息。在这种情况下,应用程序在Tab控件中展示多个EditCustomer视图实例---例如,一个客户的ID是123,另一个客户的ID是456。当用户导航到EditCustomer视图并传递客户ID456时,相应的视图需要被激活(即,想要的tab需要被选中)。如果用户导航的EditCustomer视图并传递客户ID789,一个新的实例将会被创建并展示在Tab控件中。
    导航到一个已存在的视图的能力由于各种原因都很有用,经常更新一个已存在的视图实例比起替换为一个新的相同类型的实例跟高效。类似地,激活一个已存在的视图比起复制一个视图对于用户体验来说更方便。另外,无缝地处理这些情况,而不需要太多的自定义代码的能力意味着应用程序更易于开发和维护。
    Prism 使用INavigationAware 接口的IsNavigationTarget 方法来支持前面描述的两种场景。这个方法在区域中所有的相同类型作为导航目标视图中导航是被调用。在前面的例子中国,目标视图类型是EditCustomer视图。所以在区域中所有存在于EditCustomer视图的实例中的IsNavigationTarget 方法的将会被调用。Prism从视图的假定的目标类型的简称的URI中决定目标类型。
    注意:
    为了使Prism来确定目标视图的类型,导航URI中的视图明天应该与实际目标类型的名称简称保持一致。例如,如果你的视图通过MyApp.Views.EmployeeDetailsView类实现,在导航URI中指定的视图名称应该为EmployeeDetailsView。这是Prism提供的默认的行为。你可以通过实现一个自定义的内容加载器类来自定义这个行为;你可以通过实现IRegionNavigationContentLoader接口或者从RegionVavigationContentLoader类派生来做这件事情。
    IsNavigationTaget方法的实现中可以使用NavigationContext参数来决定是否它可以处理导航请求。NavigationContext对象提供了访问导航URI和导航参数的方法。在前面的例子中,在EditCustomer视图模型中的这个方法的实现中比较了当前的客户ID与在导航请求中指定的客户ID,如果它们一直,它将返回true;
C#
public bool IsNavigationTarget(NavigationContext navigationContext)
{
    string id = navigationContext.Parameters["ID"];
    return _currentCustomer.Id.Equals(id);
}

    如果 IsNavigationTarget 方法总是返回true,不管导航参数,视图实例就可以一直被复用。这使得你可以确保只有你一个参与导航的类型视图在区域中展示。

确认或者取消导航
    你会经常发现你讲需要在导航操作过程中同用户进行交互,以使得用户可以确认或取消导航。在许多应用程序中,例如,用户可能尝试在获取或者编辑数据的期间进行导航。在这些情况下,你可能想要询问用户是否他/她想要在继续从本页面导航离开之前保存或者取消已经进行的修改的数据,或者用户是否想要取消导航操作。Prism使用IConfirmNaviagationRequest接口支持这些场景。
    IConfirmNaviagationRequest接口派生自INavigationAware接口并且添加了ConfirmNavigationRequest方法。你可以通过在你的视图或者视图模型类中实现这个接口,你可以允许它们以允许它们和用户进行交互来使得用户可以确认或取消导航的方式参加进导航队列中。你将经常使用一个InteractionRequst对戏那个,在第6章"Advanced MVVM Scenarios,"的"Using Interaction Request Objects"一节中展示一个确认的弹出窗口中描述的。
    注意:
    ConfirmNavigationRequest方法在活动的视图或者视图模型模型中被调用,与前面描述的OnNavigatedFrom方法类似。
    ConfirmNavigationRequest 方法提供了两个参数,一个是前面描述的当前导航上下文的引用和一个你可以在导航结束时继续执行回调方法。由于这个原因,这个回调被作为一个继续执行的回调。你可以存储这个继续执行的回调方法的引用那样应用程序可以在同用户完成交互之后调用这个回调方法。应用程序可以通过一个InteractionRequest对象来同用户进行交互,你可以选择调用这个继续执行的回调方法来从交互请求中国回调。下图展示了整个过程。
    下面的步骤总结了使用InteractionRequst对象导航的确认导航的过程:
  1. 通过一个RequestNaviagate调用发起导航操作。
  2. 如果视图或者视图模型实现了IConfirmNavigation,调用ConfirmNavigationRequest。
  3. 视图模型引发交互请求事件。
  4. 视图展示弹出式确认窗口并等待用户应答。
  5. 当用户关闭弹出窗口时唤醒交互请求回调。
  6. 继续执行回调被唤醒来继续或者取消这个挂起的导航操作。
  7. 导航操作完成或者被取消。
    为了阐明这点,请看View-Switching Navigation Quick Start。这个应用程序提供了供用户通过ComposeEmaiView和ComposeEmailViewModel类组合成email的能力。视图模型类实现了IConfirmNavigation接口。如果用户启动导航,比如点击了Calendar按钮,当他们在组成一个email时,ConfirmNavigationRequest方法将会被调用以使得视图模型可以被用户确认。为了支持这点,视图模型类定义了一个交互请求,如下所示。
C#
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; }
    }
}
    在ComposeEmailView类中,定义了一个交互请求触发器,并且数据绑定到了视图模型的ConfirmExitInteractionRequest属性上。当发起了这个交互请求,一个简单的弹出窗口将会展示给用户。
XAML
<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>
...
    当在一个email正在被组成的时候如果用户尝试去开始导航,那么ComposeEmailViewModel类的ConfirmNavigationRequest方法将会被调用。这个方法的实现将会唤醒之前定义的交互请求以使得用户可以确认或者取消导航操作。
C#
void IConfirmNavigationRequest.ConfirmNavigationRequest(
          NavigationContext navigationContext, Action<bool> continuationCallback)
{
    this.confirmExitInteractionRequest.Raise(
              new Confirmation {Content = "...", Title = "..."},
              c => {continuationCallback(c.Confirmed);});
}
    当用户点击的弹出窗口中的确认或者取消操作时交互请求的回调方法将会被调用。回调方法只是调用了几乎执行回调,传递了Confirmed标志的值,以及导致这个导航是继续还是被取消。
    注意:
    应该注意在交互请求事件之后引发的事件。ConfirmNavigationReques方法离开返回以使得用户可以继续界面的交互。当用户点击了弹出的窗口中的OK或者Cancel按钮时,就回调用交互请求方法中的回调,它反过来调用回调继续方法来完成导航操作。所有的方法都是在UI线程被调用。使用这个技术时,不要求后台线程。
    使用这个机制,你可以控制导航请求是被立即执行还是被延后,或是被用户或因一些其他的异步交互(例如,一个Web服务的请求结果)而挂起。为了使导航过程可用。你可以仅仅调用继续执行回调,传递true来表明它可以继续执行。类似,你也可以传递false来表明导航应该被取消。
void IConfirmNavigationRequest.ConfirmNavigationRequest(
          NavigationContext navigationContext, Action<bool> continuationCallback)
{
    continuationCallback(true);
}
    如果你想要延迟导航,你可以存储继续执行回调的一个引用,然后你可以在用户交互完成后调用这个引用。导航操作会一直被挂起指导你调用了这个继续执行回调。
    如果用户在同一时刻启动了另一个导航操作。这个导航请求将会被取消。在这种情况下,调用继续执行回调将会没有任何反应因为与它相关的导航请求已不是当前这个。类似地,如果你决定不去调用继续执行回调,这个导航操作将会被挂起指导它被替换为一个新的导航操作。
使用导航日志
    NavigationContext类提供了访问区域导航的服务,这个服务负责区域内的导航操作队列的协调。他提供了访问发生导航的区域的方法以及访问与区域相关联的导航日志的方法。区域导航服务实现了IRegionNavigationService接口,这个接口定义如下。
C#
public interface IRegionNavigationService : INavigateAsync
{
    IRegion Region {get; set;}
    IRegionNavigationJournal Journal {get;}
    event EventHandler<RegionNavigationEventArgs> Navigating;
    event EventHandler<RegionNavigationEventArgs> Navigated;
    event EventHandler<RegionNavigationFailedEventArgs> NavigationFailed;
}
    因为区域导航服务实现了INavigateAsync接口,你可以在父区域中通过调用它的RequestNavigate方法来启动导航。Navigating事件在导航操作初启动时被引发。Navigated事件在区域的导航结束时被引发。NavigationFailed在当导航期间遇到错误时被引发。
    Jorunal属性提供了获取与区域关联的导航日志的能力。导航日志实现了IRegionNavigationJournal接口,这个接口定义如下。
C#
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);
}
    你可以在导航期间通过调研弄个OnNavigatedTo方法在视图中获取或者存储一个区域导航服务的引用。默认情况下,Prism提供了一个简单的基于栈的日志来允许你在区域中向前或者向后导航。
    你额可用使用导航日志来允许用户在视图本身中导航。在下面的示例中,视图模型实现了在承载区域中使用导航日志的GoBack命令。因此,视图可以展示一个允许用户可以很容易的导航到区域中的前一个视图的Back按钮。类似,你也可以实现一个GoForward命令来实现一个向导式的工作流。
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;
    }
}
    如果你需要在一个区域中实现一个指定的工作流模式你也可以实现一个自定义的日志。
    注意:
    导航日志只能用于通过区域导航服务基于区域的导航操作。如果你在区域中使用视图发现或者视图注入来实现导航,导航日志将不会在导航期间被更新并且不能用于区域中的向前或者向后导航。
使用WPF和Silverlight导航框架
    Prism 区域导航被设计用于解决广泛的共同方案和当你在一个使用MVVM模式和依赖注入容器比如Unity,MEF,实现的松散耦合的,模块块化的应用程序中实现导航时可能遇到的的挑战。它也被设计用于支持确认和取消导航,导航至已存在的视图,导航参数和导航日志。
    通过在Prism区域中支持导航,它也支持广泛的布局控件的导航和支持在不影响导航结构的情况下改变应用程序布局的能力。它还支持提供用户导航期间丰富交互的伪同步导航。
    然而,Prism区域导航不是为了替换Silverlight导航框架或者WPF的导航框架玩儿设计的。而是,Prism 区域导航被设计被用于Silverlight和WPF导航框架的边到边(side-by-side)。
    Silverlight导航框架提供了对深层链接,浏览器集成,导航URI的映射的支持。导航放在在Frame控件中。Frame可以有选择的展示可以允许用户在Frame展示的视图之间前进或者后退的导航条。使用Silverlight导航框架来实现应用程序Shell的顶层的导航并且然后使用Rrism区域来实现应用程序的其他部分的导航的情况非常常见。在这种方式下,应用程序可以支持深层次的连接以及与浏览器日志及地址栏集成,但是它仍然需要Prism区域导航的优势。
    默认情况下,Silverlight导航框架不能直接支持用于MVVM模式或者用于依赖注入容器或MEF。然而,你可以实现一个自定义内容加载器---被Frame用于加载的控件的内容关联到指定的URI---可以实例化及初始化视图和与之关联的恰当的视图模型并展示在Frame中。你也可以实现一个自定义内容加载器来更全面的集成Silverlight框架导航和Prism区域导航机制。
    WPF导航框架不像Silverlight的导航框架那样可扩展;因此,它对于支持MVVM模式和依赖注入比较困难。它也是基于一个Frame控件提供了日志和导航界面方面的类似功能。你也可以与Prism区域导航一起使用WPF导航框架,虽然仅使用Prism区域来实现导航更容易和更灵活。
Region导航顺序
    下面的插图提供了导航操作期间操作队列的一个概况。它提供了一个Prism中各种各样的元素是如何在一个导航请求期间共同工作的参考。
更多信息
关于Prism中区域的更多信息, 请参考第7章, "Composing the User Interface."

关于MVVM模式和交互请求模式的更多信息, 请参考第5章, , "Implementing the MVVM Pattern"和第6章, "Advanced MVVM Scenarios."

关于Interaction Request 对象的更多信息, 请参考第6章 "Advanced MVVM Scenarios."的 "Using Interaction Request Objects" 

关于可视化状态管理的更多信息, 请参考MSDN上的 "VisualStateManager Class":
http://msdn.microsoft.com/en-us/library/cc626338(v=VS.95).aspx

关于Blend行为的更多信息, 请参考MSDN上的 "Working with built-in behaviors":
http://msdn.microsoft.com/en-us/library/ff724013(v=Expression.40).aspx

关于使用Blend创建自定义行为的更多信息, 请参考MSDN上的  "Creating Custom Behaviors": 
http://msdn.microsoft.com/en-us/library/ff724708(v=Expression.40).aspx

关于Silverlight导航框架的更多信息, 请参考MSDN上的 "Navigation Overview":
http://msdn.microsoft.com/en-us/library/cc838245(VS.95).aspx

关于Prism和Silverlight导航框架集成的更多信息, 请参考Karl Schifflet的博客"Integrating Prism v4 Region Navigation with Silverlight Frame Navigation" :
http://blogs.msdn.com/b/kashiffl/archive/2010/10/05/integrating-prism-v4-region-navigation-with-silverlight-frame-navigation.aspx

 

posted @ 2014-11-20 17:30  西夏  阅读(2382)  评论(0编辑  收藏  举报