之前有学过几种导航的实现方案,简单点的就是用TabControl;复杂就是给ContentControl绑定上一个Object类型的页面用户控件;再进阶一点就是使用容器装页面,ViewModel也一块注册;如果是使用了Prism,使用区域管理就变得非常简单了。
1、创建好的DryIoc空白项目中的MainWindow会有这样的一行
<ContentControl Grid.Row="1" prism:RegionManager.RegionName="ContentRegion" />
--这个时候就可以添加按钮,添加好Command,使用RequestNavigate()管理这里显示的内容
2、ViewModel中
public DelegateCommand<string> OpenViewCommand { get; private set; }
// 区域管理字段
private readonly IRegionManager _regionManager;
public MainWindowViewModel(IRegionManager regionManager)
{
OpenViewCommand = new DelegateCommand<string>(OpenView);
this._regionManager = regionManager; // 构造方法注入
}
private void OpenView(string obj)
{
_regionManager.Regions["ContentRegion"].RequestNavigate(obj);
}
-- obj就是注册的窗口名称
3、这些用户控件页面的调用是要注册的,在App.xaml.cs中注册
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<ViewA>();
containerRegistry.RegisterForNavigation<ViewB>();
containerRegistry.RegisterForNavigation<ViewC>();
}
-- 提示这个RegisterForNavigation是Prism.Wpf的拓展方法,记得要装好这个NuGet
跨模块的实现
解释:这里的跨模块指的是View位于不同的类库中(图中的ModuleA和ModuleB就是WPF的类库,Prism127是Prism.DryIoc的空项目),然后Prism127项目的写法跟上面没跨模块访问的差不多,都是实现了导航。
Prism127项目跟没跨模块的区别就是:App.xaml.cs文件中的RegisterTypes注册的用户控件页面变为重写方法ConfigureModuleCatalog中添加模块(AddModule)
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
}
// 跨模块注册(手动重写),添加模块
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
moduleCatalog.AddModule<ModuleAConfig>(); // 引入模块A
moduleCatalog.AddModule<ModuleBConfig>(); // 引入模块B
base.ConfigureModuleCatalog(moduleCatalog);
}
-- ModuleAConfig和MOduleBConfig是模块A和模块B的IModule类。Prism127项目中要添加两个WPF类库的引用
然后ModuleA和ModuleB两个类库也是需要添加上NuGet包Prism.DryIoc,View就是用户控件页面,ModuleAConfig就要实现IModule接口,在方法RegisterTypes中注册导航(这个方法跟没有跨模块导航时候的注册用户控件页面是一样的)
public class ModuleAConfig : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<ViewA>(); // 引入视图
}
}
也可以在外边把导航给注册了
public class MainModule : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
// 初始化的时候,添加一个组件到对应的区域
// 比如 左侧菜单栏
// 需要一个RegionManager
var regionManager = containerProvider.Resolve<IRegionManager>(); // 通过ioc获取regionManager对象
regionManager.RegisterViewWithRegion("LeftMenuTreeRegion", typeof(TreeMenuView)); // 向区域LeftMenuTreeRegion放入TreeMenuView
}
// 注册要放到Region的控件
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.Register<TreeMenuView>();
}
}
-- 同样是跨模块,App.xaml.cs的AddModule也是一样的。它自己在模块这边就用户控件放到区域中了
前端的使用可能是这样的
<!-- 放置菜单的区域 -->
<ContentControl Grid.Row="1" prism:RegionManager.RegionName="LeftMenuTreeRegion" />
对跨模块的实现的再次改进
改进的就是由原本App.xaml.cs中的ConfigureModuleCatalog添加模块变为CreateModuleCatalog实现
protected override IModuleCatalog CreateModuleCatalog()
{
return new DirectoryModuleCatalog() { ModulePath = @".\Modules" };
}
通过这种方法就不需要ModuleA和ModuleB类库的引用的,变为使用他们两个的dll,这里写的是bin/debug/net6.0-windows,这样就可以将AB两个类库装到Modules文件夹下了,".\"则表示运行的当前文件
自动绑定ViewModel
首先View和ViewModel的文件夹要放在正确的位置,然后在View中添加。用户控件和窗体都支持
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
--自动匹配是比较少用的,还是在注册的时候连带ViewModel一起注册的
View和ViewModel一起注册(这个是跨模块的注册):
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<ViewA, ViewAViewModel>();
}
导航的消息传递
比如点击按钮就显示某一个区域,就像文章开头时那样。怎么传递消息呢?
private void Open(string obj)
{
NavigationParameters keys = new NavigationParameters();
keys.Add("Title", "Hello!");
regionManager.Regions["ContentRegion"].RequestNavigate(obj, keys);
}
-- 创建了一个导航参数,请求区域导航的时候记得将这个参数传过去。
接收参数的窗口要接上INavigationAware接口
接口的实现方法OnNavigatedTo中获取参数:
public void OnNavigatedTo(NavigationContext navigationContext)
{
if (navigationContext.Parameters.ContainsKey("Title"))
{
Title = navigationContext.Parameters.GetValue<string>("Title");
}
}
-- 这个Title就是string类型,可以给View绑定。Title对应的消息就是Hello。
-- 同时INavigationAware接口中也有一个方法叫做IsNavigationTarget,如果返回Ture就是对这个页面重用,下次调用这个页面的时候就还是那一个,而不是重新创建一个。
-- 这个接口还有个方法就是OnNavigatedFrom方法,这个方法时切换请求其它区域的时候就会调用。
对接口IsNavigationTarget的再拓展:
有一个接口叫IConfirmNavigationRequest也是继承了接口IsNavigationTarget的,但是多了一个接口同名的方法,用来对是否导航进行验证。
public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
{
bool result = true;
// 点击否就不导航
if (MessageBox.Show("确认导航?", "温馨提示", MessageBoxButton.YesNo) == MessageBoxResult.No)
{
result = false;
}
// 这个就是判断给不给导航的
continuationCallback(result);
}
导航日志
导航日志可以记录导航的页面,方便返回
在ViewModel中定义区域导航
private IRegionNavigationJournal journal; // 导航日志
让一个按钮Command联系上导航返回
private void Back()
{
if (journal.CanGoBack)
journal.GoBack();
}
这个导航要实现是建立在每次导航请求都记录日志的基础上的,所以导航请求就要记录日志
private void Open(string obj)
{
NavigationParameters keys = new NavigationParameters();
keys.Add("Title", "Hello!");
regionManager.Regions["ContentRegion"].RequestNavigate(obj, callBack =>
{
if ((bool)callBack.Result)
{
journal = callBack.Context.NavigationService.Journal;
}
}, keys);
}