zoukankan      html  css  js  c++  java
  • Prism研究(for WPF & Silverlight)5.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。依次讨论如下:

                1.手动加载Module

                参加目录\Quickstarts\Modularity\DefiningModulesInCodeQuickstart

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

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

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

                效果图如下:

    clip_image002

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

    clip_image004

                这是因为,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, params string[] dependsOn)
    
    public ModuleCatalog AddModule(string moduleName, string moduleType, InitializationMode initializationMode, params string[] dependsOn)
    
    public ModuleCatalog AddModule(string moduleName, string moduleType, string refValue, InitializationMode initializationMode, 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的地址。

    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),参考下面的截图:

    clip_image006

          看到没有,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方法。

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

    下回内容提示:

          我们还可以把

    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模式之七十二变》。

  • 相关阅读:
    Beyond Compare3 注册密钥和添加到右键菜单
    DLL文件无法删除怎么解决
    英语单词学习方法
    JSP+Servlet+JavaBean
    把Java程序打包成jar文件包并执行
    把java文件打包成.jar (jar命令详解)
    Java程序打包成jar包
    JDK
    使用JDK开发Servlet程序
    Jdk和Jre目录和三个lib目录说明----外部扩展jar包servlet,mysql,oracle等
  • 原文地址:https://www.cnblogs.com/Jax/p/1522168.html
Copyright © 2011-2022 走看看