nesting regions

Topics: Prism v4 - WPF 4
Sep 19, 2011 at 9:08 PM

Hi,

I am using Prism 4 as my app framework, trying to be as MVVM as possible.

I ran into the following issue in a relatively simple scenario, in which I will kindly ask for your help:

My Main window contains a tab control defined as a region. I inject 2 views (actually view models) to that region- so far so good. The second viewmodel added, binds to a view that defines another tabcontrol that is defined as a region.

Now, the call:

var model - _container.Resolve<DynamicAddedViewModel>();

_regionaManager.AddToRegion(RegionNames.SecondReg, model)

fails because there is no region by this name- where it does apear in the xaml of the view added to the first region.

Can you please enlighten me?

 

Thanks!

 

Developer
Sep 20, 2011 at 3:53 PM
Edited Sep 21, 2011 at 8:18 PM

Hi,

Based on my understanding of your scenario, as you are using View Model first approach, it's likely that your are defining your region inside a DataTemplate. Regions defined inside DataTemplates cannot be found by the RegionManagerRegistrationBehavior behavior. That might be the cause of your problem.
You'll find more information regarding that in the following link:

I hope you find this useful.

Agustin Adami
http://blogs.southworks.net/aadami

Sep 20, 2011 at 8:41 PM

Hi Agustin,

Thanks for you reply. It is indeed useful and I see this is the case.

I am just doing my first steps in prism, and I could use some help in either way described in the first link you pointed:

1. I don't have a clue on how to add a region behavior that will create a region from a data template, source code is missing in the link of that other thread.

2. Regarding the second option- using IRegionCollection.Add to add the region- does this mean that I have to write code in the code behind of "MyView" and define "MyRegion" with RegionManager.SetRegionName to get the IRegion first, then add it to the collection via RegionManagerExtensions ?

My whole app is working with viewmodel first, and I'd hate to break it...

I'd appreciate your help very much

 

Thanks.

Sep 21, 2011 at 2:12 PM

Hello

 

Same trouble here

can you please give us hints on how to write a custom region behavior that discovers regions inside datatemplates ?

Thanks

Developer
Sep 21, 2011 at 11:03 PM
Edited Sep 23, 2011 at 7:15 PM

Hi,

As Agustin said, the region is created, but it does not get registered by the RegionManagerRegistrationBehavior behavior. Based on my understanding, this is caused 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. This is a known issue in Prism (there is a work item created in the issue tracker with a possible workaround modifying the Prism library).

A simple workaround for this is using the view discovery approach (this is, using the RegisterViewWithRegion method of IRegionViewRegistry) as the view is injected in the region when its created.

You can find a sample of this in the aforementioned thread (you can find the repro-sample stored here with the name "RegionInsideDataTemplateSample").

Another possible workaround for this in WPF is to define a custom UserControl that creates a RegionManager as a dynamic resource and set it as an attached property in the DataTemplate.

As an example to do this, you could use something like the following:

 

    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;
        }
    }

 

This class, ViewWithFoxForRegionsInDataTemplate, is a UserControl from where a view can inherit.

In order to use this, inside of the ContentTemplate of a ContentControl (or the control where the view with the region is injected) you need to write the following code:

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

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

Again, note that this workaround only works with WPF as the UserControl ViewWithFoxForRegionsInDataTemplate does not work properly in Silverlight. The approach is still valid, however the RegionManager should be obtained through other means.

I hope you find this useful,

Damian Cherubini
http://blogs.southworks.net/dcherubini

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
            //parent = System.Windows.Media.VisualTreeHelper.GetParent(dependencyObject);
                    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;
        }
    }
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
            //parent = System.Windows.Media.VisualTreeHelper.GetParent(dependencyObject);
                    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;
        }
    }
Sep 25, 2011 at 10:30 AM

Hi,

Thanks for your reply. Before I move on to a somewhat complicated solution, I'd like to try simple ones, because I am new to PRISM and assume that I ma not the first to do this:

As mentioned, I am using viewmodel first approach and I am using view injection.

My main window has a tab control defined as the Main Region. 2 tabs items are added thru:

 

IRegionManager _regionManager = Container.Resolve<IRegionManager>();

_regionManager.RegisterViewWithRegion(RegionNames.MainRegion, typeof(Pane1ViewModel));

_regionManager.RegisterViewWithRegion(RegionNames.MainRegion,typeof(Pane2ViewModel));

The Pane2 view model has a tabcontrol defined as a region DynamicRegion, and it has 2 buttons Add and clear, that should inject and remove a Pane3ViewModel from that tabcontrol.

Adding:

            var model = _container.Resolve<Pane3ViewModel>();

            model.Header= (count++).ToString();

            var rm = _container.Resolve<IRegionManager>();

            rm.RegisterViewWithRegion(RegionNames.DynamicRegion,()=> model);

That works fine. and moving from pane1 to pane2 thru the tabcontrol, works fine too.

Now when I try to remove them I try:

           var rm = _container.Resolve<IRegionManager>();

            if (rm.Regions.ContainsRegionWithName(RegionNames.DynamicRegion)) {

                var vms = new List<object>(rm.Regions[RegionNames.DynamicRegion].Views);

                foreach (object vm in vms) {

                    rm.Regions[RegionNames.GraphTestResultsRegion].Remove(vm);

                }

          }

 The problem is, that the region manager has ONLY the MainRegion registered, and I don't understand how can I remove those views.

Actually, even after the first injection, with a breakpoint on the add function, just before the "RegisterViewWithRegion" call, there is no such region.

I'd appreciate any help understanding this.

Thanks,

Yuvalic

Developer
Sep 26, 2011 at 9:52 PM

Hi Yuvalic,

As explained above above in my previous answer, when you have a Region defined inside a Template, that Region is never registered because the RegionManagerRegistrationBehavior cannot found the corresponding RegionManager to register the Region. This happens because the RegionManagerRegistrationBehavior tries to obtain the RegionManager from the Template. As the Template does not have a RegionManager, the RegionManagerRegistrationBehavior tries to obtain the RegionManager from the visual parent of the Template, but it does not have any visual parent. As a result, as the RegionManagerRegistrationBehavior cannot find any RegionManager, the Region is not registered.

This is why the region "DynamicRegion", which is defined inside the template used to map the "Pane3ViewModel" view model to the view, is not contained in the RegionManager. Please note that, as mentioned above,  this is currently an issue in Prism and, in order to avoid it, you might need to implement a workaround for it.

As described above, a possible approach for this is to attach a RegionManager to the Template. This is can be done using, for example, the following code where, in this case, the ContentControl would be the one containing the "DynamicRegion" in the "Pane3View":

<ContentControl prism:RegionManager.RegionManager="{Binding ScopedRegionManager}" prism:RegionManager.RegionName="DynamicRegion"/>

This sets a RegionManager as an attached property to the ContentControl so that the Region can be registered. Note that the RegionManager attached property could also be attached to the "Pane3View" as the RegionManagerRegistrationBehavior will search for that attached property in the ContentControl's visual parent. In this example, the RegionManager is obtained through a property in the view model, however, the method you use to obtain the corresponding RegionManager for that Region depends mostly of the requirements of you scenario. For example, you could use a obtain the RegionManager directly in the view model or through a Shared Service.

I hope you find this useful,

Damian Cherubini
http://blogs.southworks.net/dcherubini