C# Prism框架详解

文章目录

Region(区域)

区域设置RegionManager

csharp 复制代码
<Grid>
     <StackPanel prism:RegionManager.RegionName="ContentRegion" />
</Grid>

复制代码
//或者
<ContentControl x:Name="Header" Grid.Row="0" />
cs:
```csharp
public MainWindow(IRegionManager regionManager)
{
     InitializeComponent();
     RegionManager.SetRegionName(Header, "HeaderRegion");
}

Bootstrapper负责引导应用程序,用于配置 IoC 容器,创建根 ViewModel 的新实例,并使用显示WindowManager出来。

Bootstrapper类的责任是使用Prism类库初始化应用程序,抽象类Bootstrapper提供的函数大多都是虚方法。

Bootstrapper的一个重要的职责是创建shell(MainWindow),这是因为Shell依赖于各种服务,比如Region Manager服务,而这些服务恰恰是在Shell在创建之前需要被创建的。

如果你确认是创建MEF或者是Unity容器作为依赖注入容器,那么正确的方法是创建一个类继承自这些容器,然后重写这些基类的虚方法和抽象方法,其中最重要的几个方法是:CreateShell()、InitializeShell()、CreateModuleCatalog(),现在来分别来介绍下这些方法。

UnityBootstrapper和MefBootstrapper类实现了大多数必须的功能。

在Prism中有两种方式来定义视图与Region之间的映射关系------View DiscoveryView Injection

复制代码
<Grid.RowDefinitions>
            <RowDefinition Height="0.2*" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <ContentControl Grid.Row="0" prism:RegionManager.RegionName="HeaderRegion" />
        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="0.4*" />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <ContentControl Grid.Column="0" prism:RegionManager.RegionName="MenuRegion" />
            <ContentControl Grid.Column="1" prism:RegionManager.RegionName="ContentRegion" />
        </Grid>

在Prism中有两种方式来定义视图与Region之间的映射关系------View DiscoveryView Injection

csharp 复制代码
public MainWindow(IRegionManager regionManager)
{
     InitializeComponent();
     //View Dicovery
     regionManager.RegisterViewWithRegion("HeaderRegion", typeof(HeaderView));
     regionManager.RegisterViewWithRegion("MenuRegion", typeof(MenuView));
     regionManager.RegisterViewWithRegion("ContentRegion", typeof(ContentView));
}

区域概念

Prism框架中,模块 可以注册到导航菜单Navigation ,也可以注册到区域Region ,根据实际业务需要进行选择。Region可以更加方便的进行模块化布局等。在普通容器控件中,增加区域是prism:RegionManager.RegionName="区域名称"

Region自定义控件

不是所有的控件都可以被用作region,比如stackpanel

这里可以用一个叫RegionAdapter适配器的控件,,一个RegionAdapter需要实现IRegionAdapter接口,如果你需要自定义一个RegionAdapter,可以通过继承RegionAdapterBase类来省去一些工作。

  • step1 新建一个类StackPanelRegionAdapter.cs ,继承RegionAdapterBase ,这个类已经帮我们实现了IRegionAdapte接口。
csharp 复制代码
using Prism.Regions;
using System.Windows;
using System.Windows.Controls;

namespace Regions.Prism
{
  public class StackPanelRegionAdapter : RegionAdapterBase<StackPanel>
  {
     public StackPanelRegionAdapter(IRegionBehaviorFactory regionBehaviorFactory)
            : base(regionBehaviorFactory)
        {

        }
     protected override void Adapt(IRegion region, StackPanel regionTarget)
        {
            region.Views.CollectionChanged += (s, e) =>
            {
                if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
                {
                    foreach (FrameworkElement element in e.NewItems)
                    {
                        regionTarget.Children.Add(element);
                    }
                }
            };
        }
        protected override IRegion CreateRegion()
        {
            return new AllActiveRegion();
        }
    }
}
  • setp2 在App.xaml.cs注册绑定
csharp 复制代码
protected override void ConfigureRegionAdapterMappings(RegionAdapterMappings regionAdapterMappings)
{
     base.ConfigureRegionAdapterMappings(regionAdapterMappings);
     regionAdapterMappings.RegisterMapping(typeof(StackPanel), Container.Resolve<StackPanelRegionAdapter>());
}

我们现在可以为StackPanel实现用作Region了。

IRegionManager接口

IRegionManager是一个区域管理,用于绑定区域和视图的

AddToRegion()、RegisterViewWithRegion(),Regions属性

IRegion接口

Add与Remove(加载、剔除)、Activate与Deactivate(出现、消失)

View与Region之间映射关系

在Prism中有两种方式来定义视图与Region之间的映射关系

View Discovery(视图发现)

View Injection(视图注入)

RegisterViewWithRegion(向区域注册视图)是View Discovery方式,View并没有真正载入Region中。此时Activate与Deactivate无效

AddToRegion和Add是View Injection方式,Activate与Deactivate有效。

RegisterViewWithRegion 视图发现

通过Resolve获取到对象

可以将之前已经注册在容器里的内容,通过当前界面的构造函数直接注入进去

regionManager有一个regionname集合,集合里面就是之前在view界面里定义的那些

prism:RegionManager.RegionName="ContentRegion"

我们将他们从容器中取出来,将View注册给他们

csharp 复制代码
var regionManager = containerProvider.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion("ContentRegion", typeof(ViewA));

IRegionManager是一个区域管理,用于绑定区域和视图的,

向ContentRegion字符串对应的区域注册ViewA视图。到ContentRegion区域时,呈现ViewA

实例将添加到区域的视图集合里

AddToRegion 视图注入

csharp 复制代码
IRegionManager _regionManager; //定义变量
PrismUserControl1 viewA = new PrismUserControl1(); //new出一个类的对象
_regionManager.AddToRegion("ContentRegion", viewA);

Add 视图注入

csharp 复制代码
IRegionManager _regionManager; //定义变量
IRegion _region; //定义变量
_region = _regionManager.Regions["ContentRegion"]; //用Regions属性指定区域控件
PrismUserControl1 viewA=new PrismUserControl1();
_region.Add(viewA);
或者   Resolve解析视图
HeaderView headerView = container.Resolve<HeaderView>();
regionManager.Regions["HeaderRegion"].Add(headerView);

Region区域访问

添加显示视图

Add / Remove/ Activate/ Deactivate

Iregion region = _regionManager.Regions["RegionName"];

多适合在ItemsControl可同时显示多个视图

region.Add(viewInstance);

region.Remove(viewInstance);

多适合在ContentControl单独显示活动视图用

region.Activate(viewInstance);//激活

region.Deactivate(viewInstance);//失效

IRegionMemberLifetime接口

Prism还可以通过IRegionMemberLifetime接口的KeepAlive 布尔属性控制区域的视图的生命周期,IRegionMemberLifetime接口和IsNavigationTarget设置为false情况一样,当KeepAlive为false时,通过断点知道,重新导航回LoginMainContent页面时不会触发IsNavigationTarget方法,因此可以

知道判断顺序是:KeepAlive -->IsNavigationTarget

RegionContext

有很多场景可能需要在托管区域的视图和区域内的视图之间共享上下文信息。例如,类似主细节的视图显示一个业务实体并公开一个区域以显示该业务实体的附加详细信息。Prism使用一个名为RegionContext的概念在该区域的主机和该区域内加载的任何视图之间共享一个对象,如下图所示。


Module(模块)

模块化系统而言, 每个模块当中都有N个用户控件组成, 然后模块向区域当中注册视图

像之前的程序,一般都放在一个项目里

但是Prism的Module模块

分成了很多小的模块,一个启动页对应多个小模块,灵活分配 ,扩展性很强

模块概念

PRISMP atterns R eferencing I njection, S ervices, and M odularity)框架中,模块是一个核心概念,它帮助开发者构建高度可维护、可测试和可扩展的 WPF 或 Silverlight 应用程序。

简单来说,一个 PRISM 模块就是应用程序中一个独立的、可部署的功能单元。你可以把它想象成一个自包含的"特性"或"领域"。


Prism模块的特点

以下是 Prism模块的一些关键特点:

  • 解耦 (Decoupling):模块之间相互独立,它们不知道彼此的存在。这极大地降低了应用程序各部分之间的依赖,使得修改或替换某个模块不会影响到其他部分。
  • 可重用性 (Reusability):由于模块是独立的,它们可以轻松地在不同的应用程序中重用,或者在同一个应用程序的不同部分中被多次加载。
  • 可发现性 (Discoverability):PRISM 提供了模块发现机制,允许应用程序在运行时动态加载模块,而不是在编译时硬编码。这使得应用程序可以更灵活地进行更新和扩展。
  • 独立开发和部署 (Independent Development and Deployment):不同的团队可以并行开发不同的模块,而不会相互干扰。模块也可以独立部署,这意味着你可以只更新应用程序的某个特定功能,而无需重新部署整个应用程序。
  • 关注点分离 (Separation of Concerns):模块鼓励将应用程序的不同功能区域清晰地划分开来。例如,一个电子商务应用可能有"产品管理"模块、"订单处理"模块和"用户认证"模块。

模块是如何工作的?

在 Prism中,模块通常通过以下方式与应用程序集成:

  1. 模块定义 (Module Definition) :每个模块都是一个实现了 IModule 接口的类。这个接口通常只有一个 Initialize() 方法,用于在模块加载时执行初始化逻辑,例如注册视图、服务或导航条目。
  2. 模块目录 (Module Catalog):这是一个保存所有可用模块信息的地方。应用程序启动时,PRISM 会根据模块目录来发现和加载模块。模块目录可以通过多种方式配置,例如 XAML 文件、配置文件或者代码。
  3. 区域 (Regions):模块通常会将它们的视图放置在应用程序的"区域"中。区域是 UI 中的一个命名占位符,不同的模块可以将内容注入到同一个区域中。这使得模块可以在不直接了解主应用程序 UI 结构的情况下贡献自己的 UI。
  4. 依赖注入 (Dependency Injection) :PRISM 强烈依赖于依赖注入容器(如 Unity 或 Autofac)。模块在 Initialize() 方法中注册自己的服务和视图,使得其他模块或主应用程序可以通过容器来获取这些实例,进一步促进了模块之间的解耦。

为什么要使用 Prism模块?

使用 Prism模块化可以带来以下好处:

  • 提高开发效率:团队可以并行工作在不同的模块上。
  • 简化维护:更容易隔离和修复问题,因为功能被划分到独立的单元。
  • 增强可测试性:独立的模块更容易进行单元测试和集成测试。
  • 支持大型和复杂应用:Prism模块化模式特别适用于构建企业级、大规模的 WPF 或 Silverlight 应用程序。
  • 提高应用程序的灵活性和可伸缩性:应用程序可以根据需要动态加载或卸载功能。

总而言之,Prism 模块是构建结构清晰、易于管理和扩展的 WPF/Silverlight 应用程序的基石。它们提供了一种强大的方式来组织代码,促进了团队协作,并使得应用程序能够随着时间的推移而发展


Modules-AppConfig

主程序跟module没有直接的关系,通过配置文件Config加载

csharp 复制代码
public class ModuleAModule : IModule //继承一个imodule接口
{
   public void OnInitialized(IContainerProvider containerProvider)
   {
      var regionManager = containerProvider.Resolve<IRegionManager>();
      //是把容器当作一个工厂,走向它说"我需要一个IRegionManager类型的实例化对象"。
      regionManager.RegisterViewWithRegion("ContentRegion", typeof(ViewA));
    }
//IRegionManager是一个区域管理,用于绑定区域和视图的,
//而这里就在做把ViewA使用regionManager的RegisterViewWithRegion()方法,
//向ContentRegion字符串对应的区域注册ViewA视图。

   public void RegisterTypes(IContainerRegistry containerRegistry)
   {
   }
}

App.xaml.cs
//重写CreateModuleCatalog(),返回一个ConfigurationModuleCatalog()对象。
protected override IModuleCatalog CreateModuleCatalog()
{
    return new ConfigurationModuleCatalog();
//使用 Prism.Modularity.ConfigurationStore 构建 ConfigurationModuleCatalog 的实例作为默认存储。
}

基于GitHub实例Modules加载方法

PrismApplicationBase中定义了两个三个抽象方法和若干个虚方法:

csharp 复制代码
/// <summary>
/// Creates the container used by Prism.
/// </summary>
/// <returns>The container</returns>
protected abstract IContainerExtension CreateContainerExtension();
/// <summary>
/// Used to register types with the container that will be used by your application.
/// </summary>
protected abstract void RegisterTypes(IContainerRegistry containerRegistry);
/// <summary>
/// Creates the shell or main window of the application.
/// </summary>
/// <returns>The shell of the application.</returns>
protected abstract Window CreateShell();

//其中CreateContainerExtension方法被PrismApplication实现了:

protected override IContainerExtension CreateContainerExtension()
{
	return new UnityContainerExtension();
}

所以App继承PrismApplication后,必须实现另外两个抽象方法:

RegisterTypes()和CreateShell()

**RegisterTypes()😗*程序启动时需要注入的类型

**CreateShell()😗*程序启动时,需要启动的主窗体

数据绑定:BindableBase

Prism框架中,提供了数据绑定基类Prism.Mvvm.BindableBase,可以方便的将普通属性,转换为依赖属性

创建模块:IModule接口

添加Module类,并实现Prism.Modularity.IModule接口,实现接口的模块,视为可以被Prism发现并加载的模块。

OnInitialized

通知模块已经初始化,通过注册类型将视图与写的区域相关联

RegisterTypes

将使用的容器注入类型 View,注册对象,程序启动是需要注入的类型,容器等级类型

csharp 复制代码
public class ModuleAModule : IModule
{
// 通知模块已被初始化。
public void OnInitialized(IContainerProvider containerProvider)
{
//通过注册类型将视图与区域关联。显示区域时
//此类型将使用服务定位器解析到具体实例中。
//实例将添加到区域的视图集合中
var regionManager = containerProvider.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion("ContentRegion",typeof(NavigationPage));
regionManager.RegisterViewWithRegion("Organization",typeof(Organization));
}

// 用于在您的应用程序将使用的容器中注册类型。
public void RegisterTypes(IContainerRegistry containerRegistry)
{
  containerRegistry.RegisterForNavigation<Shell>();
  containerRegistry.RegisterForNavigation<Register>();
}
}//containerRegistry 注入容器

不同模块切换

在加载完模块之后,不论是LoadModule还是AddModule之后,都要在RequestNavigate(navigatepath)通过导航切换

不同模块之间region不能相互注册,比如ViewB模块里的view不能在ViewA模块里注册

但是要在ViewB模块里向字符串区域注册view视图

  • CommandParamter的值要跟view名相同

MVVM(视图注入)

复制代码
prism:ViewModelLocator.AutoWireViewMode = "true"
等价于
new MainWindow().DataContext = new MainWindowViewModel()

各个框架之间都有各自的通知、绑定、事件聚合器等基础的功能, 而Prsim自带的依赖注入、容器、以及导航会话等功能

ViewModelBase(MVVMLight)

csharp 复制代码
public class TestViewModel : ViewModelBase
{
    private string _message;
    public string Message
    {
        get { return _message; }
        set { _message = value; RaisePropertyChanged(); }
     }
}

BindableBase

csharp 复制代码
public class TestViewModel : BindableBase
{
    private string _message;
    public string Message
    {
        get { return _message; }
        set { _message = value; RaisePropertyChanged(); }
    }
}

1.在属性值更改时发生。

2.引发此对象的 PropertyChanged 事件。

3.检查属性是否已经匹配所需值。设置属性并仅在必要时通知侦听器。

继承BindableBase

csharp 复制代码
例如: private string _value;
public string Value
{
     get { return _value; }
     set { _value = value; }
}
需要变更为
private string _value ;
public string Value
{
    get { return _value; }
    set { SetProperty( ref _value, value);
}

方法SetProperty() (MVVMlight里面的 通知属性类似)

也可以不只是单纯的通知 View页面的Value 也可以在SetProperty 通知另外一个属性也变更

set { SetProperty( ref _value, value,"另外属性值");// 特殊情况

Command

MVVMLight

csharp 复制代码
    public class TestViewModel : ViewModelBase
    {
        public RelayCommand SendCommand { get; set; }
        public RelayCommand<string> SendMessageCommand { get; set; }
    }

Prism

csharp 复制代码
    public class TestViewModel : ViewModelBase
    {
        public DelegateCommand SendCommand { get; set; }
        public DelegateCommand<string> SendMessageCommand { get; set; }
    }

Command绑定

必须要一个 属性 那个属性必须是 ICommand类型的

例:

csharp 复制代码
public ICommand ClickCommand
{
       get =>new DelegateCommand<object>("!!! 这边需要一个 无返回值的方法用来实现Command的内容的方法 !!! ");;
}

并且这个方法 必须 new DelegateCommand 是要拥有 object这个类型参数的 一个无返回值方法

csharp 复制代码
例如: 
private void ClickButton(object a)

{
	........
}

这个 object a 的值 会是 CommandParameter 绑定的参数 下面给的是 123 那这边执行之后 这个 object a 就是123

前台在Button 上绑定Command

<Button Command="{Binding ClickCommand}"

CommandParameter="123" /> CommandParameter 绑定的给的事件参数

ObservesCanExecute

可以通过这个 来判断按钮的执行情况 当他返回false 的时候 我们绑定的这个按钮 就不能执行//变灰了

例如:

csharp 复制代码
public ICommand ClickCommand

{

get => new DelegateCommand<object>(ClickButton).ObservesCanExecute() => IsEnable);

}

还要在Value属性那边使用特殊重载 使用 ()=>{this.IsEnable=Value!="";};

例如:

csharp 复制代码
private bool _isEnable;

public bool IsEnable

{

get { return _isEnable; }

set { SetProperty(ref _isEnable, value); }

}

CompositeCommand

复合命令可以注册多个子命令


Event Aggregator(事件聚合器)

实现原理是观察者模式,观察者模式也就是常说的发布/订阅模式。

类似于MVVMLight中的Messenger,进行消息的订阅发布;区别:Messenger是通过Token或者Type来指定消息接收函数的,Prism是通过继承于PubSubEvent 的类型来区分的。

事件聚合的过程有点像收听广播,首先要有个固定的频率,然后内容就会在这个频率上广播出去,至于有没有人收听,广播电台是不知道的,它只是把内容播送了出去。而其他的人想听广播也不用跑到广播电台,只要知道频率,收听这个频率就可以了。联系广播电台和听众的就是这个频率。

在事件聚合的过程中,**事件发布方(publisher)**相当于广播电台,**事件接收方(Subscriber)**相当于听众,而事件自然就相当于频率了。

使用EventAggregation很简单,只需要知道一个接口和一个类基本上就足够了。接口是IEventAggregator,类是CompositePresentationEvent。

要想发布或订阅事件,自然得先要有事件,所以第一件工作就是要定义事件。Prism提供了一个事件基类CompositePresentationEvent,自定义的事件只需要继承这个类就可以了,泛型代表的是事件发生过程中需要传递的参数类型。如:

csharp 复制代码
public class ReceiveNewEmailEvent : CompositePresentationEvent<MailViewModel>
{
}

模块与模块之间相互独立,如果需要交互,可以通过事件聚合器IEventAggregator,采用事件的订阅和发布进行通信。

事件订阅步骤:

定义事件

定义一个类,继承自Prism.Events.PubSubEvent泛型类

csharp 复制代码
public class SavedEvent : PubSubEvent<string> { }

事件发布

通过事件聚合器的Publish方法进行发布。

csharp 复制代码
IEventAggregator.GetEvent<SavedEvent>().Publish("some value");
// IEventAggregator 事件聚合器

事件订阅

通过事件聚合器的Subscribe进行订阅

csharp 复制代码
IEventAggregator.GetEvent<SavedEvent>().Subscribe(.Subscribe(message=>
{
    //do something
});

选择接受消息

csharp 复制代码
eventAggregator.GetEvent<MessageSentEvent>().Subscribe(arg =>
{
      //do something
},ThreadOption.PublisherThread,false,
//设置条件为token等于"MessageListViewModel" 则接收消息
message => message.token.Equals(nameof(MessageListViewModel)));

页面切换必不可少,Prism可以使用Navigation功能进行页面导航,涉及到切换界面验证,传递参数,返回上一页下一页等。

注册导航页面RegisterForNavigation

分别在各自的大模块中,注册

UserControl通过RegisterForNavigation方法注册为Navigation

csharp 复制代码
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
    containerRegistry.RegisterForNavigation<ViewA>();
    containerRegistry.RegisterForNavigation<ViewB>();
    containerRegistry.RegisterForNavigation(typeof(ViewC),"ViewC");
}

第一个参数是View的类型 ,第二个参数是ViewName(可省略,则默认为View类型的类名)上述代码也可以在App.cs里面写

导航视图RequestNavigate

通过IRegionManager.RequestNavigate进行视图导航的切换用于导航

_regionManager.RequestNavigate("ContentRegion", "ViewB");

第一个参数是RegionName ,第二个参数是我们前面注册的ViewName

将获取已经注册的Navigation并且绑定到Region上去

导航回调NavigationCallback

csharp 复制代码
private void Navigation(string page)
{
     region.RequestNavigate("ContentRegion", page,NavigationCallback);
}
private void NavigationCallback(NavigationResult result)
{
     Console.WriteLine(result.Context.Uri);
     Console.WriteLine(result.Error.Message);
     Console.WriteLine(result.Result.Value);
}

导航传参NavigationParameters

  • NavigationParameters:导航时传递参数
  • Action< NavigationResult >:导航结束时的回调(导航的最后执行)
csharp 复制代码
private DelegateCommand<string> _viewACommand;
public DelegateCommand<string> ViewACommand
{
    get
    {
         if (_viewACommand == null)
            _viewACommand = new DelegateCommand<string>((view) =>
          {
          // 导航到 ViewA
          // NavigationParameters 传值
            NavigationParameters param = new NavigationParameters();
            param.Add("value", "123");
            _regionManager.RequestNavigate("ContentRegion", "ViewB",
new Action<NavigationResult>((result) =>
            {}),param);});
         return _viewACommand;
    }
    set { _viewACommand = value; }
}

IConfirmNavigationRequest

csharp 复制代码
public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
{
    bool result = true;
if (MessageBox.Show("确认导航?", "温馨提示", MessageBoxButton.YesNo) == MessageBoxResult.No)
       result = false;
//通过回调当前返回的确认结果,决定是否启动该导航
       continuationCallback(result);
}
  • INavigationAware 接口的IsNavigationTargetOnNavigatedFromOnNavigatedTo方法中IsNavigationTarget,OnNavigatedFrom和OnNavigatedTo中形参NavigationContext对象的NavigationParameters属性
  • IConfirmNavigationRequest 接口的ConfirmNavigationRequest 形参NavigationContext对象的NavigationParameters属性
  • 区域导航的INavigateAsync接口的RequestNavigate方法赋值给其形参navigationParameters
  • 导航日志IRegionNavigationJournal接口CurrentEntry属性的NavigationParameters类型的Parameters属性(下面会介绍导航日志)

导航过程INavigationAware

  • OnNavigatedTo: 导航完成前, 此处可以传递过来的参数以及是否允许导航等动作的控制。可以理解成保存该页面的数据。
  • IsNavigationTarget: 调用以确定此实例是否可以处理导航请求。否则新建实例
  • OnNavigatedFrom: 当导航离开当前页时, 类似打开A, 再打开B时, 该方法被触发。

导航中允许我们传递参数, 用于在我们完成导航之前, 进行做对应的逻辑业务处理。这时候, 我们便可以在OnNavigatedTo方法中通过导航上下文中获取到传递的所有参数

csharp 复制代码
public void OnNavigatedTo(NavigationContext navigationContext)
{
     var id = navigationContext.Parameters.GetValue<int>("id");
     var name = navigationContext.Parameters["Name"].ToString();
}

比如要从logon导航到Creat页面的时候,logon退出导航要保存信息,导航到creat页面的时候获取logon对应信息

导航日志IRegionNavigationJournal

  • GoBack() : 返回上一页
  • CanGoBack : 是否可以返回上一页
  • GoForward(): 返回后一页
  • CanGoForward : 是否可以返回后一页

生命周期 IRegionMemberLifetime

Prism还可以通过IRegionMemberLifetime接口的KeepAlive 布尔属性控制区域的视图的生命周期,IRegionMemberLifetime接口和IsNavigationTarget设置为false情况一样,当KeepAlive为false时,通过断点知道,重新导航回LoginMainContent页面时不会触发IsNavigationTarget方法,因此可以

知道判断顺序是:KeepAlive -->IsNavigationTarget

Dialog(对话)

PrismApplicationBase

所有的一切都是从一个类开始PrismApplicationBase,在这个类中加载了Prism的所有功能。

首先介绍一下这个类,这是Startup,这个类中构建了所有的Prism功能和整体的框架。这个类中大多数的方法都是虚方法,可以重载加入自定义的一些功能,Prism也希望我们如此进行设计。

在整个Prism中,UnityContainer无处不再,它就是一个大的容器,保存着所有类,在需要的时候Resolver出来。

有两个字段,ContainerExtension就UnityContainer,当然也可以自定义其他的容器,在文章中都默认为UnityContainer容器,关于容器也给一个官方说明

ModuleCatalog是定义了加载Module的方式,模块是Prism的一大优势,给一张官方说明,意图胜千言

Prism注入

在了解注入之前,我们首先了解一下几个接口及类的关系,他们是:

IContainerProvider:抽象第三方IOC框架从容器中拿对象的方法

IContainerRegistry:抽象第三方IOC框架注册对象、类型到容器的方法

IContainerExtension:将注册对象、取对象统一的接口

IContainerExtension< T >:注册对象、取对象的泛型接口

UnityContainerExtension:第三方框架Unity整合到Prism的实现方式,运用了适配器模式将Unity整合到Prism中

IUnityContainer:Unity中自己容器的接口,Unity有自己去实现

由以上得出的结论是:

取对象IContainerProvider类型

注册对象IContainerRegistry类型

IContainerExtension类型既可以注册对象,也可以取对象

关系如下图所示:

代码方式的注入,我们只能通过构造函数去注入

IContainerExtension

IContainerExtension 是 Prism 框架中对依赖注入 (DI) 容器的抽象接口。它的存在是为了让 Prism 能够支持多种不同的 DI 容器(如 Unity、DryIoc、Autofac 等),而应用程序代码在与容器交互时,无需直接依赖于某个特定的具体容器实现。

这个接口定义了 Prism 框架在模块加载、服务注册和依赖解析过程中需要使用的最基本和最通用的容器操作。

IContainerExtension 的主要方法

  • RegisterInstance(T instance)

作用: 注册一个已经存在的对象实例到容器中。当请求 T 类型时,容器总是返回这个特定的实例。

典型用途: 注册单例服务、配置对象、或者那些在应用程序启动时就已经创建好的共享对象。

等同于 Unity 的 RegisterInstance。

  • RegisterSingleton<TFrom, TTo>()

作用: 注册一个类型映射,并将其生命周期管理为【单例(Singleton)】。这意味着当容器第一次被请求 TFrom 类型时,它会创建 TTo 类型的一个实例,并在后续所有请求中返回同一个实例。

典型用途: 注册应用程序生命周期内的共享服务,如日志服务、配置管理器等。

等同于 Unity 的

RegisterType<TFrom, TTo>(new ContainerControlledLifetimeManager())。

  • RegisterScoped<TFrom, TTo>()

(Prism 7.0 + 引入,可能不是所有旧版本都有)

作用: 注册一个类型映射,并将其生命周期管理为作用域(Scoped)。这意味着在同一个作用域内,每次请求 TFrom 都会返回 TTo 的同一个实例;但如果在不同的作用域,则会创建新的实例。

典型用途: 在 ASP.NET Core Web 应用中,每个 HTTP 请求通常是一个作用域;在桌面应用中,一个模块或一个特定的 UI 流程可能定义为一个作用域。

等同于 ASP.NET Core DI 中的 AddScoped。

  • RegisterManyForOpenGeneric(Type serviceType, params Type[] implementationTypes) (Prism 7.0 + 引入)

作用: 注册开放泛型类型。例如,注册 IRepository<> 到 Repository<>。

典型用途: 注册那些带有泛型参数的通用服务或仓库。

  • Register(Type from, Type to)

作用: 注册一个类型映射。当请求 from 类型时,容器会提供 to 类型的一个实例。默认情况下,这通常是【瞬态 (Transient) 生命周期】,意味着每次请求都会创建新的实例。

典型用途: 注册那些每次使用都需要新实例的服务,如某些视图模型。

等同于 Unity 的 RegisterType(from, to)。

  • IsRegistered(Type type) / IsRegistered()

作用: 检查某个类型是否已经被注册到容器中。

典型用途: 在进行条件性注册或避免重复注册时使用。

  • Resolve() / Resolve(Type type)

作用: 从容器中解析(获取)一个类型的实例。容器会负责创建该类型及其所有依赖项的实例。

典型用途: 在你需要手动获取一个实例(例如,在 Bootstrapper 中创建 Shell,或在某些不适合直接注入的场景)时使用。在大多数情况下,Prism 鼓励通过构造函数注入来获取依赖,而不是手动 Resolve。

等同于 Unity 的 Resolve。

  • CreateScope() (Prism 7.0+ 引入)

作用: 创建一个新的 DI 作用域。在这个作用域内,Scoped 注册的类型会共享同一个实例。

典型用途: 当你需要在一个特定的上下文(例如,一个复杂的用户操作流程,或者一个模块的生命周期)中管理一组对象的生命周期时。

  • IContainerExtension 的重要性

容器无关性: 它是 Prism 框架实现其"容器无关性"原则的关键。你可以在 Bootstrapper 中配置使用 Unity、DryIoc 或其他容器,而应用程序中其他部分的 DI 代码只需要与 IContainerExtension 接口交互,从而无需修改。

统一的注册和解析接口: 它提供了一套标准的 API 来执行最常见的 DI 操作,无论底层使用的是哪个具体的 DI 容器。

模块化支持: 在 Prism 模块的 Initialize() 方法中(或者你代码中的 RegisterTypes()),通常会收到 IContainerExtension 的实例,以便模块能够将自己的服务注册到主应用程序容器中。

IContainerRegistry(注册专用接口)

职责: 它的唯一职责是向 DI 容器注册类型、实例和工厂方法。它只提供用于"写入"容器的方法。

方法: 包含所有以 Register 开头的方法,例如:

Register<TFrom, TTo>() (瞬态注册)

RegisterSingleton<TFrom, TTo>()(单例注册)

RegisterInstance(T instance)(注册现有实例)

RegisterScoped<TFrom, TTo>()(作用域注册,如果底层容器支持)

RegisterForNavigation<TView, TViewModel>()(Prism 特有,用于导航)

使用场景:

Prism 模块的 RegisterTypes(IContainerRegistry containerRegistry) 方法: 这是其主要使用场景,模块在这里声明其提供的服务。

Bootstrapper 的 ConfigureContainer() 或 RegisterTypes() 方法: 用于在应用程序启动时注册核心服务。

特点: 你不能通过 IContainerRegistry 来"解析"(获取)容器中已注册的实例。这种限制是为了确保在注册阶段不会意外地触发实例的创建,或者导致不完整的依赖图。

IContainerExtension (完整容器抽象接口)

职责: 它是对底层 DI 容器的完整抽象,包含注册 (Register) 和解析(Resolve) 两种主要功能。它是 Prism 内部以及在需要访问容器的全部功能时的主要接口。

方法: 包含 IContainerRegistry 中的所有注册方法,以及:

Resolve() / Resolve(Type type)(从容器获取实例)

IsRegistered() / IsRegistered(Type type)(检查类型是否已注册)

CreateScope()(创建新的 DI 作用域,如果底层容器支持)

使用场景:

Prism 框架的内部实现: 例如,ModuleManager 在初始化模块时,需要既能注册(如果模块实现了旧的 IModule.Initialize() 方法),又能解析模块实例。

Bootstrapper 的 CreateContainerExtension() 方法的返回值: 你的 Bootstrapper 实现会返回一个 IContainerExtension 的具体实例(如 UnityContainerExtension),这样 Prism 就能通过这个抽象来操作容器。

高级或特定场景: 当你需要在代码的某个地方既要注册又要解析时(虽然在模块内部,推荐使用 IContainerRegistry 和 IContainerProvider 来隔离)。


相关NuGet

Prism.Core 是 Prism 的基础库,提供核心功能(事件聚合、命令、MVVM 基础功能等),适用于所有平台(如 WPF、Xamarin)。
Prism.Unity 依赖于 Prism.Core,并为 Prism 提供 Unity 依赖注入支持,模块化开发、区域管理,适用于 WPF 应用。

Prism.Core

是 Prism 框架的基础和核心库。它包含了 Prism 中与平台无关的、独立于任何特定 UI 框架(如 WPF、Avalonia、MAUI 等)的抽象和功能。

Prism.Core 主要提供以下核心功能:

  • IRegionManager 接口定义: 尽管具体的实现由 Prism.Wpf 或 Prism.Avalonia 提供,但 IRegionManager 的接口定义本身是在 Prism.Core 中。这意味着你可以在你的核心业务逻辑和 ViewModel 中,不依赖具体 UI 平台地使用 IRegionManager。
  • DelegateCommand (委托命令): 这是 Prism 提供的 ICommand 实现,用于在 MVVM 模式中绑定 UI 操作到 ViewModel 中的方法。
  • EventAggregator (事件聚合器): 用于解耦组件之间的通信。
  • BindableBase (可绑定基类): 提供 INotifyPropertyChanged 接口的实现,简化 ViewModel 的属性更改通知。
  • 模块化相关接口和抽象: 例如 IModule 等,用于构建可插拔的应用程序。
  1. Prism.Core: 用于提供上述核心的、平台无关的 Prism 功能。你的 ViewModel 和大部分业务逻辑所在的类库会引用它。
  2. Prism.Avalonia: 这是 Prism 针对 Avalonia UI 框架的特定实现。它将 Prism.Core 的抽象(如 IRegionManager 的接口)与 Avalonia 的 UI 元素(如 ContentControl、ItemsControl 等)连接起来,实现区域的实际管理和视图注入。你的 Avalonia UI 项目(包含 XAML 和 View 的项目)会引用它。
  3. 一个依赖注入容器适配器(例如 Prism.Unity 或 Prism.DryIoc): 用于集成你选择的依赖注入容器(如 Unity、DryIoc 等)到 Prism 应用程序中。这个也会引用到你的主启动项目或相关核心库中。

Prism.Core 是 Prism 框架的基石,无论你使用 WPF 还是 Avalonia,它都是不可或缺的一部分。Prism.Avalonia 是建立在 Prism.Core 之上,并专门为 Avalonia UI 提供平台适配的库。所以,在 Avalonia 项目中引用 Prism.Core 是完全正确且必要的做法。


Prism.Unity

是 Prism 框架与 Unity 依赖注入容器 集成的特定实现。

  1. 将 Unity 容器集成到 Prism 的应用程序生命周期中: 允许你在 Prism 应用程序的启动和模块加载过程中使用 Unity 容器进行服务注册和解析。
  2. 提供 Unity 特定的服务解析器: 使得 Prism 的各项功能(如区域管理、导航等)能够从 Unity 容器中解析其依赖项。

就像 Prism.Core 是平台无关的一样,Prism.Unity 作为一个依赖注入容器适配器,它本身也是平台无关的。它不依赖于任何特定的 UI 框架(WPF, Avalonia 等)。它只关心如何与 Unity 容器进行交互,以及如何将这种交互桥接到 Prism 的核心功能中。

在 Avalonia 项目中使用 Prism.Unity 的典型配置:

如果你选择 Unity 作为你的 Avalonia 应用的依赖注入容器,你会在你的 Avalonia 主项目(通常是应用程序的启动项目)中同时引用:

  1. Prism.Core: 提供 Prism 核心功能。
  2. Prism.Avalonia: 提供 Prism 与 Avalonia UI 的集成。
  3. Prism.Unity: 将 Unity 容器集成到你的 Prism Avalonia 应用程序中。
  4. Unity.Container 和 Unity.Abstractions: Unity 容器本身的库。

初始化

使用Prism.Unity构建一个Prism应用

需要说明的是:老版本的Prism,构建WPF应用是新建一个类,继承自UnityBootstrapper。但是新版本的已经不建议这么做了,而是App类直接继承自PrismApplication,具体可以查看新版本中对UnitBootstrapper的注释说明:

源码地址:https://github.com/PrismLibrary/Prism/blob/master/Source/Wpf/Prism.Unity.Wpf/Legacy/UnityBootstrapper.cs

安装Prism.Unity后,Prism.Unity直接或间接依赖了这么多的包(Prism.Core, Prism.Wpf, Unity.Container)

如果我们要通过配置文件注入需要引用

Prism框架流程

PrismApplication解析

应用程序级别的组件和服务,它也被用来

配置和初始化modulecatalog和Shell的View和ViewModel。

Bootstrapper 是一个通过Prism Library来初始化一个应用程序的,我们来看看一个Bootstrapping process的基本步骤:

Modules :是能够独立开发、测试、部署的功能单元,Modules可以被设计成实现特定业务逻辑的模块(如Profile Management),也可以被设计成实现通用基础设施或服务的模块。Shell 是宿主应用程序(host application),modules将会被load到Shell中 。Shell定义了应用程序的整体布局和结构,而不关心寄宿其中的Module,Shell通常实现通用的application service和infrastructure,而应用的逻辑则实现在具体的Module中,同时,Shell也提供了应用程序的顶层窗口。

Services:是用来实现非UI相关功能的逻辑,例如logging、exceptionmanagement、dataaccess。Services可以被定义在应用程序中或者是Module中,Services通常被注册在依赖注入容器中,使得其它的组件可以很容易的定位这个服务。

Container:注入服务、其他模块依赖。

App继承PrismApplication后,必须实现另外两个抽象方法:RegisterTypes()CreateShell()

  • RegisterTypes(): 程序启动时需要注入的类型
  • CreateShell(): 程序启动时,需要启动的主窗体

Prism.Unity的注入

在了解注入之前,我们首先了解一下几个接口及类的关系,他们是:

IContainerProvider:抽象第三方IOC框架从容器中拿对象的方法

IContainerRegistry:抽象第三方IOC框架注册对象、类型到容器的方法

IContainerExtension:将注册对象、取对象统一的接口

IContainerExtension:注册对象、取对象的泛型接口

UnityContainerExtension:第三方框架Unity整合到Prism的实现方式,运用了适配器模式将Unity整合到Prism中

IUnityContainer:Unity中自己容器的接口,Unity有自己去实现

取对象用IContainerProvider类型

注册对象用IContainerRegistry类型

IContainerExtension类型既可以注册对象,也可以取对象

注入Prism.Wpf已有的类型

上面在分析PrismApplication的时候,PrismApplicationBase类有重写OnStartup方法。查看源码,我们发现OnStartup方法里调用了RegisterRequiredTypes这个方法,这个方法如下

源码地址:https://github.com/PrismLibrary/Prism/blob/master/Source/Wpf/Prism.Wpf/PrismApplicationBase.cs

csharp 复制代码
/// <summary>
/// Registers all types that are required by Prism to function with the container.
/// </summary>
/// <param name="containerRegistry"></param>
protected virtual void RegisterRequiredTypes(IContainerRegistry containerRegistry)
{
    containerRegistry.RegisterInstance(_containerExtension);
    containerRegistry.RegisterInstance(_moduleCatalog);
    containerRegistry.RegisterSingleton<ILoggerFacade, TextLogger>();
    containerRegistry.RegisterSingleton<IDialogService, DialogService>();
    containerRegistry.RegisterSingleton<IModuleInitializer, ModuleInitializer>();
    containerRegistry.RegisterSingleton<IModuleManager, ModuleManager>();
    containerRegistry.RegisterSingleton<RegionAdapterMappings>();
    containerRegistry.RegisterSingleton<IRegionManager, RegionManager>();
    containerRegistry.RegisterSingleton<IEventAggregator, EventAggregator>();
    containerRegistry.RegisterSingleton<IRegionViewRegistry, RegionViewRegistry>();
    containerRegistry.RegisterSingleton<IRegionBehaviorFactory, RegionBehaviorFactory>();
    containerRegistry.Register<IRegionNavigationJournalEntry, RegionNavigationJournalEntry>();
    containerRegistry.Register<IRegionNavigationJournal, RegionNavigationJournal>();
    containerRegistry.Register<IRegionNavigationService, RegionNavigationService>();
    containerRegistry.Register<IDialogWindow, DialogWindow>(); //default dialog host
}

这样,我们通过Resolve获取到的对象,都可以将以上已经注册在容器里的对象直接通过构造函数注入进去。

csharp 复制代码
protected override Window CreateShell()
{
	return Container.Resolve<MainWindow>();
}

通过Resolve获取到的对象,我们就可以在vm的构造函数的方法参数中,添加已经注册到容器中的任意个数类型对象,这些类型都将自动注入到vm

1.通过接口类型来注册:

在App.cs中的RegisterTypes方法中,写入如下代码:

csharp 复制代码
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
	containerRegistry.Register<IViewModel, ViewModel>();
}

这样,我们任何时候,通过容器Resolve得到的IViewModel类型都是ViewModel类型

Register<TFrom,TTo>注册的是**非单例(瞬态)**的对象,也就是每次Resolve的时候,容器每次帮我们创建了一个新对象。

如果需要容器每次给我们的是同一个对象,就需要用RegisterSingleton<TFrom, TTo>

csharp 复制代码
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
    containerRegistry.RegisterSingleton<IPerson, Man>();
}

 public MainWindow(IContainerExtension container)
 {
     InitializeComponent();
     this._container = container;

     IPerson person1 = container.Resolve<IPerson>();//man
     IPerson person2 = container.Resolve<IPerson>();//man
     bool result=person1==person2//person1和person2是同一个对象
}

以上两种类型均可以按照名称来注册类型

csharp 复制代码
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
	IPerson p1= containerRegistry.Register<IPerson, Man>("man");
}

public MainWindow(IContainerExtension container)
 {
     InitializeComponent();
     this._container = container;

     IPerson person = container.Resolve<IPerson>("man");//man
}

2.通过实例来注册:

通过实例的方式注册的对象属于单例

通过实例注册,将实例放入容器,可以按照名称来注册这个实例,也可以按照类型来注册这个实例

csharp 复制代码
// 通过名称来注册实例:
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
	IPerson person = new Man();
    containerRegistry.RegisterInstance<IPerson>(person,"man");
}

// 通过类型注册来注册实例:
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
	IPerson person = new Man();
    containerRegistry.RegisterInstance<IPerson>(person);
}

3.通过配置文件app.config注入类型到容器

通过配置文件注册,需要引用Unity.Configuration

具体通过配置文件注入,请参考Unit.Configuration里的测试用例:

https://github.com/unitycontainer/configuration/tree/master/tests/ConfigFiles

csharp 复制代码
<?xml version="1.0" encoding="utf-8" ?>
<configuration>

  <configSections>
    <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Unity.Configuration"/>
  </configSections>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
  </startup>
  <unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
    <container>
      <register type="SimplePrismAppTest.Model.IPerson,SimplePrismAppTest"
                mapTo="SimplePrismAppTest.Model.Man,SimplePrismAppTest" name="A"></register>
      <register type="SimplePrismAppTest.Model.IPerson,SimplePrismAppTest"
               mapTo="SimplePrismAppTest.Model.Woman,SimplePrismAppTest" name="B"></register>
      <register type="SimplePrismAppTest.Model.Animal,SimplePrismAppTest"
                mapTo="SimplePrismAppTest.Model.Animal,SimplePrismAppTest">
        <constructor>
          <param name="owner" >
            <dependency name="A" />
          </param>
        </constructor>
      </register>
    </container>
  </unity>
</configuration>

protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
	UnityConfigurationSection section (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
	section.Configure(containerRegistry.GetContainer());
}

// 取值
public MainWindow(IContainerExtension container)
{
    InitializeComponent();
    this._container = container;

    IPerson person1 = container.Resolve<IPerson>("A");//man
    IPerson person2 = container.Resolve<IPerson>("B");//woman
    Animal animal = container.Resolve<Animal>();
    bool result = animal.BelongPerson.Sex == person1.Sex;//true,animal的BelongPerson注入的是A
}

Prism.WPF

Prism.WPF 是 Prism 框架专门为 WPF (Windows Presentation Foundation) 应用程序提供的平台适配器库。它是 Prism 家族中的一员,旨在帮助开发者构建基于 WPF 的、大型、复杂且易于维护和测试的应用程序。

  1. WPF 特定实现:
    1. 它包含了将 Prism 核心概念(如区域)映射到 WPF UI 元素上的具体实现。例如,RegionAdapter 会将 WPF 的 ContentControl、ItemsControl 等控件转换为 Prism 的可管理区域。
    2. 它提供了 WPF 特定视图导航的机制。
  2. 依赖 WPF API:
    1. Prism.WPF 内部会引用和使用大量 WPF 框架的 API 和类。这意味着它是一个高度平台相关的库,只能在基于 WPF 的应用程序中使用。
  3. 与 Prism.Core 的关系:
    1. Prism.WPF 是建立在 Prism.Core 之上的。Prism.Core 提供通用的接口和抽象(例如 IRegionManager 的接口、DelegateCommand、EventAggregator),而 Prism.WPF 则提供这些接口在 WPF 环境下的具体实现。
  4. 与依赖注入容器的关系:
    1. Prism.WPF 本身不包含依赖注入容器。它需要配合一个 Prism 提供的容器适配器(如 Prism.UnityPrism.DryIocPrism.Autofac)来使用你选择的依赖注入容器。

Prism.DryIoc.Avalonia

Prism.DryIoc.Avalonia 是 Prism 框架生态系统中的一个 NuGet 包,它的主要目的是:

  1. 整合 Prism 的核心功能。
  2. 整合 Prism 对 Avalonia UI 的适配。
  3. 整合 Prism 对 DryIoc 依赖注入容器的适配。

简而言之,它是一个集成了 Prism、Avalonia 和 DryIoc 三者功能的便捷包。它将所有必要的依赖项(Prism.Core、Prism.Avalonia、Prism.DryIoc、以及 DryIoc 自身的库)捆绑在一起,方便开发者一次性安装。

Prism.DryIoc.Avalonia 在 Avalonia 项目中的使用

如果你选择使用 DryIoc 作为你的依赖注入容器,并且你的 UI 是 Avalonia,那么 Prism.DryIoc.Avalonia 就是你应该引用的首选包。

当你安装 Prism.DryIoc.Avalonia 后,NuGet 包管理器通常会自动帮你拉取以下依赖:

  • Prism.Core
  • Prism.Avalonia
  • Prism.DryIoc
  • DryIoc.dll (或其他 DryIoc 相关的核心库)
相关推荐
毕设源码-郭学长2 小时前
【开题答辩全过程】以 基于Java的星星儿童救助帮扶系统为例,包含答辩的问题和答案
java·开发语言
清晓粼溪2 小时前
SpringBoot3-02:整合资源
java·开发语言·spring boot
catchadmin2 小时前
如何创建和使用 Shell 脚本实现 PHP 部署自动化
开发语言·自动化·php
小尧嵌入式2 小时前
音视频入门基础知识
开发语言·c++·qt·算法·音视频
CoderYanger2 小时前
C.滑动窗口-求子数组个数-越短越合法——3134. 找出唯一性数组的中位数
java·开发语言·数据结构·算法·leetcode
ckhcxy2 小时前
继承和多态(二)
java·开发语言
raoxiaoya2 小时前
用golang开发AI Agent项目,有哪些框架可以选择
开发语言·人工智能·golang
老王熬夜敲代码2 小时前
ref关键字
开发语言·c++
程序员爱钓鱼2 小时前
Node.js 编程实战:深入理解回调函数
后端·node.js·trae