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

Prism v2 的布局管理器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (16投票s)

2009年4月13日

CPOL

5分钟阅读

viewsIcon

83497

downloadIcon

1377

为 Composite WPF (Prism) 应用程序提供布局管理

diagram.jpg

引言

在使用 Prism 项目时,您可能会遇到一个问题,即应用程序中区域(regions)和视图(views)的管理。虽然 RegionManager 在管理区域方面做得不错,但视图和区域的协调很大程度上留给了开发人员。

一种常见的方法是在通用的基础架构程序集中定义 string 常量,并使用这些常量将视图注入到区域中。这可以完成任务,但会增加应用程序的僵化性。对于需要多种布局的应用程序来说,协调区域和视图可能有点繁琐。

一种我*不*推荐的常见方法是将视图注入到模块的 Initialize 方法中。

public void Initialize()
{
var view = new MyView();
_Container.RegisterInstance<IMyView>(view);
_RegionManager.Regions[RegionNames.Shell].Add(view);
}

这违反了模块的封装性,限制了模块的重用性。

背景

在一个项目中,我们选择创建一个“布局模块”。该模块的唯一目的是将一个布局 UserControl 加载到主应用程序窗口的 Shell 区域,并将视图注入到其自身定义的区域中。这无疑是朝着正确方向迈出的一步,将模块视图与区域解耦。布局模块与其他模块一样定义和加载,但由于其依赖关系,必须是最后一个加载的模块。这种方法的一个缺点是依赖项数量的增加。布局模块必须引用它需要管理的视图的所有基础架构程序集。

尽管如此,这个解决方案仍然显得过于专门化。很快又出现了其他问题,例如对多种布局的支持。

理想情况下,我们希望实现区域和视图的完全解耦,并能够根据需要动态加载布局。

我们很喜欢使用“布局视图”(layout views)的想法,即 sole purpose 是定义区域,并且不提供任何业务或 UI 逻辑的视图。但是,这些视图的来源和引入需要是动态且灵活的。

Using the Code

LayoutManager 是我尝试解决这个问题的第一个方法。它的目的是动态管理 Prism 应用程序的一个或多个布局配置。

要编译和运行 LayoutManager,您需要 Visual Studio 2008 SP1 和最新版本的 Composite Application Guidance for WPF and Silverlight - February 2009

该解决方案是一个相当标准的 Prism 解决方案,包括基础架构、Shell 和 Modules 项目。为了简化起见,我只包含了一个 Modules 项目,而通常会有更多的项目。

solution.jpg

classDiagram.jpg

LayoutManager 维护一个 Layout 对象集合,这些对象定义了布局控件以及将驻留在其中的视图。

配置

LayoutManager 的配置由 app.config 文件中指定的 LayoutProvider 进行。

<section name="layoutProvider" type="Composite.Layout.Configuration.LayoutProviderSection, Composite.Layout"/>

目前,提供了两种提供程序:ConfigLayoutProvider 和 XamlLayoutProvider。可以通过继承 LayoutProviderBase 来使用自定义提供程序。

ConfigLayoutProvider

app.config 文件中定义 LayoutManager,如下所示:

<layoutProvider name="ConfigLayoutProvider" 
    type="Composite.Layout.Configuration.ConfigLayoutProvider, Composite.Layout">
    <layoutManager shellName="Shell" >

      <layouts>
        <layout name="FirstLayout" 
              filename="Layouts\FirstLayout.xaml" 
              fullname="First Layout" 
              isDefault="True"
              description="This is the default layout" 
              thumbnailSource="pack://application:,,,/LayoutManager.Infrastructure;
              component/Resources/Images/layout1.png">

          <views>
            <view typeName="LayoutManager.Infrastructure.IViewA,
                LayoutManager.Infrastructure" regionName="Left"  />
            <view typeName="LayoutManager.Infrastructure.IViewB,
                LayoutManager.Infrastructure" regionName="Right" />

            <viewModel typeName="LayoutManager.Infrastructure.IMenuViewModel,
                LayoutManager.Infrastructure" regionName="Menu"  viewProperty="View"/>
          </views>
        </layout>

        ...
          </layouts>
    </layoutManager>
  </layoutProvider>
XamlLayoutProvider

在 Xaml 中定义 LayoutManager。

<layoutProvider name="XamlLayoutProvider"

           type="Composite.Layout.Configuration.XamlLayoutProvider, Composite.Layout"
           filename="Layouts\LayoutConfiguration.xaml"/>

Xaml 的来源可以通过类型或文件名指定。

<Layout:LayoutManager xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

                      xmlns:Layout=
                          "clr-namespace:Composite.Layout;assembly=Composite.Layout"
                      xmlns:Infrastructure=
    "clr-namespace:LayoutManager.Infrastructure;assembly=LayoutManager.Infrastructure"
                      ShellName="Shell">
    <Layout:LayoutManager.Layouts>
        <Layout:Layout x:Name="FirstLayout"

                       Fullname="First Layout"
                       Filename="Layouts\FirstLayout.xaml"
                       Description="This is the default layout"
                       ThumbnailSource=
"pack://application:,,,/LayoutManager.Infrastructure;component/Resources/Images/layout1.png"
                       IsDefault="True">

            <Layout:Layout.Views>
                <Layout:ViewModel RegionName="Menu"
                                  Type="{x:Type Infrastructure:IMenuViewModel}"
                                  ViewProperty="View" />

                <Layout:View RegionName="Left"
                             Type="{x:Type Infrastructure:IViewA}" />
                <Layout:View RegionName="Right"
                             Type="{x:Type Infrastructure:IViewB}" />

            </Layout:Layout.Views>
        </Layout:Layout>
	...
    </Layout:LayoutManager.Layouts>
</Layout:LayoutManager>

每个 Layout 都包含一个 Views 集合。Views 集合同时支持 Views 和 ViewModels。View 指定要加载的视图控件以及要放置的区域。您还可以设置视图的可见性。使用 ViewModelViewProperty 来指定您的 ViewModel 上保存 View 的属性名称。

LayoutManager 在所有模块初始化完成后加载。在 Bootstrapper.cs 中:

        protected override void InitializeModules()
        {
            base.InitializeModules();
            InitializeLayoutManager();
        }

        private void InitializeLayoutManager()
        {
            var layoutManager = LayoutConfigurationManager.LayoutManager;
            layoutManager.Initialize(Container);
            Container.RegisterInstance(layoutManager,
                new ContainerControlledLifetimeManager());
            //parameterless LoadLayout loads the default Layout into the Shell
            layoutManager.LoadLayout();
        }

LayoutManager 需要使用 Container。加载完布局后,调用 Initialize 方法并将 container 传递进去。

完成这些后,您可以将 LayoutManager 注册到容器中,使其可供其他模块访问。

加载布局

通过调用 LayoutManagerLoadLayout 方法来加载布局。

  • LoadLayout() - 加载 Shell 中的默认布局
  • LoadLayout(string layoutName) - 加载 Shell 中指定名称的布局

MenuViewModel.cs 演示了 LoadLayout 的用法。

private void LayoutCommandExecute(ILayout layout)
{
var layoutManager = _Container.Resolve<ILayoutManager>(); 
layoutManager.LoadLayout(layout.Name);
}

加载布局的基本顺序是:

  1. 如果存在当前布局,则将其从 RegionManager 中移除。
  2. 清除绑定到任何区域的控件。此步骤是必需的,否则当您以后尝试重新加载它时,会收到 InvalidOperationException(“此控件正在与一个区域关联,但该控件已绑定到其他内容。”)。当前,LayoutManager 只支持使用 RegionManager.RegionName 附加属性的 ItemsControlsContentControlsPanels
  3. 将新的 Layout Control 添加到 RegionManager
  4. 注册 Layout Control 中包含的任何 Regions
  5. 加载与新布局关联的任何视图。

事件

LayoutManager 会引发几个事件:

  • LayoutManagerInitializedEvent - 在 Initialize 结束时引发(请参阅 MenuViewModel.cs 中订阅此事件的示例)
  • LayoutLoadingEvent - 在 LoadLayout 开始时引发
  • LayoutLoadedEvent - 在 LoadLayout 结束时引发
  • LayoutUnloadingEvent - 在当前布局即将卸载之前引发
  • LayoutUnloadedEvent - 在当前布局卸载之后引发

所有这些事件都通过 EventAggregator 发布。

限制

当前 LayoutManager 有几个局限性,它们是:

LayoutManager 目前仅支持 UserControls 作为布局控件。还有一个基本假设是,您的应用程序主窗口定义了一个区域,布局控件被注入到其中。区域必须使用 RegionManager.RegionName 附加属性在 XAML 中定义。

其他注意事项

虽然 LayoutManager 将区域与视图解耦,但它并没有完全摆脱基于 stringregion 名称。在代码中动态操作区域和视图仍将依赖于 region 名称(请参阅 MenuViewModel.cs 中的 AddCommandExecute 方法,了解如何以编程方式添加布局)。区域名称属性必须与 Layout 控件中的实际区域名称匹配。

解决此依赖关系的一个可能方法是引入一个 RegionType 枚举,例如 TopBottomLeftRightCenterStatusBarMenuToolbar 等。在这种情况下,LayoutManager 可以解析这些区域,而无需顾及 string 名称。

我尚未在所有可能的情况下测试 LayoutManager,例如嵌套布局和自定义 RegionAdapters,或与 Silverlight 一起使用。

我对 Prism 社区的反馈/评论/建议感兴趣。

关注点

要深入了解 Prism 和 WPF 的使用,请查看:

历史

  • 2009 年 4 月 13 日:首次发布
  • 2009 年 4 月 23 日:重构了 Composite Layout 程序集,包括更改命名空间。LayoutManager、Layout、View 和 ViewModel 现在是 DependencyObjects。将布局配置与 app.config 文件解耦,允许灵活配置。添加了 ConfigLayoutProvider 和 XamlLayoutProvider。添加了 token 视图和自定义菜单。
© . All rights reserved.