Introduction
To create and use a Region effectively within a Prism based WPF application, it is critical to understand the relationship between WPF Controls, RegionAdapters and Regions. RegionAdapters pays a very critical role in Region creation process and determine its behaviours. In this articles, we will learn about process of Region creation and role of RegionAdapters in it. We will create a demo application using WPF and Prism 4. We will use all three kind of RegionAdapters provided by Prism Framework (ContentControlRegionAdapter, ItemsControlRegionAdapter and SelectorRegionAdapter) in it.
Prerequisites: I assume you would be having basic understanding of WPF and Prism Framework. Prism is a framework for building Composite Applications using WPF/Silverlight. I have explained step by step and demonstrated such composite application in an article series named as "Composite Applications with Prism in WPF". For more learning about Prism, please have a look at Prism Basic Introduction and Prism Page on MSDN, and few more things about Prism.
Outlines
- Regions and RegionAdapters
- Region Creation Process
- WPF Controls and RegionAdapters
- Activate and Deactivate Views
- Show and Hide View Extension
- Learning Summary
- Overview of Demo
Regions and RegionAdapters
Region makes standard WPF controls (like Button, ListBox, ComboBox etc.) to behave like a dynamic container of Views along with certain other behaviours. At runtime, we can add, remove, activate, deactivate, show and hide a View in a region. RegionAdapters plays a central role to make this possible. A Region can adopt Views from different Modules and same we will do while creating demo application.
There are two main concepts related to Region: RegionManager and RegionAdapters. The RegionManager is responsible for creating and maintaining a collection of regions for host WPF controls. RegionAdapters are responsible for creating a region and associating it with the host WPF controls. In a typical Prism application we often need to understand and choose among different kind of WPF controls to host Region. By choosing a particular WPF host control, we indirectly choose a particular RegionAdapter and Region. This article will explain about those implicit choices and help to take better decisions while creating a Region.
Sometime we need to create custom RegionAdapters and Regions too. In Prism library, out of the box, three kind of RegionAdapters and respective Regions are provided. We will look into those in detail further. In next section we will explore Region creation process.
Region Creation Process
In XAML, we can create a Region with following code:
<ListBox Name="MainRegion" prism:RegionManager.RegionName="MainRegion"/>
In above code, we are using a standard WPF ListBox control and setting an AttachedProperty of RegionManager called RegionName. RegionManager class that AttachedProperty defined as per below code taken from Prism library source code. Focus on method DependencyProperty.RegisterAttached, which is used to create an AttachedProperty (not just Register method which creates a DependencyProperty).
public static readonly DependencyProperty RegionNameProperty = DependencyProperty.RegisterAttached(
"RegionName",
typeof(string),
typeof(RegionManager),
new PropertyMetadata(OnSetRegionNameCallback));
RegionManager looks into mapping defined between for based on particular WPF Controls and RegionAdapters and create a RegionAdapter as per the mapping given. Such mapping is defined in class "Bootstrapper.cs" as given below. It says if WPF control is a type of Selector, ItemsControl or ContentControl then RegionManager will create a SelectorRegionAdapter, ItemsControlRegionAdapter or ContentControlRegionAdapter respectively. In next section we will look into the relationship between WPF controls and RegionAdapters in details.
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;
}
And finally RegionAdapter create a specific type of Region for you. By default ContentControlRegionAdapter, ItemsControlRegionAdapter and SelectorRegionAdapter creates an instance of SingleActiveRegion, AllActiveRegion and Region class respectively. Following is pictorial description of How a Region is being created and associated with WPF Control:
WPF Controls and RegionAdapters
As we discussed in last section, based on mapping between WPF Controls and RegionAdapters, RegionManager creates a RegionAdapters. There are blow three kind of RegionAdapters provided by Prism library:
- ContentControlRegionAdapter: This adapter adapts controls of type System.Windows.Controls.ContentControl and derived classes.
- ItemsControlRegionAdapter: This adapter adapts controls of type System.Windows.Controls.ItemsControl and derived classes.
- SelectorRegionAdapter: This adapter adapts controls derived from the class System.Windows.Controls.Primitives.Selector.
It is critical to choose appropriate WPF Control while defining a region in XAML, so that appropriate RegionAdapter and Region gets created to fulfil our requirement. We can provide new mapping and create Custom RegionAdapters too, but first thing in hand we have to choose appropriate WPF control while creating Region. Below image show relationship between the controls which can be used to create Regions:
As per above image ContentControl and ItemsControl are parallel in hierarchy and both inhetited from Control and implement IAddChild interface. ContentControl supposed to host a single item or View.
But ItemsControl implements an additional interface called as IGeneratorHost to facilitate ItemContainers behaviour as it need to have multiple items. IGeneratorHost is an interface through which an ItemContainerGenerator communicates with its host.
Finally Selector is inherited from ItemsControl and provide addition functionality to select an item among multiple hosted items. Selector is an abstract class must use any concrete class inherited from Selector. To find out which controls can be used as Selector, we can visit Selector documentaion on MSDN. As shown on this page, we can use ComboBox, ListBox or TabControl etc. as Selector.
Similarly we can find out the controls inherited from ContentControl at ContentControl documentation and ItemsControl at ItemsControl documentation on MSDN. At ContentControl documentation we can see that even Label and Button (derivative of ButtonBase) can be used to create a Region. In demo code we can verify it by commenting out existing line to create Region and un-comment any other similar line with some another control.
Activate and Deactivate Views
Activation and Deactivation are used to mark a View active and deactivated. To make an efficient application, we may need to deactivate and activate Views at per scenarios. If views have implemented IActiveAware interface we can define actions to be done on activation or deactivation. A real time based example of IActiveAware interface is explained at this article. Activation and Deactivation is supported by only two RegionAdapters: ContentControlRegionAdapter and SelectorRegionAdapter.
As ContentControlRegionAdapter contains single View so if you deactivate current view, Region will be empty and if you activate some new view, the new View will be the content of the region and previous View will be deactivated.
In case of SelectorRegionAdapter you can activate and deactivate View. But Region created with SelectorRegionAdapter shows both active and deactivated Views.
ItemsControlRegionAdapter does not support Deactivation and it will throw an InvalidOperationException. If you try to deactivate a view within a Region created using ItemsControlRegionAdapter, will get an InvalidOperationException exception saying: "An unhandled exception of type 'System.InvalidOperationException' occurred in Microsoft.Practices.Prism.dll" with Additional information: Deactivation is not possible in this type of region.
Show and Hide View Extension
As we discussed in above section, SelectorRegionAdapter shows both active and deactivated Views and ItemsControlRegionAdapter does not support deactivation at all. In those cases if we need to hide/show Views, such functionality is not provided by Prism. So we need to write code to implement such functionality. There is a nice blog post by Matias Bonaventura with code of RegionExtensions class having hide and show functionality. We have used that class in our demo application to provide show and hide capabilities.
ContentControlRegionAdapter support only single View, so activate and show behaves in same way.
Learning Summary
Below table provides summary of Controls Used, RegionAdapters and other related concepts so far:
WPF Control Used to Create Region |
ContentControl or its Derived Control |
ItemControl or its Derived Control |
Selector's Derived Control |
General Use of Control |
Represents a control with a single piece of content of any type |
Represents a control that can be used to present a collection of items |
Represents a control that allows a user to select items from among its child elements |
RegionAdapter Class Used |
ContentControlRegionAdapter |
ItemsControlRegionAdapter |
SelectorRegionAdapter |
Region Class Used |
SingleActiveRegion |
AllActiveRegion |
Region |
How Many Active Views |
Single View (Always Active and Visible), if you call deactivate/hide on that single view it will make Content Control empty |
Multiple View (All Active), calling Deactivate throw exception, but you can hide views |
Multiple Views both active and Deactivated views are visible. You need to call hide/show to set visibility for active / deactivated view |
Deactivate View |
Will work but it will empty the region as Deactivation is called on single content view |
Will throw InvalidOperationException exception saying: "An unhandled exception of type 'System.InvalidOperationException' occurred in Microsoft.Practices.Prism.dll" with Additional information: Deactivation is not possible in this type of region. |
Will work |
Active vs Visible |
Active view will be visible as it is set as the content of the ContentControl (as there is only one view) |
All views (by default Active) are visible. (until we wrote extension to hide) |
All views (active and deactivated) are visible |
Hide vs Deactivation |
As only single view is supported. The Active view cannot be hidden and a deactivated view cannot be visible |
Hide works but Deactivate will throw an exception |
Call Hide explicitly as Active / Deactivated View. By default even Deactivate view will be visible |
Overview of Demo
We will explain in brief about demo application and few critical steps and how to do experiments with demo. I hope most of the code in demo is self-explanatory. Feel free to write a comment if you need any explanation.
The Demo solution is having three projects:
- RegionAdaptersDemoApp : Created using standard WPF Project template. It has Shell as MainWindow where Regions are defined. In Region we are using Views defined in other two module projects (ModuleA and ModuleB).
- RegionAdaptersDemoApp.Module A: Created using Class Library Project template. Represent Module A and having two Views: View1 and View2.
- RegionAdaptersDemoApp.ModuleB : Created using Class Library Project template. Represent Module B and having two Views: View3 and View4.
We are using Prism 4 and WPF project targeting .NET Framework 4, so that demo app can run on Visual Studio 2010 onwards without addition setups. After creation of projects, we should install "Prism.UnityExtensions" nuget package by running following command in Nuget Package Console:
Get-Project -All | Install-Package Prism.UnityExtensions -Version 4.0.0
Below screenshot shows Nuget Package Console:
In ModuleA, while adding View, add a WPF User Controls (called as "View1" and "View2") as shown below:
Add reference to "System.XAML" if already not included in that project (because it was created using Class Library project template). ModuleB is very similar to ModuleA only.
Inside RegionAdaptersDemoApp project, look into MainWindow.xaml, currently we are having Region created using ListBox. And there are commented codes which can be used to experiment with other WPF controls to create Region. And you can use Button called "View Region Creation Detail" to see the details of classes used for Region while using that WPF Control.
In Control section, you can choose views and Add/Remove/Activate/Deactivate/Show/Hide operations can be done if applicable as per current scenario, otherwise we will get exception thrown.
Final demo application will look like:
Conclusion
In this article, we learned about relationship between WPF Controls, RegionAdapters and Regions. Hope it will help you to take an informed decision while creating a Region in Prism based WPF application. Your comments / suggestions and queries are most welcome. Thanks.