目录
1.在不直接生成Prims框架项目的情况下,将普通WPF项目变成一个Prism框架
2.Prism实现mvvm
3.实现自动绑定ViewModel
4.Prism区域
5. 模块化
6.导航
6.1 传参
6.2 导航确认·和·拦截(允许跳转否)
6.3 导航日志
关键字
Prism区域:
控制区域API接口:IRegionManger
注册用户控件: RegisterForNavigation
Prism模块:
控制区域API接口:IRegionManger
注册用户控件: RegisterForNavigation
导航
传参时继承借口 :INavigationAware
导航确认拦截继承接口: IConfirmNavigationRequest
导航日志
操作日志API:IRegionNavigationJournal
1.通过NuGet安装程序包Prism.DryIoc;
2.在App.xaml中改变项目应用对象,同时添加引用空间;
wfp项目应用的是Application对象,prism项目应用的是PrismApplication
3.该变App.xaml.cs 的接口 Application》》PrismApplication,并实现接口类
public partial class App : PrismApplication { // 关系:PrismApplication 继承于 PrismApplicationBase 继承于 Application // 快捷键:查看对象源码:fn+f12,快速实现接口,方法,抽象类:alt+enter+enter // 快速创建构造函数:ctor // Window窗体, StartupUri可以启动一个窗体 // CreateShell()主要负责启动一个主页面。Main protected override Window CreateShell() { // 启动一个窗体MainWindow return Container.Resolve
(); } protected override void RegisterTypes(IContainerRegistry containerRegistry) { } } 4.因为在3中的CreateShell方法中已经启动了一个窗体,所以可以在App.xaml中,删除 StartupUri="MainWindow.xaml">,保留一个启动窗体;
1.继承接口:BindableBase
等同于 CommunityToolkit.Mvvm中的ObservableObject ;
2.数据同步方法:RaisePropertyChanged();
和Mvvmlight一样的方法;
注意:另一种写法:SetProperty(ref _title, value);
public string Name { // 通过BindableBase.RaisePropertyChanged()可以保证xaml视图上数据变化,通知ViewModel, 重新渲染到视图。 get { return name; } //set { name = value; RaisePropertyChanged(); } set { SetProperty(ref name, value); } }
3.绑定事件 :DelegateCommand
不同于mvvm框架的RelayCommand
ChangeNameCommand = new DelegateCommand
(ChangeName);
必须要满足的条件:
1.窗体|页面|用户控件必须放到Views文件夹下
2.模型必须放到ViewModels文件夹下
3.模型名称的名称,必须是窗体名称开头,且以ViewModel结尾
满足以上三条,不需要XXX.xaml.cs中手动绑定DataContext,实现自动绑定ViewModel。另注意:xaml中prism命名空间的设置,自动绑定设置(可省略)
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"注意:将主窗体移到Views文件夹后,命名空间的变化;
通过按钮切换界面
设置 Prism步骤可以分为以下几步:
一.前提准备
要有一个ContentControl 显示用户空间,用来存放区域(切换的页面,这里需要创建用户控件才可以;)
二·. 在用户控件中,给区域设置名字:将来在viewmodle中通过名字来访问区域,找到用户控件,并将找到的用户控件,跳转出来给ContentContr;
三. 在ViewModels中拿到区域,通过区域找到用户控件
这里用到重点 :接口 IRegionManger--区域管理:
为什么要用这个接口呢?
答:此接口中有API可以控制区域:Regions
因此要拿到区域:
1. 创建一个只读的 IRegionManger接口, 为了可以在当前对象的其他方法中使用IRegionManager接口
private readonly IRegionManager regionManager;
2. 注入接口IRegionManager
通过ViewModel的构造函数,把接口 IRegionManager 注入 到 当前Main2ViewModel;
目的是为了操作区域服务提供的API
public Main2ViewModel(IRegionManager regionManager) { this.regionManager = regionManager; }
3.在构造函数中,利用接口IRegionManager的APIRequestNavigate()导航,让viewModels和窗体,页面完全解耦;
注意:此导航是导航到用户控件,并把用户控件放到区域中,其中object是个字符串,并不是用户控件,只是用户控件名称;
此时,再利用接口Ip:Regions["接口名"].RequestNavigate(obj);找到区域中的用户控件;
public Main2ViewModel(IRegionManager regionManager) { OpenCommand = new DelegateCommand
(Open); this.regionManager = regionManager; } private void Open(string obj) { // 让ViewModel和窗体,页面,用户控件完全解耦 // RequestNavigate()导航 // 导航到某个用户控件,并且把某个用户控件放到此区域中. // obj是个字符串,并不是一个用户控件.只是用户控件的文件名. // 如何把一个普通的字符串,转换成真正的用户控件,需要依赖注入 this.regionManager.Regions["ContentRegin"].RequestNavigate(obj); } 4. 在区域中 注册用户控件;
此操作在APP.xaml.cs中的RegisterTypes重写方法中注册 关键字:RegisterForNavigation
protected override void RegisterTypes(IContainerRegistry containerRegistry) { // 把用户控件注册一下,让PageA,PageB控件公开,对ViewModel公开. //containerRegistry.Register
(); //containerRegistry.Register (); containerRegistry.RegisterForNavigation (); containerRegistry.RegisterForNavigation (); } ViewModels完整代码:
public class Main2ViewModel : BindableBase { // 如果在ViewModel通过区域名称拿到某个区域呢? IRegionManager接口, 此接口中有API可以控制区域 public DelegateCommand
OpenCommand { get; private set; } // 用来存储注入进来的IRegionManager接口, 才可以在当前对象的其他方法中使用IRegionManager接口 private readonly IRegionManager regionManager; // 通过ViewModel的构造函数,把接口 IRegionManager 注入 到 当前Main2ViewModel public Main2ViewModel(IRegionManager regionManager) { OpenCommand = new DelegateCommand (Open); this.regionManager = regionManager; } private void Open(string obj) { // 让ViewModel和窗体,页面,用户控件完全解耦 // RequestNavigate()导航 // 导航到某个用户控件,并且把某个用户控件放到此区域中. // obj是个字符串,并不是一个用户控件.只是用户控件的文件名. // 如何把一个普通的字符串,转换成真正的用户控件,需要依赖注入 //理解记忆:Regions区域, RequestNavigate导航需求 this.regionManager.Regions["ContentRegin"].RequestNavigate(obj); } }
注意:模块化功能和区域相似,但模块化范围更广,它是将项目当作模块来充当跳转页面(因此项目中的view需要是用户控件);而区域只是把用户控件当作跳转界面;
步骤:****************************************
a.WPF类库项目
b.安装程序包Prism.DryIoc
c.按约定编写Views
d.创建配置文件XXXProfile,配置文件实现IMoudle接口,并注册页面。模块化,除了d和区域不一样,前面其他的操作都一样,最后有一点,是要把wpf项目通管属性改为类库项目;
开始配置XXXProfile文件:
1.创建XXXProfile类,并继承实现IMoudle接口
注意:区域注册是在App.xaml.cs中,而模块是在此文件中注册
public class ModuleAProfile : IModule { public void OnInitialized(IContainerProvider containerProvider) { } public void RegisterTypes(IContainerRegistry containerRegistry) { // 注册一个用户控件,让将来可跳转 // RegisterForNavigation<视图,视图模型> containerRegistry.RegisterForNavigation
(); } } 2.在App.xaml.cs中 管理注册的模块
方式一:在ConfigureModuleCatalog重写方法中配合Profile管理模块
此方法需要在主窗体中将其dell文件引入其中
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog) { //需要在ModuleMain项目中把依赖的两个模块ModuleA和ModuleB管理起来 //ConfigureModuleCatalog()主要负责把依赖的两个模块添加到Prism框架中进行管理. moduleCatalog.AddModule
(); base.ConfigureModuleCatalog(moduleCatalog); } 方式二:CreateModuleCatalog()方法
给依赖的模块(ModuleA,ModuleB)设置一个查找的路径,并将ModuleA debug中的文件复制到当前项目modules中;
protected override IModuleCatalog CreateModuleCatalog() { // 给依赖的模块(ModuleA,ModuleB)设置一个查找的路径 // 当前项目的bin/debug/Modules return new DirectoryModuleCatalog() { ModulePath = @".\Modules" }; }
方法三:CreateModuleCatalog()方法
通过配置文件app.config实现模块化,提醒:需要把ModuleA.dll和ModuleB.dll放到主项目的bin/debug文件夹
protected override IModuleCatalog CreateModuleCatalog() { return new ConfigurationModuleCatalog(); } //配置app.config
导航是在区域的基础上拓展实现的;
导航可以实现区域的跳转页面外还可以实现传参, 导航确认,导航拦截,导航日志等;
6.1 传参
1. 需要额外继承一个接口:INavigationAware,并实现接口
经过测试发现, Main 到 PageA: 先执行Main页面的OnNavigatedFrom,再执行PageA页面的OnNavigatedTo
public class PageAViewModel : BindableBase, INavigationAware { void INavigationAware.OnNavigatedTo(NavigationContext navigationContext) { } bool INavigationAware.IsNavigationTarget(NavigationContext navigationContext) { } void INavigationAware.OnNavigatedFrom(NavigationContext navigationContext) { } }
2.从主窗体将参数传递给导航窗体 NavigationParameters定义导航参数
通过NavigationParameters设置参数,并通过RequestNavigate传递
//定义导航参数 NavigationParameters parameter= new NavigationParameters(); parameter.Add("Name","张三"); parameter.Add("Age","12"); parameter.Add("Sex","女"); this.regionManager.Regions["ContentRegin"].RequestNavigate(obj,parameter); //先在此确定定位,再判断是否拦截
3.接收参数
在1中已经了解到三个方法的执行顺序,所以接收参数写在To中;
public void OnNavigatedTo(NavigationContext navigationContext) { //三种方式 if (navigationContext.Parameters.ContainsKey("Name")) Name = navigationContext.Parameters.GetValue
("Name"); // 推荐使用 if (navigationContext.Parameters.ContainsKey("Age")) Age = (int)navigationContext.Parameters["Age"]; if (navigationContext.Parameters.ContainsKey("Sex")) Sex = navigationContext.Parameters.GetValue ("Sex"); } 6.2 导航确认·和·拦截(允许跳转否)
注:这里介绍的导航拦截和确认是在 导航与导航之间
1. 注意:导航拦截是,需要把传参时继承的接口INavigationAware改为IConfirmNavigationRequest(后者有委托类型的回调函数),并实现PageA中ConfirmNavigationRequest方法;
public void ConfirmNavigationRequest(NavigationContext navigationContext, Action
continuationCallback) { bool result = true; if (MessageBox.Show("是否允许进入此页面?", "确认", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.No) { result = false; } // 是否允许下一步进入某个页面********** continuationCallback(result); } 2.因为是从PageA跳转到PageB,所以拦截和确认是写在PageA中;
3.执行顺序
当从PageA跳转到PageB时,会先执行主页面中的:
this.regionManager.Regions["ContentRegin"].RequestNavigate(obj,parameter); //先在此确定定位,再判断是否拦截
再执行,是否拦截ConfirmNavigationRequest()方法;
6.3 导航日志
1.需要将IRegionNavigationJournal接口注入构造函数,因为它包含导航日志相关的API
public MainViewModel(IRegionManager regionManager, IRegionNavigationJournal journal) { OpenCommand = new DelegateCommand
(Open); BackCommand = new DelegateCommand(Back); GoCommand = new DelegateCommand(Go); this.regionManager = regionManager; this.journal = journal; } 2. 当页面发生跳转时,通过callback回传参数记录导航日志;
(个人理解:当页面发生跳转,会默认有记录,不过需要通过回调函数callback来回传参数才可以,和Prism的弹窗回传参数一样都是用callback)
private void Open(string obj) { // 定义导航参数 NavigationParameters parameters = new NavigationParameters(); parameters.Add("Name", "张三"); parameters.Add("Age", 20); parameters.Add("Sex", true); // 怎么传递导航参数? 通过RequestNavigate()第三个参数。 // 第二个参数:跳转时,记录导航日志 this.regionManager.Regions["ContentRegion"].RequestNavigate(obj,(callback)=> { // callback.Context当前导航的上下文 // NavigationService导航服务 // Journal导航日志 journal = callback.Context.NavigationService.Journal; }, parameters); }
3. 还可以设置 Page页面中前进后退效果
private void Back() { // CanGoBack是否有返回的日志,CanGoForward是否有前时的日志,GoBack返回(),GoForward()前进,Clear()清空日志 if (journal.CanGoBack) { journal.GoBack(); } } private void Go() { // CanGoBack是否有返回的日志,CanGoForward是否有前时的日志,GoBack返回(),GoForward()前进,Clear()清空日志 if (journal.CanGoForward) { journal.GoForward(); } }