关于我们

质量为本、客户为根、勇于拼搏、务实创新

< 返回新闻公共列表

云南大王-.NET Core 3 WPF MVVM框架 Prism系列之导航系统

发布时间:2020-04-16 00:00:00
本文将介绍如何在.NET Core3环境下使用MVVM框架Prism基于区域Region的导航系统 在讲解Prism导航系统之前,我们先来看看一个例子,我在之前的demo项目创建一个登录界面: 我们看到这里是不是一开始想象到使用WPF带有的导航系统,通过Frame和Page进行页面跳转,然后通过导航日志的GoBack和GoForward实现后退和前进,其实这是通过使用Prism的导航框架实现的,下面我们来看看如何在Prism的MVVM模式下实现该功能 一.区域导航 我们在上一篇介绍了Prism的区域管理,而Prism的导航系统也是基于区域的,首先我们来看看如何在区域导航 1.注册区域 LoginWindow.xaml: 2.注册导航 App.cs: protected override void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.Register(); containerRegistry.Register(); containerRegistry.Register(); //注册全局命令 containerRegistry.RegisterSingleton(); containerRegistry.RegisterInstance(Container.Resolve()); //注册导航 containerRegistry.RegisterForNavigation(); containerRegistry.RegisterForNavigation(); } 3.区域导航 LoginWindowViewModel.cs: public class LoginWindowViewModel:BindableBase { private readonly IRegionManager _regionManager; private readonly IUserService _userService; private DelegateCommand _loginLoadingCommand; public DelegateCommand LoginLoadingCommand => _loginLoadingCommand ?? (_loginLoadingCommand = new DelegateCommand(ExecuteLoginLoadingCommand)); void ExecuteLoginLoadingCommand() { //在LoginContentRegion区域导航到LoginMainContent _regionManager.RequestNavigate(RegionNames.LoginContentRegion, "LoginMainContent"); Global.AllUsers = _userService.GetAllUsers(); } public LoginWindowViewModel(IRegionManager regionManager, IUserService userService) { _regionManager = regionManager; _userService = userService; } } LoginMainContentViewModel.cs: public class LoginMainContentViewModel : BindableBase { private readonly IRegionManager _regionManager; private DelegateCommand _createAccountCommand; public DelegateCommand CreateAccountCommand => _createAccountCommand ?? (_createAccountCommand = new DelegateCommand(ExecuteCreateAccountCommand)); //导航到CreateAccount void ExecuteCreateAccountCommand() { Navigate("CreateAccount"); } private void Navigate(string navigatePath) { if (navigatePath != null) _regionManager.RequestNavigate(RegionNames.LoginContentRegion, navigatePath); } public LoginMainContentViewModel(IRegionManager regionManager) { _regionManager = regionManager; } } 效果如下: 这里我们可以看到我们调用RegionMannager的RequestNavigate方法,其实这样看不能很好的说明是基于区域的做法,如果将换成下面的写法可能更好理解一点: //在LoginContentRegion区域导航到LoginMainContent _regionManager.RequestNavigate(RegionNames.LoginContentRegion, "LoginMainContent"); 换成 //在LoginContentRegion区域导航到LoginMainContent IRegion region = _regionManager.Regions[RegionNames.LoginContentRegion]; region.RequestNavigate("LoginMainContent"); 其实RegionMannager的RequestNavigate源码也是大概实现也是大概如此,就是去调Region的RequestNavigate的方法,而Region的导航是实现了一个INavigateAsync接口: public interface INavigateAsync { void RequestNavigate(Uri target, Action navigationCallback); void RequestNavigate(Uri target, Action navigationCallback, NavigationParameters navigationParameters); } 我们可以看到有RequestNavigate方法三个形参: target:表示将要导航的页面Uri navigationCallback:导航后的回调方法 navigationParameters:导航传递参数(下面会详解) 那么我们将上述加上回调方法: //在LoginContentRegion区域导航到LoginMainContent IRegion region = _regionManager.Regions[RegionNames.LoginContentRegion]; region.RequestNavigate("LoginMainContent", NavigationCompelted); private void NavigationCompelted(NavigationResult result) { if (result.Result==true) { MessageBox.Show("导航到LoginMainContent页面成功"); } else { MessageBox.Show("导航到LoginMainContent页面失败"); } } 效果如下: 二.View和ViewModel参与导航过程 1.INavigationAware 我们经常在两个页面之间导航需要处理一些逻辑,例如,LoginMainContent页面导航到CreateAccount页面时候,LoginMainContent退出页面的时刻要保存页面数据,导航到CreateAccount页面的时刻处理逻辑(例如获取从LoginMainContent页面的信息),Prism的导航系统通过一个INavigationAware接口: public interface INavigationAware : Object { Void OnNavigatedTo(NavigationContext navigationContext); Boolean IsNavigationTarget(NavigationContext navigationContext); Void OnNavigatedFrom(NavigationContext navigationContext); } OnNavigatedFrom:导航之前触发,一般用于保存该页面的数据 OnNavigatedTo:导航后目的页面触发,一般用于初始化或者接受上页面的传递参数 IsNavigationTarget:True则重用该View实例,Flase则每一次导航到该页面都会实例化一次 我们用代码来演示这三个方法: LoginMainContentViewModel.cs: public class LoginMainContentViewModel : BindableBase, INavigationAware { private readonly IRegionManager _regionManager; private DelegateCommand _createAccountCommand; public DelegateCommand CreateAccountCommand => _createAccountCommand ?? (_createAccountCommand = new DelegateCommand(ExecuteCreateAccountCommand)); void ExecuteCreateAccountCommand() { Navigate("CreateAccount"); } private void Navigate(string navigatePath) { if (navigatePath != null) _regionManager.RequestNavigate(RegionNames.LoginContentRegion, navigatePath); } public LoginMainContentViewModel(IRegionManager regionManager) { _regionManager = regionManager; } public bool IsNavigationTarget(NavigationContext navigationContext) { return true; } public void OnNavigatedFrom(NavigationContext navigationContext) { MessageBox.Show("退出了LoginMainContent"); } public void OnNavigatedTo(NavigationContext navigationContext) { MessageBox.Show("从CreateAccount导航到LoginMainContent"); } } CreateAccountViewModel.cs: public class CreateAccountViewModel : BindableBase,INavigationAware { private DelegateCommand _loginMainContentCommand; public DelegateCommand LoginMainContentCommand => _loginMainContentCommand ?? (_loginMainContentCommand = new DelegateCommand(ExecuteLoginMainContentCommand)); void ExecuteLoginMainContentCommand() { Navigate("LoginMainContent"); } public CreateAccountViewModel(IRegionManager regionManager) { _regionManager = regionManager; } private void Navigate(string navigatePath) { if (navigatePath != null) _regionManager.RequestNavigate(RegionNames.LoginContentRegion, navigatePath); } public bool IsNavigationTarget(NavigationContext navigationContext) { return true; } public void OnNavigatedFrom(NavigationContext navigationContext) { MessageBox.Show("退出了CreateAccount"); } public void OnNavigatedTo(NavigationContext navigationContext) { MessageBox.Show("从LoginMainContent导航到CreateAccount"); } } 效果如下: 修改IsNavigationTarget为false: public class LoginMainContentViewModel : BindableBase, INavigationAware { public bool IsNavigationTarget(NavigationContext navigationContext) { return false; } } public class CreateAccountViewModel : BindableBase,INavigationAware { public bool IsNavigationTarget(NavigationContext navigationContext) { return false; } } 效果如下: 我们会发现LoginMainContent和CreateAccount页面的数据不见了,这是因为第二次导航到页面的时候当IsNavigationTarget为false时,View将会重新实例化,导致ViewModel也重新加载,因此所有数据都清空了 2.IRegionMemberLifetime 同时,Prism还可以通过IRegionMemberLifetime接口的KeepAlive布尔属性控制区域的视图的生命周期,我们在上一篇关于区域管理器说到,当视图添加到区域时候,像ContentControl这种单独显示一个活动视图,可以通过Region的Activate和Deactivate方法激活和失效视图,像ItemsControl这种可以同时显示多个活动视图的,可以通过Region的Add和Remove方法控制增加活动视图和失效视图,而当视图的KeepAlive为false,Region的Activate另外一个视图时,则该视图的实例则会去除出区域,为什么我们不在区域管理器讲解该接口呢?因为当导航的时候,同样的是在触发了Region的Activate和Deactivate,当有IRegionMemberLifetime接口时则会触发Region的Add和Remove方法,这里可以去看下Prism的RegionMemberLifetimeBehavior源码 我们将LoginMainContentViewModel实现IRegionMemberLifetime接口,并且把KeepAlive设置为false,同样的将IsNavigationTarget设置为true LoginMainContentViewModel.cs: public class LoginMainContentViewModel : BindableBase, INavigationAware,IRegionMemberLifetime { public bool KeepAlive => false; private readonly IRegionManager _regionManager; private DelegateCommand _createAccountCommand; public DelegateCommand CreateAccountCommand => _createAccountCommand ?? (_createAccountCommand = new DelegateCommand(ExecuteCreateAccountCommand)); void ExecuteCreateAccountCommand() { Navigate("CreateAccount"); } private void Navigate(string navigatePath) { if (navigatePath != null) _regionManager.RequestNavigate(RegionNames.LoginContentRegion, navigatePath); } public LoginMainContentViewModel(IRegionManager regionManager) { _regionManager = regionManager; } public bool IsNavigationTarget(NavigationContext navigationContext) { return true; } public void OnNavigatedFrom(NavigationContext navigationContext) { MessageBox.Show("退出了LoginMainContent"); } public void OnNavigatedTo(NavigationContext navigationContext) { MessageBox.Show("从CreateAccount导航到LoginMainContent"); } } 效果如下: 我们会发现跟没实现IRegionMemberLifetime接口和IsNavigationTarget设置为false情况一样,当KeepAlive为false时,通过断点知道,重新导航回LoginMainContent页面时不会触发IsNavigationTarget方法,因此可以 知道判断顺序是:KeepAlive -->IsNavigationTarget 3.IConfirmNavigationRequest Prism的导航系统还支持再导航前允许是否需要导航的交互需求,这里我们在CreateAccount注册完用户后寻问是否需要导航回LoginMainContent页面,代码如下: CreateAccountViewModel.cs: public class CreateAccountViewModel : BindableBase, INavigationAware,IConfirmNavigationRequest { private DelegateCommand _loginMainContentCommand; public DelegateCommand LoginMainContentCommand => _loginMainContentCommand ?? (_loginMainContentCommand = new DelegateCommand(ExecuteLoginMainContentCommand)); private DelegateCommand _verityCommand; public DelegateCommand VerityCommand => _verityCommand ?? (_verityCommand = new DelegateCommand(ExecuteVerityCommand)); void ExecuteLoginMainContentCommand() { Navigate("LoginMainContent"); } public CreateAccountViewModel(IRegionManager regionManager) { _regionManager = regionManager; } private void Navigate(string navigatePath) { if (navigatePath != null) _regionManager.RequestNavigate(RegionNames.LoginContentRegion, navigatePath); } public bool IsNavigationTarget(NavigationContext navigationContext) { return true; } public void OnNavigatedFrom(NavigationContext navigationContext) { MessageBox.Show("退出了CreateAccount"); } public void OnNavigatedTo(NavigationContext navigationContext) { MessageBox.Show("从LoginMainContent导航到CreateAccount"); } //注册账号 void ExecuteVerityCommand(object parameter) { if (!VerityRegister(parameter)) { return; } MessageBox.Show("注册成功!"); LoginMainContentCommand.Execute(); } //导航前询问 public void ConfirmNavigationRequest(NavigationContext navigationContext, Action continuationCallback) { var result = false; if (MessageBox.Show("是否需要导航到LoginMainContent页面?", "Naviagte?",MessageBoxButton.YesNo) ==MessageBoxResult.Yes) { result = true; } continuationCallback(result); } } 效果如下: 三.导航期间传递参数 Prism提供NavigationParameters类以帮助指定和检索导航参数,在导航期间,可以通过访问以下方法来传递导航参数: INavigationAware接口的IsNavigationTarget,OnNavigatedFrom和OnNavigatedTo方法中IsNavigationTarget,OnNavigatedFrom和OnNavigatedTo中形参NavigationContext对象的NavigationParameters属性 IConfirmNavigationRequest接口的ConfirmNavigationRequest形参NavigationContext对象的NavigationParameters属性 区域导航的INavigateAsync接口的RequestNavigate方法赋值给其形参navigationParameters 导航日志IRegionNavigationJournal接口CurrentEntry属性的NavigationParameters类型的Parameters属性(下面会介绍导航日志) 这里我们CreateAccount页面注册完用户后询问是否需要用当前注册用户来作为登录LoginId,来演示传递导航参数,代码如下: CreateAccountViewModel.cs(修改代码部分): private string _registeredLoginId; public string RegisteredLoginId { get { return _registeredLoginId; } set { SetProperty(ref _registeredLoginId, value); } } public bool IsUseRequest { get; set; } void ExecuteVerityCommand(object parameter) { if (!VerityRegister(parameter)) { return; } this.IsUseRequest = true; MessageBox.Show("注册成功!"); LoginMainContentCommand.Execute(); } public void ConfirmNavigationRequest(NavigationContext navigationContext, Action continuationCallback) { if (!string.IsNullOrEmpty(RegisteredLoginId) && this.IsUseRequest) { if (MessageBox.Show("是否需要用当前注册的用户登录?", "Naviagte?", MessageBoxButton.YesNo) == MessageBoxResult.Yes) { navigationContext.Parameters.Add("loginId", RegisteredLoginId); } } continuationCallback(true); } LoginMainContentViewModel.cs(修改代码部分): public void OnNavigatedTo(NavigationContext navigationContext) { MessageBox.Show("从CreateAccount导航到LoginMainContent"); var loginId= navigationContext.Parameters["loginId"] as string; if (loginId!=null) { this.CurrentUser = new User() { LoginId=loginId}; } } 效果如下: 四.导航日志 Prism导航系统同样的和WPF导航系统一样,都支持导航日志,Prism是通过IRegionNavigationJournal接口来提供区域导航日志功能, public interface IRegionNavigationJournal { bool CanGoBack { get; } bool CanGoForward { get; } IRegionNavigationJournalEntry CurrentEntry {get;} INavigateAsync NavigationTarget { get; set; } void GoBack(); void GoForward(); void RecordNavigation(IRegionNavigationJournalEntry entry, bool persistInHistory); void Clear(); } 我们将在登录界面接入导航日志功能,代码如下: LoginMainContent.xaml(前进箭头代码部分): BoolToVisibilityConverter.cs: public class BoolToVisibilityConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value==null) { return DependencyProperty.UnsetValue; } var isCanExcute = (bool)value; if (isCanExcute) { return Visibility.Visible; } else { return Visibility.Hidden; } } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } LoginMainContentViewModel.cs(修改代码部分): IRegionNavigationJournal _journal; private DelegateCommand _loginCommand; public DelegateCommand LoginCommand => _loginCommand ?? (_loginCommand = new DelegateCommand(ExecuteLoginCommand, CanExecuteGoForwardCommand)); private DelegateCommand _goForwardCommand; public DelegateCommand GoForwardCommand => _goForwardCommand ?? (_goForwardCommand = new DelegateCommand(ExecuteGoForwardCommand)); private void ExecuteGoForwardCommand() { _journal.GoForward(); } private bool CanExecuteGoForwardCommand(PasswordBox passwordBox) { this.IsCanExcute=_journal != null && _journal.CanGoForward; return true; } public void OnNavigatedTo(NavigationContext navigationContext) { //MessageBox.Show("从CreateAccount导航到LoginMainContent"); _journal = navigationContext.NavigationService.Journal; var loginId= navigationContext.Parameters["loginId"] as string; if (loginId!=null) { this.CurrentUser = new User() { LoginId=loginId}; } LoginCommand.RaiseCanExecuteChanged(); } CreateAccountViewModel.cs(修改代码部分): IRegionNavigationJournal _journal; private DelegateCommand _goBackCommand; public DelegateCommand GoBackCommand => _goBackCommand ?? (_goBackCommand = new DelegateCommand(ExecuteGoBackCommand)); void ExecuteGoBackCommand() { _journal.GoBack(); } public void OnNavigatedTo(NavigationContext navigationContext) { //MessageBox.Show("从LoginMainContent导航到CreateAccount"); _journal = navigationContext.NavigationService.Journal; } 效果如下: 选择退出导航日志 如果不打算将页面在导航过程中不加入导航日志,例如LoginMainContent页面,可以通过实现IJournalAware并从PersistInHistory()返回false public class LoginMainContentViewModel : IJournalAware { public bool PersistInHistory() => false; } 五.小结: prism的导航系统可以跟wpf导航并行使用,这是prism官方文档也支持的,因为prism的导航系统是基于区域的,不依赖于wpf,不过更推荐于单独使用prism的导航系统,因为在MVVM模式下更灵活,支持依赖注入,通过区域管理器能够更好的管理视图View,更能适应复杂应用程序需求,wpf导航系统不支持依赖注入模式,也依赖于Frame元素,而且在导航过程中也是容易强依赖View部分,下一篇将会讲解Prism的对话框服务 六.源码  最后,附上整个demo的源代码:PrismDemo源码

相关阅读

centos7系统中忘记了root管理员账号密码的解决方式公司管理必须的20条军规[参考]云南昆明天猫旗舰店如何开_怎么开_要什么条件云南大王-通俗理解spring源码(三)—— 获取xml的验证模式 云南大王-用户登录 云南大王-【Golang进阶】指针的详细讲解 云南大王-Java 单线程代码ThreadLocal串值问题 云南大王-Java 实例级别的锁和类级别的锁 云南大王-工作流引擎会签,加签,主持人,组长模式专题讲解 云南大王-Android连载5云南大王-NTP对时器(NTP对时服务器)重要性!京准电子科技 云南大王-关于redis单线程的分析 云南大王-CVE云南大王-PHP SESSION反序列化本地样例分析 云南大王-这不就是多线程ThreadPoolExecutor和阻塞队列吗 云南大王-Tomcat AJP 文件包含漏洞(CVE云南大王-讲真,这两款idea插件,能治愈你英语不好的病 云南大王-消息中间件ActiveMQ、RabbitMQ、RocketMQ、ZeroMQ、Kafka如何选型? 云南大王-JVM系列十三(类加载器). 云南大王-Win10安装MySQL8压缩包版 云南大王-初始WebApi(2) 云南大王-初识人工智能(二):机器学习(一):sklearn特征抽取 云南大王-Popup中ListBox的SelectChange事件关闭弹出窗体后主窗体点击无效BUG 云南大王-基础知识记录 云南大王-FastDFS搭建图片服务器 云南大王-git/sourcetree解决本地仓库历史合并到线上仓的历史数据合并问题_refusing to merge unrelated histories 云南大王-js判断字符是否在数组中【转】 云南大王-Python 云南大王-面向对象之多线程(可捎带电梯调度) 云南大王-Python练习题2.5求奇数分之一序列前N项和(存在问题) 云南大王-React 中的前端路由 react云南大王-VSCODE 远程开发树莓派 云南大王-React新闻网站云南大王-vs .net CS0006 C# 未能找到元数据文件 .dll 云南大王-Vue.js 技术揭秘 云南大王-流程控制语句云南大王-Python学习笔记:Python的时间操作(time,datetime,timedelta,calendar) 云南大王-流程控制语句云南大王-golang Gin framework with websocket 云南大王-多重判断if..else嵌套语句 云南大王-用户登录 云南大王-流程控制语句云南大王-密码类 云南大王-Unity2018发布WebGL注意事项总结 云南大王-web系统安全运营之基础云南大王- 流程控制语句云南大王-中型WPF客户端开发项目总结(3.3.4) 云南大王-流程控制语句云南大王-流程控制语句云南大王-流程控制语句云南大王-中型WPF客户端开发项目总结(4) 云南大王-流程控制语句云南大王-ASP.NET Core笔记(4) 云南大王-C# 基础知识系列云南大王-让 .NET 轻松构建中间件模式代码(二) 云南大王-基于 HTML5 WebGL 的 水泥工厂可视化系统 云南大王-.NET Core 3 WPF MVVM框架 Prism系列之导航系统 云南大王-《JavaScript异步编程》精读笔记 云南大王-合理使用CSS框架,加速UI设计进程 云南大王-CLSID 为 {000209FF云南大王-从零基础转行到前端大牛,需要经过哪几个阶段? 云南大王-一个简单的例子看明白 async await Task 云南大王-【目前】宇宙第一IDE Visual Studio 合并压缩css、js扩展组件云南大王-写一个通用的List集合导出excel的通用方法 云南大王-Bootstrap4 按钮组+徽章(Badges)+进度条+分页+列表组 云南大王-Web前端工程师需要学些什么? 云南大王-react嵌套路由 云南大王-【java框架】Struts2(2) 云南大王-javaSE笔记云南大王-.net core 集成 sentry 进行异常报警 云南大王-Java の 四种引用 云南大王-JVM 虚拟机&&类加载(一) 云南大王-使用Fastjson实现JSON与JavaBean之间互相转换 云南大王-Python操作Oracle数据库:cx_Oracle 云南大王-为什么要用内插字符串代替string.format 云南大王-作为字节跳动的面试官,有些话我不得不说! 云南大王-微信公众号自定义菜单与启用服务器配置冲突(图文消息、链接及文本回复) 云南大王-C#队列学习笔记:RabbitMQ延迟队列 云南大王-Disruptor 基础篇 云南大王-C#获取设备(Audio和Video)名称 简单整理 云南大王-基于注解的IOC配置 云南大王-C#调用EnumDevice获取设备信息 云南大王-Jenkins基础系统之更换镜像源 云南大王-Jenkins基础系统之完整的.net项目编译 云南大王-Scala学习系列(二)——环境安装配置 云南大王-WinForm中DataGridView复制选中单元格内容解决方案 云南大王-关键词匹配优化(第0篇)—— 问题和思路 云南大王-ASP.NET CORE WEBAPI文件下载 云南大王-GC垃圾回收器 云南大王-多线程之旅(Task 任务) 云南大王-当模板方法遇到了委托函数,你的代码又可以精简了 云南大王-基于.NetCore3.1搭建项目系列 —— 使用Swagger导出文档 (补充篇) 云南大王-关键词匹配优化(第1篇)—— 测试计算过程 云南大王-原理解密 → Spring AOP 实现动态数据源(读写分离),底层原理是什么 云南大王-Navicat 密码加密算法 云南大王-【WPF学习】第六十六章 支持可视化状态 云南大王-composer安装 windows 云南大王-ASP.NET Core中的Controller 云南大王-HttpClient来自官方的JSON扩展方法 云南大王-Python3标准库:http.cookies HTTP cookie
/template/Home/Zkeys/PC/Static