深入了解 Prism WPF 应用中的区域创建和区域适配器





5.00/5 (11投票s)
要在基于 Prism 的 WPF 应用程序中有效地处理区域,我们必须理解 WPF 控件、RegionAdapters 和 Regions 之间的关系。在本文中,我们将通过一个演示应用程序了解区域的创建过程以及 RegionAdapters 所扮演的关键角色。
引言
要在基于 Prism 的 WPF 应用程序中有效创建和使用区域,理解 WPF 控件、RegionAdapters 和 Regions 之间的关系至关重要。RegionAdapters 在区域创建过程中发挥着至关重要的作用,并决定其行为。在本文中,我们将学习区域的创建过程以及 RegionAdapters 在其中的作用。我们将使用 WPF 和 Prism 4 创建一个演示应用程序。我们将在其中使用 Prism 框架提供的所有三种 RegionAdapters(ContentControlRegionAdapter、ItemsControlRegionAdapter 和 SelectorRegionAdapter)。
先决条件: 我假设您对 WPF 和 Prism 框架有一定的基本了解。Prism 是一个用于使用 WPF/Silverlight 构建复合应用程序的框架。我已在名为“Prism in WPF - Part One”的文章系列中进行了分步解释和演示。要了解更多关于 Prism 的知识,请参阅 Prism 入门介绍、MSDN 上的 Prism 页面 以及关于 Prism 的 更多信息。
目录
- 区域和 RegionAdapters
- 区域创建过程
- WPF 控件和 RegionAdapters
- 激活和停用视图
- 显示和隐藏视图扩展
- 学习总结
- 演示概述
区域和 RegionAdapters
区域使标准的 WPF 控件(如 Button、ListBox、ComboBox 等)能够像动态的视图容器一样运行,并具有某些其他行为。在运行时,我们可以在区域中添加、删除、激活、停用、显示和隐藏视图。RegionAdapters 在实现这一点方面起着核心作用。区域可以采用来自不同模块的视图,我们将在创建演示应用程序时这样做。
与区域相关的两个主要概念是:RegionManager 和 RegionAdapters。RegionManager 负责为宿主 WPF 控件创建和维护区域集合。RegionAdapters 负责创建区域并将其与宿主 WPF 控件关联。在典型的 Prism 应用程序中,我们通常需要理解并选择不同的 WPF 控件来承载区域。通过选择特定的 WPF 宿主控件,我们**间接选择了特定的 RegionAdapter 和 Region**。本文将解释这些隐式选择,并帮助您在创建区域时做出更好的决策。
有时我们还需要创建自定义的 RegionAdapters 和 Regions。在 Prism 库中,开箱即用提供了三种 RegionAdapters 和相应的 Regions。我们将在后面详细介绍它们。下一节将探讨区域创建过程。
区域创建过程
在 XAML 中,我们可以使用以下代码创建一个区域
<ListBox Name="MainRegion" prism:RegionManager.RegionName="MainRegion"/>
在上面的代码中,我们使用了一个标准的 WPF ListBox 控件,并设置了 RegionManager 的一个附加属性 RegionName。RegionManager 类定义了附加属性,如下面的 Prism 库源代码所示。关注 DependencyProperty.RegisterAttached 方法,它用于创建附加属性(而不是仅创建依赖属性的 Register 方法)。
public static readonly DependencyProperty RegionNameProperty = DependencyProperty.RegisterAttached( "RegionName", typeof(string), typeof(RegionManager), new PropertyMetadata(OnSetRegionNameCallback));
RegionManager 会根据特定的 WPF 控件和 RegionAdapters 之间的映射关系,根据指定的映射创建 RegionAdapter。这种映射定义在 "Bootstrapper.cs" 类中,如下所示。它表示如果 WPF 控件的类型是 Selector、ItemsControl 或 ContentControl,那么 RegionManager 将分别创建 SelectorRegionAdapter、ItemsControlRegionAdapter 或 ContentControlRegionAdapter。下一节我们将详细介绍 WPF 控件和 RegionAdapters 之间的关系。
protected virtual RegionAdapterMappings ConfigureRegionAdapterMappings() { RegionAdapterMappings regionAdapterMappings = ServiceLocator.Current.GetInstance<RegionAdapterMappings>(); if (regionAdapterMappings != null) { regionAdapterMappings.RegisterMapping(typeof(Selector), ServiceLocator.Current.GetInstance<SelectorRegionAdapter>()); regionAdapterMappings.RegisterMapping(typeof(ItemsControl), ServiceLocator.Current.GetInstance<ItemsControlRegionAdapter>()); regionAdapterMappings.RegisterMapping(typeof(ContentControl), ServiceLocator.Current.GetInstance<ContentControlRegionAdapter>()); } return regionAdapterMappings; }
最后,RegionAdapter 会为您创建一个特定类型的区域。默认情况下,ContentControlRegionAdapter、ItemsControlRegionAdapter 和 SelectorRegionAdapter 分别创建 SingleActiveRegion、AllActiveRegion 和 Region 类 的实例。下图是区域如何被创建并与 WPF 控件关联的图示。
WPF 控件和 RegionAdapters
正如我们在上一节中讨论的,根据 WPF 控件和 RegionAdapters 之间的映射,RegionManager 会创建 RegionAdapters。Prism 库提供了以下三种 RegionAdapters:
- ContentControlRegionAdapter: 此适配器用于适配 System.Windows.Controls.ContentControl 及其派生类。
- ItemsControlRegionAdapter: 此适配器用于适配 System.Windows.Controls.ItemsControl 及其派生类。
- SelectorRegionAdapter: 此适配器用于适配 System.Windows.Controls.Primitives.Selector 的派生类。
在 XAML 中定义区域时,**选择合适的 WPF 控件至关重要**,以便创建合适的 RegionAdapter 和 Region 来满足我们的需求。我们可以提供新的映射并创建自定义的 RegionAdapters,但首先需要选择合适的 WPF 控件来创建区域。下图展示了可用于创建区域的控件之间的关系。
根据上图,ContentControl 和 ItemsControl 在层次结构上是并行的,它们都继承自 Control 并实现了 IAddChild 接口。ContentControl 用于承载单个项或视图。
但 ItemsControl 实现了一个名为 IGeneratorHost 的附加接口,以支持 ItemContainers 的行为,因为它需要多个项。 IGeneratorHost 是 ItemContainerGenerator 与其宿主通信的接口。
最后,Selector 继承自 ItemsControl,并提供了在多个托管项中选择一项的附加功能。**Selector 是一个抽象类,必须使用继承自 Selector 的具体类**。要查找哪些控件可以用作 Selector,我们可以访问 MSDN 上的 Selector 文档。如该页面所示,我们可以使用 ComboBox、ListBox 或 TabControl 等作为 Selector。
类似地,我们可以在 MSDN 上的 ContentControl 文档 和 ItemsControl 文档 中找到继承自 ContentControl 和 ItemsControl 的控件。在 ContentControl 文档 中,我们可以看到**即使是 Label 和 Button(ButtonBase 的派生类)也可以用来创建区域**。在演示代码中,您可以注释掉现有的创建区域的代码,然后取消注释另一个使用不同控件的类似代码来验证这一点。
激活和停用视图
激活和停用用于将视图标记为活动和非活动状态。为了提高应用程序的效率,我们可能需要根据不同场景来停用和激活视图。如果视图实现了 IActiveAware 接口,我们可以定义激活或停用时执行的操作。在 本文 中对 IActiveAware 接口进行了实时示例的解释。只有两种 RegionAdapters 支持激活和停用:ContentControlRegionAdapter 和 SelectorRegionAdapter。
由于 ContentControlRegionAdapter 只包含一个视图,因此如果您停用当前视图,区域将为空;如果您激活一个新的视图,新视图将成为区域的内容,而之前的视图将被停用。
在 SelectorRegionAdapter 的情况下,您可以激活和停用视图。但是,使用 SelectorRegionAdapter 创建的区域会显示活动和非活动的视图。
ItemsControlRegionAdapter 不支持停用,尝试停用将引发 InvalidOperationException。如果您尝试在 ItemsControlRegionAdapter 创建的区域内停用视图,将收到一个 InvalidOperationException 异常,显示:“在 Microsoft.Practices.Prism.dll 中发生未处理的 System.InvalidOperationException 类型的异常”,附加信息为:在此类区域中无法停用。
显示和隐藏视图扩展
正如我们在上一节讨论的,SelectorRegionAdapter 会显示活动和非活动的视图,而 ItemsControlRegionAdapter 完全不支持停用。在这些情况下,如果我们想要隐藏/显示视图,Prism 并不提供此功能。因此,我们需要编写代码来实现此功能。Matias Bonaventura 有一篇很棒的 博客文章,其中包含具有隐藏和显示功能的 RegionExtensions 类的代码。我们在演示应用程序中使用了该类来提供显示和隐藏功能。
ContentControlRegionAdapter 只支持单个视图,因此激活和显示的操作方式相同。
学习总结
下表总结了到目前为止使用的控件、RegionAdapters 和其他相关概念。
用于创建区域的 WPF 控件 | ContentControl 或其派生控件 | ItemControl 或其派生控件 | Selector 的派生控件 |
---|---|---|---|
控件的通用用途 | 表示一个具有任何类型单个内容的控件 | 表示一个可用于显示项目集合的控件 | 表示一个允许用户从其子元素中选择项目的控件 |
使用的 RegionAdapter 类 | ContentControlRegionAdapter | ItemsControlRegionAdapter | SelectorRegionAdapter |
使用的 Region 类 | SingleActiveRegion | AllActiveRegion | 区域 |
活动视图的数量 | 单个视图(始终活动且可见),如果您对该单个视图执行停用/隐藏操作,ContentControl 将变为空 | 多个视图(全部活动),执行停用会引发异常,但您可以隐藏视图 | 多个视图,活动视图和非活动视图都可见。您需要调用隐藏/显示来设置活动/非活动视图的可见性。 |
停用视图 | 会起作用,但由于是对单个内容视图调用停用,因此区域将变为空 | 将引发 InvalidOperationException 异常,显示:“在 Microsoft.Practices.Prism.dll 中发生未处理的 System.InvalidOperationException 类型的异常”,附加信息为:在此类区域中无法停用。 | 会起作用 |
活动与可见 | 活动视图将是可见的,因为它被设置为 ContentControl 的内容(因为只有一个视图) | 所有视图(默认情况下活动)都可见。(直到我们编写了扩展来隐藏) | 所有视图(活动和非活动)都可见 |
隐藏与停用 | 由于只支持单个视图,因此活动视图无法隐藏,非活动视图也无法显示。 | 隐藏可用,但停用会引发异常 | 显式调用隐藏,因为它是活动/非活动视图。默认情况下,即使是非活动视图也会显示。 |
演示概述
我们将简要介绍演示应用程序和一些关键步骤,以及如何进行实验。我希望演示中的大部分代码都是自 explanatory 的。如果您需要任何解释,请随时发表评论。
演示解决方案包含三个项目
- RegionAdaptersDemoApp: 使用标准的 WPF 项目模板创建。它包含 Shell(MainWindow),其中定义了区域。在区域中,我们使用了其他两个模块项目(ModuleA 和 ModuleB)中定义的视图。
- RegionAdaptersDemoApp.Module A: 使用类库项目模板创建。代表模块 A,包含两个视图:View1 和 View2。
- RegionAdaptersDemoApp.ModuleB: 使用类库项目模板创建。代表模块 B,包含两个视图:View3 和 View4。
我们正在使用 Prism 4 和目标为 .NET Framework 4 的 WPF 项目,以便演示应用程序可以在 Visual Studio 2010 及更高版本上运行,无需额外设置。创建项目后,我们应该通过在 Nuget 包管理器控制台中运行以下命令来安装“Prism.UnityExtensions”nuget 包。
Get-Project -All | Install-Package Prism.UnityExtensions -Version 4.0.0
下图显示了 Nuget 包管理器控制台。
在 ModuleA 中,添加视图时,请添加 WPF 用户控件(称为“View1”和“View2”),如下所示。
如果尚未包含,请为“System.XAML”添加引用(因为它使用的是类库项目模板)。ModuleB 与 ModuleA 非常相似。
在 RegionAdaptersDemoApp 项目中,查看 MainWindow.xaml。目前我们使用 ListBox 创建了区域。并且有注释掉的代码,可用于**尝试使用其他 WPF 控件**来创建区域。您可以使用名为“View Region Creation Detail”的按钮来查看使用该 WPF 控件创建区域时所使用的类的详细信息。
在控件部分,您可以选择视图,执行添加/删除/激活/停用/显示/隐藏操作,**前提是当前场景适用**,否则将引发异常。
最终的演示应用程序外观如下:
结论
在本文中,我们学习了 WPF 控件、RegionAdapters 和 Regions 之间的关系。希望这能帮助您在创建基于 Prism 的 WPF 应用程序中的区域时做出明智的决策。非常欢迎您的评论/建议和疑问。谢谢。