RadTabControl + MEF + PRISM: Loading View with region to TabControl, navigation best practices

Topics: Prism v4 - WPF 4
Mar 8, 2013 at 12:40 PM
Hi!

We are developing a Dashboard application that has Telerik's RadTabControl and RadTabItems are created based on views in MEF modules (we are using MEF bootstrapper).

Creating these tabitems works ok at the moment; RadTabControl is a region in which module's "main view" (DashboardTabView) is loaded. TabControl can have multiple instances of these module views. Module's main view contains a Prism region (ContentControl) and nothing else. To this region we want to load multiple views based on user action (one at a time).

DashboardTabView.xaml:
<ContentControl x:Name="ContentRegion" Prism:RegionManager.RegionName="{x:Static inf:RegionNames.TabContentRegion}"/>
So basically DashboardTabView.xaml has a region "TabContentRegion" and we would like to change the view that is shown in its region when needed.

First problem is that Prism region manager does not have a region "TabContentRegion" but I've managed to overcome this problem by using scoped region manager. But how to get this scoped region manager from the view/viewmodel?

This is how I'm adding module's view (which has "TabControlRegion" region) to TabControl's region:
IRegion tabRegion = regionManager.Regions[RegionNames.TabRegion];
var myTabView = container.GetExportedValue<DashboardTabView>();
// Create scoped region manager
IRegionManager tabRegionManager = tabRegion.Add(myTabView, null, true);
// Set scoped region manager to view model's property
viewModel.TabRegionManager = tabRegionManager;
tabRegion.Activate(myTabView);
After this we want to navigate to first view that the user sees under this tab:
tabRegionManager.RegisterViewWithRegion(RegionNames.TabContentRegion, typeof(FirstTestView));

tabRegionManager.RequestNavigate(RegionNames.TabContentRegion,
                new Uri("FirstTestView", UriKind.Relative));
So far so good, FirstTestView is successfully navigated to. But now when I want to navigate to next view from FirstTestView I don't have access to scoped region manager that has "TabContentRegion" region.

I try to use RequestNavigate from FirstTestView's viewmodel when user clicks a button:
TabRegionManager.RequestNavigate(RegionNames.TabContentRegion,
                new Uri("SecondTestView", UriKind.Relative));
TabRegionManager is a property which I have not been able to set when using RequestNavigate. With other approach I can set it like shown above.

Do I have to set this scoped region manager as a property for the viewmodel or is there another way to do this? If I want to use RequestNavigate instead how to set scoped region manager?

We would like to use Prism navigation but I haven't found very good examples on that. Is it required that I register each view using RegisterViewWithRegion() before it can be navigated to?

If I register view before navigating to it it seems to work but how to get scoped region manager available to be able to navigate to the next view?

I would greatly appreciate an example project that uses MEF+Prism navigation with nested regions.

Thank you,

Br,

Kalle
Developer
Mar 8, 2013 at 5:25 PM
Hi,

As far as I know, Prism does not provide any mechanism to access scoped region managers as out of the box. But as a possible approach I could believe you could use my RegionManagerAwareBehavior , which you can find in the following blog post:
Basically, when injecting a view with a view model that implements the IRegionManagerAware interface, the aforementioned region behavior will save the corresponding region manager in the RegionManager property. For example, if the FirstTestViewModel implements IRegionManagerAware , the TabRegionManager will automatically be stored in the view model's RegionManager property, allowing you to access the TabContentRegion and navigate to another view:
// FirstTestView's view model
public FirstTestViewModel : IRegionManagerAware
{
    // The TabRegionManager will be stored here automatically...
    public IRegionManager RegionManager { get; set; }

    . . .
   
    // And the you can use it to navigate
    this.RegionManager.Regions[RegionNames.TabContentRegion].RequestNavigate(new Uri("SecondTestView", UriKind.Relative));
}
The same applies to the SecondTestView and so on. If you wish to use this behavior, please remember that you need to register it by overriding the ConfigureDefaultRegionBehaviors method of the bootstrapper.

Regarding the use of RegisterViewWithRegion , this method is used to register a view's type against a region name, so that a new instance of that view will be injected as soon as the region is available. This approach is usually called " view registration " and it does not form part of the navigation progress. Hence, it's not required to register the view before performing a navigation request. What's more, using both could result in two instances of the same view injected in the region.

Finally, one of the known limitations of Prism is that you cannot perform a navigation request to a view and specify that you want to create a scoped region manager for it at the same time. In other words, the RequestNavigate method does not contain any overload that accepts a parameter to create a scoped region manager (like with the Add method.) However, as you can navigate to existing views in the region, the common approach is to inject the view with the Add method and then navigate to it using a NavigationRequest .

I hope you find this useful,

Damian Cherubini
http://blogs.southworks.net/dcherubini
Mar 11, 2013 at 8:00 AM
Edited Mar 11, 2013 at 10:01 AM
Hi!

Thanks for the reply, I think it will be helpful but now that I added these files: IRegionManagerAware.cs, RegionManagerAwareBehavior.cs and RegionManagerObservableObject.cs to my project I'm having problems with Assembly References.

I'm using .NET Framework 4.0 at the moment (maybe 4.5 later) and I don't see System.Windows reference in Add Reference... (Windows 7, VS2012)
It does not seem to recognize this FrameworkElement in ReqionManagerAwareBehavior.cs and when I added PresentationFramework the error changed to:

// Error 2 Cannot implicitly convert type 'System.Windows.DependencyObject' to 'object' ...\DashboardLib\RegionManagerAwareBehavior.cs 68 50 DashboardLib
// line 68
var frameworkElementParent = frameworkElement.Parent as FrameworkElement;
Error 3 'System.Windows.FrameworkElement' does not contain a definition for 'GetValue' and no extension method 'GetValue' accepting a first argument of type 'System.Windows.FrameworkElement' could be found (are you missing a using directive or an assembly reference?) ...\DashboardLib\RegionManagerAwareBehavior.cs 100 72 DashboardLib
// line 100
IRegionManager attachedRegionManager = element.GetValue(RegionManager.RegionManagerProperty) as IRegionManager;
So now I need to figure out which References to Add to the project.

If I understood your answer correctly I should Add view at least when I want to create a scoped Region Manager and then navigate to that view instead of using region.Activate? Or should I always use Add before navigating to a view? I guess this might give me an error that view already exists in region...

EDIT: Nevermind this part about finding the correct References, when I tried to build the project it suggested me what references I should add. Now my problem is that for some reason I don't get the view "FirstTestView" to show up under the tab unless I register view before calling RequestNavigate... Any ideas?

EDIT2: I was missing contractName from the view export like:
[Export("FirstTestView", typeof(FirstTestView)), PartCreationPolicy(CreationPolicy.NonShared)]
Now view is showing when I call RequestNavigate :)
            // This part works, it adds DashboardTabView as a new TabItem for the TabControl and opens it
            var myTabView = container.GetExportedValue<DashboardTabView>();
            DashboardTabViewModel viewModel = myTabView.DataContext as DashboardTabViewModel;
            viewModel.ViewTitle = TitleText;
            IRegionManager tabRegionManager = tabRegion.Add(myTabView, null, true);
            viewModel.TabRegionManager = tabRegionManager;
            tabRegion.Activate(myTabView);

            // Without registering the following RequestNavigate does not show anything
            //tabRegionManager.RegisterViewWithRegion(RegionNames.TabContentRegion, typeof(FirstTestView));

            tabRegionManager.Regions[RegionNames.TabContentRegion].RequestNavigate(new Uri("FirstTestView", UriKind.Relative));
Br,

Kalle
Mar 11, 2013 at 8:32 AM
Edited Mar 11, 2013 at 8:56 AM
Hi,

I'm now trying to use your RegionManagerAwareBehavior and I get following exception in bootstrapper.Run():
An exception occurred while trying to create region objects. 

    - The most likely causing exception was: 'System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> Microsoft.Practices.Prism.Regions.Behaviors.RegionCreationException: An exception occurred while creating a region with name 'TabRegion'. The exception was: Microsoft.Practices.ServiceLocation.ActivationException: Activation error occured while trying to get instance of type RegionManagerAwareBehavior, key "" ---> Microsoft.Practices.ServiceLocation.ActivationException: Activation error occured while trying to get instance of type RegionManagerAwareBehavior, key ""

   at Microsoft.Practices.Prism.MefExtensions.MefServiceLocatorAdapter.DoGetInstance(Type serviceType, String key)

   at Microsoft.Practices.ServiceLocation.ServiceLocatorImplBase.GetInstance(Type serviceType, String key)

   --- End of inner exception stack trace ---

   at Microsoft.Practices.ServiceLocation.ServiceLocatorImplBase.GetInstance(Type serviceType, String key)

   at Microsoft.Practices.ServiceLocation.ServiceLocatorImplBase.GetInstance(Type serviceType)

   at Microsoft.Practices.Prism.Regions.RegionBehaviorFactory.CreateFromKey(String key)

   at Microsoft.Practices.Prism.Regions.RegionAdapterBase`1.AttachDefaultBehaviors(IRegion region, T regionTarget)

   at Microsoft.Practices.Prism.Regions.RegionAdapterBase`1.Initialize(T regionTarget, String regionName)

   at Microsoft.Practices.Prism.Regions.RegionAdapterBase`1.Microsoft.Practices.Prism.Regions.IRegionAdapter.Initialize(Object regionTarget, String regionName)

   at Microsoft.Practices.Prism.Regions.Behaviors.DelayedRegionCreationBehavior.CreateRegion(DependencyObject targetElement, String regionName).  ---> Microsoft.Practices.ServiceLocation.ActivationException: Activation error occured while trying to get instance of type RegionManagerAwareBehavior, key "" ---> Microsoft.Practices.ServiceLocation.ActivationException: Activation error occured while trying to get instance of type RegionManagerAwareBehavior, key ""

   at Microsoft.Practices.Prism.MefExtensions.MefServiceLocatorAdapter.DoGetInstance(Type serviceType, String key)

   at Microsoft.Practices.ServiceLocation.ServiceLocatorImplBase.GetInstance(Type serviceType, String key)

   --- End of inner exception stack trace ---

   at Microsoft.Practices.ServiceLocation.ServiceLocatorImplBase.GetInstance(Type serviceType, String key)

   at Microsoft.Practices.ServiceLocation.ServiceLocatorImplBase.GetInstance(Type serviceType)

   at Microsoft.Practices.Prism.Regions.RegionBehaviorFactory.CreateFromKey(String key)

   at Microsoft.Practices.Prism.Regions.RegionAdapterBase`1.AttachDefaultBehaviors(IRegion region, T regionTarget)

   at Microsoft.Practices.Prism.Regions.RegionAdapterBase`1.Initialize(T regionTarget, String regionName)

   at Microsoft.Practices.Prism.Regions.RegionAdapterBase`1.Microsoft.Practices.Prism.Regions.IRegionAdapter.Initialize(Object regionTarget, String regionName)

   at Microsoft.Practices.Prism.Regions.Behaviors.DelayedRegionCreationBehavior.CreateRegion(DependencyObject targetElement, String regionName)

   --- End of inner exception stack trace ---

   at Microsoft.Practices.Prism.Regions.Behaviors.DelayedRegionCreationBehavior.CreateRegion(DependencyObject targetElement, String regionName)

   at Microsoft.Practices.Prism.Regions.Behaviors.DelayedRegionCreationBehavior.TryCreateRegion()

   at Microsoft.Practices.Prism.Regions.Behaviors.DelayedRegionCreationBehavior.OnUpdatingRegions(Object sender, EventArgs e)

   --- End of inner exception stack trace ---

   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)

   at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)

   at System.Delegate.DynamicInvokeImpl(Object[] args)

   at System.Delegate.DynamicInvoke(Object[] args)

   at Microsoft.Practices.Prism.Events.WeakDelegatesManager.Raise(Object[] args)

   at Microsoft.Practices.Prism.Regions.RegionManager.UpdateRegions()'.

    But also check the InnerExceptions for more detail or call .GetRootException().
This is what my TabRegion looks like:
        <telerik:RadTabControl  
                Margin="0" 
                x:Name="TabRegion"
                Prism:RegionManager.RegionName="{x:Static inf:RegionNames.TabRegion}" 
                AllowDragReorder="True"
                SelectedItemRemoveBehaviour="SelectPrevious"
                ItemContainerStyle="{StaticResource TabDataTemplate}"
                >
        </telerik:RadTabControl>
EDIT: Adding configuration for this behavior triggers the exception.
        protected override IRegionBehaviorFactory ConfigureDefaultRegionBehaviors()
        {
            IRegionBehaviorFactory behaviors = base.ConfigureDefaultRegionBehaviors();

            // Program runs when commented, otherwise an exception is thrown
            //behaviors.AddIfMissing(RegionManagerAwareBehavior.BehaviorKey, typeof(RegionManagerAwareBehavior));

            return behaviors;
        }
I must be missing something...

Br,

Kalle
Mar 11, 2013 at 1:32 PM
Hi,

Sorry for the "spam" but it seems to work now!

I was not adding RegionManagerAwareBehavior to my catalog and it caused to exception:
            this.AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(RegionManagerAwareBehavior).Assembly));
            this.AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(DashboardBootstrapper).Assembly));
Thanks alot for this solution!

Br,

Kalle
Developer
Mar 11, 2013 at 7:16 PM
Hi Kalle,

I am glad you find this useful and that you could find the cause behind those problems. Also, thanks for sharing this with the rest of the community as it might be helpful for other users that could be experiencing the same problems.

Regards,

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