Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prism 4 - locally scoped RegionManager

I have silverlight 4 application with PRISM 4, I'm using MEF.

My Shell defines one main region in which modules are loaded, I want modules to have their own RegionManager, so regions that they define are places in local RegionManager instead of global. And I want this local RegionManager to be resolved by container (for type IRegionManager) when inside the module.

However the method from documentation:

IRegion detailsRegion = this.regionManager.Regions["DetailsRegion"];
View view = new View();
bool createRegionManagerScope = true;
IRegionManager detailsRegionManager = detailsRegion.Add(view, null, 
                            createRegionManagerScope);

Doesnt work for me, when resolving IRegionManager from inside child view I still get GlobalRegionManager.

like image 397
Alex Burtsev Avatar asked Jul 05 '11 12:07

Alex Burtsev


2 Answers

I was in the same situation as you, I had a nested region manager, but all of the child views were still getting the global region manager. I came up with what I consider a reasonable solution.

First I define an interface:

/// <summary>
/// An interface which is aware of the current region manager.
/// </summary>
public interface IRegionManagerAware
{
    /// <summary>
    /// Gets or sets the current region manager.
    /// </summary>
    /// <value>
    /// The current region manager.
    /// </value>
    IRegionManager RegionManager { get; set; }
}

Then I setup a RegionBehavior like so:

/// <summary>
/// A behaviour class which attaches the current scoped <see cref="IRegionManager"/> to views and their data contexts.
/// </summary>
public class RegionAwareBehaviour : RegionBehavior
{
    /// <summary>
    /// The key to identify this behaviour.
    /// </summary>
    public const string RegionAwareBehaviourKey = "RegionAwareBehaviour";

    /// <summary>
    /// Override this method to perform the logic after the behaviour has been attached.
    /// </summary>
    protected override void OnAttach()
    {
        Region.Views.CollectionChanged += RegionViewsChanged;
    }

    private void RegionViewsChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        Contract.Requires<ArgumentNullException>(e != null);

        if (e.NewItems != null)
            foreach (var item in e.NewItems)
                MakeViewAware(item);
    }

    private void MakeViewAware(object view)
    {
        Contract.Requires<ArgumentNullException>(view != null);

        var frameworkElement = view as FrameworkElement;
        if (frameworkElement != null)
            MakeDataContextAware(frameworkElement);

        MakeAware(view);
    }

    private void MakeDataContextAware(FrameworkElement frameworkElement)
    {
        Contract.Requires<ArgumentNullException>(frameworkElement != null);

        if (frameworkElement.DataContext != null)
            MakeAware(frameworkElement.DataContext);
    }

    private void MakeAware(object target)
    {
        Contract.Requires<ArgumentNullException>(target != null);

        var scope = target as IRegionManagerAware;
        if (scope != null)
            scope.RegionManager = Region.RegionManager;
    }
}

Then apply this to all regions in your bootstrapper:

protected override IRegionBehaviorFactory ConfigureDefaultRegionBehaviors()
{
    var behaviours = base.ConfigureDefaultRegionBehaviors();

    behaviours.AddIfMissing(RegionAwareBehaviour.RegionAwareBehaviourKey, typeof(RegionAwareBehaviour));

    return behaviours;
}

Then all you have to do is implement IRegionManagerAware on your view/viewmodel, probably like so:

public class MyView : IRegionManagerAware
{
    IRegionManager RegionManager { set; get; }
}

Then when this view is added to a region, the behaviour will correctly set the RegionManager property to the currently scoped region manager.

like image 91
Lukazoid Avatar answered Oct 06 '22 04:10

Lukazoid


If you read the next line in the documentation it says "The Add method will return the new RegionManager that the view can retain for further access to the local scope."

so I would create a property in the view and pass the IRegionManger to it.

inside your view/viewModel.

public IRegionManager rm { get; set; }

then pass the IregionManager which was returned after adding the new view

view.rm = detailsRegionManager;

Hope that helps.

like image 28
Organ Grinding Monkey Avatar answered Oct 06 '22 04:10

Organ Grinding Monkey