WPF Prism - 使用Prism Regions的意义是什么?

16

我想知道“区域(regions)”的意义是什么,我猜我不理解它们所解决的问题

例如,我看到很多人将区域用于导航区域,但为什么不只是使用绑定到ObservableCollection的ItemsControl,而不是有一个区域并加载不同的导航元素到该区域中?


一个现实生活中对其使用/优势的例子会非常好!


1
我不得不重新阅读问题,但我猜你不是在谈论 #region。我本来要写一个答案说:“完全没有意义——编译器会忽略它们,而无能的程序员会用它们代替重构”。 - Merlyn Morgan-Graham
2
从Prism的上下文来看,这很明显,不是吗? - Tim Lloyd
@chibacity:对于任何使用过Prism并知道什么是Prism Region的人来说,这是显而易见的。但他在任何时候都没有说“Prism Region”,他只是说“region”。能够用ItemsControl替换Region解决了那些熟悉WPF的人的歧义问题,标签也几乎解决了这个问题,但我还是不得不Google一下才确定。 - Merlyn Morgan-Graham
1
@Merlyn,标题写的是Prism。你有看标题吗? - Tim Lloyd
@MerlynMorgan-Graham 标题是否随标签一起更改?它们表明了Prosm视图与代码编辑器功能之间更高的优先级上下文。 - ProfK
显示剩余4条评论
3个回答

21

区域可以让您在程序中定义一个特定用途的位置。例如,您可能会有一个菜单区域或页脚区域。然后,您可以将这些特定区域的Views/ViewModels分离到程序的自己部分。

因此,您不再需要在ApplicationViewModel中包含MenuViewModelFooterViewModel属性,并使View绑定每个部分到这些属性。相反,您将为菜单和页脚拥有独立的ViewModels,而您的ApplicationViewModel仅需要处理内容。这是一种更好的方式来分隔应用程序中的某些逻辑边界。

个人认为,区域被过度使用和滥用了。除非我有像页眉或页脚这样与我的应用程序代码无关的内容,否则我几乎从不使用它们。它们也大多在视图优先的开发中使用,我更喜欢 ViewModel 优先。


19
比较RegionManager和EventAggregator,你会发现它的优势...
EventAggregator允许不同的组件发布/订阅事件而不相互耦合。同样的事情也适用于RegionManager...你可以将一个视图加载到一个区域中而不让其他视图知道里面发生了什么。这使得你的视图互相解耦...这并不是说每个视图都应该不知道其他视图...有时候一个视图需要知道另一个视图。
看看Microsoft Outlook(注意:我在这里捏造了一些东西,包括名称,因为Outlook不是用WPF编写的,而是用C++):
主界面将包含以下区域:
- 菜单区域 - 导航区域 - 内容区域 - 侧边栏区域
区域在标准控件上定义(因此您仍然需要标准控件),更具体地说,是ItemsControl、ContentControl和Selector(您可以扩展其他控件以支持“区域”)。它们允许另一部分代码通过解析和加载适当的视图到这些区域来管理区域。基本上,这是为了保持解耦。
您的主界面不需要知道关于您的应用程序的所有信息;相反,它只需要知道它有菜单、导航、内容和侧边栏区域。实际放置在这些区域中的视图并不重要。现在,这并不意味着每个视图都应该相互解耦。我稍后会讲到这一点。
那么,它是如何解耦的呢?这里是一个场景:单击导航控件中的日历图标。那么当您这样做时,应该发生什么?
- NavigationView - 按钮(图标)绑定到一个ICommand,因此调用ExecuteLoadCalendar()函数。 - NavigationViewModel - ExecuteLoadCalendar()函数使用EventAggregator来宣布用户正在尝试启动日历。 - ContentController - ContentController已经订阅了LoadCalendarAggregateEvent,因此执行它。在这里,它使用IRegionManager和区域名称来解决/激活耦合的CalendarView。应该通过获取ICalendarView而不是CalendarView来执行此操作。
整个过程中每个部分都是解耦的,除了ContentController和CalendarView/ICalendarView。当然,你可以说NavigationView/NavigationViewModel有点知道CalendarView/CalendarViewModel,因为它们有一个ICommand和函数。但“有点”并不等于“他们确实有”,因为代码后台和视图模型代码不应该引用实际的CalendarView/CalendarViewModel对象。
另外,我们可以使这个执行更加通用,去掉“有点”这个限制。它可以有一个LoadContent(NavigationItem item)函数,其中AggregateEvent负载是某种标识,例如item.Name(String)(从数据库、XML等加载)。ContentController使用相同的数据来解析“日历”,而不是ICalendarView(因为实际上它不关心在ContentRegion中被解析/激活的接口/类型——它只需要一个对象来激活)。我使用MEF,所以可以通过以下代码块实现:
[Export("Calendar")]
public class CalendarView : UserControl, ICalendarView { }

那么,视图之间能够相互了解吗?是的!例如,我的EmailUserControl有一个搜索栏/邮件列表和一个预览窗格。这两个控件可以是EmailListUserControl,它由一个搜索栏和一个ItemsControl组成,以及一个EmailContentUserControl,它只是所选电子邮件的预览窗格。它们必须是单独的控件吗?不一定,但如果是,我们可以在单独的窗口中打开电子邮件时重用EmailContentUserControl。因此,这是一个例子,其中EmailUserControl与两个不同的视图(EmailListUserControlEmailContentUserControl)耦合。


为什么这比其他方法更好/不同:它使视图之间解耦(并防止视图模型需要知道视图的情况发生)。


据我所知,主要的好处是如果您打算在运行时重新绑定区域,并使用事件聚合器,则视图和视图模型不必包含此逻辑? - Merlyn Morgan-Graham
EventAggregator并不是必需的,ContentController可以作为IContentControllerService被解析为服务并注入到您的视图模型中(就像EventAggregator被解析为IEventAggregator并注入一样)。我只是想既然这与PRISM相关,为什么不充分利用所有prism工具的力量,并将视图模型与ContentController分开。耦合越少,单元测试就越容易。真正“耦合”代码的只有那些被注入的“服务”。 - myermian
重点是最终总结:“将视图从彼此解耦(并防止视图模型需要了解视图)”...我想你可以说,如果不需要耦合,那么重点是使更多的代码解耦。 - myermian
我的观点是,你可以在不使用区域的情况下将视图注入到视图模型中,并将相应的属性绑定到ContentPlaceHolder控件上。楼主说“相对于其他选择”,而这是我在一个应用程序中成功使用过的一种替代方法。但是我没有事件聚合器,这使得这个解决方案更具吸引力。有了它,我认为你可以将视图选择逻辑从父视图中解耦出来。我认为这意味着父视图不需要了解选择逻辑,视图模型也不需要,而且选择逻辑也不需要知道哪个父视图. - Merlyn Morgan-Graham
@MerlynMorgan-Graham:这不是一个好主意,因为它会破坏MVVM。将View注入到ViewModel中意味着您的ViewModel必须了解View。ViewModel不应该知道什么是“ContentControl”,因为它可能绑定到控制台应用程序,而控制台应用程序对“ContentControl”没有用处。这也是我从不使用“FileDialog”的原因。但是,如果您想要破坏MVVM,我想最接近的方法就是将视图作为“ContentControl”的类型注入到VM中。 - myermian
我想现在我和你的想法一致了。我的意图是让您与其他解决方案进行比较和对比,因为OP说“相比其他方案使用/好处”。但我想您解释了好处,这意味着其他解决方案没有这种好处(我知道它们没有,只是不确定答案是否清楚表达了这一点)。 - Merlyn Morgan-Graham

1

Region 可以使用的场景

我浏览了这篇文章来获取答案: http://www.developmentalmadness.com/archive/2009/10/14/mvvm-and-prism-101-ndash-part-3-regions.aspx

据我所知,Region 功能的设计目的在于允许 View 在既不是您的视图也不是您的视图模型的代码中进行注册/注入。

举个例子,如果您使用 ContentPresenter 控件而不是 Region,则您的视图模型必须返回具体视图,以使主视图保持对其子视图的不可知性。

如果您使用 ItemsControl 并将其绑定到视图模型上的任意数据,则需要在主视图上的 DataTemplate 中指定要实例化的视图。

使用 Region,您可以将视图注册到依赖注入容器中的某个区域。视图和对应的视图模型都不需要知道在运行时将使用哪个具体视图。它会由容器注入进来。
这样可以完全解耦主视图与子视图之间的关系,而无需强制视图模型了解这些子视图。
具体使用案例
这有多么有用,以及这将启用什么具体场景,我不确定。我使用过一个带有插件架构的ContentPresenter,但我不确定它是否适合这种模型。在插件模型中,您希望将视图和视图模型绑定在一起,因此此方法对您没有任何好处。
我想如果您发现有大量不相关的视图并希望将它们拆分,那么它可能效果最佳。通过一次只注入视图和视图模型的部分内容,可能有助于将集成测试彼此隔离。

我正在寻找引人入胜的现实用例 :) 在几乎所有情况下,您可以简单地使用 UserControl,并且可以放弃整个动态注册过程,而没有真正的不利影响。


1
注意:我从未使用过Prism。我不知道他们是否正确使用了依赖注入容器(只有你的入口点代码才能看到容器),或者他们是否鼓励或要求您使用ServiceLocator模式。那篇文章似乎做错了 :) 使用正确的DI容器,您永远不想在除了Main函数(或等效的WPF应用程序启动方法)之外调用Resolve。一些DI库没有围绕这种用法设计其容器,因此对于这些容器的作者来说,与我意见不合似乎是很自然的事情。不确定Prism是否是这样的。 - Merlyn Morgan-Graham

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接