I recently set out to build a WPF application using the Composite Client Application Guidance (lovingly known as “Prism”) code base. Additionally, I wanted to make use of the Blacklight controls – specifically, the DragDockPanelHost and DragDockPanel controls – in the application. I soon discovered that these two code bases didn’t work together “right out of the box”. I found a problem, and a solution, and this post describes both the issue and its resolution.
The architecture promoted by Prism is that of an application made up of an executable shell and a set of module assemblies. In this pattern, each of the application views are implemented in their own module assembly, which is loaded by the shell at runtime. So far, so good.
Since I was interested in using the DragDockPanelHost and DragDockPanel controls in my WPF application, my approach was to put the DragDockPanelHost in my shell application, and a DragDockPanel in each of my Prism modules. With this design, and after assigning a Prism region name to the DragDockPanelHost in the shell application, Prism would do its thing and load each of the DragDockPanels from each module and add them into my DragDockPanelHost region at runtime.
You may be asking yourself, “And how did that work out for you?” To quote Stewie on Family Guy, “Not well, Brian. Not well.”
My first mistake was in assuming that Prism regions worked with any kind of container control, and, therefore, putting a region name on my DragDockPanelHost XAML element would be enough. It wasn’t. Prism, by default, only supports regions defined on containers derived from one of the following classes:
The DragDockPanelHost class is derived from Canvas, which explains the lack of love that I got from Prism.
Fortunately, Prism has a way to add support for other containers defined as regions through the RegionAdapterBase class. In my solution, I wrote a class, derived from RegionAdapterBase, that defined a region adapter for the Blacklight DragDockPanelHost. The class looks like this:
1: public class DragDockPanelHostRegionAdapter : RegionAdapterBase<DragDockPanelHost>
3: public DragDockPanelHostRegionAdapter(IRegionBehaviorFactory BehaviorFactory) : base(BehaviorFactory)
7: protected override void Adapt(IRegion region, DragDockPanelHost regionTarget)
9: region.Views.CollectionChanged += (s, e) =>
11: if (e.Action == NotifyCollectionChangedAction.Add)
13: foreach (FrameworkElement CurrentElement in e.NewItems)
15: UserControl CurrentElementAsUserControl = CurrentElement as UserControl;
16: DragDockPanel PanelToAdd = CurrentElementAsUserControl.Content as DragDockPanel;
17: CurrentElementAsUserControl.Content = null;
21: else if (e.Action == NotifyCollectionChangedAction.Remove)
23: foreach (FrameworkElement CurrentElement in e.OldItems)
29: protected override IRegion CreateRegion()
31: return new AllActiveRegion();
The generic type used in the RegionAdapterBase class is the type of the class for which you need Prism to support as a region. The body of the effort is in the Adapt() method, which is called when controls are added or removed from the container.
This class needs to be used in the shell’s Prism bootstrapper through an overloaded method called ConfigureRegionAdapterMappings():
1: internal class Bootstrapper : UnityBootstrapper
3: protected override DependencyObject CreateShell()
5: // ... snip
8: protected override IModuleCatalog GetModuleCatalog()
10: // ... snip
13: protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
15: RegionAdapterMappings Mappings = base.ConfigureRegionAdapterMappings();
16: Mappings.RegisterMapping(typeof(DragDockPanelHost), Container.Resolve<DragDockPanelHostRegionAdapter>());
17: return Mappings;
The region adapter is registered with Prism in the RegisterMapping() method of the RegionAdapterMappings class.
With this, I can now define a user control in my Prism module that defines a DragDockPanel:
1: <UserControl x:Class="Narrative.Units.View"
5: <blacklight:DragDockPanel Header="Title" />
Then I can, in my Prism shell, define a DragDockPanelHost with a region:
1: <Window x:Class="App.MainWindow"
6: Title="App Title" Height="300" Width="300" x:Name="Main">
7: <Grid x:Name="MainGrid">
9: <RowDefinition Height="auto"/>
10: <RowDefinition Height="auto"/>
11: <RowDefinition Height="*"/>
13: <blacklight:DragDockPanelHost Background="Khaki" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Width="Auto" Height="Auto" Grid.Row="2" x:Name="PanelHost" Margin="15" regions:RegionManager.RegionName="PanelHostRegion" />
This solved the issue, and I can now have my DragDockPanelHost in a Prism executable shell and a DragDockPanel user control in a Prism module.