Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Scoped Prism regions (Fix for Prism work item 8927)

0.00/5 (No votes)
15 Mar 2013 1  
Creating scoped regions during view discovery or region navigation.

Introduction

Region in Prism is a named area of the screen where content may be rendered. (More information about regions and UI composition is available here).

Regions designed to contain any number of views of any type. If region contains just a single view everything is simple. Complications start when multiple views are associated with the same region. When views are of the same type and contain nested regions conflict occur. RegionManager requires all regions to have unique name but when more then one nested region is created the name is no longer unique.

To work around this issue RegionManager may create a scope and put view in that scope. Essentially a new instance of RegionManager is created and assigned to the view. This isolates the view from other views within parent region. More information about Scoped RegionManager available here.

Prism programmer’s guide states that "Scoped regions are available only with view injection". In other words, the only time when you could create a view with scoped region manager is when you call Add(Object view, string viewName, bool createRegionManagerScope ) on an instance of a region or manually create scoped region manager and attach it to the view with a call to SetRegionManager( DependencyObject target, IRegionManager value).

This article demonstrates a way to enable creation of scoped regions during view discovery as well as during region navigation.

Background

Regions are created by decorating elements with RegionManager.RegionName attached property. This associates this element and an instance of RegionManager which handles this region. When RegionManager.RegionName property is attached to the element RegionManager also attaches two more properties RegionManager.RegionManager and RegionManager.RegionContext to the same element. This is very important as you will see later. For in depth description of RegionManager.RegionContext and RegionManager.RegionManager properties please follow this link.

The key feature of RegionManager.RegionManager attribute is that it is initialized with the reference to the instance of RegionManager that holds the view. So if the view was created with scope the attribute will hold reference to that scoped RegionManager.

Problem

Implementation of Scoped Regions support could be divided into two separate aspects:

  1. At the time of creating the View PRISM library needs to be told (perhaps dynamically, on case by case bases) that Scoped Region is required for this particular View.
  2. Once View is created it should have direct access to the RegionManager associated with the view and its scope.

Solution

Require scoped region

During View Discovery or Region Navigation Prism implementation simply resolves View instance from DI container and adds it to the region. To enable support for scoped regions we need to inject code in between these two steps to determine if Scope is required and if yes add view to the region with the scope. We must have some kind of flag indicating if Scoped Region is required. This information could be delivered in few different ways:

  • A View class may implement certain Interface to indicate that scope is required. There could be a member on it returning Boolean indicating if scope is required at runtime.
  • Attribute could be used to mark the type as required scoped region.

There are other ways but for simplicity we will use these two.

Access correct region manager

Solution to second problem is not so straight forward. There is no direct access to an instance of RegionManager created for View’s scope. No reference is returned and no list of nested scope region managers is available anywhere in the region tree. To solve this obstacle we could use RegionManager.RegionManager attached property. When the View is being added to a Region it initializes this attached property with reference to an instance of RegionManager that handles this particular region. If Scoped region has been requested it would hold a reference to an instance of scoped RegionManager instead of parent/root RegionManager. To gain access to the correct instance of RegionManager all we have to do is to bind this attached property to a member parameter of the view. After all attached properties were designed just for that.

Implementation

Current implementation

PRISM 4 uses IRegionNavigationContentLoader to create new Views and add them to regions. IRegionNavigationContentLoader is implemented in RegionNavigationScopedContentLoader class. The key method where all the magic happens is called LoadContent. Examining source code reviles that after certain manipulations required for region navigation the loader executes these three lines:

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

It creates new view, adds it to the region and returns new view to caller. So to extend this behavior and add support for scoped regions all we have to do is to determine if scoped region is required and add view with the scope. The most logical way would be to pass a flag as a parameter to LoadContent function. Unfortunately during view discovery or region navigation content loader is called internally by the PRISM library and there is no easy way to change it.
Instead of redesigning PRISM we could make View class to carry this information. To do so we could mark type with custom attribute or make class to implement certain interface. There are pros and cons for each of these methods so for demonstration purposes I’ll implement both.

Interface

In order to add a view to a region with a scope IRegion.Add(view, viewName, createRegionManagerScope) method should be called. In this function the view parameter is the new view we just created and the Name and Scope flag are missing info we need to provide. We will implement interface that deliver missing pieces of information:

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

By implementing this interface a view class could at run time provide info if scope is required and could provide unique name for the view's instance.

Attribute

Sometimes it is enough to just mark a class with an attribute indicating that each instance of this view requires scoped region. For that we could use following attribute:

// Attribute used to indicate if scoped region manager needs to be
// instantiated and attached to created view.
public class ScopedRegionManagerAttribute : Attribute, IProvideRegionScopeInfo
{
    // By default it requires Scoped RegionManager and no Name;
    public ScopedRegionManagerAttribute()
    {
        CreateRegionManagerScope = true;
    }

    // Name of the View when added to Region by 
    // calling <see cref="Add(Object view, string viewName,
    //                  bool createRegionManagerScope"/>
    public string ViewName { get; set; }

    // Indicates if new scoped RegionManaged should be associated with the View
    public bool CreateRegionManagerScope { get; set; }
}

To unify access to both the interface and the attribute I’ve made the attribute to implement IProvideRegionScopeInfo interface as well. So after adding all the necessary checks to the code we should have something like this:

...

view = this.CreateNewRegionItem(candidateTargetContract);
// Check if scoped region is required

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;

Accessing RegionManager

Gaining access to an appropriate region manager requires few extra lines of code. Usually we would use dependency injection container to resolve IRegionManager interface. We can still do that to access root RegionManager but in order to access RegionManager responsible for this instance of the View we have to use RegionManager.RegionManager attached property. Doing so is very simple and is done in View’s constructor with following code:

Binding binding = new Binding("LocalRegionManager") 
                      { Mode= BindingMode.OneWayToSource, Source = viewModel };

this.SetBinding(Microsoft.Practices.Prism.Regions.RegionManager.RegionManagerProperty, binding);

Mode of the binding is BindingMode.OneWayToSource because we do not want to allow this property to be changeable from within the source. Source is either a View or ViewModel or both if separate Binding object is used.

So, combination of these two enhancements would allow using Scoped Regions with View discovery as well as with PRISM region navigation.

Using the code

I’ve included sample application demonstrating this solution. In order to use this code in your projects just add RegionNavigationScopedContentLoader.cs to your solution and register it with DI container instead of default implementation. For sample please reference Bootstrapper.cs file.

Credits

Sample for this article is based on the project posted on Prism Work Item 8927. It was modified to implement this fix but preserved rest of the project to enable comparison. 

History

  • 02/01/2012 – Initial release.
  • 04/01/2012 - Added credits.
  • 04/10/2012 - Changed language on few paragraphs. 
  • 03/04/2013 - Changed title to include work item reference.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here