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

is a XAML enthusiast. 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.

Related Posts

Comments