技术 WPF C# Prism ZEROKO14 2023-11-24 2025-07-08 此处记录prism框架的方方面面
MIT开源地址
Prism 是一个用于创建模块化和易于维护的应用程序的框架,它主要起到以下作用:
支持 MVVM 模式 :提供了一些工具和类来帮助开发人员实现 MVVM 模式,使视图和模型之间的耦合度降低,提高了代码的可维护性和可测试性。
提供依赖注入容器 :可以方便地管理应用程序中的依赖关系,使代码更加简洁和易于理解。
支持模块化开发 :可以将应用程序拆分成多个模块,每个模块可以独立开发、测试和部署,提高了开发效率和代码的复用性。
提供导航和视图切换功能 :可以方便地实现应用程序中的导航和视图切换,使应用程序的用户体验更加流畅。
支持多种平台 :除了 WPF 平台,Prism 还支持其他平台,如 UWP、Xamarin.Forms 等,使开发人员可以在不同的平台上使用相同的框架和技术。
Prism在vs2019已经下架了,在vs2022上架
Getting Started | Prism (prismlibrary.com)
包含如下内容
Region(区域管理)
Module(模块)
View Injection(视图注入)
ViewModelLocationProvider(视图模型定位)
Command(绑定相关)
Event Aggregator (事件聚合器)
Navigation(导航)
Dialog(对话框)
使用prism只需要在nuget包中引入Prism.WPF和Prism.Dryloc
Prism的初始化过程
PrismApplicationBase其实也是继承Application,App到Application的继承链上隔了一层PrismApplication
vs插件支持 Prism Template Pack
提供了:
Prism也提供了基于Xamarin的项目模板, 因为Prism是一个基于多个平台的框架
ride支持 要在ride中新建prism项目,需要使用安装nuget包:Prism.Templates
然后在ride中新建项目的时候就可以看到有prism框架相关选择了
窗体切换 prism中的窗体显示,使用下面的语法:
1 2 3 4 5 6 7 8 var main = Application.Current.MainWindow;var window=Container.Resolve<MainWindow>();Application.Current.MainWindow = window; window.Show(); main.Close();
需要在App.xaml.cs中通过依赖注入创建视图模型 ,并注册 窗体视图模型,才可以执行视图切换
1 2 3 var mainWindow = new MainWindow();containerRegistry.Register<ViewModels.MainWindowViewModel>();
对比使用下面WPF常规方式显示窗口 ,将丧失导航功能等
1 2 3 var mainWindow = new MainWindow();mainWindow.Show();
Region 参考链接
Region作为Prism当中模块化的核心功能,其主要目的是弱化了模块与模块之间的耦合关系。 在普遍的应用程序开发中,界面上的元素及内容往往被固定。
基本使用方法 定义Region的方式有两种,一个是在XAML界面指定,另一种这是代码当中指定。
XAML中指定:
1 <ContentControl prism:RegionManager.RegionName="自定义区域名"/>
代码创建:(这种是初始化的时候没有绑定视图,只是注册视图容器)
1 RegionManager.SetRegionName(指定ContentControl的name,自定义区域名);
代码中给区域注册用户控件 (这种是初始化就绑定了视图)
1 2 3 4 5 6 public MainWindowViewModel (IRegionManager regionManager ) { regionManager.RegisterViewWithRegion("ContentRegion" , typeof (Views.view1)); }
案例 完全符合MVVM的区域切换实现方式
区域名称的定义和绑定
1 2 3 4 <ContentControl x:Name="MainContentRegion" Grid.Column="1" prism:RegionManager.RegionName="{Binding RegionName}">
区域名称通过 ViewModel 的 RegionName 属性来控制
实现了区域名称的动态绑定,可以在 ViewModel 中随时修改
区域注册的触发时机
1 2 3 4 5 6 <i:Interaction.Triggers> <i:EventTrigger EventName="Loaded"> <prism:InvokeCommandAction Command="{Binding SetRegionCommand}" CommandParameter="{Binding ElementName=MainContentRegion}" /> </i:EventTrigger> </i:Interaction.Triggers>
当ContentControl加载完成时触发,通过Loaded事件确保控件已经完全初始化
1 2 3 4 5 6 7 8 private void SetRegion (ContentControl contentControl ){ if (contentControl != null ) { RegionManager.SetRegionName(contentControl, RegionName); RegionManager.SetRegionManager(contentControl, _regionManager); } }
区域切换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 _regionManager.RequestNavigate(RegionName, navigatePath, NavigationCallback); private void NavigationCallback (NavigationResult result ){ if (result.Result == true ) { Log.Debug($"导航成功: {result.Context.Uri} " ); } else { Log.Error($"导航失败: {result.Error?.Message} " ); } }
区域适配器 RegionAdapter区域适配器
上面能在Grid中能使用prism:RegionManager.RegionName属性是因为官方给Grid提供了区域适配器 ,像StackPanel就没有区域适配器
Prism提供了许多内置得RegionAdapter
ContentControlRegionAdapter
ItemsControlRegionAdapter
SelectorRegionAdapter - ComboBox - ListBox - Ribbon - TabControl
自定义区域适配器 自定义区域适配器 是用于定义如何将视图(View)添加到区域(Region)中的机制。区域适配器负责将特定类型的 UI 元素(如 UserControl、Window、ContentControl 等)与区域关联起来,以便在运行时可以将视图加载到这些区域中
以添加StackPanel区域适配器为例
新建名为StackPanelRegion类
下面针对TabControl
TabControl参考此处
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class TabControlAdapter : RegionAdapterBase <TabControl > { public TabControlAdapter (IRegionBehaviorFactory regionBehaviorFactory ) : base (regionBehaviorFactory ) { } protected override void Adapt (IRegion region, TabControl regionTarget ) { region.Views.CollectionChanged += (s, e) => { if (e.Action == NotifyCollectionChangedAction.Add) foreach (UserControl item in e.NewItems) { regionTarget.Items.Add(new TabItem { Header = item.Name, Content = item }); } else if (e.Action == NotifyCollectionChangedAction.Remove) foreach (UserControl item in e.OldItems) { var tabTodelete = regionTarget.Items.OfType<TabItem>().FirstOrDefault(n => n.Content == item); regionTarget.Items.Remove(tabTodelete); } }; } protected override IRegion CreateRegion () { return new SingleActiveRegion(); } }
继承RegionAdapterBase<T> T为你要针对的控件
RegionManager 除了定义区域,还有以下功能:
下面贴个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var tabRegion = _regionManager.Regions[RegionNames.AnalysisTabRegion];if (tabRegion.Views.Any()) return ; var historyJournalView = _containerProvider.Resolve<HistoryJournalView>();var historyDataView = _containerProvider.Resolve<HistoryDataView>();var jobJournalView = _containerProvider.Resolve<JobJournalView>();tabRegion.Add(historyJournalView); tabRegion.Add(historyDataView); tabRegion.Add(jobJournalView); tabRegion.Activate(historyJournalView);
Module 本质上来说,对于一个应用程序而言,特定功能的所有View、Logic.Service等都可以独立存在。那么意味着,每个独立的功能我们都可以称之为模块。而往往实际上,我们在一个项目当中,他的结构通常是如下所示:
当我们开始考虑划分模块之间的关系的时候,并且采用新的模块化解决方案,结构将变成如下所示:
创建模块 实现 IModule接口的类 可以将该包标识为模块
创建Module实际上是将模块独立与类库存在,主程序通过加载类库添加模块。以下步骤: 创建Module
首先, 我们创建一个基于WPF的应用程序, 暂且定义为ModuleA, 接下来为ModuleA定义一个类,并且实现IModule接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class ModuleAModule : IModule { public void OnInitialized (IContainerProvider containerProvider ) { } public void RegisterTypes (IContainerRegistry containerRegistry ) { var regionManager = containerProvider.Resolve<IRegionManager>(); regionManager.RegisterViewWithRegion("MyModuleView" , typeof (Views.ThisModuleView)); } }
加载模块 ModuleCatalog保存了应用程序可以使用的模块信息。该目录本质上是ModuleInfo类的集合。每个模块都在一个ModuleInfo类中描述,记录了模块的名称、类型和位置等属性。有几种典型的方法可以用来填充ModuleCatalog ,使其包含ModuleInfo 实例
主程序配置模块目录 配置方式如下:
(代码方式)Code
(配置文件)App.config
(磁盘目录)Disk/Directory
(XAML定义)XAML
(自定义)Custom Register Catalog with PrismApplication Register Modules with Catalog
您应该使用的注册和发现机制取决于您的应用程序需要什么。使用配置文件或XAML文件可以使您的应用程序不需要引用模块。使用目录可以使应用程序发现模块,而无需在文件中指定它们
代码方式 在启动项目当中,添加ModuleA的应用, 打开App.xaml.cs, 重写ConfigureModuleCatalog方法,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public partial class App { protected override Window CreateShell () { return Container.Resolve<MainWindow>(); } protected override void RegisterTypes (IContainerRegistry containerRegistry ) { } protected override void ConfigureModuleCatalog (IModuleCatalog moduleCatalog ) { moduleCatalog.AddModule<ModuleAModule>(); } }
Directory配置模块目录 通过读取根目录Modules文件夹查找模块
1 2 3 4 5 6 7 8 public partial class App { protected override IModuleCatalog CreateModuleCatalog () { return new DirectoryModuleCatalog() { ModulePath=@".\Modules" }; } }
需要将模块需要的三个文件放到指定的文件夹(上面是Modules文件夹中)中,如:Login.deps.json,Login.dll,Login.pdb
这样的方式可以配合类库上右键-属性0-生成-事件-生成后事件中添加如下内容:
1 2 xcopy "$(ProjectDir)\bin\Debug\net6.0-windows\$(ProjectName).dll" "$(SolutionDir)\IonImplantationSystem\bin\Debug\net6.0-windows\Modules\" /Y /S xcopy "$(ProjectDir)\bin\Release\net6.0-windows\$(ProjectName).dll" "$(SolutionDir)\IonImplantationSystem\bin\Release\net6.0-windows\Modules\" /Y /S
这样就成功设置可以很简单的导入模块了
App.Config配置模块目录 1 2 3 4 5 6 7 public partial class App { protected override IModuleCatalog CreateModuleCatalog () { return new ConfigurationModuleCatalog(); } }
然后,为应用程序添加配置文件app.config, 添加以下内容:
1 2 3 4 5 6 7 8 9 10 <configuration> <configSections> <section name="modules" type="Prism.Modularity.ModulesConfigurationSection, Prism.Wpf" /> </configSections> <startup> </startup> <modules> <module assemblyFile="ModuleA.dll" moduleType="ModuleA.ModuleAModule, ModuleA, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" moduleName="ModuleAModule" startupLoaded="True" /> </modules> </configuration>
XAML配置模块目录 修改CreateModuleCatalog方法, 从指定XAML文件读取模块配置
1 2 3 4 5 6 7 public partial class App { protected override IModuleCatalog CreateModuleCatalog () { return new XamlModuleCatalog(new Uri("/Modules;component/ModuleCatalog.xaml" , UriKind.Relative)); } }
创建模块名为ModuleCatalog.xaml文件, 添加模块信息
1 2 3 4 5 6 7 8 9 <m:ModuleCatalog xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:m="clr-namespace:Prism.Modularity;assembly=Prism.Wpf"> <m:ModuleInfo ModuleName="ModuleAModule" ModuleType="ModuleA.ModuleAModule, ModuleA, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> </m:ModuleCatalog>
Prism控制何时加载模块
应用程序可以尽快初始化模块,称为“when available”
在应用程序需要它们时初始化,称为“on-demand”
视图注入 应用程序模块后,每个子模块中的视图可以独立的进行依赖注入 。再使用IRegionManager来实现页面导航。
步骤: 1.利用Region进行导航功能。 2.使用Module将应用程序模块化。 3.将独立模块的视图、服务使用注入到容器当中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class ModuleAModule : IModule { private readonly IRegionManager _regionManager; public ModuleAModule (IRegionManager regionManager ) { _regionManager = regionManager; } public void OnInitialized (IContainerProvider containerProvider ) { _regionManager.RegisterViewWithRegion("ContentRegion" , typeof (ViewA)); } public void RegisterTypes (IContainerRegistry containerRegistry ) { } }
依赖注入 盘点注册方法
Register:这是一种通用的注册方法,用于将具体的实现类与接口或抽象类进行关联。通过这种方式,容器可以在需要时提供相应的实例。
RegisterSingleton:用于将某个实现类注册为单例模式。这意味着在整个应用程序的生命周期中,只会创建一个该实现类的实例,并在需要时进行共享。
RegisterInstance:允许你直接提供一个特定的实例进行注册。这意味着容器将使用你提供的具体实例,而不是创建新的实例。
RegisterScoped:表示注册为作用域范围内的单例。在特定的作用域(例如请求范围)内,会有一个唯一的实例。
1 2 3 4 5 6 7 8 9 containerRegistry.Register<IService, MyServiceImpl>(); containerRegistry.RegisterSingleton<IService, MyServiceImpl>(); var instance = new MyServiceImpl();containerRegistry.RegisterInstance<IService>(instance); containerRegistry.RegisterScoped<IService, MyServiceImpl>();
以最常用的单例注册 为例:
1 2 containerRegistry.RegisterSingleton<IService, MyServiceImpl>();
如何使用该注册的单例
手动注入方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class MyViewModel : BindableBase { private readonly IContainerProvider _containerProvider; public MyViewModel (IContainerProvider containerProvider ) { _containerProvider = containerProvider; } public void SomeMethod () { var myService = _containerProvider.Resolve<IMyService>(); myService.DoSomething(); } }
最常用的构造函数注入:
1 2 3 4 5 6 7 8 9 public class MyViewModel : BindableBase { private readonly IMyService _myService; public MyViewModel (IMyService myService ) { _myService = myService; } }
单例的好处包括
节省资源:只创建一个对象,避免了不必要的资源消耗。
一致性:确保在整个系统中使用的是同一个对象。
易于管理和维护:减少了对象的创建和销毁逻辑。
依赖注入的好处:
依赖注入:通过接口来获取实例,实现了依赖注入的原则,提高了代码的灵活性和可维护性。
解耦:使得代码不再直接依赖具体的实现类,而是依赖接口。
ViewModelLocator 在WPF当中,需要为View与ViewModel建立连接 , 我们需要找到View的DataContext
wpf中主要是两种建立连接的方式
第三方的MVVM框架, 标准的ViewModelLocator可能如下所示
这些方式都可以建立View-ViewModel关系。 但是,这一切并不是Prism想表达的内容, 甚至不建议你按上面的方式去做, 因为这样几乎打破了开发的所有原则。 (我们把View与ViewModel的关系编码的方式通过命名的方式固定了下来 , 通过静态类去维护ViewModel的关系…)
在Prism当中, 你可以基于命名约定以及文件夹结构 , 便能够轻松的将View/ViewModel建议关联
假设你已经为项目添加Views/ViewModels 文件夹。此时, 你的页面为ViewA, 则对应的ViewModel名称为 ViewAViewModel
大致意思为View这个字符串会重叠收纳
注意不仅仅名字遵循约定,并且View要在Views文件夹下,ViewModel要在ViewModels文件夹下
当遵循了命名规范后, 此时还需要在View当中声明,允许当前View自动装配ViewModel
1 2 3 4 5 6 7 <UserControl x:Class="SettingBinding Context.Views.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:prism="http://prismlibrary.com" prism:ViewModelLocator.AutowireViewModel="True" > </UserControl>
这种命名约定和文件夹结构也可以在代码中修改:此处略,参考这里
Prism中的MVVM 前面 介绍了Prism中ViewModel如何与View进行连接
Prism与常见的MVVM框架区别 如果你了解WPF当中的ICommand, INotifyPropertyChanged的作用, 就会发现 众多框架都是基于这些进行扩展, 实现其通知、绑定、命令等功能
对于不同的MVVM框架而言, 大体使用上会在声明方式上的差异, 以及特定功能上的差别,如下图
功能↓ / →框架名
Prism
Mvvmlight
Micorosoft.Toolkit.Mvvm
通知
BindableBase
ViewModelBase
ObservableObject
命令
DelegateCommand
RelayCommand
Async/RelayCommand
聚合器
IEventAggregator
IMessenger
IMessenger
模块化
√
×
×
容器
√
×
×
依赖注入
√
×
×
导航
√
×
×
对话
√
×
×
各个框架之间都有各自的通知、绑定、事件聚合器等基础的功能, 而Prsim自带的依赖注入、容器、以及导航会话 等功能, 可以为你提供更加强大的功能
如何在ViewModel实现基础绑定、Command、事件聚合器等操作 实现基础绑定 下面例子既有命令绑定,也有属性绑定
BindableBase 在Prism当中,需要用到绑定的控件的ViewModel需要继承ViewModel,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class TestViewModel : BindableBase { public TestViewModel () { Message = "hello" ; SendCommand = new DelegateCommand(() => { Message = "hello world!" ; }); } private string _message; public string Message { get { return _message; } set { _message = value ; RaisePropertyChanged(); } } public DelegateCommand SendCommand { get ; private set ; } }
绑定端代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 <!--截取的内容--> xmlns:prism ="http://prismlibrary.com/" mc:Ignorable="d" prism:ViewModelLocator.AutoWireViewModel="True" Title="ViewA" Height="450" Width="800"> <Grid> <StackPanel> <Button Width="100" Height="30" Content="更新TextBlock" Command="{Binding SendCommand}"/> <TextBlock Text="{Binding Message}" FontSize="38" HorizontalAlignment="Center"/> </StackPanel> </Grid>
绑定多命令 复合命令CompositeCommand
对于单个Command而言, 只是触发单个对应的功能, 而复合命令是Prism当中非常强大的功能, CompositeCommand简单来说是一个父命令, 它可以注册N个子命令
相关语法
1 2 3 4 5 public CompositeCommand 复合命令名 { get ; private set ; }复合命令名 = new CompositeCommand(); 复合命令名.RegisterCommand(命令名); 复合命令名.RegisterCommand(命令名2 );
当复合命令被激活,它将触发对所有的子命令, 如果任意一个命令CanExecute=false,它将无法被激活,如下所示:
此图中按下Save All按钮后什么也不执行,因为复合命令被禁用了,假设复合命令被启用,那么将会执行的是Save A和Save C
实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class ViewAViewModel : BindableBase { private string message; public ViewAViewModel () { Message = "hello" ; SendCommand = new DelegateCommand(() => { Message = "hello world!" ; }); SendCommand2=new DelegateCommand(() => { Message += "zeroko14" ; }); SendAll = new CompositeCommand(); SendAll.RegisterCommand(SendCommand); SendAll.RegisterCommand(SendCommand2); } public string Message { get { return message; }set { message = value ; RaisePropertyChanged(); } } public DelegateCommand SendCommand { get ; private set ; } public DelegateCommand SendCommand2 { get ; private set ; } public CompositeCommand SendAll { get ; private set ; } }
最终标题会被设置为hello world!zeroko14 ,Command={Binding SendAll}会同时调用SendCommand和SendCommand2
事件聚合器 事件聚合器IEventAggregator
事件聚合器负责接收订阅以及发布消息。订阅者可以接收到发布者发送的内容
功能盘点:
松耦合基于事件通讯
多个发布者和订阅者
微弱的事件
过滤事件
传递参数
取消订阅
AViewModel订阅了一个消息接收的事件, 然后BViewModel当中给指定该事件推送消息,此时AViewModel接收BViewModel推送的内容
1 2 3 4 5 6 7 8 9 10 11 public class SavedEvent : PubSubEvent <string > { }IEventAggregator.GetEvent<SavedEvent>().Publish("some value" ); IEventAggregator.GetEvent<SavedEvent>().Subscribe(message=> { });
事件过滤 在实际的开发过程当中,我们往往会在多个位置订阅一个事件, 但是对于订阅者而言, 他并不需要接收任何消息,事件过滤Filtering Events应运而生
在Prism当中, 我们可以指定为事件指定过滤条件
1 2 3 4 5 6 7 8 eventAggregator.GetEvent<MessageSentEvent>().Subscribe( arg =>{ }, ThreadOption.PublisherThread, false , message => message.token.Equals(nameof (MessageListViewModel)));
关于Subscribe当中的4个参数, 详解:
action: 发布事件时执行的回调委托。
ThreadOption枚举: 指定在哪个线程上接收委托回调。
PublisherThread: 在发布事件的线程上调用订阅者的委托回调。
BackgroundThread: 在后台线程上调用订阅者的委托回调。
UIThread: 在UI线程上调用订阅者的委托回调(通常用于更新UI)
keepSubscriberReferenceAlive: 如果为true,则Prism.Events.PubSubEvent保留对订阅者的引用因此它不会回收垃圾。
长期订阅,需要保持状态的情况下设置为true.(基本上可以这么认为,只有贯穿程序一生的订阅才需要设置为true)
临时订阅,不再需要时要被释放资源的情况下设置为false
filter: 可选参数,用于筛选事件通知,订阅者可根据筛选条件来决定是否接收特定的事件通知。返回true表示接受通知,返回false表示不接受
实例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 namespace prismTestNew.ViewModels { public class ViewAViewModel : BindableBase { private readonly IEventAggregator eventAggregator; private string message; public ViewAViewModel (IEventAggregator eventAggregator ) { Message = "hello" ; SendCommand = new DelegateCommand(() => { eventAggregator.GetEvent<MessageEvent>().Subscribe(OnMessageReceived, ThreadOption.PublisherThread, false , (x) => { return x == "hello" ; }); }); TriggerCommand = new DelegateCommand(() => { eventAggregator.GetEvent<MessageEvent>().Publish("hello" ); }); this .eventAggregator = eventAggregator; } public void OnMessageReceived (string messageFromMessageEvent ) { Message += messageFromMessageEvent + "\r\n" ; } public string Message { get { return message; } set { message = value ; RaisePropertyChanged(); } } public DelegateCommand SendCommand { get ; private set ; } public DelegateCommand TriggerCommand { get ; private set ; } } public class MessageEvent : PubSubEvent <String >{ } }
视图如下:
1 2 <Button Width="100" Height="30" Content="订阅" Command="{Binding SendCommand}" Margin="10"/> <Button Width="100" Height="30" Content="发布" Command="{Binding TriggerCommand}" Margin="10"/>
点击订阅之后,点击发布,就会执行到自定义的OnMessageReceived函数
取消订阅 为注册的消息取消订阅, Prism提供二种方式取消订阅,如下:
通过委托的方式取消订阅
1 2 var event = IEventAggregator.GetEvent<MessageSentEvent>();event .Unsubscribe(OnMessageReceived);
通过获取订阅者token取消订阅
1 2 3 var event = eventAggregator.GetEvent<MessageSentEvent>();var token = _event.Subscribe(OnMessageReceived);event .Unsubscribe(token);
区域导航 在普遍的业务场景当中, 必不可少的是页面切换, 而Prism就可以使用Navigation功能来进行页面导航
prism实现区域导航,需要满足两点基本条件
注册显示区域 注册视图类型或添加别名, 如果未指定别名,名称默认为当中类型的名称
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class ModuleAModule : IModule { public void OnInitialized (IContainerProvider containerProvider ) { } public void RegisterTypes (IContainerRegistry containerRegistry ) { containerRegistry.RegisterForNavigation<ViewA>("CustomName" ); containerRegistry.RegisterForNavigation<ViewB>(); } }
注册时指定ViewModel或添加别名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class ModuleAModule : IModule { public void OnInitialized (IContainerProvider containerProvider ) { } public void RegisterTypes (IContainerRegistry containerRegistry ) { containerRegistry.RegisterForNavigation<ViewA, ViewAViewModel>(); containerRegistry.RegisterForNavigation<ViewB, ViewBViewModel>("CustomName" ); } }
使用导航 在某窗口对应的viewModel文件中需要使用导航,要在构造函数参数中接受IRegionManager regionManager,并将该值存入成员属性中,就可以使用regionManager来进行下述操作了
Region的注册以及管理、导航等, 我们可以使用IRegionManager接口,所以,我们现在便可以使用该接口实现导航功能
1 2 IRegionManager regionManager = …; regionManager.RequestNavigate("RegionName" , "ViewName" );
调用了IRegionManager接口的RequestNavigate方法, 并且传递了两个参数:
RegionName: 该参数为注册的区域名称
ViewName: 该参数实际为我们上面注册过的导航页, 字符串类型, 对应的是我们注册页面的nameof
带参数导航 导航页前传递一些参数, 则可以使用NavigationParameters
1 2 3 4 5 6 7 8 9 var param = new NavigationParameters();param.Add("Parameter" , param); _regionManger.RequestNavigate("RegionName" , "ViewName" , param); _regionManger.RequestNavigate("RegionName" , "ViewName?id=1&Name=xiaoming" );
控制导航过程 INavigationAware 该接口包含3个方法, 每个方法中都包含当前导航的上下文, 如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public void OnNavigatedTo (NavigationContext navigationContext ) { } public bool IsNavigationTarget (NavigationContext navigationContext ){ return true ; } public void OnNavigatedFrom (NavigationContext navigationContext ){ }
OnNavigatedTo: 导航完成前, 此处可以传递过来的参数以及是否允许导航等动作的控制。
IsNavigationTarget: 调用以确定此实例是否可以处理导航请求。否则新建实例
OnNavigatedFrom: 当导航离开当前页时, 类似打开A, 再打开B时, A中的该方法被触发。
执行流程:
获取导航请求参数 导航中允许我们传递参数, 用于在我们完成导航之前, 进行做对应的逻辑业务处理。这时候, 我们便可以在OnNavigatedTo方法中通过导航上下文中获取到传递的所有参数
1 2 3 4 5 public void OnNavigatedTo (NavigationContext navigationContext ){ var id = navigationContext.Parameters.GetValue<int >("id" ); var name = navigationContext.Parameters["Name" ].ToString(); }
是否拦截导航请求 IConfirmNavigationRequest接口 :该接口继承于INavigationAware, 所以, 它多了一个功能: 允许用户针对导航请求判断是否进行拦截
1 2 3 4 5 6 7 8 9 10 11 public void ConfirmNavigationRequest (NavigationContext navigationContext, Action<bool > continuationCallback ){ bool result = true ; if (MessageBox.Show("确认导航?" , "温馨提示" , MessageBoxButton.YesNo) == MessageBoxResult.No) result = false ; continuationCallback(result); }
导航日志 Navigation Journal导航日志 , 其实就是对导航系统的一个管理功能, 理论上来说, 我们应该知道我们上一步导航的位置、以及下一步导航的位置, 包括我们导航的历史记录。以便于我们使用导航对应用程序可以灵活的控制。
IRegionNavigationJournal 该接口包含以下功能:
GoBack() : 返回上一页
CanGoBack : 是否可以返回上一页
GoForward(): 返回后一页
CanGoForward : 是否可以返回后一页
完整案例 github上完整代码参考
1 2 3 4 5 6 7 8 9 10 11 12 13 │ ├── App.xaml ├── App.xaml.cs │ ├── Views\ │ ├── MainWindow.xaml │ ├── ViewA.xaml │ └── ViewB.xaml │ └── ViewModels\ ├── MainWindowViewModel.cs ├── ViewAViewModel.cs └── ViewBViewModel.cs
新建两个视图ViewA.xaml和ViewB.xaml,并在App.xaml.cs中注册两个界面
App.xaml.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 using Prism.Ioc;using System.Windows;using testNavigation.Views;namespace testNavigation { public partial class App { protected override Window CreateShell () { return Container.Resolve<MainWindow>(); } protected override void RegisterTypes (IContainerRegistry containerRegistry ) { containerRegistry.RegisterForNavigation<ViewA>("PageA" ); containerRegistry.RegisterForNavigation<ViewB>(); } } }
MainWindowViewModel.cs以及MainWindow.xaml中对视图进行完善并绑定相关command
MainWindowViewModel.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 using Prism.Commands;using Prism.Mvvm;using Prism.Regions;using System.Runtime.CompilerServices;namespace testNavigation.ViewModels { public class MainWindowViewModel : BindableBase { private string _title = "Prism Application" ; public string Title { get { return _title; } set { SetProperty(ref _title, value ); } } private readonly IRegionManager regionManager; IRegionNavigationJournal journal; public DelegateCommand OpenACommand { get ; set ; } public DelegateCommand OpenBCommand { get ; set ; } public DelegateCommand GoBackCommand { get ; set ; } public DelegateCommand GoForwordCommand { get ; set ; } public MainWindowViewModel (IRegionManager regionManager ) { OpenACommand = new DelegateCommand(OpenA); OpenBCommand = new DelegateCommand(OpenB); GoBackCommand = new DelegateCommand(GoBack); GoForwordCommand = new DelegateCommand(GoForword); this .regionManager = regionManager; } private void OpenA () { NavigationParameters param = new NavigationParameters(); param.Add("Value" , "Hello" ); regionManager.RequestNavigate("ContentRegion" , $"PageA?Value=Hello1" , arg => { journal = arg.Context.NavigationService.Journal; }); } private void OpenB () { regionManager.RequestNavigate("ContentRegion" , nameof (Views.ViewB), arg => { journal = arg.Context.NavigationService.Journal; }); } private void GoBack () { journal.GoBack(); } private void GoForword () { journal.GoForward(); } } }
MainWindow.xaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <Window x:Class="testNavigation.Views.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:prism="http://prismlibrary.com/" prism:ViewModelLocator.AutoWireViewModel="True" Title="{Binding Title}" Height="350" Width="525" > <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <StackPanel Grid.Row="0" Orientation="Horizontal"> <Button Content="上一步" Width="auto" Height="auto" Margin="3" Command="{Binding GoBackCommand}"/> <Button Content="下一步" Width="auto" Height="auto" Margin="3" Command="{Binding GoForwordCommand}"/> <Button Content="打开A" Width="auto" Height="auto" Margin="3" Command="{Binding OpenACommand}"/> <Button Content="打开B" Width="auto" Height="auto" Margin="3" Command="{Binding OpenBCommand}"/> </StackPanel> <ContentControl Grid.Row="1" prism:RegionManager.RegionName="ContentRegion" /> </Grid> </Window>
这行代码在 XAML 中定义了一个 TabControl 控件,并使用 Prism 框架的 RegionManager 来将该控件与名为 ContentRegion 的区域关联起来。这种关联允许在该区域内动态加载和管理与 TabControl 相关的视图
ViewAViewModel.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 using Prism.Mvvm;using Prism.Regions;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Windows;namespace testNavigation.ViewModels { public class ViewAViewModel : BindableBase ,IConfirmNavigationRequest { private string title; public string Title { get { return title; } set { title = value ; } } public void ConfirmNavigationRequest (NavigationContext navigationContext, Action<bool > continuationCallback ) { bool result = true ; if (MessageBox.Show("确认导航" ,"温馨提示" ,MessageBoxButton.YesNo)==MessageBoxResult.No) result = false ; continuationCallback(result); } public bool IsNavigationTarget (NavigationContext navigationContext ) { return true ; } public void OnNavigatedFrom (NavigationContext navigationContext ) { } public void OnNavigatedTo (NavigationContext navigationContext ) { title = navigationContext.Parameters.GetValue<string >("Value" ); } } }
ViewA.xaml 1 2 3 4 5 6 7 8 9 10 11 12 <UserControl x:Class="testNavigation.Views.ViewA" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:testNavigation.Views" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <Grid Background="Yellow"> <TextBlock Text="{Binding Title}"/> </Grid> </UserControl>
对话服务 Prism提供了一组对话服务, 封装了常用的对话框组件的功能
RegisterDialog/IDialogService (注册对话及使用对话)
打开对话框传递参数/关闭对话框返回参数
回调通知对话结果
实现对话框
创建对话框,(注意)通常是一组用户控件 ,并且实现 IDialogAware
1 2 3 4 5 6 7 8 public interface IDialogAware { string Title { get ; } event Action<IDialogResult> RequestClose; bool CanCloseDialog () ; void OnDialogClosed () ; void OnDialogOpened (IDialogParameters parameters ) ; }
例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 using Prism.Commands;using Prism.Mvvm;using Prism.Services.Dialogs;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace testDialog.ViewModels { public class MsgViewModel : BindableBase ,IDialogAware { public string Title{ set ; get ; } public event Action<IDialogResult> RequestClose; public DelegateCommand OkCommand { get ; set ; } public DelegateCommand CancleCommand { get ; set ; } public MsgViewModel () { OkCommand = new DelegateCommand(() => { DialogParameters param = new DialogParameters(); param.Add("Value" , Title); RequestClose?.Invoke(new DialogResult(ButtonResult.OK, param)); }); CancleCommand = new DelegateCommand(() => { RequestClose?.Invoke(new DialogResult(ButtonResult.No)); }); } public bool CanCloseDialog () { return true ; } public void OnDialogClosed () { } public void OnDialogOpened (IDialogParameters parameters ) { Title = parameters.GetValue<string >("Value" ); } } }
注册对话框 注册对话框 RegisterDialog
1 2 3 4 5 6 7 8 9 10 protected override void RegisterTypes (IContainerRegistry containerRegistry ){ containerRegistry.RegisterDialog<MessageDialog>(); containerRegistry.RegisterDialog<MessageDialog, MessageDialogViewModel>(); containerRegistry.RegisterDialog<MessageDialog>("DialogName" ); }
使用Dialog 使用IDialogService接口 Show/ShowDialog 方法调用对话框
1 2 3 4 5 6 7 8 9 10 11 12 private readonly IDialogService dialogService;private void ShowDialog (){ DialogParameters keys = new DialogParameters(); keys.Add("message" , "Hello,Prism!" ); dialogService.ShowDialog("MessageDialog" , keys, arg => { }); }
调用Show/ShowDialog,我们通过注册时候的名称进行打开, 并且可以传递参数, 以及回调方法(主要用于返回对话框的返回结果)
使用对话框实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 using Prism.Commands;using Prism.Mvvm;using Prism.Services.Dialogs;using System.Windows;using testDialog.Views;namespace testDialog.ViewModels { public class MainWindowViewModel : BindableBase { public DelegateCommand ShowDialogCommand { get ; private set ; } private readonly IDialogService dialog; private string _title = "Prism Application" ; public string Title { get { return _title; } set { SetProperty(ref _title, value ); } } public MainWindowViewModel (IDialogService dialog ) { ShowDialogCommand = new DelegateCommand(ShowDialog); this .dialog = dialog; } private void ShowDialog () { DialogParameters param = new DialogParameters(); param.Add("Value" , "Hello" ); dialog.ShowDialog("dialogView" ,param, arg => { if (arg.Result == ButtonResult.OK) { MessageBox.Show(arg.Parameters.GetValue<string >("Value" )); } }); } } }
封装Dialog API 对于常用的公共对话框, 我们可以封装成扩展方法, 以便于我们在应用程序的任何位置可以使用到它
1 2 3 4 5 6 7 public static void ShowNotification (this IDialogService dialogService,string message, Action<IDialogResult> callback ){ var p = new DialogParameters();p.Add("message" , message); dialogService.ShowDialog(“NotificationDialog", p, callback); }
实例参考 测试实例参考
弹窗数据传递的核心机制 传入数据给对话框 => [[ 对话框实现 ]](#弹窗 ViewModel 实现 IDialogAware) => 传出数据给父级窗口处理程序
Prism的对话框服务遵循以下流程:
参数传递: 调用弹窗时通过 DialogParameters 向弹窗传递初始参数
结果回调: 弹窗关闭时通过 IDialogResult 返回用户输入的数据
接口实现: 弹窗的 ViewModel 需实现 IDialogAware 接口,处理打开、关闭和数据回传逻辑
传入及传出参数 在父窗口的 ViewModel 中,使用 IDialogService.ShowDialog 方法打开弹窗,并通过 DialogParameters 传递参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 private void OpenDialog (){ DialogParameters parameters = new DialogParameters(); parameters.Add("initialValue" , "默认文本" ); dialogService.ShowDialog("UserInputDialog" , parameters, result => { if (result.Result == ButtonResult.OK) { string userInput = result.Parameters.GetValue<string >("userInput" ); } }); } dialogService.ShowDialog("SelectLinkPointDialogView" , new DialogParameters { { "input" , "测试输入内容111" }, }, r => { if (r.Result == ButtonResult.OK) { var values = r.Parameters.GetValue<string >("output" ); } });
UserInputDialog:弹窗的注册名称。
result:回调参数,包含用户操作结果(确定/取消)和返回的数据
弹窗 ViewModel 实现 IDialogAware 弹窗的 ViewModel 需实现 IDialogAware 接口,处理数据接收与返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class UserInputDialogViewModel : BindableBase ,IDialogAware { public string Title => "对话框标题" ; public bool CanCloseDialog () { return true ; } public void OnDialogClosed () { } public void OnDialogOpened (IDialogParameters parameters ) { if (parameters.ContainsKey("Value" )) Title = parameters.GetValue<string >("Value" ); } public event Action<IDialogResult> RequestClose; private DelegateCommand<string > _closeDialogCommand; public DelegateCommand<string > CloseDialogCommand => _closeDialogCommand ?? (_closeDialogCommand = new DelegateCommand<string >(ExecuteCloseDialogCommand)); void ExecuteCloseDialogCommand (string parameter ) { var dialogResult = new DialogResult(ButtonResult.OK); dialogResult.Parameters.Add("values" , new Dictionary<string , object >(_inputValues)); RequestClose?.Invoke(dialogResult); } }
实现的对话框需要注册才能使用 在 Prism 的模块或容器中注册弹窗:
1 2 3 4 protected override void RegisterTypes (IContainerRegistry containerRegistry ){ containerRegistry.RegisterDialog<UserInputDialog, UserInputDialogViewModel>("UserInputDialog" ); }
注册时需要指定弹窗的 View 和 ViewModel,并为弹窗命名
参数与返回值的封装 为提高代码可维护性,可封装自定义参数类:
1 2 3 4 5 6 7 8 public class UserInputResult { public string InputText { get ; set ; } public bool IsConfirmed { get ; set ; } } var result = result.Parameters.GetValue<UserInputResult>("result" );
通过强类型对象代替松散键值对,减少硬编码风险
对话框窗口外观设置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <prism:Dialog.WindowStyle> <Style TargetType="Window"> <!--定义了对话框的启动位置。`CenterScreen` 表示窗口将在屏幕中央启动--> <Setter Property="prism:Dialog.WindowStartupLocation" Value="CenterScreen" /> <!--决定窗口是否在任务栏中显示。`True` 表示该窗口会在任务栏中显示--> <Setter Property="ShowInTaskbar" Value="True" /> <!--定义窗口的大小行为。`WidthAndHeight` 表示窗口的大小将根据其内容自动调整,以适应窗口中显示的内容的宽度和高度--> <Setter Property="SizeToContent" Value="WidthAndHeight" /> <!--指定了窗口的样式。`ToolWindow` 通常用于工具窗口,具有较小的标题栏和不同的外观,适合用于辅助工具或面板--> <Setter Property="WindowStyle" Value="ToolWindow" /> <!--使窗口不可被手动改变大小--> <Setter Property="ResizeMode" Value="NoResize" /> </Style> </prism:Dialog.WindowStyle>