8. Prism导航
1. 背景
导航是为了实现不同界面的切换,是一种组织系统功能的方式。提供两种导航,一是基于View切换,二是基于ViewModel状态。
2. 基于View切换的导航
最常用导航方式。在该种导航中首先需要定义一个域(Region),然后注册所有需要的View。通过切换不同视图,可以实现导航。下面具体阐述:
2.1 定义域
一般使用ContentControl,ItemControl以及二者的子类定义域,同时为域命名,代码如下:
<ContentControl Grid.Row="1" Grid.ColumnSpan="2"
prism:RegionManager.RegionName="ContentRegion"/>
2.2 注册View
我们需要在DI容器中注册需要的View,如果直接使用DI容器的注册方法,注册的View只能够作为普通类使用。Prism提供各种容器的扩展方法RegisterTypeForNavigation,使用此方法注册后就可以实现导航,以Unity容器为例:
_unityContainer.RegisterTypeForNavigation<CalendarView>();
2.3 导航
类RegionManager提供方法RequestNavigate用于导航,有两种方式进行导航。
2.3.1 URI导航
用户只需调用该方法就可以实现导航,如下:
_regionManager.RequestNavigate("ContentRegion", calendarViewUri);
第一个参数是目标域名称,第二参数calendarViewUri是一个Uri类型的对象,该对象名称是需要导向View的名称。对象实例化方法是:
private static Uri calendarViewUri = new Uri("CalendarView", UriKind.Relative);
导航时还可以向目标View传递参数,同时也可以设置回调函数。
2.3.2 名字导航
可以直接将View名称传入方法RequestNavigate,
_regionManager.RequestNavigate("ContentRegion", "CalendarView");
使用这种方法好处是你可以导航至任意模块的View,前提是:
- 已使用RegisterTypeForNavigation注册View;
- View名或其别名需要在所有模块中唯一。
2.4 传递数据
这里涉及两个问题:
- 当前view如何传入数据?
- 目标view如何解析数据?
对于问题一,RequestNavigate方法提供方法重载,允许我们传入数据。传入的数据类型是NavigationParameters,这是一个IEnumerable类型,使用add方法添加参数,key-value形式。示例如下:
using Prism.Regions;
private readonly IRegionManager _regionManager;
//...
NavigationParameters para = new NavigationParameters();
para.Add("name", "ZZL");
_regionManager.RequestNavigate("ContentRegion", "HelloView", para);
对于问题二,还需要继续细分,目标view如何感知传递来的数据,知道以后又如何解析?Prism框架提供接口INavigationAware,与View对应的ViewModel只需实现接口就可以自动感知。该接口包含三个方法:
IsNavigationTarget
函数签名:
bool IsNavigationTarget(NavigationContext navigationContext);
指定当前View是否是导航目标,返回值为TRUE则导航至该View实例。如果不是则返回FALSE,RegionManager创建目标View的新实例。
OnNavigatedFrom
函数签名:
void OnNavigatedFrom(NavigationContext navigationContext);
从当前View离开时调用。
OnNavigatedTo
函数签名:
void OnNavigatedTo(NavigationContext navigationContext);
导航至该View时调用。一般如果重新进入View需要刷新或重置页面时,逻辑均需写在该方法中。传入参数navigationContext有一个属性Parameters,类型为NavigationParameters。迭代读取该属性就可以解析数据。示例如下:
using Prism.Mvvm;
using Prism.Regions;
namespace ModuleA.ViewModels
{
public class ViewAViewModel : BindableBase, INavigationAware
{
private string _name;
public string Name
{
get { return _name; }
set
{
SetProperty(ref _name, value);
}
}
public ViewAViewModel()
{
}
public void OnNavigatedTo(NavigationContext navigationContext)
{
NavigationParameters p = navigationContext.Parameters;
foreach (var item in p)
{
if(item.Key == "name")
Name = (string)item.Value;
}
}
public bool IsNavigationTarget(NavigationContext navigationContext)
{
return true;
}
public void OnNavigatedFrom(NavigationContext navigationContext)
{
}
}
}
三个方法的调用顺序是OnNavigatedFrom,IsNavigationTarget,OnNavigatedTo。
2.5 确认和取消导航
对于重要情况,有时需要提供确认导航功能。接口INavigationAware虽提供数据传递功能,方法IsNavigationTarget标明是否是导航目标,但是不能取消导航,
Prism提供INavigationAware的扩展接口IConfirmNavigationRequest,该接口提供一个额外方法ConfirmNavigationRequest实现确认导航机制。
public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
{
bool result = true;
if (MessageBox.Show("Do you to navigate?", "Navigate?", MessageBoxButton.YesNo) == MessageBoxResult.No)
result = false;
continuationCallback(result);
}
2.6 用后即焚
默认情况下所有已导航View实例一直保存在Region中,直至程序结束。而有些View在软件运行时使用频率低,这时就希望如果不使用该View时就将View实例销毁。Prism提供接口IRegionMemberLifetime实现“用后即焚”,该接口提供方法KeepAlive属性,如果返回FALSE,当View实例不显示时,就将该View的实例从Region中移除。下次需要使用则重新实例化View。
3. 基于状态的导航
在此类导航中,UI的更新是基于ViewModel状态变化以及用户交互。此类导航适用于:
- View需要以不同样式或者格式显示同一数据
- View需要基于View Model状态改变样式或者布局
- View需要初始化有限的模态交互或非模态交互。
此类导航不适用于:
- UI需要为用户显示不同数据;
- UI需要执行不同任务。