Prism区域和模块化(Region & Module)
Prism 一直是围绕依赖注入构建的。这有助于您构建可维护和可测试的应用程序,并帮助您减少或消除对静态和循环引用的依赖。
Prism区域概念(Region)
什么是区域(Region)
区域(Region)作为Prism当中模块化的核心功能,其主要目的是弱化了模块与模块之间的耦合关系。在普遍的应用程序开发中,界面上的元素及内容往往被固定。如下图所示:假设应用程序包含Header、Menu、Content内容
因此我们可以为这个页面设计一些元素,例如:
Menu可以设置ListBox;
Content可以放置一个ContentControl;
Header可以放置一些ToolBar;
prism中,可以不再为其固定内容,从而有了区域的概念;现在,将页面每个部分定于一个唯一区域(Region),那么在运行时,我们可以对每个区域动态设置内容。
定义Region方式(RegionManager)
Prism给出的额区域定义有两种方式:
RegionManager.RegionName(XAML)
RegionManager.SetRegionName(Code)
第一种:在XAML中定义Region
运行结果如下所示:
第二种:在Code中定义Region
RegionManager除了定义区域,还有以下功能:
- 维护区域集合 public IRegionCollection Regions
- 提供对区域的访问 regionManager.Regions["Content"];
- 合成视图 View Composition
- 区域导航 Region Navigation
- 定义区域
区域适配器(RegionAdapter)
实际上,Prism内置了几个区域适配器,所以我们可以在ContentControl当中定义区域,实际可以在任何元素上定义区域,如果定义的范围不在官方提供的默认适配器当中,则会引发异常。
官方提供的适配器类型:
- ContentControlRegionAdapter 此适配器适应类型 System.Windows.Controls.ContentControl 和派生类的控件,例如 ContentControl 。
- SelectorRegionAdapter 该适配器适配从类派生的控件 System.Windows.Controls.Primitives.Selector ,例如 TabControl 控件。
- ItemsControlRegionAdapter 此适配器适应类型 System.Windows.Controls.ItemsControl 和派生类的控件,例如 Toolbar/Ribbon 控件。
对于如StackPanel等其他控件,区域适配器需要自己编写,编写过程如下:
Prism模块化概念(Module)
什么是模块(Module)
本质上来说,对于一个应用程序而言,特定的所有View、Logic、Service等都可以独立存在。那么意味着,每个独立的功能我们都可以称之为模块。而往往实际上,我们在一个项目当中,他的结构通常是如下所示
所有的模块都在一个项目当中,这使得应用程序当中,我们难以区分单独的模块,他们似乎变成了一个整体。
当我们开始考虑划分模块之间的关系时,采用新的模块化解决方案,他的结构将变成如下所示:
创建模块实例
创建Module实际上是将模块独立与类库存在,模块实现IModule接口,主程序通过加载类库添加模块。以下步骤:
添加ModuleAModule类实现IModule接口
模块生命周期
Prism 中的模块加载过程包括以下顺序:
- 注册模块(Registering modules) 是通过在类内部实现 IModule 接口来创建的。
- 发现模块(Discovering modules) 在运行时为特定应用程序加载的模块在模块目录中定义。目录包含有关要加载的模块的信息,例如它们的位置和加载顺序。
- 加载模块(Loading modules) 包含模块的程序集被加载到内存中。
- 初始化模块(Initializing modules) 这意味着创建模块类的实例,通过 IModule 接口调用它们的 RegisterTypes 和 OnInitialized 方法,完成模块初始化。
主程序配置模块目录的方式
- Code(代码方式)
- App.config(配置文件)
- Disk/Directorty(磁盘路径)
- XAML(XAML定义)
视图注入(View Injection)
应用程序模块加载后,每个子模块中的视图可以独立的进行依赖注入。再使用IRegionManager来实现页面导航。
1.利用Region进行导航功能。
2.使用Module将应用程序模块化(若无模块化,则不需要)。
3.将独立模块的视图、服务使用注入到容器中。
依赖注入(Dependency Injection)
Prism项目中的 App 继承于 PrismApplication ,必须要重写 CreateShell() 和 RegisterTypes() 方法,其中 RegisterTypes()用于依赖注入容器,该函数使用 IContainerRegistry 类型的对象将用户自定义的对象注入容器。
依赖注入的几种方法:
- Register:每一次解析都会创建一个实例
- RegisterInstance:将一个服务实例注册到容器中
- RegisterSingleton :整个应用程序生命周期以内只创建一个实例(单例)
Prism模块加载方式
新建两个项目用于测试
MainWindow中定义按钮和区域,点击按钮后将ModuleA项目中的ViewA加载到MainWindow区域中
MainWindow
<Window x:Class="Zhaoxi.PrismModule.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Zhaoxi.PrismModule"
xmlns:p="http://prismlibrary.com/"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<DockPanel>
<StackPanel Width="200">
<Button Content="打开ViewA" Command="{Binding OpenCommand}"/>
<Button Content="打开ViewB"/>
</StackPanel>
<ContentControl p:RegionManager.RegionName="MainRegion"/>
</DockPanel>
</Window>
MainWindowViewModel
public class MainWindowViewModel
{
public ICommand OpenCommand { get; set; }
public MainWindowViewModel(
IRegionManager regionManager,
IModuleManager moduleManager)
{
OpenCommand = new DelegateCommand(() =>
{
moduleManager.LoadModule("AAA");
regionManager.RequestNavigate("MainRegion", "ViewA");
});
}
}
ViewA
<UserControl x:Class="Zhaoxi.PrismModule.ModuleA.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:Zhaoxi.PrismModule.ModuleA"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<TextBlock Text="ViewA" Foreground="Orange" FontSize="30"/>
</Grid>
</UserControl>
AModule
public class AModule : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<ViewA>();
}
}
Startup
public class Startup : PrismBootstrapper
{
protected override DependencyObject CreateShell()
{
return Container.Resolve<PrismModule.MainWindow>();
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
}
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
}
}
public partial class App : Application
{
public App()
{
new PrismLesson.Startup().Run();
}
}
程序内直接加载——ConfigureModuleCatalog
添加ModuleA项目的引用
在Startup类中重写ConfigureModuleCatalog方法
两种方式等价,第一种方式的实际执行与方式二类似
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
// 第一种方式
moduleCatalog.AddModule<AModule>();
// 第二种方式
Type type = typeof(AModule);
moduleCatalog.AddModule(new ModuleInfo
{
ModuleName = type.Name,
ModuleType = type.AssemblyQualifiedName
});
}
配置文件加载——App.config配置
取消ModuleA项目的引用,实现项目之间的解耦
配置App.config文件
moduleType与上一方法中ModuleType取出的数据一致
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<!--<section name="引用的别名" type="命名控件中具体的方法路径, 命名控件"/>-->
<section name="modules" type="Prism.Modularity.ModulesConfigurationSection, Prism.Wpf"/>
</configSections>
<modules>
<!--assemblyFile="项目名称"
moduleName="类名称"
moduleType="模块信息"-->
<module assemblyFile="Zhaoxi.PrismModule.ModuleA.dll"
moduleName="AModule"
moduleType="Zhaoxi.PrismModule.ModuleA.AModule, Zhaoxi.PrismModule.ModuleA, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</modules>
</configuration>
Startup类中重写CreateModuleCatalog方法
protected override IModuleCatalog CreateModuleCatalog()
{
// 第三种方式
// App.config
// 这里可以不用引用对应Moduler程序集
return new ConfigurationModuleCatalog();
}
两个项目分别执行生成指令后将ModuleA的Dbug目录下生成的dll复制到PrismModule的Dbug目录下
自动生成到指定目录下的方法
1.修改项目属性中的程序生成路径
2.使用生成事件,在项目生成成功后自动执行CMD指令将文件复制到指定位置
xcopy $(TargetPath) $(SolutionDir)Zhaoxi.PrismModule\bin\Debug\net7.0-windows\ModulePath\$(TargetFileName) /y
xcopy:CMD指令复制
TargetPath:源路径
SolutionDir:目标路径
TargetFileName:文件名称
配置文件加载——Xaml配置
项目中创建Xaml文件,并将生成操作改为资源
ModuleName和ModuleType与App.config配置一致
<?xml version="1.0" encoding="utf-8" ?>
<p:ModuleCatalog xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:p="clr-namespace:Prism.Modularity;assembly=Prism.Wpf">
<p:ModuleInfo ModuleName="AModule"
ModuleType="Zhaoxi.PrismModule.ModuleA.AModule, Zhaoxi.PrismModule.ModuleA, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</p:ModuleCatalog>
Startup类中重写CreateModuleCatalog方法
protected override IModuleCatalog CreateModuleCatalog()
{
// 第三种方式
// App.config
// 这里可以不用引用对应Moduler程序集
//return new ConfigurationModuleCatalog();
// 第四种方式
// XML文件配置
// 所有需要加载的Module程序集必须由当前程序集引用
// 如果引用,会出现以下异常:
// Prism.Modularity.ModuleTypeLoaderNotFoundException:“There is currently no moduleTypeLoader in the ModuleManager that can retrieve the specified module.”
//return new XamlModuleCatalog(".\\ModuleConfig.xml");
return new XamlModuleCatalog("pack://application:,,,/ZHaoxi.PrismModule;component/ModuleConfig.xml");
}
此方法需要引用程序集,无法实现项目之间的解耦
自动扫描目录加载
可以使用CMD指令在生成事件后自动复制到指定路径下
xcopy $(TargetPath) $(SolutionDir)Zhaoxi.PrismModule\bin\Debug\net7.0-windows\ModulePath\$(TargetFileName) /y
Startup类中重写CreateModuleCatalog方法
返回一个DirectoryModuleCatalog对象,对象中的ModulePath属性给定一个需要扫描的路径
protected override IModuleCatalog CreateModuleCatalog()
{
// 第三种方式
// App.config
// 这里可以不用引用对应Moduler程序集
//return new ConfigurationModuleCatalog();
// 第四种方式
// XML文件配置
// 所有需要加载的Module程序集必须由当前程序集引用
// 如果引用,会出现以下异常:
// Prism.Modularity.ModuleTypeLoaderNotFoundException:“There is currently no moduleTypeLoader in the ModuleManager that can retrieve the specified module.”
//return new XamlModuleCatalog(".\\ModuleConfig.xml");
//return new XamlModuleCatalog("pack://application:,,,/ZHaoxi.PrismModule;component/ModuleConfig.xml");
// 第五种方式(推荐)
return new DirectoryModuleCatalog()
{
// 配置将在扫描的目录
ModulePath = ".\\ModulePath"
};
}
Module按需加载
在Module中添加特性
ModuleName:标签名
OnDemand:为true时,在项目运行跳过加载次模块
[Module(ModuleName = "AAA", OnDemand = true)]
public class AModule : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<ViewA>();
}
}
注入IModuleManager接口,使用moduleManager.LoadModule("AAA")手动加载模块
public class MainWindowViewModel
{
public ICommand OpenCommand { get; set; }
public MainWindowViewModel(
IRegionManager regionManager,
IModuleManager moduleManager)
{
OpenCommand = new DelegateCommand(() =>
{
moduleManager.LoadModule("AAA");
regionManager.RequestNavigate("MainRegion", "ViewA");
});
}
}
使用项目中ConfigureModuleCatalog方法加载模块,需要在ModuleInfo中标记OnDemand
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
// 第一种方式
//moduleCatalog.AddModule<AModule>();
// 第二种方式
Type type = typeof(AModule);
moduleCatalog.AddModule(new ModuleInfo
{
ModuleName = type.Name,
ModuleType = type.AssemblyQualifiedName,
// 标记Module按需加载
InitializationMode= InitializationMode.OnDemand
});
}