All posts by Damian Cherubini

PopupWindowAction: using custom views instead of windows in WPF and Prism

Hi everybody,

A couple weeks ago Agustin ported the Prism’s PopupChildWindowAction for Silverlight to WPF:

As you can read in its blog post, his PopupModalWindowAction had some limitations when defining a custom ChildWindow, mainly because you cannot reuse the same instance of a Window after it’s closed. Talking with Agustin about how we could workaround that limitation, we came to the idea of passing custom views instead of custom windows and I decided to extend his PopupModalWindowAction to include this functionality along with other ones.

You can find a sample with the new PopupWindowAction at the end of the post.

PopupWindowAction

The main difference between this implementation and the aforementioned one is that the ChildWindow property was replaced with a WindowContent property that can receive FrameworkElements (views) to show in a wrapper Window. As a view can be added and removed from a Window without problems, it can be used more than once.

Another functionality that was added is the possibility to raise the Window as a modal window or not. This can be achieved through the IsModal property of the PopupWindowAction.

Also, you can define if you want the Window to appear right over the view that invoked it or not by setting the CenterOverAssociatedObject property.

Of course, this PopupWindowAction supports the default Windows for the Notification and Confirmation classes in case you only need to show a simple message.

Using custom views

When you raise an InteractionRequest passing a Notification (or Confirmation) that Notification will be automatically set as the DataContext of the Window that will host your custom view. If your view does not have its own DataContext it will inherit it from the host Window, and you will be able to show the Notification’s data. Also, when the host control is closed, the aforementioned Notification will be passed back in the callback to the invoker of the InteractionRequest.

However, this leaves us with some new challenges to be addressed:

  • You might want to interact with more complex data in the popup view, which would require the view to have its own view model.
  • You might want to communicate the results of your popup view, to the invoker view.
  • You might need a way to be able to close the host Window from the view’s view model.
  • As you are working with views in Prism, you might want to define regions inside those views.

This PopupWindowAction allows you to address all of this:

View models

You can pass a view model to your view in the following ways:

  • Creating a view model that inherits from Notification, resolving it in the invoker view model and passing it as the Notification of the InteractionRequest. This way the popup view will inherit it from the wrapper Window automatically. Also, this view model will be automatically passed back to the invoker in the callback, allowing it to access its data.
  • The view could inject its own view model through dependency injection (using the ServiceLocator) which wouldn’t require for the view model to inherit from Notification and would decouple the popup view model from the invoker view model. However, your view and view model would not be able to interact with the Notification passed in the InteractionRequest (I will explain how to solve this in the following section).

Which one you should use will depend mostly of your personal preferences and the requirements of your scenario.

Interacting with the host Window and the Notification

In order for your popup view / view model to interact with the host Window or the corresponding Notification I have created an interface named IPopupWindowActionAware which defines two properties: HostWindow and HostNotification. If your popup view or view model implements the aforementioned interface, the PopupWindowAction will automatically set the aforementioned properties allowing you to access the Window (e.g. you would be able to close it when the user clicks a button) and the corresponding Notification (e.g. you could obtain and return data from / to the invoker view model through it.)

As a side note, take into account that you can also communicate between the view models in a loosely coupled manner by following one of the several approaches mentioned in the following chapter of the Prism documentation:

Using scoped regions

Finally, this PopupWindowAction checks if the popup view has an attached RegionManager or not. If not, a new scoped RegionManager is attached to this view by default in order to handle regions inside the view.

However, regardless if the view already has a RegionManager or if a new one is attached, you need to be able to access to this RegionManager in order to interact with the regions in the view. To solve this, I made this PopupWindowAction to be compatible with an already known interface from my previous posts: IRegionManagerAware. If your view or view model implements this interface, the PopupWindowAction will automatically set the corresponding RegionManager.

Note: Take into account that in this case we are not using the RegionManagerRegistrationBehavior for which the aforementioned interface was initially designed for and we have not tested if this PopupWindowAction is compatible with it or not.

Sample application

Next you can find a sample application which uses the PopupWindowAction in four different ways:

  • Showing a modal, centered, default Confirmation popup.
  • Showing a non-modal, centered, default Notification popup.
  • Showing a modal, centered, custom view which receives its view model as the Notification passed in the InteractionRequest.
  • Showing a non-modal, non-centered, custom view which obtains its own view model through the ServiceLocator and has scoped regions.

You can download it from my SkyDrive account under the name PopupWindowActionSample:

I hope that you find it useful!

Note: Please remember that this code is provided “AS IS” with no warranties and confers no rights.

Sync active state when adding views

Hi everybody,

Since the version 4.1 of Prism, the RegionActiveAwareBehavior has been modified to be able to sync the state (that is, the IsActive property of the IActiveAware interface) of “child” views and view models with their parents through the inclusion of the SyncActiveState attribute. However, a user in the following work item has posted that this behavior fails when adding views “at start-up” (for example, in the Initialize method of an IModule class.) We were able to reproduce the problem, find the cause behind it and implement a possible workaround for it:

How the RegionActiveAwareBehavior works

Based on my understanding, the RegionActiveAwareBehavior subscribes to the CollectionChanged event of the ActiveViews collection of the corresponding Region. When the aforementioned collection is changed, the behavior changes the state of the views that are added or removed from the collection. This means that this behavior only executes its logic when a view is activated or deactivated.

Through the inclusion of the SyncActiveState attribute, during the state change, this behavior now also checks for any scoped views with the aforementioned attribute and set its state too.

The problem

It seems that the aforementioned logic does not take into account one scenario: when a view is added to a region. For example, it is possible to add a “child view” into a “parent view” that is deactivated. In this case, as the ActiveViews collection would not be modified, the logic in the RegionActiveAwareBehavior will not execute. As a  result the value of the parent view’s IsActive property would be false while the value in the child view’s property could be true.

A quick work around for this could be to activate and deactivate all the views in the corresponding region in order to update the state of all the views. However, activating and deactivating all the views each time a view is added to a region might be bothersome, especially if any of those view or their corresponding view models perform some kind of logic when activated.

As another possible approach to solve this, I have created a behavior named RegionActiveAwareSyncOnAdditionBehavior that complements the existing RegionActiveAwareBehavior.

RegionActiveAwareSyncOnAdditionBehavior

This behavior has a similar logic to the RegionActiveAwareBehavior but performs a different task. Instead of subscribing to the changes in the ActiveViews collection, this behavior subscribes to the Views collection of the region, meaning that it will be notified when any view is added to the region. When a view is added to the region, the behavior check if it’s decorated with the SyncActiveState attribute. If so, the behavior then searches for a parent view or view model that implements the IActiveAware interface. If a parent is found, the value of the IsActive property of the added view is changed to match the one of its parent.

private void OnViewsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    if (e.Action == NotifyCollectionChangedAction.Add)
    {
        foreach (var view in e.NewItems)
        {
            // We only sync the state of the view if it's an active view in its corresponding region
            if (this.Region.ActiveViews.Contains(view))
            {
                if (ShouldSyncActiveState(view))
                {
                    IActiveAware parent = FindIActiveAwareParent(view as DependencyObject);
                    if (parent != null)
                    {
                        InvokeOnActiveAwareElement(view, activeAware => activeAware.IsActive = parent.IsActive);
                    }
                }
            }
        }
    }
}

The ShouldSyncActiveState and InvokeOnActiveAwareElement methods used in the aforementioned code snippet are the same that the ones used in the RegionActiveAwareBehavior.

The question now is how the FindIActiveAwareParent method find the corresponding parent. The answer is simple: this method uses a modified version of the same logic used by the RegionManagerRegistrationBehavior to find the corresponding RegionManager, only that this method searches for IActiveAware elements instead:

protected static IActiveAware FindIActiveAwareParent(DependencyObject element)
{
    if (element == null)
    {
        return null;
    }

    DependencyObject parent = null;

#if SILVERLIGHT
    FrameworkElement frameworkElement = element as FrameworkElement;
    if (frameworkElement != null)
    {
        parent = frameworkElement.Parent;
    }

#else
    parent = LogicalTreeHelper.GetParent(element);

#endif

    if (parent == null)
    {
        return null;
    }

    // We check if the parent control implements IActiveAware
    IActiveAware activeAwareParent = parent as IActiveAware;
    if (activeAwareParent != null)
    {
        return activeAwareParent;
    }

    // If not, we check if the parent control's data context implements IActiveAware
    FrameworkElement parentFrameworkElement = parent as FrameworkElement;
    if (parentFrameworkElement != null)
    {
        IActiveAware activeAwareParentDataContext = parentFrameworkElement.DataContext as IActiveAware;
        if (activeAwareParentDataContext != null)
        {
            return activeAwareParentDataContext;
        }
    }

    // If none of the two implements IActiveAware, we keep climbing the tree.
    return FindIActiveAwareParent(parent);
}

You can find the behavior inside a sample at the end of this post. As a side note, I believe that this behavior and the RegionActiveAwareBehavior could be merged into one without problems; however, as they have a different logic, I am posting it as a different behavior (which depends of the original one to work) in order to make its code more readable.

Sample

The sample application is very simple. The behavior is registered in the Bootstrapper overriding the ConfigureDefaultRegionBehaviors method:

protected override IRegionBehaviorFactory ConfigureDefaultRegionBehaviors()
{
    IRegionBehaviorFactory factory = base.ConfigureDefaultRegionBehaviors();

    factory.AddIfMissing(RegionActiveAwareSyncOnAdditionBehavior.BehaviorKey, typeof(RegionActiveAwareSyncOnAdditionBehavior));

    return factory;
}

The Shell contains a MainRegion which is a TabControl. During the initialization of the HelloWorlModule five instances of a view containing a scoped region (named “SubRegion“) that implement IActiveAware are injected in that region. Also, in each of those scoped regions, another view implementing IActiveAware and decorated with SyncActiveState attribute is injected.

public void Initialize()
{
    IRegion mainRegion = this.regionManager.Regions["MainRegion"];

    for (int i = 0; i < 5; i++)
    {
        FrameworkElement view = ServiceLocator.Current.GetInstance<Views.ScopedRegionView>();
        IRegionManager scopedManager = mainRegion.Add(view, null, true);

        FrameworkElement subView = ServiceLocator.Current.GetInstance<Views.SubView>();
        scopedManager.Regions["SubRegion"].Add(subView);
    }
}

The application’s Shell has a “Check Views’ States” button that when clicked will write in the console the state of each view (you can find its output in the Output window of Visual Studio.) You will see that the state of each view is now synchronized.

You can find the sample in my SkyDrive account:

I hope that you find it useful!

Note: This code is provided “AS IS” with no warranties and confers no rights. The behavior portraying in this blog post has not been tested in all scenarios and it’s unknown if it might cause an unexpected behavior in some applications.

Regions inside DataTemplates in Prism v4 using a region behavior

Hi everybody,

A couple of months ago I published a blog post (Regions inside DataTemplates in Prism v4) about a possible work around for placing a Prism Region inside a DataTemplate in WPF.

However, a few days ago I was talking with Guido Maliandi (after he published his blog post about Binding to commands from inside DataTemplates) and we found another (and possibly better!) approach to be able to have a Region inside a DataTemplate (which also works in Silverlight too!).

The problem

There is a known issue in Prism where, if a Region is defined inside a DataTemplate, the Region is never register in the corresponding RegionManager.

Based on our understanding, the region is created, but it does not get registered by the RegionManagerRegistrationBehavior behavior. This happens because the RegionManagerRegistrationBehavior tries to obtain the RegionManager from the visual parent of the element containing the region. As a DataTemplate doesn’t have a visual parent, the RegionManagerRegistrationBehavior cannot find the RegionManager and the region is never registered.

In my previous post, the approach used to workaround this problem was to attach the RegionManager to the Region inside the DataTemplate. This was possible because the RegionManager was exposed as a dynamic resource and it was found in the code-behind of a custom user control from which your view had to inherit.

The new approach

This time we are going to find the corresponding RegionManager through a region behavior.

First, we are going to create an interface named IRegionManagerAware. This interface will simply expose a RegionManager property of type IRegionManager. The idea is that this interface is going to be implemented by either the view that contains the DataTemplate where the Region is going to be, or its view model.

After that, we are going to define a simple region behavior named RegionManagerAwareBehavior (which is based on the RegionActiveAwareBehavior). When a view is added to a region this behavior will check if that view or its view model implements IRegionManagerAware. If that is the case, the behavior will set the RegionManager property to the corresponding RegionManager for that view.

Finally, in the Region inside the DataTemplate, we are going to bind the RegionManager.RegionManager attached property of the control defining the Region to the RegionManager of our IRegionManagerAware object, which is going to be either our view or our view model. This is where the ObservableObject<T> class comes in. As we usually cannot create a binding from a DataTemplate to the view model or view, we are going to create a resource that inherits from ObservableObject<T>, which will act as a kind of “bridge” between the DataTemplate and our IRegionManagerAware object.

The result is that, when the DataTemplate is applied, the RegionManager of its view is attached to the Region inside the DataTemplate and the Region is registered correctly.

Below you can find the code of the aforementioned interface and behavior plus a sample application containing them. Please note that you need to override the RegisterDefaultRegionBehaviors method in your bootstrapper to register the RegionActiveAwareBehavior behavior to apply this workaround.

IRegionManagerAware

    public interface IRegionManagerAware
    {
        IRegionManager RegionManager { get; set; }
    }

RegionManagerAwareBehavior

    /// <summary>
    /// Behavior that monitors a <see cref="IRegion"/> object and 
    /// changes the value for the <see cref="IRegionManagerAware.RegionManager"/> property when
    /// an object that implements <see cref="IRegionManagerAware"/> gets added or removed 
    /// from the collection.
    /// </summary>
    public class RegionManagerAwareBehavior : IRegionBehavior
    {
        /// <summary>
        /// Name that identifies the <see cref="RegionManagerAwareBehavior"/> behavior in a collection of <see cref="IRegionBehavior"/>.
        /// </summary>
        public const string BehaviorKey = "RegionManagerAware";

        /// <summary>
        /// The region that this behavior is extending
        /// </summary>
        public IRegion Region { get; set; }

        /// <summary>
        /// Attaches the behavior to the specified region
        /// </summary>
        public void Attach()
        {
            INotifyCollectionChanged collection = this.GetCollection();
            if (collection != null)
            {
                collection.CollectionChanged += OnCollectionChanged;
            }
        }

        /// <summary>
        /// Detaches the behavior from the <see cref="INotifyCollectionChanged"/>.
        /// </summary>
        public void Detach()
        {
            INotifyCollectionChanged collection = this.GetCollection();
            if (collection != null)
            {
                collection.CollectionChanged -= OnCollectionChanged;
            }
        }

        private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Add)
            {
                foreach (object item in e.NewItems)
                {
                    IRegionManager correspondingRegionManager = this.Region.RegionManager;

                    // If the view was created with a scoped region manager, the behavior uses that region manager instead.
                    FrameworkElement element = item as FrameworkElement;
                    if (element != null)
                    {
                        IRegionManager attachedRegionManager = element.GetValue(RegionManager.RegionManagerProperty) as IRegionManager;
                        if (attachedRegionManager != null)
                        {
                            correspondingRegionManager = attachedRegionManager;
                        }
                    }

                    InvokeInRegionManagerAwareElement(item, regionManagerAware => regionManagerAware.RegionManager = correspondingRegionManager);
                }
            }
            else if (e.Action == NotifyCollectionChangedAction.Remove)
            {
                foreach (object item in e.OldItems)
                {
                    InvokeInRegionManagerAwareElement(item, regionManagerAware => regionManagerAware.RegionManager = null);
                }
            }

            // May need to handle other action values (reset, replace). Currently the ViewsCollection class does not raise CollectionChanged with these values.
        }

        private static void InvokeInRegionManagerAwareElement(object item, Action<IRegionManagerAware> invocation)
        {
            var regionManagerAware = item as IRegionManagerAware;
            if (regionManagerAware != null)
            {
                invocation(regionManagerAware);
            }

            var frameworkElement = item as FrameworkElement;
            if (frameworkElement != null)
            {
                var regionManagerAwareDataContext = frameworkElement.DataContext as IRegionManagerAware;
                if (regionManagerAwareDataContext != null)
                {
                    // If a view doesn't have a data context (view model) it will inherit the data context from the parent view.
                    // The following check is done to avoid setting the RegionManager property in the view model of the parent view by mistake. 

                    var frameworkElementParent = frameworkElement.Parent as FrameworkElement;
                    if (frameworkElementParent != null)
                    {
                        var regionManagerAwareDataContextParent = frameworkElementParent.DataContext as IRegionManagerAware;
                        if (regionManagerAwareDataContextParent != null)
                        {
                            if (regionManagerAwareDataContext == regionManagerAwareDataContextParent)
                            {
                                // If all of the previous conditions are true, it means that this view doesn't have a view model
                                // and is using the view model of its visual parent.
                                return;
                            }
                        }
                    }

                    // If any of the previous conditions is false, the view has its own view model and it implements IRegionManagerAware
                    invocation(regionManagerAwareDataContext);
                }
            }
        }

        private INotifyCollectionChanged GetCollection()
        {
            return this.Region.ActiveViews;
        }
    }

A sample DataTemplate with a Region

    <UserControl.Resources>
        <inf:RegionManagerObservableObject x:Key="ObservableRegionManager" Value="{Binding RegionManager}"/>
    </UserControl.Resources>

    ...

    <DataTemplate>
        <StackPanel Background="Beige" Orientation="Vertical">
            <TextBlock Text="Region inside data template:" />
            <Border BorderThickness="2" BorderBrush="Black">
                <ScrollViewer MaxHeight="110" VerticalScrollBarVisibility="Auto">
                    <ItemsControl MinWidth="400" MinHeight="100" prism:RegionManager.RegionName="RegionInTemplate" 
                        prism:RegionManager.RegionManager="{Binding Value, Source={StaticResource ObservableRegionManager}}"/>
                </ScrollViewer>
            </Border>
        </StackPanel>
    </DataTemplate>

Sample

You can find a sample with this workaround stored here with the name RegionManagerAwareBaheviorSample.

(This code is provided “AS IS” with no warranties and confers no rights.)

I hope that you find this useful,

Registering a view in a specific region manager

Hi everybody,

The AutoPopulateRegionBehavior provided in Prism (that is, the one that it’s used with the RegisterViewWithRegion method, as explained in this section of the Prism documentation) is a behavior that automatically populates a region with the views that have been registered for it as soon as it is created or when a new view registration occurs.

This behavior populates the regions according to their names. For example, if you have a region named “MainRegion,” the views registered with that region name will be injected in the region and, if you have three regions named “MyRegion”, all the views registered with that region name will be injected in the three regions.

However, there might be a scenario where you have many regions with the same name (in different Region Managers), but you only want to register a view in one of them. In this blog post I will show you a possible approach to create a view-region registration that is specific to a certain region manager, without refactoring the Prism library.

The approach

To accomplish this we are going to define our own behavior named AutoPopulateRegionBehaviorInRegionManager which will inherit from the default AutoPopulateRegionBehavior behavior. Before adding a view, this behavior will check if the hash code of the region manager of the region that it’s attached is the same that the hash code of the region manager in which the we registered the view.

To use the region manager hash code as a “filter” to decide whether to add a view to a region or not, we need to let theAutoPopulateRegionBehaviorInRegionManager behavior know in which region manager a view is intended to be added. This is where the class ViewRegistrationWithRegionManager comes in. This class will wrap the view and hash code and will be stored in theRegionViewRegistry through a delegate. (The RegionViewRegistry is where the registrations of the view are made and it’s later used by the AutoPopulateRegionBehavior behavior to obtain the views registered for each region.) So, in ourAutoPopulateRegionBehaviorInRegionManager we will check if we can cast the “view” to the ViewRegistrationWithRegionManager type. If we can’t, it means that it was registered using the usual method. If we can, we will apply our custom logic to determine if it’s the correct region and add the views when corresponding.

The last thing to do is to add a couple of extension methods to the IRegionManager to be able to use this functionality.

Remember that we need to register this behavior in the ConfigureDefaultRegionBehaviors method of the bootstrapper in order to use it. Also, note that you will need to register the RegionManagerRegistrationBehavior before this behavior in order for it to work properly.

Below you can find the code for the custom classes I mentioned, and at the end of the post, a sample using this behavior (note that the sample is based on the HelloWorld QuickStart included with Prism).

RegisterViewWithRegionExtensions

    public static class RegisterViewWithRegionExtensions
    {
        public static IRegionManager RegisterViewWithRegion(this IRegionManager regionManager, string regionName, Type viewType, bool inThisRegionManager)
        {
            return regionManager.RegisterViewWithRegion(regionName, () => ServiceLocator.Current.GetInstance(viewType), inThisRegionManager);
        }

        public static IRegionManager RegisterViewWithRegion(this IRegionManager regionManager, string regionName, Func<object> getContentDelegate, bool inThisRegionManager)
        {
            var regionViewRegistry = ServiceLocator.Current.GetInstance<IRegionViewRegistry>();

            if (inThisRegionManager == true)
            {
                /* The view and the region manager's hash code are wrapped in a ViewRegistrationWithRegionManager and given to the
                 * RegisterViewWithRegion method through a delegate, so it should be transparent for the RegionViewRegistry */
                Func<object> viewRegistration = () => new ViewRegistrationWithRegionManager(getContentDelegate, regionManager.GetHashCode());
                regionViewRegistry.RegisterViewWithRegion(regionName, viewRegistration);
            }
            else
            {
                regionViewRegistry.RegisterViewWithRegion(regionName, getContentDelegate);
            }

            return regionManager;
        }
    }

ViewRegistrationWithRegionManager

    public class ViewRegistrationWithRegionManager
    {
        public ViewRegistrationWithRegionManager(Func<object> viewDelegate, int regionManagerHashCode)
        {
            this.RegionManagerHashCode = regionManagerHashCode;
            this.ViewDelegate = viewDelegate;
        }

        public int RegionManagerHashCode { get; set; }

        public Func<object> ViewDelegate { get; set; }
    }

AutoPopulateRegionBehaviorInRegionManager

    public class AutoPopulateRegionBehaviorInRegionManager : AutoPopulateRegionBehavior
    {
        public AutoPopulateRegionBehaviorInRegionManager(IRegionViewRegistry regionViewRegistry)
            : base(regionViewRegistry)
        {
        }

        protected override void AddViewIntoRegion(object viewToAdd)
        {
            ViewRegistrationWithRegionManager viewRegistration = viewToAdd as ViewRegistrationWithRegionManager;

            if (viewRegistration == null)
            {
                base.AddViewIntoRegion(viewToAdd);
            }
            else
            {
                /*If the paramenter is a ViewRegistrationWithRegionManager object and the stored hash code is
                 * the same that the hash code of this region's region manager (this would mean it's the same
                 * object) then we obtain the view and add it to the region. */
                if (viewRegistration.RegionManagerHashCode == this.Region.RegionManager.GetHashCode())
                {
                    base.AddViewIntoRegion(viewRegistration.ViewDelegate());
                }
            }
        }
    }

ConfigureDefaultRegionBehaviors method in the bootstrapper

        protected override IRegionBehaviorFactory ConfigureDefaultRegionBehaviors()
        {
            var defaultRegionBehaviorTypesDictionary = ServiceLocator.Current.GetInstance<IRegionBehaviorFactory>();

            if (defaultRegionBehaviorTypesDictionary != null)
            {
                // This behavior must be registered before the AutoPopulateRegionBehaviorInRegionManager, so that the regions
                // have its RegionManager property set before the aforementioned behavior tries to add the views to them
                defaultRegionBehaviorTypesDictionary.AddIfMissing(
                                                                RegionManagerRegistrationBehavior.BehaviorKey,
                                                                typeof(RegionManagerRegistrationBehavior));

                // Here we register our AutoPopulateRegionBehaviorInRegionManager instead of the default AutoPopulateRegionBehavior
                defaultRegionBehaviorTypesDictionary.AddIfMissing(
                                                                AutoPopulateRegionBehavior.BehaviorKey,
                                                                typeof(AutoPopulateRegionBehaviorInRegionManager));

                // The following code was taken for the ConfigureDefaultRegionBehaviors method for the parent class.
                defaultRegionBehaviorTypesDictionary.AddIfMissing(
                                                                BindRegionContextToDependencyObjectBehavior.BehaviorKey,
                                                                typeof(BindRegionContextToDependencyObjectBehavior));

                defaultRegionBehaviorTypesDictionary.AddIfMissing(
                                                                RegionActiveAwareBehavior.BehaviorKey,
                                                                typeof(RegionActiveAwareBehavior));

                defaultRegionBehaviorTypesDictionary.AddIfMissing(
                                                                SyncRegionContextWithHostBehavior.BehaviorKey,
                                                                typeof(SyncRegionContextWithHostBehavior));

                defaultRegionBehaviorTypesDictionary.AddIfMissing(
                                                                RegionMemberLifetimeBehavior.BehaviorKey,
                                                                typeof(RegionMemberLifetimeBehavior));
            }

            return defaultRegionBehaviorTypesDictionary;
        }

Sample

You can find a sample using this behavior here with the name RegisterViewWithRegion with RegionManager.

(This code is provided “AS IS” with no warranties and confers no rights.)

I hope that you find this useful,

Regards.

Regions inside DataTemplates in Prism v4

Note: You can find an updated another approach in this post.

Hi everybody,

There is a known issue in Prism where, if a Region is defined inside a DataTemplate, the Region is never register in the corresponding RegionManager.

Based on our understanding, the region is created, but it does not get registered by the RegionManagerRegistrationBehavior behavior. This happens because the RegionManagerRegistrationBehavior tries to obtain the RegionManager from the visual parent of the element containing the region. As a DataTemplate doesn’t have a visual parent, the RegionManagerRegistrationBehavior cannot found the RegionManager and the region is never registered.

There are various approaches proposed as workarounds for this issue. For example, in the aforementioned work item, you can find an approach proposed by the the user mstrobel modifying the Prism library.

However, in this blog post we will propose a couple of approaches that doesn’t require to modify and recompile the Prism library. I hope you find them useful.

Using the view discovery approach

One of the simplest workarounds for this is using the view discovery approach as the view is injected in the region when its created and does not depends on the RegionManagerRegistrationBehavior behavior . This is, for example, using the RegisterViewWithRegion method of IRegionViewRegistry.

Attaching the RegionManager in the DataTemplate as a property

In this approach the purpose is to attach the corresponding RegionManager to the DataTemplate as an attached property. Note that to do this, you should obtain the RegionManager as a dynamic resource.

For example, if the Template will be injected inside a ContentControl, you could do something like the following:

<ContentControl>
    <ContentControl.ContentTemplate>
        <DataTemplate>
            <ItemsControl prism:RegionManager.RegionManager="{DynamicResource RegionManager}"
                                   prism:RegionManager.RegionName="RegionInsideDataTemplate"/>
        </DataTemplate>
    </ContentControl.ContentTemplate>
</ContentControl>

The are various methods to obtain the appropriate RegionManager for the template (e.g. using a Shared Service, resolving it in the code-behind of the view and exposing it as a property, etc).

One of those methods is to create a custom UserControl, from which the views can inherit, that obtains the RegionManager and exposes it as a resource:

[Note: The following class is for WPF only, it does not work properly in Silverlight as the OnVisualParentChanged method does not exist in Silverlight.]

    public class ViewWithFixForRegionsInDataTemplates : UserControl
    {
        bool regionManagerFound = false;

        protected override void OnVisualParentChanged(DependencyObject oldParent)
        {
            base.OnVisualParentChanged(oldParent);

            IRegionManager regionManager;

            if ((regionManager = this.FindRegionManager(this)) != null && !regionManagerFound)
            {
                this.Resources.Add("RegionManager", regionManager);
                regionManagerFound = true;
            }
        }

        private IRegionManager GetRegionmanager(DependencyObject element)
        {
            return element.GetValue(RegionManager.RegionManagerProperty) as IRegionManager;
        }

        private IRegionManager FindRegionManager(DependencyObject dependencyObject)
        {
            var regionmanager = this.GetRegionmanager(dependencyObject);
            if (regionmanager != null)
            {
                return regionmanager;
            }

            DependencyObject parent = null;

#if SILVERLIGHT

            FrameworkElement frameworkElement = dependencyObject as FrameworkElement;
            if (frameworkElement != null)
            {
                parent = frameworkElement.Parent;
            }

#else
            parent = LogicalTreeHelper.GetParent(dependencyObject);
#endif

            if (parent != null)
            {
                return this.FindRegionManager(parent);
            }

            return null;
        }
    }

You can find a sample with this workaround stored here with the name RegionInsideDataTemplateWithFix.

I hope you find this useful.