WPF Prism - What is the point of using Prism Regions?
Asked Answered
L

3

17

I'm just wondering what the point of regions are. I guess I don't understand the problem they solve.

For example, I see a lot of people using regions for a navigation region, but then why not just have an ItemsControl bound to an ObservableCollection instead of having a region and load in different navigation elements into that region?


A real-world example of it's use/benefits over the alternatives would rock!

Latton answered 1/11, 2011 at 17:45 Comment(9)
I had to re-read the question, but I assume you're not talking about #region. I was about to write an answer that said "absolutely no point - the compiler ignores them, and incompetent programmers use them instead of refactoring"Orthorhombic
@Merlyn From the Prism context it's obvious, no?Zoroastrianism
@chibacity: It is obvious for anyone who has used Prism and knows what a Prism Region is. But he doesn't say "Prism Region" at any point, he just says "region". Being able to replace a Region with an ItemsControl resolves the ambiguity for people who know WPF, and the tags nearly solve the problem, but I had to google to be sure.Orthorhombic
@Merlyn The title says Prism. You did read the title?Zoroastrianism
@chibacity: Someone edited the title. I might have missed the edit if it happened before my comment, as I was heading out the door. I agree that it is clear nowOrthorhombic
The title did state Prism prior to me updating it, however, I did change it to say "Prism Regions" instead of just "Regions".Bilbo
@MerlynMorgan-Graham Did the tags change along with the title. They suggest a higher precedence context of Prosm views vs. code editor features.Midwinter
@Midwinter get a time machine and ask me 6 years ago, and I'll probably know the answer then :) I think this refers to a prism framework feature, not #region blocks, but I honestly don't know anymoreOrthorhombic
It is clearly about "Prism Regions", not #region.Midwinter
B
19

Compare the RegionManager to the EventAggregator and you'll see the advantage of it...

The EventAggregator allows different components to publish/subscribe to events without being coupled to each other. The same goes with the RegionManager... you can load a view into a region without your other view knowing what is going on in there. It decouples your views from each other... that's not to say EVERY view should be unaware of each other... there are times where a view should know about another view.


Look at Microsoft Outlook (note: I'm making things up here, including names, as Outlook is not written in WPF, but instead C++):

The main UI would have the following regions:

  • MenuRegion
  • NavigationRegion
  • ContentRegion
  • SideRegion

Regions are defined on standard controls (so you still need standard controls), more specifically ItemsControl, ContentControl, and Selector out of the box (you can extend other controls to be "region supportive"). They allow another section of code to manage the regions by resolving and loading the appropriate views into those regions. Basically, to keep things decoupled.

You're main UI does not need to know everything about your application; instead, it just simply needs to know that it has a Menu, Navigation, Content, and Side Region. Which views that actually are placed in the regions doesn't matter. Now, that doesn't mean every view should be decoupled from each other. I'll get to that later.

So, how does it actually decouple? Here's a scenario: click on the calendar icon in the Navigation control. So what should happen when you do that?

  1. NavigationView - The Button (Icon) is bound to an ICommand, so it calls the ExecuteLoadCalendar() function.
  2. NavigationViewModel - The ExecuteLoadCalendar() function uses the EventAggregator to announce that the user is attempting to launch the calendar.
  3. ContentController - The ContentController had subscribed to the LoadCalendarAggregateEvent, so that is executed. Here, it resolves/activates the CalendarView (COUPLED) using the IRegionManager and the region name. It should do this by grabbing an ICalendarView instead of a CalendarView.

Throughout the process each part is decoupled except for the ContentController and the CalendarView/ICalendarView. Of course, you could say that the NavigationView/NavigationViewModel sort of knew about the CalendarView/CalendarViewModel by having an ICommand and function. Well, "sort of" isn't the same as "they do", because the code-behind and view-model code should never reference the actual CalendarView/CalendarViewModel objects.

Also, we can remove the "sort of" by making the execute generic. Instead of having a ExecuteLoadCalendar() function, it can have a LoadContent(NavigationItem item) function where the AggregateEvent payload is an identification of some sort, for example, item.Name (String) (loaded in from a DB, XML, etc) to state they clicked "Calendar". The ContentController uses the same data to resolve "Calendar" instead of an ICalendarView (because really it shouldn't care what interface/type is resolved/activated in the ContentRegion -- it just needs an Object to activate). I use MEF, so this can be achieved with the following code block:

[Export("Calendar")]
public class CalendarView : UserControl, ICalendarView { }

So, can views know about each other? Yes! For example, my EmailUserControl has a search bar/list of emails as well as a preview pane. These 2 controls can be EmailListUserControl, which is composed of a search bar and an ItemsControl, and a EmailContentUserControl, which is just a preview pane for the selected email. Do they have to be separate controls? No, but if they are then we can reuse the EmailContentUserControl when we open the email in a separate window. So, this is an example where the EmailUserControl is coupled to 2 different views (the EmailListUserControl and the EmailContentUserControl).


Why is this better/different from other approaches: It decouples views from each other (and guard against view-models needing to know about views).

Bilbo answered 1/11, 2011 at 18:33 Comment(6)
So from what I gather, the main benefit is if you intend to rebind the region at runtime, using the event aggregator, so the view and view model don't have to contain this logic?Orthorhombic
The EventAggregator is not necessary, the ContentController could be an IContentControllerService that gets resolved as a service and injected into your view models (just as the EventAggregator is resolved as an IEventAggregator and injected). I just figured since this was PRISM-related, why not use the full force of all the prism tools and keep the view-model separate from the ContentController. The less coupling, the easier the unit testing. Really only "services" that are injected are the things that "couple" the code all-together.Bilbo
The point is the final summary: "To DECOUPLE views from each other (and guard against view-models needing to know about views)" ... I guess you could say the point is to make more code decoupled if it doesn't need to be coupled.Bilbo
My point is that you can inject views into view models without using regions, and bind the respective property into a ContentPlaceHolder control. The OP said "over other alternatives", and this is an alternative I've successfully used in an app. But I didn't have the event aggrigator, which makes this solution more compelling. With it I think you can decouple the view selection logic from the parent view. I think this means the parent view doesn't have to know about the selection logic, the view model doesn't either, and the selection logic doesn't have to know which parent view.Orthorhombic
@MerlynMorgan-Graham: That's not a good idea because it breaks MVVM. Injecting a View into the ViewModel means your ViewModel must have knowledge of the view. The ViewModel shouldn't know what a ContentControl is, because it might be the bound to a Console application, which has no use for a ContentControl. It's the same reason why I never use FileDialog. But, yes, if you want to break MVVM I guess the closest thing to do is to inject a view into a VM as a type of ContentControl.Bilbo
I think I'm on the same page as you now. My intent was to get you to compare and contrast vs other solutions, since the OP said "use/benefits over the alternatives". But I guess you explaining the benefits implies that other solutions don't have that benefit (I know they don't, I'm just not sure if that is clear in the answer or not).Orthorhombic
A
22

Regions allow you to define a spot in your program that exists for a specific purpose. So for example, you might have a Menu Region, or a Footer Region. You can then separate out the Views/ViewModels for those specific regions into their own section of the program.

So instead of having an ApplicationViewModel containing properties for the MenuViewModel and FooterViewModel, and having the View bind each section to those properties, you would have separate ViewModels for Menu and Footer, and your ApplicationViewModel would only deal with the content. It's a better way to separate some logical boundries in your application.

Personally I think Regions are horribly overused and abused. I almost never use them unless I have something like a Header or a Footer that is unrelated to the rest of my Application code. They're also mostly used in View-First development, and I prefer ViewModel-First.

Across answered 1/11, 2011 at 18:0 Comment(0)
B
19

Compare the RegionManager to the EventAggregator and you'll see the advantage of it...

The EventAggregator allows different components to publish/subscribe to events without being coupled to each other. The same goes with the RegionManager... you can load a view into a region without your other view knowing what is going on in there. It decouples your views from each other... that's not to say EVERY view should be unaware of each other... there are times where a view should know about another view.


Look at Microsoft Outlook (note: I'm making things up here, including names, as Outlook is not written in WPF, but instead C++):

The main UI would have the following regions:

  • MenuRegion
  • NavigationRegion
  • ContentRegion
  • SideRegion

Regions are defined on standard controls (so you still need standard controls), more specifically ItemsControl, ContentControl, and Selector out of the box (you can extend other controls to be "region supportive"). They allow another section of code to manage the regions by resolving and loading the appropriate views into those regions. Basically, to keep things decoupled.

You're main UI does not need to know everything about your application; instead, it just simply needs to know that it has a Menu, Navigation, Content, and Side Region. Which views that actually are placed in the regions doesn't matter. Now, that doesn't mean every view should be decoupled from each other. I'll get to that later.

So, how does it actually decouple? Here's a scenario: click on the calendar icon in the Navigation control. So what should happen when you do that?

  1. NavigationView - The Button (Icon) is bound to an ICommand, so it calls the ExecuteLoadCalendar() function.
  2. NavigationViewModel - The ExecuteLoadCalendar() function uses the EventAggregator to announce that the user is attempting to launch the calendar.
  3. ContentController - The ContentController had subscribed to the LoadCalendarAggregateEvent, so that is executed. Here, it resolves/activates the CalendarView (COUPLED) using the IRegionManager and the region name. It should do this by grabbing an ICalendarView instead of a CalendarView.

Throughout the process each part is decoupled except for the ContentController and the CalendarView/ICalendarView. Of course, you could say that the NavigationView/NavigationViewModel sort of knew about the CalendarView/CalendarViewModel by having an ICommand and function. Well, "sort of" isn't the same as "they do", because the code-behind and view-model code should never reference the actual CalendarView/CalendarViewModel objects.

Also, we can remove the "sort of" by making the execute generic. Instead of having a ExecuteLoadCalendar() function, it can have a LoadContent(NavigationItem item) function where the AggregateEvent payload is an identification of some sort, for example, item.Name (String) (loaded in from a DB, XML, etc) to state they clicked "Calendar". The ContentController uses the same data to resolve "Calendar" instead of an ICalendarView (because really it shouldn't care what interface/type is resolved/activated in the ContentRegion -- it just needs an Object to activate). I use MEF, so this can be achieved with the following code block:

[Export("Calendar")]
public class CalendarView : UserControl, ICalendarView { }

So, can views know about each other? Yes! For example, my EmailUserControl has a search bar/list of emails as well as a preview pane. These 2 controls can be EmailListUserControl, which is composed of a search bar and an ItemsControl, and a EmailContentUserControl, which is just a preview pane for the selected email. Do they have to be separate controls? No, but if they are then we can reuse the EmailContentUserControl when we open the email in a separate window. So, this is an example where the EmailUserControl is coupled to 2 different views (the EmailListUserControl and the EmailContentUserControl).


Why is this better/different from other approaches: It decouples views from each other (and guard against view-models needing to know about views).

Bilbo answered 1/11, 2011 at 18:33 Comment(6)
So from what I gather, the main benefit is if you intend to rebind the region at runtime, using the event aggregator, so the view and view model don't have to contain this logic?Orthorhombic
The EventAggregator is not necessary, the ContentController could be an IContentControllerService that gets resolved as a service and injected into your view models (just as the EventAggregator is resolved as an IEventAggregator and injected). I just figured since this was PRISM-related, why not use the full force of all the prism tools and keep the view-model separate from the ContentController. The less coupling, the easier the unit testing. Really only "services" that are injected are the things that "couple" the code all-together.Bilbo
The point is the final summary: "To DECOUPLE views from each other (and guard against view-models needing to know about views)" ... I guess you could say the point is to make more code decoupled if it doesn't need to be coupled.Bilbo
My point is that you can inject views into view models without using regions, and bind the respective property into a ContentPlaceHolder control. The OP said "over other alternatives", and this is an alternative I've successfully used in an app. But I didn't have the event aggrigator, which makes this solution more compelling. With it I think you can decouple the view selection logic from the parent view. I think this means the parent view doesn't have to know about the selection logic, the view model doesn't either, and the selection logic doesn't have to know which parent view.Orthorhombic
@MerlynMorgan-Graham: That's not a good idea because it breaks MVVM. Injecting a View into the ViewModel means your ViewModel must have knowledge of the view. The ViewModel shouldn't know what a ContentControl is, because it might be the bound to a Console application, which has no use for a ContentControl. It's the same reason why I never use FileDialog. But, yes, if you want to break MVVM I guess the closest thing to do is to inject a view into a VM as a type of ContentControl.Bilbo
I think I'm on the same page as you now. My intent was to get you to compare and contrast vs other solutions, since the OP said "use/benefits over the alternatives". But I guess you explaining the benefits implies that other solutions don't have that benefit (I know they don't, I'm just not sure if that is clear in the answer or not).Orthorhombic
O
1

Scenarios that Region enables

I skimmed this article to get the answer: http://www.developmentalmadness.com/archive/2009/10/14/mvvm-and-prism-101-ndash-part-3-regions.aspx

As far as I can tell, the Region feature is designed to enable View registration/injection in code that is neither your view nor your view model.

Take for example a ContentPresenter control. If you used it instead of a Region, your view model would have to return concrete views in order to keep the main view agnostic of its child views.

If you used a ItemsControl and bound it to arbitrary data on the view model, you would need to specify which views to instantiate in a DataTemplate on the main view.

With a Region, you can register a view to a Region in the Dependency Injection container. Neither the view nor the corresponding view models need to know anything about the concrete view that will be used at runtime. It will be injected in by the container.

This lets you completely decouple a main view from knowing anything about its child views, without forcing your view model to know anything about those child views.

Concrete use cases

How useful this is, and what concrete scenarios this would enable, I'm not sure. I've used a ContentPresenter with a plugin architecture, but I am not sure that fits well with this model. With a plugin model, you want the view and view model to be bound together, so this approach buys you nothing.

I suppose it would work best if you find you have big unrelated views and want to split them up. It might be useful for isolating your integration tests from each other, by injecting only part of the view and view model at a time.

I'm grasping at straws for compelling real-world use cases :) In nearly all the cases you could simply use a UserControl instead, and could forgo the whole dynamic registration thing, with no real down side.

Orthorhombic answered 1/11, 2011 at 17:58 Comment(1)
Note: I've never used Prism. I don't know if they use the Dependency Injection container correctly (only your entry-point code sees the container), or if they actually encourage or require you to use the ServiceLocator pattern. That articles seems to do it wrong :) With a proper DI container, you never ever want to call Resolve except in your Main function (or the equivalent - the App startup method in WPF). Some DI libs haven't designed their container around this use, so it will seem perfectly natural to those container's authors to disagree with me. Not sure if Prism is this way or not.Orthorhombic

© 2022 - 2024 — McMap. All rights reserved.