Using the RadDocking control with Prism

by XAML Team | Comments 20

Prism can be thought of as a set of libraries that help Silverlight applications to be scalable and testable. It has a number of features (modularity, view regions and commanding) that help with this. A common scenario is to use Prism with a Docking control. You may want to create a shell with a Docking control and to mark some of the pane groups as regions and to use panes (or their content) as views.

In this article we will create a sample application that accomplishes this scenario with the RadDocking control for Silverlight. We will need one additional step to do this – we will need to create a custom region adapter for the RadPaneGroup control. We need to do this, because the RadPaneGroup doesn’t work as a normal ItemsControl – the panes initially added in it may be dragged around, unpinned or moved in another group. The problem with the standard region adapter for the ItemsControl is that it uses the ItemsSource property of the ItemsControl that cannot be used for the RadPaneGroup as the Items collection is manipulated when the panes are dragged around or unpinned.

Our first step will be to create the custom region adapter. We will name it RadPaneGroupRegionAdapter and it will inherit from RegionAdapterBase<RadPaneGroup>. We will override the CreateRegion method first. What we need here is to create new instance of a class that implements IRegion interface. We will use the AllActiveRegion implementation as we want to implement the most simple scenario for now. Here is the code:

protected override IRegion CreateRegion()
{
 return new AllActiveRegion();
}

Now we need to implement the the method that will do the job – the Adapt method. This method has two parameters – one of type IRegion and one of type RadPaneGroup. Its job is to initialize the relationship between the targeted control (RadPaneGroup in our case) and the region. In the default implementation for the ItemsControl this method is implemented in the following way – it just checks is the Items collection or the ItemsSource property of the ItemsControl already used and if not, it sets the ItemsSource to be the the Views collection of the region. As I already mentioned – we cannot do that with the RadPaneGroup, because the Docking control uses its Items collection. Firs of all we need to allow the views to be only of type RadPane and we will keep synchronized the views that are currently in the Views collection of the region with the panes currently added to the Docking control. This means that when a pane is added we will add it to the Items collection of the PaneGroup, If a pane is removed we will call its RemoveFromParent method (if the pane was moved out of its original pane group or it is unpinned it won’t be in the Items collection of its targeted control). When a pane is replaced we may do one of the following:

  • remove the old one from its parent and add the new one to the Items collection the region target.
  • find the current parent of the pane and replace the pane in its parent’s Items collection.

We will prefer the second one. Here is the code that implements this behavior:

protected override void Adapt(IRegion region, RadPaneGroup regionTarget)
{
    region.Views.CollectionChanged += (s, e) =>
    {
 switch (e.Action)
        {
 case NotifyCollectionChangedAction.Add:
 foreach (var item in e.NewItems.OfType<RadPane>())
                {
                    regionTarget.Items.Add(item);
                }
 break;
 case NotifyCollectionChangedAction.Remove:
 foreach (var item in e.OldItems.OfType<RadPane>())
                {
                    item.RemoveFromParent();
                }
 break;
 case NotifyCollectionChangedAction.Replace:
                var oldItems = e.OldItems.OfType<RadPane>();
                var newItems = e.NewItems.OfType<RadPane>();
                var newItemsEnumerator = newItems.GetEnumerator();
 foreach (var oldItem in oldItems)
                {
                    var parent = oldItem.Parent as ItemsControl;
 if (parent != null && parent.Items.Contains(oldItem))
                    {
                        parent.Items[parent.Items.IndexOf(oldItem)] = newItemsEnumerator.Current;
 if (!newItemsEnumerator.MoveNext())
                        {
 break;
                        }
                    }
 else
                    {
                        oldItem.RemoveFromParent();
                        regionTarget.Items.Add(newItemsEnumerator.Current);
                    }
                }
 break;
 case NotifyCollectionChangedAction.Reset:
                regionTarget
                    .EnumeratePanes()
                    .ToList()
                    .ForEach(p => p.RemoveFromParent());
 break;
 default:
 break;
        }
    };
 
 foreach (var view in region.Views.OfType<RadPane>())
    {
        regionTarget.Items.Add(view);
    }
}

This accomplishes most of our task. Now we need to implement a Bootstrapper class that will add this adapter to the region adapter mappings (it will tell Prism to use this adapter when the region target is RadPaneGroup). The bootstrapper class should initialize the application and must inherit form the UnityBootsrapper class. It must initialize the Shell, the region adapter mappings and the module catalog. To setup the region adapter mappings you need to override the ConfigureRegionAdapterMappings method. Here is the code we need to add our new adapter to the mappings:

protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
{
    var mappings = base.ConfigureRegionAdapterMappings();
 
    mappings.RegisterMapping(typeof(RadPaneGroup), this.Container.Resolve<RadPaneGroupRegionAdapter>());
 
 return mappings;
}

I will not explain in details the other part of the sample project as is not very interesting and there are very good tutorials how to create an application using Prism around the web (this video is really good: http://development-guides.silverbaylabs.org/Video/Silverlight-Prism). The idea of the example is just to demonstrate how to use the custom region adapter.

The sample application attached to this post has one main project that implements the Bootstrapper class, the custom region adapter class and the Shell and two module projects that have one module class and two views in each module. All the views inherit from RadPane, because our region adapter expects all the views to be RadPanes. If you don’t like this it can be easily fixed in the custom region adapter, but for simplicity I preferred to use RadPanes as views.

 

Here is the whole solution including the Prism .dlls -click here to download.

Update: The example files are updated - the .zip file includes WPF version of the example.

About the author

Miroslav Nedyalkov

Miroslav Nedyalkov

is a XAML enthusiast and Technical Lead in Telerik. Speaker at various local user groups and events. Miroslav is passionate about cutting edge technologies and likes to share his knowlage with others. You can follow him on Twitter at @miro_nedyalkov.

20 Comments

Chris
Hi Miroslav

We are looking for a set of SL controls that enable us to create the kind of dashboard layout with draggable/resizable content panes you see here:

http://www.mscui.net/PatientJourneyDemonstrator/

Does this article demonstrate something similar (we can;t dl the sample as we are not set up with Telerik controls yet)

Thanks for any pointers

Chris
Miroslav Nedyalkov
Hello, Chris,

This control is slightly different from the control, demonstrated on the page you've sent. You can see demos of this control here: http://demos.telerik.com/silverlight/#Docking/FirstLook.

We are currently working on a control that is more like the control you mentioned. A beta version of this control will be released this week and you will be able to download and explore it.

Regards,
Miroslav Nedyalkov,
Telerik team.
Shemesh
Hi,
i m writing a custom adapter to work with a Grid control.
in your code it seems like your are adding the item two times.
once inside the switch > add.
and a second time after the switch.

why?
Miroslav Nedyalkov
Hello,

The switch is in the body of a lambda expression that is used as an event handler of the CollectionChanged event and is not executed when the Adapter virtual method is called. The foreach that adds the items outside the switch is outside the lambda as well and is executed when the Adapter virtual method is called.

Regards,
Miroslav Nedyalkov,
Telerik team.
Maurizio
Hi Miroslav,
I was trying to create an application with prism and the docking control. Your example is perfect for me so I downloaded it as soon as I saw it.
It works fine but if I put a control with a x:Name into a ModelView when I try to unpin a pane I get an error.
Try to change this xaml code in the Pane1.xaml of the ModuleA:

<

 

 

TextBox Text="A" />

 

 

like this:

<

 

 

TextBox x:Name="tbText" Text="A" />

 

 

When you try to unpin the pane you get this error:
Error: Unhandled Error in Silverlight Application
Code: 2028   
Category: ParserError      
Message: The name already exists in the tree: tbText.     

Any suggestions?

Thank you
Maurizio 
Miroslav Nedyalkov
Hello Maurizio,

Unfortunately I don't know what causes this problem.
Could you please open a support ticket in our support system and post a sample project that reproduces the problem? This way it will be easier for us to investigate what the problem is.

Regards,
Miroslav Nedyalkov,
Telerik team.
Oleg
Hi Miroslav,

Did you notice that _regionManager.Regions collection doesn't contain MainRegion? There is no problem if you use _regionManager.RegisterViewWithRegion method to attach view to this region. But if you need for example deactivate the view in MainRegion you can't do it because you can't retrieve this region from _regionManager. 

Regards,
Oleg 
Oleg
Hi Miroslav,

Did you notice that _regionManager.Regions collection doesn't contain MainRegion? There is no problem if you use _regionManager.RegisterViewWithRegion method to attach view to this region. But if you need for example deactivate the view in MainRegion you can't do it because you can't retrieve this region from _regionManager. 

Regards,
Oleg 
Oleg
Hi Miroslav,

Did you notice that _regionManager.Regions collection doesn't contain MainRegion? There is no problem if you use _regionManager.RegisterViewWithRegion method to attach view to this region. But if you need for example deactivate the view in MainRegion you can't do it because you can't retrieve this region from _regionManager. 

Regards,
Oleg 
Richard

Hi Miroslav,

I have the same problem as Oleg.
I want to have a outlook bar on the left side and from there populate the center dockingpane. I can do it if i use RegisterViewWithRegion but I dont want to that, since i most of the time only want to show only one panel in the center.

Richard

Hi Miroslav,

I have the same problem as Oleg.
I want to have a outlook bar on the left side and from there populate the center dockingpane. I can do it if i use RegisterViewWithRegion but I dont want to that, since i most of the time only want to show only one panel in the center.

Miroslav Nedyalkov
Hello Oleg and Richard,

Thank you for telling me about this problem. We will investigate the problem and may update the article.

Regards,
Miroslav Nedyalkov,
Telerik team.
Justin
For some reason Prism is unable to find the RegionManager for a region if it is defined on a control within the DocumentHost property of the RadDocking control (as a main region typically would). This can be fixed by explicitly setting the RegionManager property on the DocumentHost like this:

1 _documentHost.SetValue(RegionManager.RegionManagerProperty, regionManager); 

Cheers,
Justin
Miroslav Nedyalkov
Justin, thank you for your suggestion. This should work-around the problem.

Regards,
Miroslav Nedyalkov,
Telerik team.
Matt
Mike,

I'm running into a bit of an issue using the code.  Everything works great until I unpin a tab and move it to another location, then I try to add another view to the region and I get a "Element is already the child of another element." error on the "NotifyCollectionChangeAction.Reset piece of the region adapter.

Any thoughts?  I really need to be able to move the tabs once I have placed them, and be able to add new ones on the fly.
Matt
Just as a quick update, I found that if I did this

                            try
                            {
                                regionTarget.Items.Add(view);
                            }
                            catch (System.Exception ex)
                            {
                                // do nothing I want to see what happens.
                            }
                            try
                            {
                                regionTarget.Items.Add(view);
                            }
                            catch (System.Exception ex)
                            {
                                // do nothing I want to see what happens.
                            }
                            try
                            {
                                regionTarget.Items.Add(view);
                            }
                            catch (System.Exception ex)
                            {
                                // do nothing I want to see what happens.
                            }
try
{
regionTarget.Items.Add(view);
}
catch (System.Exception ex)
{
// do nothing
}

It works like a charm...

What are the ramifications of this though?
Axente Adrian
All the adapters found on the internet including this site are very poor implementations with no use in a real life application.
After a long research I had no choice but implement my own one which is 80 % percent finished.
I will put it on the internet after I fill finish it.

My implementation consist in a Region and and a RegionAdapter
The RadDockingRegion region is derived from My SingleActiveRegionEx just passes the ViewMetadata from the region to the adapter (I need it for  the serialization Tag and it was the most elegant solution till now)

The SingleActiveRegionEx which 2 feature over the statndard one:
1. It can cancel a view removal and pass to it a remove callback wich latter the view can use to realy close it's self (if the view implements a custom interface IComfirmRemove)
2. It can inform the view about it's removal from the region if it also implements another custom interface IRemoveAware.

The RadDockingAdapter is actually the complex part where all the logic relies.

It is responsible to attach/detach views to RadPanes, create split containers, and RadPaneGroups and put the RadGroups to the right RadSplitCotainers.

So briefly just using a single Region the RadDocking region I'am able to put views where a like just specifying docking metadata by attached properties to views (this is the solution at this moment. maybe i'll find a better one)

Currently I have some issues with the serialization which I will solve very soon and then I will run some test and post my solution

Good Luck,
My best regards.


Axente Adrian
All the adapters found on the internet including this site are very poor implementations with no use in a real life application.
After a long research I had no choice but implement my own one which is 80 % percent finished.
I will put it on the internet after I fill finish it.

My implementation consist in a Region and and a RegionAdapter
The RadDockingRegion region is derived from My SingleActiveRegionEx just passes the ViewMetadata from the region to the adapter (I need it for  the serialization Tag and it was the most elegant solution till now)

The SingleActiveRegionEx which 2 feature over the statndard one:
1. It can cancel a view removal and pass to it a remove callback wich latter the view can use to realy close it's self (if the view implements a custom interface IComfirmRemove)
2. It can inform the view about it's removal from the region if it also implements another custom interface IRemoveAware.

The RadDockingAdapter is actually the complex part where all the logic relies.

It is responsible to attach/detach views to RadPanes, create split containers, and RadPaneGroups and put the RadGroups to the right RadSplitCotainers.

So briefly just using a single Region the RadDocking region I'am able to put views where a like just specifying docking metadata by attached properties to views (this is the solution at this moment. maybe i'll find a better one)

Currently I have some issues with the serialization which I will solve very soon and then I will run some test and post my solution

Good Luck,
My best regards.


Viktor
Great post!
It took me a while to realize that module views inherit telerik:RadPane instead of UserControl :) (Shame on me)
Thanks for the tutorial and the workarround!
Viktor

Comments

  1.    
      
      
       
  2. (optional, emails won't be shown on public pages)
  3. (optional)
Read more articles by XAML Team - or - read latest articles in Developer Tools
Product Families