65.9K
CodeProject 正在变化。 阅读更多。
Home

Prism for Silverlight/MEF 简单示例。第一部分 - Prism 模块

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (245投票s)

2011年2月10日

CPOL

16分钟阅读

viewsIcon

1311652

downloadIcon

7297

Prism 教程,简单示例

下载 PrismTutorialSamples_PART1.zip - 286.62 KB

重要提示

各位朋友,我注意到这篇文章的下载次数远远超过了投票和收藏的数量。所以,如果您使用了本教程并且喜欢它,请务必投票支持。收藏一下也无妨 :) 谢谢。

引言

Microsoft Prism 是一个软件框架,旨在简化 WPF 和 Silverlight 应用程序的构建。

Prism(及其源代码、示例和文档)可以从 CodePlex 上的 Prism 下载。

Prism 的主要优势在于它允许将 WPF 或 Silverlight 软件构建成一组几乎独立的模块,这些模块可以单独开发、测试和调试,并通过 Prism 框架整合在一起成为一个应用程序。

将软件开发为一组独立模块的重要性怎么强调都不为过。模块之间的关联性越小,在不影响其他模块或不受其他模块干扰的情况下修改、测试和调试一个模块就越容易。当涉及到团队开发时,模块的独立性变得更加重要,因为多个开发人员希望尽可能少地担心他人同时进行的更改,从而独立地开发他们的模块。

可以通过 Prism 文档以及 Prism 快速入门和其他示例来学习 Prism,这些示例可以与 Prism 源代码一起从 CodePlex 上的 Prism 下载。还有 MS Prism 在线文档以及可以在网上找到的一些教程(例如 Mark J. Miller 的 Prism 教程)。

我希望本教程能够因以下几点而脱颖而出

  • 本教程基于非常简单的示例来解释 Prism 的核心功能——每个示例仅关注一两个 Prism 功能,并进行详细解释。不幸的是,许多 Prism 示例(包括快速入门)相当复杂——这使得人们更难弄清楚快速入门中的哪些功能实际上来自 Prism,以及如何在快速入门之外使用它们。
  • Prism 的主要目标是实现独立模块的开发并将它们整合到一个应用程序中。实现这一目标的最重要要求之一是不同模块不应通过项目引用相互依赖。不幸的是,许多快速入门通过让其主模块(应用程序)引用其他模块来破坏这一要求。本教程中的所有模块都是真正相互独立的,并且与应用程序独立。
  • 本教程中的所有示例都基于 MEF:Prism 最初是一个由 Unity 支持的项目,但现在 MEF 组合框架在微软的采用方面似乎领先于 Unity(基于最后一次 Unity 源代码转储是在 2010 年 5 月,似乎 Unity 已经停滞不前,尽管有些人并不这么认为)。目前 Prism 可以与 MEF 和 Unity 一起使用,但许多快速入门和其他教程是以前编写的,并且侧重于 Unity 而不是 MEF。
  • 本教程中的所有示例都基于 Silverlight(而非 WPF)。还有许多其他基于 Silverlight 的教程,但 WPF 方面略有优势。Silverlight 结合 MEF 带来了另一个层面的复杂性,因为组合不是在 .dll 上执行,而是在 .xap 文件上执行。如果本教程成功且我有时间,我将来可能会撰写另一篇基于 WPF 的教程。

教程概述

我计划在本教程(包括未来的续集)中涵盖 Prism 的以下部分。

  1. Prism 模块(在本教程的第一部分中涵盖)。
  2. 导航(在本教程的第二部分中涵盖:Prism for Silverlight/MEF 简单示例。第二部分 - Prism 导航)。
  3. 模块之间的通信(在本教程的第三部分中涵盖:Prism for Silverlight/MEF 简单示例。第三部分 - 模块之间的通信)。

我特意省略了 Prism 命令,因为在我看来,使用 MS Expression SDKActions/Behaviors 可以以更小的开销达到相同的目的(从 XAML 代码向 ViewModel 发送通信信号)。

本教程的目的是通过非常简单的示例来全面覆盖 Prism 的核心功能,这些示例一个接一个地突出这些功能,而不是讨论设计模式。由于 MVVM 代码有点难以理解,我在这里尽量避免使用它,尽管我本人是 MVVM 的忠实粉丝。

我假设阅读本教程的人对 C#、MEF 和 Silverlight 有一定的了解。

模块化概述和示例

如上所述,Prism 的目的是将独立开发的模块整合到一个应用程序中。在整个教程中,我们将 Prism 的主模块称为 Prism 应用程序(或简称应用程序),而将其他模块称为模块或可插入模块。Prism 应用程序在模块可用或需要时加载它们,并基于它们创建一个可视化的应用程序。Prism 应用程序内的不同模块可以使用后面介绍的各种通信方法在它们之间以及与应用程序进行通信。

Prism 应用程序的创建和模块加载由 Bootstrapper 类促进。Bootstrapper 可以是 MEF 或 Unity 的(它将相应地派生自 Prism 提供的 MefBootstrapper 或 UnityBootstrapper 类)。在这里,我们将只处理 MEF Bootstrapper。

最简单的模块加载示例

我们的第一个示例位于 SimpleNonVisualModuleSample.sln 解决方案下。这是最简单的 Silverlight 项目,演示了如何将 Prism 模块加载到 Prism 应用程序中。它甚至没有任何可视化元素(为了“欺骗”Silverlight,我们将应用程序的 VisualRoot 设置为空的 Grid 面板)。证明模块正在加载的唯一方法是在模块的构造函数或初始化器中设置一个断点,在调试器中运行 Silverlight 应用,并确保断点被命中。

让我们回顾一下涉及的代码。应用程序项目:“SimpleNonVisualModuleSample”没有任何视图文件(因为它是一个非可视化的测试应用程序)。它只有 App.xaml、App.xaml.cs 和 TheBootstrapper.cs 文件。App 文件是 Visual Studio 2010 在创建项目时生成的。App.xaml.cs 文件需要稍作修改,以便在 Application_Startup 方法中运行 TheBootstrapper,而不是为 VisualRoot 分配应用程序。

        private void Application_Startup(object sender, StartupEventArgs e)
        {
            // start the bootstrapper
            (new TheBootstrapper()).Run();
        }

该项目中最核心的类是 TheBootstrapper。正如您所见,它派生自 Prism 的 MefBootstrapper 类。

    public class TheBootstrapper : MefBootstrapper
    {
        protected override void InitializeShell()
        {
            base.InitializeShell();

            // set the visual root of the Silverlight application
            // to the Shell (application's main view)
            Application.Current.RootVisual = (UIElement)Shell;
        }

        // create the shell - shell is the main view for 
        // Prism application (in this case we use an empty Grid panel in its place)
        protected override DependencyObject CreateShell()
        {
            return new Grid();
        }

        protected override IModuleCatalog CreateModuleCatalog()
        {
            ModuleCatalog moduleCatalog = new ModuleCatalog();

            // this is the code responsible 
            // for adding Module1 to the application
            moduleCatalog.AddModule
            (
                new ModuleInfo
                {
                    InitializationMode = InitializationMode.WhenAvailable,
                    Ref = "Module1.xap",
                    ModuleName = "Module1Impl",
                    ModuleType = "Module1.Module1Impl, Module1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
                }
            );

            return moduleCatalog;
        }
    }

Bootstrapper 中最有趣的类显然是 CreateModuleCatalog()。它负责指示 Prism 加载 Module1 模块。

现在,让我们看看模块代码。Module1 项目只有一个非常简单的类:Module1Impl

    // ModuleExport attribute makes the module loadable
    [ModuleExport(typeof(Module1Impl))]
    public class Module1Impl : IModule
    {

        #region IModule Members

        public void Initialize()
        {
            
        }

        #endregion
    }

要验证模块是否已加载,请在 Module1Impl 类的 Initialize() 方法中设置一个断点,然后在调试模式下启动应用程序。断点命中意味着模块已加载。

练习:尝试从头开始重新创建解决方案,通过执行以下步骤来更改项目和类名称

  1. 请参阅 附录 A附录 B,了解创建 Silverlight Prism 应用程序和模块项目的详细说明。
    重要提示:确保包含 Prism dll 文件 Microsoft.Practices.Prism.dll 和 Microsoft.Practices.Prism.MefExtensions.dll 的目录(这两个文件都需要被 Prism 应用程序和模块项目引用)也包含 Microsoft.Practices.ServiceLocation.dll(您可以从示例的 PrismDll 目录中复制它们)。Microsoft.Practices.ServiceLocation.dll 是 Prism 工作所必需的(尽管它没有被我们的项目引用)。
    另一个重要提示:确保模块项目中对 Prism 文件的引用已将其“复制本地”属性设置为 false,如 附录 B 中所述。否则 MEF 将无法工作。
  2. 创建项目后,从这两个项目中删除 MainView.xaml 和 MainView.xaml.cs 文件,并从模块项目中删除 App.xaml 和 App.xaml.cs 文件。
  3. 将 Bootstrapper 类添加到应用程序项目中;向其中添加所需的代码,并替换应用程序项目 App.xaml.cs 文件中 Application_Startup 方法的主体以运行 Bootstrapper。
  4. 将模块实现类添加到模块项目中,并向其中添加所需的代码。
  5. 通过在模块的 Initialize 方法中设置断点并在调试器中运行应用程序来测试模块是否已加载。
  6. 如果断点被命中,请给自己一个赞;如果未命中,请仔细检查每个步骤,并与示例代码进行比较,找出问题所在。

带有模块加载的可视化 Prism 应用程序示例

虽然之前的示例几乎没有可视化元素,但在本例中,应用程序和模块都将具有可视化元素。应用程序视图(称为 Shell)根据区域名称确定在哪里放置模块视图。

本示例的源代码位于 SimpleVisualModuleLoadingApp.sln 解决方案下。运行后您将看到以下屏幕:

Shell 视图包含“这是 Shell,不是模块”的文本,而模块包含“这是 Module1”的文本。

让我们看一下代码。项目的创建方式与前一个示例完全相同,只是我们没有删除 MainView.xaml 和 MainView.xaml.cs 文件。相反,它们及其类已被重命名为应用程序和模块视图的 Shell 和 Module1View。

在这个特定的示例中,这无关紧要,但为了将来的示例,我们将 Shell 变成一个 MEF 可组合类,方法是向其添加 [Export] 属性。这将允许 MEF 组合它,以防它具有某些 MEF 导入。Shell 的 MEF 组合是通过在 TheBootstrapper 类中重写 MefBootstrapper 类的 ConfigureAggregateCatalog 方法来实现的,我们通过添加以下一行代码来实现这一点:

            // Prism's AggregateCatalog is a catalog of all MEF composable parts
            // within the application.
            // We add the parts corresponding to the current assembly to it
            AggregateCatalog.Catalogs.Add(new AssemblyCatalog(this.GetType().Assembly));

这一行将当前应用程序程序集的 MEF 部分添加到 Prism 的 AggregationCatalog 所控制的所有 MEF 可组合部分。

Shell.xaml 文件包含“这是 Shell,不是模块”的 TextBlock。它还包含模块视图的占位符。

    <ContentControl HorizontalAlignment="Center"
                       VerticalAlignment="Center"
                       prism:RegionManager.RegionName="MyRegion1"/>

RegionName 是 Prism RegionManager 类中定义的附加属性,允许指定用于插入模块视图的区域。

切换到模块源代码,我们发现 Module1Impl 类现在从 Prism 导入 IRegionManager。

        // import IRegionManager singleton from Prism
        [Import]
        public IRegionManager TheRegionManager { private get; set; }

我们还注意到 Initialize() 函数现在将 Module1View 注册到名为“Region1”的区域。

        public void Initialize()
        {
            TheRegionManager.RegisterViewWithRegion("MyRegion1", typeof(Module1View)); 
        }

模块的视图根据区域名称的匹配放置在应用程序控件之上。

请注意,TheRegionManager.RegisterViewWithRegion 会强制进行视图创建(通过 MEF 或基于委托)。这种将视图与区域关联的方法称为**视图发现**。另一种方法允许将已创建的视图插入到区域中,这称为**视图注入**。视图注入将在后面介绍。

Module1View 被标记为 MEF 可组合类,方法是添加 Export 属性。

    [Export(typeof(Module1View))]
    public partial class Module1View : UserControl
    {
        public Module1View()
        {
            InitializeComponent();
        }
    }

最后,Module1View.xaml 文件包含非常简单的内容:一个“这是 Module1”的 TextBlock

练习:自己创建一个类似的示例,更改项目、类和区域名称。确保它能运行并显示模块视图。
重要提示:确保模块项目中对 Prism 文件的引用已将其“复制本地”属性设置为 false,如 附录 B 中所述。否则 MEF 将无法工作。(**这是我最后一次提及这一点**)。

可选练习
如上所示,我们使用了 ContentControl 来托管模型视图在主应用程序中(还记得 Shell.xaml 文件吗?)。Prism 还允许使用其他控件来实现此目的:Selector、ItemsControl 和 TabControl。这些控件允许在同一区域内插入多个视图。这些视图将根据相应控件的功能进行排列。

我将其作为可选练习,在 Module1 中创建另一个视图,在模块中将此新创建的视图注册到“MyRegion1”,同时将 Shell.xaml 文件中的 ContentControl 更改为以下内容:

        <ItemsControl HorizontalAlignment="Center"
                      VerticalAlignment="Center"
                      prism:RegionManager.RegionName="MyRegion1">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>

结果是不同的模块视图将根据 StackPanel 的布局进行排列:一个堆叠在另一个之上,应用程序将如下所示:

不幸的是,由于下载是本地的,并且下载的模块很小,您将无法看到进度条的渐变变化——一切都会一次性下载。

练习:自己构建一个类似的应用程序。

按需下载模块示例

以上所有示例都“在可用时”下载其模块,即当模块在服务器端可用时,在我们的示例中,这意味着在应用程序初始化期间。这是通过在 Bootstrapper 的 CreateModuleCatalog() 方法中将相应模块的 InitializationMode 设置为 InitializationMode.WhenAvailable 值来实现的。

            moduleCatalog.AddModule
            (
                new ModuleInfo
                {
                    // THIS IS WHERE WE SET THE MODULE'S INITIALIZATION MODE!
                    InitializationMode = InitializationMode.WhenAvailable,
                    Ref = "Module1.xap",
                    ModuleName = "Module1Impl",
                    ModuleType = "Module1.Module1Impl, Module1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
                }
            );

如果我们将其初始化模式更改为 InitializationMode.OnDemand,则模块不会在初始化阶段加载。实际上,您需要编写使用 ModuleManager 类(在前一个示例中介绍)的代码来加载模块。

示例的代码位于 ModuleLoadingOnDemandSample.sln 解决方案下。与前一个示例相比,只有 Shell.xaml 和 Shell.xaml.cs 文件发生了变化。

Shell.xaml 现在包含一个按钮来触发模块下载。

Shell.xaml.cs 文件在其构造函数中获取的模块管理器对象存储在 _moduleManager 类字段中。一旦点击“下载模块”按钮,就会调用 ModuleManager 的 LoadModule 方法来下载模块。

        void DownloadModuleButton_Click(object sender, RoutedEventArgs e)
        {
            // we pass the module name to LoadModule method
            _moduleManager.LoadModule("Module1Impl");
        }

模块加载后,按钮将被禁用。

        _moduleManager.LoadModuleCompleted += _moduleManager_LoadModuleCompleted;
        ...
        void _moduleManager_LoadModuleCompleted(object sender, LoadModuleCompletedEventArgs e)
        {
            DownloadModuleButton.IsEnabled = false;
        }

要验证示例是否有效,请确保应用程序开始时 Shell 中没有显示模块视图。然后点击“下载模块”按钮,模块视图应出现在窗口中间,并且按钮应被禁用。

练习:创建一个类似的演示。

模块依赖关系示例

Prism 允许一些“按需”模块在加载依赖于它们的另一个模块时被自动加载。模块依赖关系可以在 Bootstrapper 的 CreateModuleCatalog 方法中填充模块目录时指定。

此示例的源代码位于 ModuleDependencySample.sln 解决方案下。

应用程序中有两个模块需要加载(而不是通常的一个):Module0 和 Module1。Module1 依赖于 Module0,如 Bootstrapper 的 CreateModuleCatalog 函数中定义的。

        protected override IModuleCatalog CreateModuleCatalog()
        {
            ModuleCatalog moduleCatalog = new ModuleCatalog();

            // this is the code responsible 
            // for adding Module0 to the application
            moduleCatalog.AddModule
            (
                new ModuleInfo
                {
                    InitializationMode = InitializationMode.OnDemand,
                    Ref = "Module0.xap",
                    ModuleName = "Module0",
                    ModuleType = "Module0.Module0Impl, Module0, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
                }
            );

            // this is the code responsible 
            // for adding Module1 to the application
            moduleCatalog.AddModule
            (
                new ModuleInfo
                {
                    InitializationMode = InitializationMode.OnDemand,

                    // set depends on property to include "Module0"
                    DependsOn = new System.Collections.ObjectModel.Collection<string>{"Module0Impl"},
                    Ref = "Module1.xap",
                    ModuleName = "Module1",
                    ModuleType = "Module1.Module1Impl, Module1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
                }
            );

            return moduleCatalog;
        } 

这种依赖关系意味着 Module0 始终在 Module1 之前加载。为了验证这一点,Shell.xaml 的区域控件已更改为 ItemsControl,其 ItemsPanel 按加载顺序排列已加载的视图。

  <ItemsControl HorizontalAlignment="Center"
                VerticalAlignment="Center"
                prism:RegionManager.RegionName="MyRegion1">
    <ItemsControl.ItemsPanel>
      <ItemsPanelTemplate>
        <StackPanel />
      </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
  </ItemsControl>

按下“下载模块”按钮后的结果如下所示:

可以看到,两个模块都已加载,并且 Module0 在 Module1 之前加载。

练习:创建一个类似的演示。

视图注入

如上所示,将模块视图注册到区域称为视图发现。以下是视图发现的典型代码(通常位于模块的 Initialize() 方法中)。

     TheRegionManager.RegisterViewWithRegion("MyRegion1", typeof(Module1View));         

然而,上述方法总是会创建一个新视图——如果我们想自己创建视图然后将其放入区域,我们就需要采用视图注入。

视图注入示例位于 ViewInjectionSample.sln 解决方案下。

通过查看 Shell.xaml 文件,您可以看到有两个区域:“MyRegion1”和“MyRegion2”。“MyRegion1”区域像以前一样简单地显示模块视图(这是已发现的)。然而,“MyRegion2”用于注入非常简单的视图(仅包含一个 TextBlock)。

如果我们查看模块的 Module1View.xaml 文件,我们将看到模块视图有一个“Inject View”按钮。按下此按钮将触发 Module1View.xaml.cs 文件中的视图注入代码。

        void InjectViewButton_Click(object sender, RoutedEventArgs e)
        {
            IRegion region = TheRegionManager.Regions["MyRegion2"];

            // View Injection
            region.Add
             (
                new TextBlock 
                { 
                    Text = "Hello" + ++i, 
                    FontSize = 20, 
                    Foreground = new SolidColorBrush(Colors.Blue)
                }
            );
        }

以下是应用程序在几次“Inject View”按钮单击后将显示的样子:

练习:创建一个类似的演示。

结论

这是 Prism 模块功能在小型简单示例中的详细概述。如果您能投票支持本文并留下评论,我将非常感激。我很乐意听取您关于改进和扩展本教程内容的建议。

更多教程即将推出,重点关注 Prism 导航和通信功能。

历史

2011 年 2 月 14 日 - 已更改源代码以使用最新的 Prism dll(来自 Prism 版本 52595)——代码更改非常小——只有一个项目(具有模块依赖关系)受到影响。

2012 年 1 月 8 日 - 删除了指向 Eric Mork 教程的死链接。感谢读者 onefootswil 列出它们。

2012 年 1 月 18 日 - 为附录添加了正确的 HTML 锚点。感谢读者 Sunara Imdadhusen 指出锚点丢失且指向附录的超链接不起作用。

附录 A:在 Visual Studio 2010 中创建 Prism 应用程序 Silverlight 项目

重要提示:为了能够在 VS 2010 中使用 Silverlight,您需要安装 Microsoft Silverlight 4 Tool for Vistual Studion 2010

以下是创建 Prism 主模块(应用程序)项目的步骤:

  1. 打开 VS 2010。转到文件菜单 -> 新建 -> 项目
  2. 在打开的窗口中,选择“Silverlight 应用程序”作为项目类型,确保选择了 .Net Framework 4,选择项目名称和目录,如下图所示:

    单击“确定”按钮。
  3. 保留所有默认设置,除非您想更改 Silverlight 托管 Web 项目的名称。

    单击“确定”按钮。
  4. 通过右键单击 Silverlight 项目下的“引用”并选择“添加引用”,选择“浏览”选项卡,浏览到包含 Prism .dll 文件的目录并选择这些文件,来添加对 Microsoft.Practices.Prism.dll 和 Microsoft.Practices.Prism.MefExtensions.dll 文件的引用。

    然后单击“确定”按钮。
    重要提示:确保包含这些 Prism dll 的目录也包含 Microsoft.Practices.ServiceLocation.dll(您可以从示例的 PrismDll 目录中复制它们)。Microsoft.Practices.ServiceLocation.dll 是 Prism 工作所必需的(尽管它没有被我们的项目引用)。
  5. 通过执行与上述类似的步骤,添加对 MEF 组合 dll(它是 .NET 4 的一部分)的引用,只选择 .NET 选项卡并选择 System.ComponentModel.Composition 引用。

附录 B:创建(非主)模块 Silverlight 项目在 Visual Studio 2010 中

创建模块项目与创建主模块(应用程序)项目非常相似。

重要提示:Silverlight 的部署单元是 .xap 文件,而不是 .dll 库,因此模块也应创建为 Silverlight 应用程序,而不是 Silverlight 类库。

以下是创建 Prism 模块项目的步骤:

  1. 在 Visual Studio 2010 解决方案资源管理器中右键单击解决方案,然后选择“添加”->“新建项目”来启动模块项目的创建。
  2. 与应用程序项目一样,选择“Silverlight 应用程序”作为项目类型,也选择项目名称和位置。

    重要提示:未将 Prism 引用的“复制本地”设置为 False 会导致 MEF 错误。我花了一整天时间才弄清楚为什么我的模块在 Prism 应用程序中无法加载。
    另一个重要提示:与 Prism 应用程序一样,确保包含这些 Prism dll 的目录也包含 Microsoft.Practices.ServiceLocation.dll(您可以从示例的 PrismDll 目录中复制它们)。Microsoft.Practices.ServiceLocation.dll 是 Prism 工作所必需的(尽管它没有被我们的项目引用)。
  3. 就像在 Prism 应用程序中所做的那样,添加 System.ComponentModel.Composition .NET 引用。
  4. 通过在 Visual Studio 解决方案资源管理器中右键单击并选择“删除”来从模块项目中删除 App.xaml 和 App.xaml.cs 文件。
© . All rights reserved.