Prism研究(for WPF & Silverlight)5.Module研究

Prism研究(for WPF & Silverlight)5.Module研究,第1张

概述终于要坐下来说一说Module的相关技术了。       本来不想讨论Module,因为一旦写好这些框框,以后就再也不会改变了。要知道,我们在Prism中更关心的是MVP模式的拆分。       Module相关技术包括两部分,一是如何加载Module,也就是在Shell的Bootstarpper中重写它的InitializeModules方法,从而把所有需要的Module加载到主程序中。二是如何 终于要坐下来说一说Module的相关技术了。

      本来不想讨论Module,因为一旦写好这些框框,以后就再也不会改变了。要知道,我们在Prism中更关心的是MVP模式的拆分。

      Module相关技术包括两部分,一是如何加载Module,也就是在Shell的Bootstarpper中重写它的InitializeModules方法,从而把所有需要的Module加载到主程序中。二是如何创建映射,即在每个Module内,实现IModule接口的Initialize方法,从而建立Region和VIEw之间的MapPing关系。

      (一)在Bootstarpper中加载模块:对抽象类Unitybootstrapper的实现

      Unitybootstrapper这个类可称得上是Prism框架的中枢神经。

      我们知道,在App.xaml.cs中,执行的是Bootstarpper类的Run方法,就在这个方法中,都包括了哪些关键步骤呢?

            1.调用ConfigureContainer()方法,配置容器。包括3小步:

                  1)注入ILoggerFacade,于是我们可以定义自己的Log系统,这个技术我会在以后章节单独介绍。

                  2)调用GetModuleCatalog方法,从而获取并注册全部Module。我们通常会重写这个方法。

                  3)根据Run方法中的布尔值(默认为true),决定是否要默认注册8对接口和相应的类:

protected virtual voID ConfigureContainer(){    Container.RegisterInstance<ILoggerFacade>(LoggerFacade);    IModuleCatalog catalog = GetModuleCatalog();    if (catalog != null)    {        this.Container.RegisterInstance(catalog);    }    if (useDefaultConfiguration)    {        RegisterTypeIfMissing(typeof(IServiceLocator),typeof(UnityServiceLocatorAdapter),true);        RegisterTypeIfMissing(typeof(IModuleInitializer),typeof(ModuleInitializer),true);        RegisterTypeIfMissing(typeof(IModuleManager),typeof(ModuleManager),true);        RegisterTypeIfMissing(typeof(regionadapterMapPings),typeof(regionadapterMapPings),true);        RegisterTypeIfMissing(typeof(IRegionManager),typeof(RegionManager),true);        RegisterTypeIfMissing(typeof(IEventAggregator),typeof(EventAggregator),true);        RegisterTypeIfMissing(typeof(IRegionVIEwRegistry),typeof(RegionVIEwRegistry),true);        RegisterTypeIfMissing(typeof(IRegionBehaviorFactory),typeof(RegionBehaviorFactory),true);        ServiceLocator.SetLocatorProvIDer(() => this.Container.Resolve<IServiceLocator>());    }}

            我们经常会重写这个方法,从而加上自己的逻辑,存在两种场景:

                  1) 基类方法中默认注册的MapPing不满意,注销其中的一个或全部。代码如下:

                  2) 当我们想要连Shell都进行MVP模式重构时,以下是strokeTrader RI中的示例代码:

protected overrIDe voID ConfigureContainer(){    Container.RegisterType<IShellVIEw,Shell>();    base.ConfigureContainer();}

            2.调用ConfigureregionadapterMapPings方法,注册基础控件和相应适配器之间的MapPing关系。WPF下有3个基础控件(Selector、ItemsControl、ContentControl,我们经常使用的是ContentControl),在Silverlight下还要额外注册TabControl。

        protected virtual regionadapterMapPings ConfigureregionadapterMapPings()        {            regionadapterMapPings regionadapterMapPings = Container.TryResolve<regionadapterMapPings>();            if (regionadapterMapPings != null)            {#if SILVERliGHT                regionadapterMapPings.RegisterMapPing(typeof(TabControl),this.Container.Resolve<TabControlregionadapter>());#endif                regionadapterMapPings.RegisterMapPing(typeof(Selector),this.Container.Resolve<Selectorregionadapter>());                regionadapterMapPings.RegisterMapPing(typeof(ItemsControl),this.Container.Resolve<ItemsControlregionadapter>());                regionadapterMapPings.RegisterMapPing(typeof(ContentControl),this.Container.Resolve<ContentControlregionadapter>());            }            return regionadapterMapPings;        }

            我们可以定义自己的适配器,这将在以后章节中看到。

            这三个基础控件的使用场合是什么呢?我一开始是很明白的,后来完全被Prism自带的Sample搞糊涂了。我会在RI项目分析时,讨论这个问题。

            3.调用ConfigureDefaultRegionBehaviors方法,配置Behavior。

            这块涉及到另一门新的技术了,我准备另开章节,这里不宜展开。

            4.调用RegisterFrameworkExceptionTypes方法,这涉及到Prism内部的异常处理机制,再议再议。

            5.调用CreateShell方法,注意到,Unitybootstrapper类之所以是抽象的,就是因为这个方法是虚的,所以我们要重写它,创建并返回Shell窗体对象,并依赖注入IRegionManager对象。

            6.调用InitializeModules方法,决定了哪些Module要被加载。终于说到这里了,累死我了。这个方法是要仔细分析的。

            如何把Module加载到Shell中呢?一种方式就是重写前面的GetModuleCatalog方法,如下所示:

const string moduleBAssemblyQualifIEdname = "Modules.ModuleB.ModuleB,Modules,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null";
protected overrIDe IModuleCatalog GetModuleCatalog(){    ModuleCatalog catalog = new ModuleCatalog();    catalog.AddModule(typeof (ModuleA),"ModuleD")        .AddModule(typeof (ModuleD),"ModuleB")        .AddModule(typeof (ModuleC),InitializationMode.ondemand)        ;    catalog.AddModule(new ModuleInfo("ModuleB",moduleBAssemblyQualifIEdname));            return catalog;}

            另一种方式就是重写InitializeModules方法了,也能达到同样的效果:

protected overrIDe voID InitializeModules(){    IModule projectModule = this.Container.Resolve<ProjectModule>();    projectModule.Initialize();    IModule employeeModule = this.Container.Resolve<EmployeeModule>();    employeeModule.Initialize();}

            只是这次,我们要手动调用Module的Initialize方法了。

            而在GetModuleCatalog方法中是自动加载Module的:实例化ModuleManager,依次调用该实例的Run方法——LoadModulesWhenAvailable方法——LoadModuleTypes方法——LoadModulesThatAreReadyForLoad方法——InitializeModule方法——调用ModuleInitializer实例的Initialize方法——调用IModule的Initialize方法。

            终于绕出来了,算是殊途同归了吧。

            那么,为什么要有两种调用方法呢?我曾经困惑过一段时间,莫衷一是。不过在研究了Prism框架的源码时候我明白了,这两个方法都是可以重写的,只是调用GetModuleCatalog方法的位置位于InitializeModules的前面,我们可以任选一个而忽略另一个。什么?你要在GetModuleCatalog方法中加载ModuleA,而在InitializeModules方法中加载ModuleB——等着被同事Complaint吧。

            现在我们着重分析GetModuleCatalog方法。

            通常有两种加载模块的方式:一,手动编码加载;二,在配置文件中设置。

            此外,在WPF中,还可以遍历目录进行加载;而在Silverlight中,又有一种Remoting加载的技术。

            Prism文档为这4种技术分别提供了一个Demo。依次讨论如下:

            @H_217_301@1.手动加载Module

            参加目录\Quickstarts\Modularity\DefiningModulesInCodeQuickstart

            这种方式是很直接的。就像我们刚才写的代码那样:

protected overrIDe IModuleCatalog GetModuleCatalog(){    ModuleCatalog catalog = new ModuleCatalog();    catalog.AddModule(typeof (ModuleA),moduleBAssemblyQualifIEdname));            return catalog;}

            不要小看这几行代码,麻雀虽小五脏俱全,涵盖了手动加载的所有技巧。

            效果图如下:

            最好是F5一步步调试,就可以看到,先加载模块B,然后是D,最后是A。而模块C开始时并不显示,只有当点击模块B中的按钮时,才会显示。

            这是因为,AddModule方法有5种重载:

public ModuleCatalog AddModule(Type moduleType,params string[] dependsOn)public ModuleCatalog AddModule(Type moduleType,InitializationMode initializationMode,params string[] dependsOn)public ModuleCatalog AddModule(string modulename,string moduleType,string refValue,params string[] dependsOn)

            在前两个重载方法中,我们指定了第1个参数moduleType为模块的类型,而dependsOn参数指定了前面的模块依赖于后面这个模块,initializationMode参数是一个枚举。

public enum InitializationMode{    WhenAvailable,    ondemand}

            WhenAvailable是默认选项,就是说一开始就加载;而ondemand则表示按需加载,一开始并不会进行加载。因为我们在代码中指定了

.AddModule(typeof (ModuleC),InitializationMode.ondemand)

            所以,一开始并不会显示模块C。在点击模块B中按钮的时候,调用ModuleManager实例的LoadModule方法:

private voID OnLoadModulecclick(object sender,RoutedEventArgs e){    moduleManager.LoadModule("ModuleC");}

            顺藤摸瓜,沿着LoadModule方法一级级找下去——调用LoadModuleTypes方法——LoadModulesThatAreReadyForLoad方法——InitializeModule方法——调用ModuleInitializer实例的Initialize方法——调用IModule的Initialize方法。于是,模块C被加载了。

            第3个和第4个重载方法不常用,因为modulename实际上就是moduleType.name。第5个重载方法是给Silverlight用的,它有一个refValue参数,用来指定远程XAP的地址。

@H_217_301@2.根据配置文件动态加载Module

            还是刚才那个效果,我们把模块的前后顺序和依赖关系写在App.config的配置节点modules中:

<?xml version="1.0" enCoding="utf-8" ?><configuration>  <configSections>    <section name="modules" type="Microsoft.Practices.Composite.Modularity.ModulesConfigurationSection,Microsoft.Practices.Composite"/>  </configSections>  <modules>    <module assemblyfile="Modules/ModuleD.dll" moduleType="ModuleD.ModuleD,ModuleD" modulename="ModuleD">      <dependencIEs>        <dependency modulename="ModuleB"/>      </dependencIEs>    </module>    <module assemblyfile="Modules/ModuleB.dll" moduleType="ModuleB.ModuleB,ModuleB" modulename="ModuleB"/>    <module assemblyfile="Modules/ModuleA.dll" moduleType="ModuleA.ModuleA,ModuleA" modulename="ModuleA">      <dependencIEs>        <dependency modulename="ModuleD"/>      </dependencIEs>    </module>    <module assemblyfile="Modules/ModuleC.dll" moduleType="ModuleC.ModuleC,ModuleC" modulename="ModuleC" startupLoaded="false"/>  </modules></configuration>

            这样的话,我只需在GetModuleCatalog方法中直接返回ConfigurationModuleCatalog对象就可以了:

protected overrIDe IModuleCatalog GetModuleCatalog(){    ModuleCatalog catalog = new ConfigurationModuleCatalog();    return catalog;}

            罗嗦几句,根据配置文件加载Module的方式远远优于手动编程的方式,虽然配置起来很麻烦,但是在大型项目中是首选。

 

      (二)具体Module内的编程:IModule接口

      Module介于Shell和VIEw之间,我们可以认为它是VIEw的载体。因此,在把一个复杂的xaml拆分成若干零散的VIEw的时候,我们会手动创建若干以Module名称命名的项目,并把这些VIEw按照类别放到不同的Module项目中。比如说Prism自带的StrockTraderRI(简称RI),参考下面的截图:

      看到没有,RI有4个Module,它们都作为项目而存在,并且每个项目都带有一个类似于MarketModule这样的类,它派生自接口IModule:

public interface IModule{    voID Initialize();}

      于是,实现了IModule接口的类,都具有这样的格式:

public class MarketModule : IModule{    private Readonly IUnityContainer container;    private Readonly IRegionManager regionManager;    public MarketModule(IUnityContainer container,IRegionManager regionManager)    {        this.container = container;        this.regionManager = regionManager;    }    #region IModule Members    public voID Initialize()    {        RegisterVIEwsAndServices();        this.regionManager.RegisterVIEwWithRegion(Regionnames.ResearchRegion,() => this.container.Resolve<ITrendlinePresentationModel>().VIEw);    }    protected voID RegisterVIEwsAndServices()    {        container.RegisterType<IMarketHistoryService,MarketHistoryService>(new ContainerControlledlifetimeManager());        container.RegisterType<IMarketFeedService,MarketFeedService>(new ContainerControlledlifetimeManager());        container.RegisterType<ITrendlineVIEw,TrendlineVIEw>();        container.RegisterType<ITrendlinePresentationModel,TrendlinePresentationModel>();    }    #endregion}

      我们看到:

            1.要在构造函数中实现依赖注入,需要什么就注入什么。

            2.实现Initialize方法,包括:

                  1)在容器中注册接口和实现了该接口的类的mapPing关系,比如说Service、VIEw、Model、Presenter,我们一般都会同时添加相应的接口。我们将这些注册封装在一个名为RegisterVIEwsAndServices的方法中。

                  2)还要在Initialize方法中,注册Register和VIEw之间的关系,也就是RegisterVIEwWithRegion方法。

            所有的Module类都是按照这样的格式来实现。而关于注册VIEw的技术,还有一些小变体。

            比如说,我们可以把

this.regionManager.RegisterVIEwWithRegion("MainRegion",typeof(VIEws.HelloWorldVIEw));

      替换为:

regionVIEwRegistry.RegisterVIEwWithRegion("MainRegion",typeof(VIEws.HelloWorldVIEw));

      这里,regionVIEwRegistry的声明和注入是这样的:

private Readonly IRegionVIEwRegistry regionVIEwRegistry;public ProjectModule(IRegionVIEwRegistry regionVIEwRegistry){    this.regionVIEwRegistry = regionVIEwRegistry;}

      这就引入了一个新的问题,IRegionVIEwRegistry和IRegionManager都具有RegisterVIEwWithRegion方法,二者有区别么?

      答案是——没有。我们已经分析过,在Unitybootstrapper的中,已经默认建立了IRegionManager和RegionManager的映射关系。所以,只要查看Prism框架中的RegionManager就可以了。

      以下则是RegionManager的RegisterVIEwWithRegion方法,这是一个扩展方法:

public static IRegionManager RegisterVIEwWithRegion(this IRegionManager regionManager,string regionname,Type vIEwType){    var regionVIEwRegistry = ServiceLocator.Current.GetInstance<IRegionVIEwRegistry>();    regionVIEwRegistry.RegisterVIEwWithRegion(regionname,vIEwType);    return regionManager;}

      哦,原来还是要间接地调用RegionVIEwRegistry的RegisterVIEwWithRegion方法。

      还是那句老话,殊途同归。

 

 

@H_217_301@下回内容提示:

      我们还可以把

this.regionManager.RegisterVIEwWithRegion("MainRegion",typeof(VIEws.HelloWorldVIEw));

      替换为:

this.regionManager.Regions["MainRegion"].Add(new VIEws.HelloWorldVIEw());

      ——这就涉及到了VIEw的两种模式:VIEw Injection和VIEw discovery。

 

      我们还可以将其替换为:

this.regionManager.RegisterVIEwWithRegion(Regionnames.ResearchRegion,() => this.container.Resolve<IHelloWorldVIEwPresentationModel>().VIEw);

      ——这就涉及到了MVP的两种变体:VIEw -first和Presenter-first。

 

      欲知详情,请看下回《Prism研究——MVP模式之七十二变》。

总结

以上是内存溢出为你收集整理的Prism研究(for WPF & Silverlight)5.Module研究全部内容,希望文章能够帮你解决Prism研究(for WPF & Silverlight)5.Module研究所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/1008316.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-22
下一篇2022-05-22

发表评论

登录后才能评论

评论列表(0条)

    保存