Fill My Prism Region, Please
I’ve been working on fine tuning a Silverlight progress control for an article with Simple-Talk.com. Along the way I wanted to create a background that dims the entire page while the progress control is up and running. Easy stuff … just stick it in a Grid panel with HorizontalAlignment and VerticalAlignment set to Stretch. Works like a charm … except when it doesn’t :)
Enter regions in Prism … they are a very cool concept, kind of like a special zone in your app where you can place Views. In this case I have a Prism region I want to stick my Progress Control View in. Regions come out of the box in the form of either a ContentControl, TabControl, ItemsControl or Selector. None of these allowed me to stretch the control to the entire region.
According to the Prism docs, ContentControl is intended to store a single item. When I used this for my control, it put it in the upper left. Not good for what I needed. The Prism docs says the ItemsControl allows adding multiple views into a region (it is an items control after all). Not what I was looking for, but I tried it and of course it added the item to the top of a list (of 1) and centered my control horizontally, but not vertically. The Selector had similar results. And of course the TabControl region was not what I was looking for.
So … what to do … I decided to create a Region Adapter that is based off of the Grid. The Grid does exactly what I want in this case, it lets me put my progress control with a Rectangle background to fill and stretch to the entire region. The Prism docs explain that the 4 region types are all created using the RegionAdapter and that you can extend your own. My class does not need to do everything a Grid does, I just want a single column and a single row Grid panel. (Of course it can be extended to handle this if need be.)
The first step is to create a new class and derive from RegionAdapterBase<T>. In this case T is a Grid and I name my new class GridRegionAdapter. The code below is pretty straightforward, the important part is to implement the Adapt method. This is the method that fires when you add, remove, replace items from your region container. Since I just care about 1 row and 1 column, this is very straightforward.
using System.Windows;
using System.Windows.Controls;
using Microsoft.Practices.Composite.Presentation.Regions;
using Microsoft.Practices.Composite.Regions;
namespace MyUI.Infrastructure
{
public class GridRegionAdapter : RegionAdapterBase<Grid>
{
public GridRegionAdapter(IRegionBehaviorFactory behaviorFactory) :
base(behaviorFactory)
{
}
protected override void Adapt(IRegion region, Grid regionTarget)
{
region.Views.CollectionChanged += (s, e) =>
{
//Add
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
foreach (FrameworkElement element in e.NewItems)
regionTarget.Children.Add(element);
//Removal
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
foreach (FrameworkElement element in e.OldItems)
if (regionTarget.Children.Contains(element))
regionTarget.Children.Remove(element);
};
}
protected override IRegion CreateRegion()
{
return new AllActiveRegion();
}
}
}
Now that the adapter exists, I can now use a Grid as a type of container in my Prism app and have my my control fill the entire screen.
The last step is to register the adapter in your Prism application. This can be done by adding the following code to the bootstrapper class.
protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
{
RegionAdapterMappings mappings = base.ConfigureRegionAdapterMappings();
mappings.RegisterMapping(typeof(Grid), Container.Resolve<GridRegionAdapter>());
return mappings;
}
It is quite simple to extend and create your own Region Adapters. However, I cannot stress enough that you need to carefully implement the Adapt method to handle everything your container needs to do. So just be careful to make sure it covers your bases.