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
    });
}

posted @ 2023-09-03 15:54  ZHIZRL  阅读(1155)  评论(0编辑  收藏  举报