理解PRISM框架中的区域管理

在现代用户界面设计中,区域(Region)的概念是至关重要的。区域可以包含任意数量的视图,并且可以是任意类型。当一个区域只包含一个视图时,一切看起来都很简单。但是,当一个区域关联了多个视图时,复杂性就开始出现了。如果这些视图是相同类型,并且包含嵌套区域,那么冲突就可能发生。

为了解决这个问题,PRISM框架提供了一个名为RegionManager的工具,它要求所有区域都有一个唯一的名称。但是,当创建多个嵌套区域时,名称就不再唯一了。为了解决这个问题,RegionManager可能会创建一个作用域,并将视图放入该作用域中。本质上,这是创建了一个新的RegionManager实例,并将其分配给视图。这将视图与其他父区域中的视图隔离开来。

区域是通过使用RegionManager.RegionName附加属性来创建的。这将元素与处理该区域的RegionManager实例关联起来。当RegionManager.RegionName属性附加到元素上时,RegionManager还会附加另外两个属性:RegionManager.RegionManagerRegionManager.RegionContext。这两个属性非常重要,稍后将会看到。

关键特性是RegionManager.RegionManager属性,它初始化时会引用持有视图的RegionManager实例。所以,如果视图是带有作用域创建的,那么这个属性将持有对该作用域RegionManager的引用。

问题

实现作用域区域支持可以分为两个不同的方面:

1. 在创建视图时,需要告诉PRISM库(可能是动态的,逐个案例)这个特定的视图需要作用域区域。

2. 一旦视图创建,它应该能够直接访问与视图及其作用域相关联的RegionManager

解决方案

为了解决第一个问题,需要在视图发现或区域导航期间,在PRISM实现中注入代码,以确定是否需要作用域,如果需要,就将视图添加到具有作用域的区域中。需要某种标志来指示是否需要作用域区域。这个信息可以通过几种不同的方式传递:

- 视图类可以实现某个接口,以指示需要作用域。它可以有一个成员在运行时返回布尔值,指示是否需要作用域。

- 可以使用属性来标记需要作用域区域的类型。

还有其他方法,但为了简单起见,将使用这两种方法。

为了解决第二个问题,解决方案并不那么直接。没有直接访问为视图的作用域创建的RegionManager实例。没有返回引用,也没有在区域树中的任何地方提供嵌套作用域区域管理器的列表。为了解决这个障碍,可以使用RegionManager.RegionManager附加属性。当视图被添加到区域时,它将这个附加属性初始化为处理这个特定区域的RegionManager实例的引用。如果已经请求了作用域区域,它将持有对作用域RegionManager实例的引用,而不是父/根RegionManager。为了获得正确的RegionManager实例,所要做的就是将这个附加属性绑定到视图的成员参数上。毕竟,附加属性的设计就是为了这个目的。

实现

当前的PRISM4实现使用IRegionNavigationContentLoader来创建新视图并将其添加到区域中。IRegionNavigationContentLoaderRegionNavigationScopedContentLoader类中实现。关键方法是LoadContent。检查源代码揭示了在区域导航所需的某些操作之后,加载器执行以下三行代码:

view = this.CreateNewRegionItem(candidateTargetContract); region.Add(view); return view;

它创建了新视图,将其添加到区域,并返回新视图给调用者。因此,为了扩展这种行为并添加对作用域区域的支持,所要做的就是确定是否需要作用域区域,如果需要,就将视图添加到具有作用域的区域中。最合乎逻辑的方法是将标志作为参数传递给LoadContent函数。不幸的是,在视图发现或区域导航期间,内容加载器是由PRISM库内部调用的,没有简单的方法可以改变它。

与其重新设计PRISM,不如让视图类携带这个信息。为此,可以用自定义属性标记类型,或者让类实现某个接口。每种方法都有利弊,为了演示目的,将实现这两种方法。

为了将视图添加到具有作用域的区域中,应该调用IRegion.Add(view, viewName, createRegionManagerScope)方法。在这个函数中,view参数是刚刚创建的新视图,Name和Scope标志是需要提供的信息。将实现一个接口来传递缺失的信息:

public interface IProvideRegionScopeInfo { string ViewName { get; } bool CreateRegionManagerScope { get; } }

通过实现这个接口,视图类可以在运行时提供信息,指示是否需要作用域,并为视图的实例提供唯一名称。

有时,只需要用一个属性标记一个类,表明每个实例都需要作用域区域。为此,可以使用以下属性:

[Attribute] public class ScopedRegionManagerAttribute : Attribute, IProvideRegionScopeInfo { public ScopedRegionManagerAttribute() { CreateRegionManagerScope = true; } public string ViewName { get; set; } public bool CreateRegionManagerScope { get; set; } }

为了统一访问接口和属性,让属性也实现了IProvideRegionScopeInfo接口。因此,在代码中添加了所有必要的检查之后,应该有类似这样的东西:

view = this.CreateNewRegionItem(candidateTargetContract); IProvideRegionScopeInfo info = (view as IProvideRegionScopeInfo) ?? (ScopedRegionManagerAttribute)view.GetType().GetCustomAttributes( typeof(ScopedRegionManagerAttribute), false ).FirstOrDefault(); if (null == info) region.Add(view); else region.Add(view, info.ViewName, info.CreateRegionManagerScope); return view;

获得适当的区域管理器需要一些额外的代码。通常会使用依赖注入容器来解析IRegionManager接口。仍然可以这样做来访问根RegionManager,但为了访问负责这个视图实例的RegionManager,必须使用RegionManager.RegionManager附加属性。这样做非常简单,可以在视图的构造函数中使用以下代码:

Binding binding = new Binding("LocalRegionManager") { Mode = BindingMode.OneWayToSource, Source = viewModel }; this.SetBinding(Microsoft.Practices.Prism.Regions.RegionManager.RegionManagerProperty, binding);

绑定的模式是BindingMode.OneWayToSource,因为不希望允许这个属性从源内部更改。源可以是一个视图或视图模型,或者两者都是,如果使用单独的绑定对象。

这两种增强的结合将允许在视图发现以及PRISM区域导航中使用作用域区域。

使用代码

已经包含了一个示例应用程序来演示这个解决方案。为了在项目中使用这段代码,只需将RegionNavigationScopedContentLoader.cs添加到解决方案中,并将其与默认实现一起注册到DI容器中。有关示例,请参考Bootstrapper.cs文件。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485