As we saw in the previous post, it’s pretty complicate to create a custom design in WPF to override the default style of the TabControl, but it’s pretty simple to extend the behavior of it.

As a senior dev, I usually don’t like to: 1) touch what is already working, 2) reinvent the wheel just to write the same code twice. For that there is the refactoring process, at most!

So, let’s start by the requirement we had in the previous post, we need to emulate the VS IDE in our applications, that’s it! We also saw that Prism has the RegionAdapter, so now we just need a cool control. Well there is the Avalon Dock project that is open source, really well done, flexible and ready for WPF 4. So let’s use it for our purposes. The final result should be something like this:

image

Avalon dock is crazy powerful and allows you to build a complete system of docking and modal windows into your WPF application. But before doing that you need to write a custom region adapter for it!

So, this is the basic concept of a custom Region Adapter in Prism. You create you custom adapter class by inheriting from RegionAdapterBase<T> in the following way:

Region adapter for Avalon Dock
  1. public sealed class AvalonRegionAdapter : RegionAdapterBase<DocumentPane>
  2. {
  3.     public AvalonRegionAdapter(IRegionBehaviorFactory factory)
  4.         : base(factory)
  5.     {
  6.  
  7.     }

 

Avalon dock has a lot of future, and you should create a Region adapter able to attach any type of dock view. In my case I will use only the Tab region adapter that is called DocumentPane. Now the RegionAdapterBase requires that you implement three methods:

Create region
  1. protected override IRegion CreateRegion()
  2. {
  3.     return new AllActiveRegion();
  4. }

 

The create region which specify what base adapter you want to use. In this case we want to handle any type of view added to this adapter, like an ItemsContainer or a TabControl.

Adapt
  1. protected override void Adapt(IRegion region, DocumentPane regionTarget)
  2. {
  3.     region.Views.CollectionChanged += delegate(Object sender, NotifyCollectionChangedEventArgs e)
  4.     {
  5.         OnViewsCollectionChanged(sender, e, region, regionTarget);
  6.     };
  7. }

Then we override the adapt method. This method is called once so in my case, because I have a DocumentPane I will then listen for the event Views.CollectionChanged. In this way I know every time if a view has been added or removed from the region.

Code Snippet
  1. private void OnViewsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e, IRegion region, DocumentPane regionTarget)
  2. {
  3.     if (e.Action == NotifyCollectionChangedAction.Add)
  4.     {
  5.         //Add content panes for each associated view.
  6.         foreach (object item in e.NewItems)
  7.         {
  8.             UIElement view = item as UIElement;
  9.  
  10.             if (view != null)
  11.             {
  12.                 DockableContent newContentPane = new DockableContent();
  13.                 newContentPane.Content = item;
  14.                 //if associated view has metadata then apply it.
  15.                 newContentPane.Title = view.GetType().ToString();
  16.                 //When contentPane is closed remove the associated region
  17.                 newContentPane.Closed += (contentPaneSender, args) =>
  18.                 {
  19.  
  20.                 };
  21.                 regionTarget.Items.Add(newContentPane);
  22.                 newContentPane.Activate();
  23.             }
  24.         }
  25.     }
  26.     else
  27.     {
  28.         if (e.Action == NotifyCollectionChangedAction.Remove)
  29.         {
  30.  
  31.         }
  32.     }
  33. }

Now, here there are two major steps. First we want to know if the item has been added or removed from the collection. If it’s added we create a new DockableContent and we set the Content to our view. Then we need to set a couple of properties like Title and name. In my case I am just adding the view, we will see later how to implement our TabModel property. What we can do then is to attach a listener to the close event of this tab. Why? Because when avalon will close a dock document we need also to destroy the corresponding view.

Then the second part is when the regionAdapter has a request of closing a view. We want to destroy the corresponding tab control.

Now go back a little bit and change the code in this way:

Code Snippet
  1. TabViewModel viewModel = ((UserControl)view).DataContext as TabViewModel;
  2. if (view != null)
  3. {
  4.     DockableContent newContentPane = new DockableContent();
  5.     newContentPane.Content = item;
  6.     if (viewModel != null)
  7.     {
  8.         Image img = new Image();
  9.         img.Source = new BitmapImage(new Uri(@”Resources/Alerts.png”, UriKind.Relative));
  10.         newContentPane.Title = viewModel.TabModel.Title;
  11.         newContentPane.IsCloseable = viewModel.TabModel.CanClose;
  12.         newContentPane.Icon = img.Source;
  13.     }

It’s a little bit dirty but what we are trying to do here is to cast the View.DataContext to a type TabViewModel. It it’s the right type, as NET won’t throw an exception but simply returns an empty instance … we will populate the tab controls with our info.

The final result is this one:

SNAGHTML388d63e

The first can’t be closed and the second one can be, also we added a special icon to the context menu. More than that, this is still a WPF controls where you can apply your custom style. That’s it!

Ops, this is the new code in the MainView, of course:

XAML Avalon Dock adapter
  1. <ad:DockingManager Grid.Column=”1″ Grid.Row=”1″ >
  2.     <ad:DocumentPane cal:RegionManager.RegionName=”TabRegion” Name=”TabRegion”>
  3.         <ad:DockableContent Title=”Some title”>
  4.  
  5.         </ad:DockableContent>
  6.     </ad:DocumentPane>
  7. </ad:DockingManager>

Final Note: As you can see building a custom region adapter is easy but you must know the control behind that, the one that will act as a region adapter. When you know that part you can play as you want. For example I have a region adapter for the ToolBar so every time you load a view, I pass the corresponding Toolbar to the main toolbar region. Same for the ribbon and for the Outlook bar.

Comments are closed.

Post Navigation