Click here to Skip to main content
15,884,425 members
Articles / Desktop Programming / WPF

Simple Visual Studio like Pane Resizing, Docking, and Collapsing

Rate me:
Please Sign up or sign in to vote.
4.94/5 (15 votes)
28 Feb 2013CPOL13 min read 67.2K   5.9K   63   16
Learn to create a simple custom control that allows users to dock.

Introduction

Every once in a while, when reading a programming book (in this case "WPF 4 unleashed"), I see an example that I would like to expand on. Doing so usually ensures I understand the concepts been discussed and gives me a fun project to work on. The Visual Studio like layout example in Chapter 5 of Adam Nathan's book is the example of choice I have decided to expand on. In the book, Adam puts up an example of collapsible, dock-able, and resizable Visual Studio like panes. This was done to show how WPF layout features are used to create complex user interfaces. In his example, Adam dealt with two hard coded panes and mention that one would most likely abstract the code into a custom control for more general use. That is exactly what I have done, and will try to describe in this article.

The code discussed below will build on the concept used to create the two panes, and create a custom control that allows for a generic manner of adding panes to either columns (left or right) or rows. These panes will be collapsible, dock-able, and resizable.

Image 1

The above image shows you a complete example of a dock-able window (the code is attached with this article).

Background

While I will do my best to explain the basic concepts used in creating this layout custom control, I would suggest reading the chapter and the original code example (I would rather not plagiarize, or repeat information that the author explains in better terms). Also note, that although I have written functional code that does exactly what I describe in this article, there are many more improvements that can be done to make my example a truly usable/ customize-able custom control. At the end of article I will mention some of the things that would probably need to be changed/ refactored.

To start off this article, I will describe the task I am trying to solve and then how I intend to solve it.

Task

I want a Visual Studio like frame where the main content fills the window, I want to have the ability to have dock-able content to either the left side, right side, or at the bottom of the main content. I want to be able to resize the dock-able content, when pinned relative to the main content and when unpinned regardless of the main content. I want to be able to choose which content to pin and when a content is pinned to get rid of the button that is activate in the dock-able content. When a dock-able content is hovering freely, I want to be able to collapse it by moving over the main window. Lastly I want to able to lay out my dock-able content declaratively in XAML.

Solution

First and foremost choosing a container that allows the user the ability to interactively re-size on the go means using a Grid (with GridSplitters). However since the dock-able content also needs to the ability to be re-sized regardless of the main content (allowing for overlaps), I will need more than one Grid.

The solution used here, will be for every dock-able content, a Grid will be used in its construction. The Grids will then be layered on top of one another with the main content being layer 0. To allow the windows to be synchronized when docked, we will use the

Grid
's SharedSizeGroup property. When a layer is docked, we will programmatically add a new shared column or row definition to the layers below it and add to the layer itself any shared column or row definition needed for already docked layers that are above it in hierarchy. Since we are using the SharedSizeGroup feature of the Grid we are able to keep the proportions in terms of width (when it comes to columns) and height (when it comes to rows) in sync with all layers. To mimic the collapsing of the dock-able content, all we have to do is set the Visibility of the Grid to Collapsed.

We will use StackPanels as the button bars for the columns and rows. When all the dock-able content is docked, the StackPanels collapse.

Image 2

The image above shows an example of one of the dock-able content layers overlapping the main content below it.

Using the Code

Prior to getting into the details of how we achieve the functionality as shown in the image above, I believe this is an appropriate time to walk you through the process of creating the custom control project.

  1. Create a new solution (I have named my solution DockableVSExample).
  2. Click on the solution in Solution Explorer and add a new folder named Common.
  3. Add a new WPF Custom Control Library named Controls in the Common folder.
  4. In the Themes folder add a new resource named LayeredGrid.xaml.
  5. Rename the controls.cs file auto generated when the Controls project was created to LayeredGrid.cs.
  6. Then add a new Layer.cs class file to the Controls project.
  7. In the Generic .xaml (note the build action on this should be set to Page), add the below entry inside the ResourceDictionary.MergedDictionaries tag.
  8. XML
    <ResourceDictionary Source="/Controls;component/Themes/LayeredGrid.xaml"/>

Once the above steps are done, we can now work on the LayeredGrid.xaml file (as it needs to be setup prior to working on the LayeredGrid class).

Open up the LayeredGrid.xaml for editing and in the ResourceDictionary tag, add the Controls namespace within the tag xmlns:local="clr-namespace:Controls;assembly=Controls". Then add the below XAML to the LayeredGrid.xaml file.

XML
<Style TargetType="{x:Type local:LayeredGrid}">
<Setter 
    Property="Template">
    <Setter.Value>
        <ControlTemplate>
            <DockPanel 
                VerticalAlignment="Stretch"
                HorizontalAlignment="Stretch"
                LastChildFill="True"
                Name="PART_ParentPanel">
                <DockPanel.BitmapEffect>
                    <BevelBitmapEffect  
                        BevelWidth="15" 
                        EdgeProfile="BulgedUp"/>
                </DockPanel.BitmapEffect>
                <DockPanel.Resources>
                    <Color 
                        x:Key="PrimaryColor">
                        CadetBlue
                    </Color>
                    <Color 
                        x:Key="SecondaryColor">
                        #CC0D1000
                    </Color>
                    <SolidColorBrush 
                        x:Key="PrimaryBrush" 
                        Color="{StaticResource PrimaryColor}" />
                    <SolidColorBrush 
                        x:Key="TextBrush" 
                        Color="Black" />
                    <SolidColorBrush 
                        x:Key="DisabledColor" 
                        Color="#8CFFFFFF" />
                    <SolidColorBrush 
                        x:Key="BackgroundBrush" 
                        Color="#FFFFFFFF" />
                    <Style 
                        x:Key="buttonStyle" 
                        TargetType="{x:Type Button}">
                        ..
                        ..
                        ..
                    </Style>
                    <RadialGradientBrush 
                        x:Key="myColorfulLabelBrush"
                        RadiusX="0.5"
                        RadiusY="1"
                        >
                        <GradientStop 
                            Color="#CC0D1000" 
                            Offset="0.1"/>
                        <GradientStop 
                            Color="CadetBlue" 
                            Offset="0.9"/>
                    </RadialGradientBrush>
                    <RadialGradientBrush 
                        x:Key="myColorfulBorderBrush"
                        RadiusX="0.4"
                        RadiusY="0.6"
                        >
                        <GradientStop Color="#CC3D2614" Offset="0.3"/>
                        <GradientStop Color="Gold" Offset="0.8"/>
                    </RadialGradientBrush>
                </DockPanel.Resources>
                <StackPanel 
                    Name="PART_BottomCntl"
                    Background="{StaticResource myColorfulLabelBrush}"
                    Orientation="Horizontal" 
                    Panel.ZIndex="1"
                    DockPanel.Dock="Bottom">
                </StackPanel>
                <StackPanel 
                     Name="PART_LeftCntl"
                    Background="{StaticResource myColorfulLabelBrush}"
                    Orientation="Horizontal" 
                    DockPanel.Dock="Left">
                    <StackPanel.LayoutTransform>
                        <RotateTransform Angle="90"/>
                    </StackPanel.LayoutTransform>
                </StackPanel>
                <StackPanel 
                     Name="PART_RightCntl"
                    Background="{StaticResource myColorfulLabelBrush}"
                    Orientation="Horizontal" 
                    DockPanel.Dock="Right">
                    <StackPanel.LayoutTransform>
                        <RotateTransform Angle="90"/>
                    </StackPanel.LayoutTransform>

                </StackPanel>
                <Grid 
                    Name="PART_MasterGrid"
                    Grid.IsSharedSizeScope="True">
                </Grid>
            </DockPanel>
        </ControlTemplate>
    </Setter.Value>
</Setter>
</Style>

Within the XAML content, there are a few things worth mentioning. First is the DockPanel (named PART_ParentPanel), which serves the purpose of allowing me to lay out three StackPanels (left, right, and bottom Button bar) and a Grid (serving as a content placeholder). The three StackPanels (named PART_LeftCntl, PART_RightCntl, and PART_ButtomCntl) allow for the placement of the buttons used to activate dock-able content layers. The <code>Grid (named PART_MasterGrid) represent the panel container where all layers will be contained. PART_MasterGrid has the attached property IsSharedSizeScope set to true, this allows any child Grid to share size information.

The first class that will be discussed is, the Layer class. The purpose of the Layer class is to define the properties that each layer of dock-able content needs to describe its location (Left or Right), its orientation (Row or Column), its level (a number which tells me in which order to place the layers, the name placed on the dock-able panel, and the content it hosts). This is the class that I would put any property that I want a Layer as a whole to inherit. The class implementation is shown below and is easy to follow, as such will not be explained further.

C#
public class Layer : UIElement
{
    public enum LayerOrientation
    {
        Row,
        Column
    }

    public enum LayerColumnLocation
    {
        Left,
        Right
    }
    
    public static readonly DependencyProperty LevelProperty;
    public static readonly DependencyProperty ContentProperty;
    public static readonly DependencyProperty OrientationProperty;
    public static readonly DependencyProperty NameProperty;
    public static readonly DependencyProperty ColumnLocationProperty;

    public int Level
    {
        get { return (int)GetValue(LevelProperty); }
        set { SetValue(LevelProperty, value); }
    }

    public UIElement Content
    {
        get { return (UIElement)GetValue(ContentProperty); }
        set { SetValue(ContentProperty, value); }
    }

    public LayerOrientation Orientation
    {
        get { return (LayerOrientation)GetValue(OrientationProperty); }
        set { SetValue(OrientationProperty, value); }
    }
 
    public LayerColumnLocation ColumnLocation
    {
        get { return (LayerColumnLocation)GetValue(ColumnLocationProperty); }
        set { SetValue(ColumnLocationProperty, value); }
    }

    public string Name
    {
        get { return (string)GetValue(NameProperty); }
        set { SetValue(NameProperty, value); }
    }

    static Layer()
    {
        LevelProperty = DependencyProperty.Register(
                                            "Level",
                                            typeof(int),
                                            typeof(Layer)
                                            );
        ContentProperty = DependencyProperty.Register(
                                             "Content",
                                             typeof(UIElement),
                                             typeof(Layer)
                                             );
        OrientationProperty = DependencyProperty.Register(
                                                "Orientation",
                                                typeof(LayerOrientation),
                                                typeof(Layer));
        NameProperty = DependencyProperty.Register(
                                                "Name",
                                                typeof(string),
                                                typeof(Layer));
        ColumnLocationProperty = DependencyProperty.Register(
                                                    "ColumnLocation",
                                                    typeof(LayerColumnLocation),
                                                    typeof(Layer),
                                                    new PropertyMetadata
                                                       (
                                LayerColumnLocation.Left
                                                                                      )
                                                );
    }
}

The LayeredGrid class (which inherits from the ContentControl class) serves as the class backing the custom control. The LayeredGrid class has only one DependencyProperty which is named LayersProperty. This dependency property is of type ObservableCollection< Layer>, and its purpose is to allow us to add one or more

Layer
objects to the LayeredGrid. As this is the class that does all the heavy lifting, I will try and explain its implementation in detail.

The below code is just the fields that are used within the next set of methods I will be discussing.

C#
#region fields 
    private Grid PART_MasterGrid;
    private StackPanel PART_RightCntl;
    private StackPanel PART_LeftCntl;
    private StackPanel PART_BottomCntl;
    private DockPanel PART_ParentPanel;
    private readonly ObservableCollection<Layer> _aValues = new ObservableCollection<Layer>();
    private readonly List<GridnFloatingBtnCombo> _columnLayers = new List<GridnFloatingBtnCombo>();
    private readonly List<GridnFloatingBtnCombo> _rowLayers = new List<GridnFloatingBtnCombo>();
    private const string ColumnStr = "column";
    private const string RowStr = "row";
    private const string LayerStr = "Layer";
    private const string PinStr = "btn";
#endregion 

The readonly _columnLayers and _rowLayers lists declared above are of type GridnFloatingBtnCombo. GridnFloatBtnCombo is the class used to hold each individual Layer's Grid and the Button that is associated with its activation (the button placed in the button bar StackPanel). The GridnFloatingBtnCombo defines a list of Grid.ColumnDefinitions which holds a ColumnDefinition for all column oriented Layers that have a Level greater than that of the Layer that ties to the class. The same explanation goes for the list of Grid.RowDefinitions that is defined in the class. The defined List of ColumnLocations in the class, is synchronized with the list of ColumnDefintions and serves the purpose of associating each ColumnDefinition with a location on the Grid. That is if the ColumnLocation is defined as Right we just add the ColumnDefintion to the Grid.Children collection, else if it is Left we insert the ColumnDefintion into the Grid at index 0. Because of the possibility of inserting ColumnDefinitions at index 0, we need the ability to maintain the Grid column index of the main content, hence the definition of the MainContentLocation property. The GridnFloatingBtnCombo then has two methods to increment the MainContentLocation (MainContentPositionIncrement()) and decrement the MainContentLocation (MainContentPositionDecrement()). The implementation of the GridnFloatingBtnCombo is shown below.

C#
#region layer grid, button btn, columns and rows definition holder class
    private class GridnFloatingBtnCombo
    {
        public readonly Grid Grid;
        public readonly Button Btn;
        public readonly List<ColumnDefinition> ColumnDefinitions;
        public readonly List<Layer.LayerColumnLocation> ColumnLocations;
        public readonly List<RowDefinition> RowDefinitions;
        public int MainContentLocation { get; private set; }

        public GridnFloatingBtnCombo(Grid grid, Button btn)
        {
            Grid = grid;
            Btn = btn;
            ColumnDefinitions = new List<ColumnDefinition>();
            RowDefinitions = new List<RowDefinition>();
            ColumnLocations = new List<Layer.LayerColumnLocation>();
            MainContentLocation = 1;
        }

        public void MainContentPositionIncrement()
        {
            MainContentLocation++;
        }

        public void MainContentPositionDecrement()
        {
            if (MainContentLocation > 1)
                MainContentLocation--;
        }

    }
#endregion

As mentioned above the LayeredGrid class has only one Dependency Property named LayersProperty. As this is a collection that needs to be initialized internally in code (an exception occurs otherwise), I register the dependency property as a readonly property and then in the non-static constructor initialize the collection.

C#
#region properties and DPs
    private static readonly DependencyPropertyKey LayersPropertyKey = 
                                           DependencyProperty.RegisterReadOnly("Layers",
                                                                               typeof (
                                                                               ObservableCollection
                                                                               <Layer>),
                                                                               typeof (LayeredGrid),
                                                                               new PropertyMetadata(null));

    public static readonly DependencyProperty LayersProperty = LayersPropertyKey.DependencyProperty;
    public ObservableCollection<Layer> Layers
    {
        get { return (ObservableCollection<Layer>)GetValue(LayersProperty); }
        set { SetValue(LayersProperty, value); }
    }

    #endregion

    #region constructor
    static LayeredGrid()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(LayeredGrid), 
                                                 new FrameworkPropertyMetadata(typeof(LayeredGrid)));
        
    }

    /// <summary>
    /// Initializes the Layers collection
    /// </summary>
    public LayeredGrid()
    {
        SetValue(LayersPropertyKey, _aValues); 
    }
#endregion

The overridden OnApplyTemplate method is where the layers are built up and put together. This method gets all PART_* controls mentioned above, sets up a parent Grid, sets up the first layer (Layer 0) that it adds to the parent Grid, after which all column layers are then setup (these also get added to the parent grid), followed by all row layers being then setup, and finally the parent Grid is then added to the PART_MasterGrid.

C#
public override void OnApplyTemplate()
{
    base.OnApplyTemplate();
    //get the part controls 
    PART_MasterGrid = GetTemplateChild("PART_MasterGrid") as Grid;
    PART_RightCntl = GetTemplateChild("PART_RightCntl") as StackPanel;
    PART_LeftCntl = GetTemplateChild("PART_LeftCntl") as StackPanel;
    PART_BottomCntl = GetTemplateChild("PART_BottomCntl") as StackPanel;
    PART_ParentPanel = GetTemplateChild("PART_ParentPanel") as DockPanel;
    //verify master grid exist
    if (PART_MasterGrid == null) 
        return;
    //setup parent grid
    var parentGrid = new Grid();
    SetUpParentGrid(parentGrid);
    //set up layers
    var layer0 = Layers.FirstOrDefault(x => x.Level == 0);
    if (layer0 == null)
        return;

    var columnLayers =
        Layers.Select(x => x)
              .Where(x => 
                                    x.Level > 0 && 
                                    x.Orientation == Layer.LayerOrientation.Column)
              .OrderBy(x => x.Level);
    var rowLayers =
        Layers.Select(x => x)
              .Where(x=> 
                                   x.Level>0 && 
                                   x.Orientation==Layer.LayerOrientation.Row)
              .OrderBy(x=> x.Level);
    var item = SetupLayer0(layer0,
                   columnLayers, 
                   rowLayers.Count());
    parentGrid.Children.Add(item);
    Grid.SetRow(item, 0);
    //setup the column grid layers
    if (columnLayers.Any())
    {
        foreach (var layer in columnLayers)
        {
            SetupColumnLayers(parentGrid, layer, columnLayers.Count());
        }
    }
    //setup the row grid layers
    if(rowLayers.Any())
    {
        foreach (var layer in rowLayers)
        {
            SetupRowLayers(parentGrid, layer, rowLayers.Count());
        }
    }

    //add parent grid to master grid
    PART_MasterGrid.Children.Add(parentGrid);
    Grid.SetRow(parentGrid,0);
}

The SetupParentGrid method adds two RowDefinitions to the parent Grid. The first RowDefinition of Height star is meant to house the main content.

C#
private static void SetUpParentGrid(Grid parent)
{
    var row1 = new RowDefinition 
                           { 
                Height = new GridLength(1, GridUnitType.Star) 
               };
    var row2 = new RowDefinition { Height = GridLength.Auto };
    parent.RowDefinitions.Add(row1);
    parent.RowDefinitions.Add(row2);
}

The SetupLayer0 method adds three Grid ColumnsDefinitions. The first column represents the placement location of all left dock-able content, the second column represents the location of the main content, and finally the third column represents the placement location of all right dock-able content. An event handler is attached to the Grid.MouseEnter event to collapse any Layer's Grid if its activate Layer Button is visible (meaning the Layer is not docked). Finally for every row and column Layer a RowDefintion and ColumnDefinition is added to an instance of GridnFloatingBtnCombo for Layer 0. Point to note, is how I set the SharedSizeGroup property of the Column/Row definitions.

C#
private Grid SetupLayer0(Layer layer0, IEnumerable<Layer> columnLayers, int numberofRows)
{
    var grid = new Grid { Name = ColumnStr + LayerStr + layer0.Level };
    grid.ColumnDefinitions.Add(new ColumnDefinition 
                                     { 
                          Width = GridLength.Auto 
                     });
    grid.ColumnDefinitions.Add(new ColumnDefinition 
                                     { 
                          Width = new GridLength(1, GridUnitType.Star) 
                                         });
    grid.ColumnDefinitions.Add(new ColumnDefinition 
                                     { 
                                              Width = GridLength.Auto 
                              });
    grid.RowDefinitions.Add(new RowDefinition());
    grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
    if (layer0.Content != null)
        grid.Children.Add(layer0.Content);
    if (layer0.Content != null)
    Grid.SetColumn(layer0.Content,1);
    grid.MouseEnter += (o, e) =>
    {
        for (var i = 1; i < _columnLayers.Count; i++)
        {
            if (_columnLayers[i].Btn.Visibility == Visibility.Visible)
            {
                _columnLayers[i].Grid.Visibility = Visibility.Collapsed;
            }
        }

        for (var i = 1; i < _rowLayers.Count; i++)
        {
            if (_rowLayers[i].Btn.Visibility == Visibility.Visible)
            {
                _rowLayers[i].Grid.Visibility = Visibility.Collapsed;
            }
        }
    };
    var gnb = new GridnFloatingBtnCombo(grid, null);
    if (columnLayers.Any())
    {
        var list = columnLayers.ToList();
        for (int i = 0; i < columnLayers.Count(); i++)
        {
            gnb.ColumnDefinitions.Add(new ColumnDefinition 
                                            { 
                               SharedSizeGroup = ColumnStr + 
                                                 (i + 1) + 
                                         list[i].ColumnLocation, 
                               Width = GridLength.Auto 
                            });
            gnb.ColumnLocations.Add(list[i].ColumnLocation);
        }
    }
    if (numberofRows > 0)
    {
        for (int i = 0; i < numberofRows; i++)
        {
            gnb.RowDefinitions.Add(new RowDefinition 
                                         { 
                              SharedSizeGroup = RowStr + (i + 1), 
                                Height = GridLength.Auto 
                                              });
        }
    }
    _columnLayers.Add(gnb);
    _rowLayers.Add(gnb);
    return grid;
} 

The SetupColumnLayers method deals with creating all column layers irrespective of the ColumnLocation. To build up a column Layer, we create a Grid with three ColumnDefinitions just as with Layer 0, however in this case the dock-able content will be place in column 0 for Layers that have their ColumnLocation as Left and column 2 for Layers that have their ColumnLocation as Right. This is evident in how the ColumnDefinition's SharedSizedGroup property is setup. An internal Grid is created with two RowDefinitions, the first row houses a DockPanel that in turn contains a docking pin button (docked to the right), and a TextBlock that holds the Layer's Name (docked to the left). A GridSplitter is added to the Layer's Grid along side the internal Grid to allow for resizing the content. The docking pin Button has a Click event handle that deals with the docking and undocking of the Layer. Finally for any Layer with a higher Level than the current Layer we create a ColumnDefinition (with its SharedGroupSize property set accordingly).

C#
private void SetupColumnLayers(Grid parentGrid, Layer layer, int columnLayerCnt)
{
    var grid = new Grid
        {
            Name = ColumnStr + LayerStr + layer.Level, 
            Visibility = Visibility.Collapsed
        };
    grid.ColumnDefinitions.Add(new ColumnDefinition
                                {
                                    SharedSizeGroup = 
                                    layer.ColumnLocation == Layer.LayerColumnLocation.Left 
                                                        ? ColumnStr + layer.Level+layer.ColumnLocation 
                                                        : null,
                                    Width = GridLength.Auto
                                });
    grid.ColumnDefinitions.Add(new ColumnDefinition
                                {
                                    Width = new GridLength(1, GridUnitType.Star)
                                });
    grid.ColumnDefinitions.Add(new ColumnDefinition
                                {
                                    SharedSizeGroup = 
                                    layer.ColumnLocation == Layer.LayerColumnLocation.Right 
                                                        ? ColumnStr + layer.Level + layer.ColumnLocation 
                                                        : null, 
                                    Width = GridLength.Auto
                                });
    var internalGrid = new Grid();
    internalGrid.RowDefinitions.Add(new RowDefinition
                                    {
                                        Height = GridLength.Auto
                                    });
    internalGrid.RowDefinitions.Add(new RowDefinition ());
    internalGrid.Background = (RadialGradientBrush) PART_MasterGrid.FindResource( "myColorfulLabelBrush" );
    grid.Children.Add(internalGrid);
    Grid.SetColumn(internalGrid, 
                   layer.ColumnLocation == Layer.LayerColumnLocation.Left ? 0 : 2
                  );
    var dockpanel = new DockPanel();
    internalGrid.Children.Add(dockpanel);
    Grid.SetRow(dockpanel, 0);
    var btn = new Button
                {
                    Name = ColumnStr + PinStr + layer.Level,
                    Width = 28.0,
                    Background = Brushes.Transparent,
                    BorderBrush = Brushes.Transparent,
                    Style = (Style) PART_MasterGrid.FindResource( "buttonStyle" ),
                    Content =  new Path
                                    {
                                        Stroke = Brushes.Black, 
                                        Fill=Brushes.Gold, 
                                        StrokeThickness = 1, 
                                        Stretch = Stretch.Fill, 
                                        Width = 9.0, 
                                        Height = 15,
                                        Data = PinPathgeometry()
                                    }

                };
    dockpanel.Children.Add(btn);
    DockPanel.SetDock(btn, Dock.Right);
    btn.Click += (o, e) =>
    {
        int level = layer.Level;
        var item = _columnLayers[level].Btn;
        if (item.Visibility == Visibility.Collapsed)
            ColumnUndockPane(level, o as Button);
        else
            ColumnDockPane(level, o as Button);
    };
    var textblock = new TextBlock
        {
            Padding = new Thickness(8),
            TextTrimming = TextTrimming.CharacterEllipsis,
            Foreground = Brushes.Gold,
            Text = layer.Name
        };
    dockpanel.Children.Add(textblock);
    DockPanel.SetDock(textblock,Dock.Left);
    if (layer.Content != null)
    {
        internalGrid.Children.Add(layer.Content);
        Grid.SetRow(layer.Content, 1);
    }
    var gridSplitter = new GridSplitter
                            {
                                Width = 2, 
                                Background = Brushes.CadetBlue, 
                                HorizontalAlignment = 
                                layer.ColumnLocation == Layer.LayerColumnLocation.Right 
                                                    ? HorizontalAlignment.Left 
                                                    : HorizontalAlignment.Right
                            };
    grid.Children.Add(gridSplitter);
    Grid.SetColumn(gridSplitter, 
                   layer.ColumnLocation == Layer.LayerColumnLocation.Right ? 2 : 0
                  );
    grid.MouseEnter += (o, e) =>
                        {
                            var level = layer.Level;
                            for (var i = (level + 1); i < _columnLayers.Count; i++)
                            {
                                if (_columnLayers[i].Btn.Visibility == Visibility.Visible)
                                    _columnLayers[i].Grid.Visibility = Visibility.Collapsed;
                            }
                        };
    parentGrid.Children.Add(grid);
    Grid.SetRow(grid, 0);
    var gnb = new GridnFloatingBtnCombo( grid, 
                                         AddToColumnStackPanel(layer)
                                       );
    if (columnLayerCnt > 0)
    {
        for (int i = layer.Level; i < columnLayerCnt; i++)
        {
            gnb.ColumnDefinitions
                    .Add(new ColumnDefinition
                                {
                                    SharedSizeGroup = ColumnStr + (i + 1) + layer.ColumnLocation, 
                                    Width = GridLength.Auto
                                });
            gnb.ColumnLocations.Add(layer.ColumnLocation);
        }
    }
    _columnLayers.Add(gnb);

}

The SetupRowLayers method, just like its column Layer setup counterpart, creates a Layer Grid. In this case however, two RowDefinitions are created, with the second row serving the purpose of housing the dock-able content. Also unlike the column Layer setup, the row Layer setup uses a DockPanel to host both the docking pin Button and the Layer's Content. Finally rather than adding the Layer's Grid to the parent Grid, we add this directly to the PART_MasterGrid (this allows the dockable row span across the dock-able columns when docked).

C#
private void SetupRowLayers(Grid parentGrid, Layer layer, int numberofRows)
{
    var grid = new Grid
        {
            Name = RowStr + LayerStr + layer.Level, 
            Visibility = Visibility.Collapsed
        };

    grid.RowDefinitions.Add(new RowDefinition
                                {
                                    Height = new GridLength(1,GridUnitType.Star)
                                });
    grid.RowDefinitions.Add(new RowDefinition
                                {
                                    SharedSizeGroup = RowStr+layer.Level,Height = GridLength.Auto
                                });
    //set up dock panel
    var dockpanel = new DockPanel
                        {
                            Margin = new Thickness(0, 4, 0, 0),
                            Background = 
                                (RadialGradientBrush) PART_MasterGrid.FindResource("myColorfulLabelBrush"),
                            LastChildFill = true
                        };
    grid.Children.Add(dockpanel);
    Grid.SetRow(dockpanel, 1);
    var gridsplitter = new GridSplitter
                            {
                                Height = 4,
                                Background = Brushes.CadetBlue,
                                ResizeDirection = GridResizeDirection.Rows,
                                HorizontalAlignment = HorizontalAlignment.Stretch,
                                VerticalAlignment = VerticalAlignment.Top
                            };
    grid.Children.Add(gridsplitter);
    Grid.SetRow(gridsplitter, 1);
    //set up stackpanel
    var stackpanel = new StackPanel
                        {
                            Height = 25.0, 
                            HorizontalAlignment = HorizontalAlignment.Stretch
                        };
    dockpanel.Children.Add(stackpanel);
    DockPanel.SetDock(stackpanel,Dock.Top);
    //set up btn
    var btn = new Button
                    {
                        Name = RowStr + PinStr + layer.Level,
                        Width = 26.0,
                        HorizontalAlignment = HorizontalAlignment.Right,
                        Background = Brushes.Transparent,
                        BorderBrush = Brushes.Transparent,
                        Style = (Style) PART_MasterGrid.FindResource("buttonStyle"),
                        BorderThickness = new Thickness(0)
                    };
    stackpanel.Children.Add(btn);
    var path = new Path
                    {   
                        Stroke = Brushes.Black, 
                        Fill = Brushes.Gold,
                        StrokeThickness = 1, 
                        Stretch = Stretch.Fill, 
                        Width = 9.0, 
                        Height = 15
                    };
    var pathgeometry = PinPathgeometry();
    path.Data = pathgeometry;
    btn.Content = path;
    btn.Click += (o, e) =>
        {
            int level = layer.Level;
            var pgrid = parentGrid;
            var item = _rowLayers[level].Btn;
            if (item.Visibility == Visibility.Collapsed)
                RowUndockPane(level, o as Button, pgrid);
            else
                RowDockPane(level, o as Button, pgrid);
        };

    if (layer.Content != null)
    {
        dockpanel.Children.Add(layer.Content);
        DockPanel.SetDock(layer.Content,Dock.Top);
    }
        

    grid.MouseEnter += (o, e) =>
        {
            var level = layer.Level;
            for (var i = 1; i < _rowLayers.Count; i++)
            {
                if (i == level)
                    continue;
                if (_rowLayers[i].Btn.Visibility == Visibility.Visible)
                {
                    _rowLayers[i].Grid.Visibility = Visibility.Collapsed;
                }
            }
        };
    PART_MasterGrid.Children.Add(grid);
    Grid.SetRow(grid,0);
    var gnb= new GridnFloatingBtnCombo(grid, AddToRowStackPanel(layer));
    if (numberofRows > 0)
    {
        for (int i = layer.Level; i < numberofRows; i++)
        {
            gnb.RowDefinitions.Add(new RowDefinition
                {
                    SharedSizeGroup = RowStr + (i + 1), 
                    Height = GridLength.Auto
                });
        }
    }
    _rowLayers.Add(gnb);
}

The PinPathGeometry method just deals with the drawing of the pin that is housed within the pin Button for all dock-able content.

C#
private static PathGeometry PinPathgeometry()
{
    return new PathGeometry
        {
            Figures = new PathFigureCollection
                {
                    new PathFigure
                        {
                            StartPoint = new Point(10,0),
                            
                            IsFilled = true,
                            
                            Segments = new PathSegmentCollection
                                {
                                    new LineSegment{Point = new Point(10,0)},
                                    new LineSegment{Point = new Point(30,0)},
                                    new LineSegment{Point = new Point(30,5)},
                                    new LineSegment{Point = new Point(10,5)},
                                    new LineSegment{Point = new Point(10,0)}
                                }
                        },
                    new PathFigure
                        {
                            StartPoint = new Point(4.5,5),
                            Segments = new PathSegmentCollection
                                {
                                    new LineSegment{Point = new Point(40.5,5)}
                                }
                        },
                    new PathFigure
                        {
                            StartPoint = new Point(22,5),
                            Segments = new PathSegmentCollection
                                {
                                    new LineSegment{Point = new Point(22,10)}
                                }    
                        }
                }
        };
}

The AddToColumnStackPanel serves the sole purpose of creating a button that is then added to either the PART_RightCntl or the PART_LeftCntl based on the Layer's ColumnLocation. The Button has a Click event handler attached that sets the visibility of the current Layer's Grid and collapses any other column Layer that is not docked.

C#
private Button AddToColumnStackPanel(Layer layer)
{
    var btn = new Button
    {
        Background = Brushes.Transparent,
        BorderBrush = Brushes.Transparent,
        BorderThickness = new Thickness(0),
        Height = 22,
        MinWidth = 65.0,
        Padding = new Thickness(10,0,15,0),
        FontWeight = FontWeights.Bold, 
        Style = (Style)PART_MasterGrid.FindResource("buttonStyle"),
        Content = layer.Name
    };
    btn.Click += (o, e) =>
    {
        var level = layer.Level;
        var item = _columnLayers[level];
        item.Grid.Visibility = Visibility.Visible;
        Grid.SetZIndex(item.Grid, 1);
        for (int i = 1; i < _columnLayers.Count; i++)
        {
            if (i == level)
                continue;
            var loc = _columnLayers[i];
            Grid.SetZIndex(loc.Grid, 0);
            if (loc.Btn.Visibility == Visibility.Visible)
                loc.Grid.Visibility = Visibility.Collapsed;

        }

    };
    if (layer.ColumnLocation==Layer.LayerColumnLocation.Right)
        PART_RightCntl.Children.Add(btn);
    else
        PART_LeftCntl.Children.Add(btn);
    return  btn;
}

The AddToRowStackPanel serves the sole purpose of creating a button that is then added to the PART_BottomCntl StackPanel. The Button has a Click event handler attached that sets the visibility of the current Layer's Grid and collapses any other row Layer that is not docked.

C#
private Button AddToRowStackPanel(Layer layer)
{
    var btn = new Button
        {
            Background = Brushes.Transparent,
            BorderBrush = Brushes.Transparent,
            BorderThickness = new Thickness(0),
            Height = 24,
            Padding = new Thickness(10, 0, 15, 0),
            FontWeight = FontWeights.Bold, 
            Style = (Style)PART_MasterGrid.FindResource("buttonStyle"),
            Content = layer.Name
        };
    btn.Click += (o, e) =>
        {
            var level = layer.Level;
            var item = _rowLayers[level];
            item.Grid.Visibility = Visibility.Visible;
            Grid.SetZIndex(item.Grid,1);
            for(int i=1; i<_rowLayers.Count; i++)
            {
                if (i==level)
                    continue;
                var loc = _rowLayers[i];
                Grid.SetZIndex(loc.Grid,0);
                if (loc.Btn.Visibility == Visibility.Visible)
                    loc.Grid.Visibility = Visibility.Collapsed;
                
            }

        };
    PART_BottomCntl.Children.Add(btn);
    return btn;
}

The ColumnDockPane method, deals with docking layered content to either the left side or right side of the main content. It starts the process off by checking what column location the Layer should reside. If it is on the right the Layer 0 Grid adds a ColumnDefinition whose SharedGroupSize is equivalent to that of the current Layer being docked, else if it is on the left side, the ColumnDefinition is inserted at index 0 and the MainContentLocation is incremented by 1. We then call the Grid.SetColumn to set Layer 0's main content to its new column location. The next step is to perform the exact same logic done for Layer 0 to the current Layer we are trying to dock (i.e., we add a ColumnDefinition for any docked Layer whose level is above that of the current Layer being docked). Lastly we perform the exact same operation of adding a ColumnDefintion (the SharedGroupSize being equivalent to that of the current Layer) to every docked Layer whose level is less than the current Layer we are trying to dock.

C#
private void ColumnDockPane(int level, Button btn)
{
    var item = _columnLayers[level];
    item.Btn.Visibility = Visibility.Collapsed;
    var rtTrans = new RotateTransform(90);
    btn.LayoutTransform = rtTrans;
    if (_columnLayers[0].ColumnLocations[level - 1] == Layer.LayerColumnLocation.Right)
        _columnLayers[0].
                    Grid.
                    ColumnDefinitions.
                    Add(_columnLayers[0].ColumnDefinitions[level - 1]);
    else
    {
        _columnLayers[0].MainContentPositionIncrement();
        _columnLayers[0].
                    Grid.
                    ColumnDefinitions.
                    Insert(0, _columnLayers[0].ColumnDefinitions[level - 1]);
        Grid.SetColumn(_columnLayers[0]. Grid.Children[0], 
                       _columnLayers[0].MainContentLocation);
    }

    for (var i = level + 1; i < _columnLayers.Count; i++)
    {
        if (_columnLayers[i].Btn.Visibility != Visibility.Collapsed) 
            continue;
        if (item.ColumnLocations[i-level - 1] == Layer.LayerColumnLocation.Right)
            item.Grid.ColumnDefinitions.Add(item.ColumnDefinitions[i - level - 1]);
        else
        {
            item.MainContentPositionIncrement();
            item.Grid.ColumnDefinitions.Insert(0, item.ColumnDefinitions[i-level - 1]);
            foreach (UIElement child in item.Grid.Children)
            {
                Grid.SetColumn(child, item.MainContentLocation - 1);
            }
        }
    }
    for (var i = 1; i < level; i++)
    {
        var loc = _columnLayers[i];
        if (loc.Btn.Visibility != Visibility.Collapsed) 
            continue;
        if (loc.ColumnLocations[level - 1 - i] == Layer.LayerColumnLocation.Right)
            loc.Grid.ColumnDefinitions.Add(loc.ColumnDefinitions[level - 1 - i]);
        else
        {
            loc.MainContentPositionIncrement();
            loc.Grid.ColumnDefinitions.Insert(0, loc.ColumnDefinitions[level - 1 - i]);
            foreach (UIElement child in loc.Grid.Children)
            {
                Grid.SetColumn(child, loc.MainContentLocation - 1);
            }
            
        }
    }
}

The ColumnUndockPane method is the exact opposite of the ColumnDockPane method and is self explanatory once the reader understands the latter method.

C#
private void ColumnUndockPane(int level, Button btn)
{
    var item = _columnLayers[level];
    item.Btn.Visibility = Visibility.Visible;
    btn.LayoutTransform = null;
    item.Grid.Visibility = Visibility.Visible;

    for (var i = 0; i < level; i++)
    {

        if (_columnLayers[i].ColumnLocations[level - 1-i] == Layer.LayerColumnLocation.Left)
        {
            
            _columnLayers[i].MainContentPositionDecrement();
            if(i==0)
                Grid.SetColumn(_columnLayers[i].Grid.Children[0], 
                               _columnLayers[i].MainContentLocation);
            else
            {
                foreach (UIElement child in _columnLayers[i].Grid.Children)
                {
                    Grid.SetColumn(child, _columnLayers[i].MainContentLocation - 1);
                }

            }
        }
        _columnLayers[i].
                    Grid.
                    ColumnDefinitions.
                        Remove(_columnLayers[i].ColumnDefinitions[level - 1 - i]);
    }
    int v = 0;
    foreach (var t in item.ColumnDefinitions)
    {
        
        if (item.ColumnLocations[v++] == Layer.LayerColumnLocation.Left)
        {
            item.MainContentPositionDecrement();
            foreach (UIElement child in item.Grid.Children)
            {
                Grid.SetColumn(child, item.MainContentLocation - 1);
            }
        }
        item.Grid.ColumnDefinitions.Remove(t);
    }
}

The RowDockPane method is simpler than its column equivalent, as we do not have to worry about an up or down location. We just add to the parent Grid the RowDefinition that ties to the current row Layer we are trying to dock. The next step is to perform the exact same logic done for Layer 0 to the current Layer we are trying to dock (i.e., we add a RowDefinition for any docked Layer whose level is above that of the current Layer being docked). Lastly we perform the exact same operation of adding a RowDefinition (the SharedGroupSize being equivalent to that of the current Layer) to every docked Layer whose level is less than the current Layer we are trying to dock.

C#
private void RowDockPane(int level, Button btn, Grid parentGrid)
{
    var item = _rowLayers[level];
    item.Btn.Visibility = Visibility.Collapsed;
    var rtTrans = new RotateTransform(90);
    btn.LayoutTransform = rtTrans;
    parentGrid.RowDefinitions.Add(_rowLayers[0].RowDefinitions[level - 1]);
    for(var i=level+1; i<_rowLayers.Count; i++)
    {
        if (_rowLayers[i].Btn.Visibility == Visibility.Collapsed)
            item.Grid.RowDefinitions.Add(item.RowDefinitions[i-level-1]);
    }
    for(var i =1; i<level;i++)
    {
        var loc = _rowLayers[i];
        if(loc.Btn.Visibility==Visibility.Collapsed)
            loc.Grid.RowDefinitions.Add(loc.RowDefinitions[level-1-i]);
    }
}

RowUndockPane is the exact opposite of RowDockPane, so no explanation is needed here.

C#
private void RowUndockPane(int level, Button btn, Grid parentGrid)
{
    var item = _rowLayers[level];
    item.Btn.Visibility = Visibility.Visible;
    btn.LayoutTransform = null;
    item.Grid.Visibility = Visibility.Visible;
    parentGrid.RowDefinitions.Remove(_rowLayers[0].RowDefinitions[level - 1]);
    for(int i=1; i<level; i++)
    {
        _rowLayers[i].Grid.RowDefinitions.Remove(_rowLayers[i].RowDefinitions[level - 1-i]);
    }
    foreach (RowDefinition t in item.RowDefinitions)
    {
        item.Grid.RowDefinitions.Remove(t);
    }
}

Finally to show a working example as the images you saw above, in MainWindow.xaml of the solution you created, you add the below XAML, and voila! you have a working docking application (comment out the toolbars as they include images that are enclosed within the project). Although the XAML is placed here, I would suggest collapsing it when reading and download the project itself.

XML
<Window x:Class="DockableVsExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:controls="clr-namespace:Controls;assembly=Controls"
        Title="MainWindow" Height="800" Width="1024">
    <Window.Resources>
        <RadialGradientBrush 
            x:Key="myColorfulLabelBrush"
            RadiusX="0.5"
            RadiusY="1"
            >
            <GradientStop Color="#CC0D1000" Offset="0.1"/>
            <GradientStop Color="CadetBlue" Offset="0.9"/>
        </RadialGradientBrush>
        <RadialGradientBrush 
            x:Key="myColorfulBorderBrush"
            RadiusX="0.4"
            RadiusY="0.6"
            >
            <GradientStop Color="#CC3D2614" Offset="0.3"/>
            <GradientStop Color="Gold" Offset="0.8"/>
        </RadialGradientBrush>
    </Window.Resources>
    <DockPanel >
        <DockPanel.BitmapEffect>
            <BevelBitmapEffect  BevelWidth="15" EdgeProfile="BulgedUp"/>
        </DockPanel.BitmapEffect>
        <Menu DockPanel.Dock="Top">
            <MenuItem  Header="File">
                File
            </MenuItem>
            <MenuItem  Header="Edit">
                Edit
            </MenuItem>
            <MenuItem  Header="View">
                View
            </MenuItem>
            <MenuItem  Header="Project">
                Project
            </MenuItem>
            <MenuItem  Header="Build">
                Build
            </MenuItem>
            <MenuItem  Header="Data">
                Data
            </MenuItem>
            <MenuItem  Header="Tools">
                Tools
            </MenuItem>
            <MenuItem  Header="Window">
                Window
            </MenuItem>
            <MenuItem  Header="Community">
                Community
            </MenuItem>
            <MenuItem  Header="Help">
                Help
            </MenuItem>
        </Menu>
        <Border
            DockPanel.Dock="Top"
            BorderBrush="{StaticResource myColorfulBorderBrush}"
            BorderThickness="0">
            <Label 
                Background="{StaticResource myColorfulLabelBrush}" 
                Foreground="Wheat"
                FontWeight="ExtraBlack"
                FontSize="16"
                HorizontalContentAlignment="Center">
                Docking Yeahhh!!!!
            </Label>
            <Border.BitmapEffect>
                <EmbossBitmapEffect  />
            </Border.BitmapEffect>
        </Border>
        <StatusBar 
            DockPanel.Dock="Bottom" 
            Background="{StaticResource myColorfulLabelBrush}"
            Height="15"/>
        <controls:LayeredGrid
                                Grid.Row="1" 
                                Grid.Column="2"
                                Grid.RowSpan="3">
            <controls:LayeredGrid.Layers>
                <controls:Layer Level="2" Orientation="Row" Name="Text Manager 2">
                    <controls:Layer.Content>
                        <controls:LayeredGrid>
                            <controls:LayeredGrid.Layers>
                                <controls:Layer Level="1" Orientation="Row" Name="Logger">
                                    <controls:Layer.Content>
                                        <ListBox  
                                                            MinHeight="60"
                                                                >
                                            <ListBoxItem Content="{Binding Title}"></ListBoxItem>
                                        </ListBox>
                                    </controls:Layer.Content>
                                </controls:Layer>
                                <controls:Layer Level="1" Orientation="Column" 
                                         Name="Solution Explorer" ColumnLocation="Right">
                                    <controls:Layer.Content>
                                        <Grid >
                                            <Grid.RowDefinitions>
                                                <RowDefinition Height="Auto"/>
                                                <RowDefinition />
                                            </Grid.RowDefinitions>
                                            <ToolBar Grid.Row="0">
                                                <ToggleButton >
                                                    <Image Source="Images\Home-icon.png"/>
                                                </ToggleButton>
                                                <ToggleButton>
                                                    <Image Source="Images\Next-icon.png"/>
                                                </ToggleButton>
                                                <ToggleButton>
                                                    <Image Source="Images\Next-icon.png">
                                                        <Image.LayoutTransform>
                                                            <RotateTransform Angle="180"/>
                                                        </Image.LayoutTransform>
                                                    </Image>
                                                </ToggleButton>
                                            </ToolBar>
                                            <TreeView 
                                                                Grid.Row="1"
                                                                >
                                                <TreeViewItem Header="Solution Explorer">
                                                    <TreeViewItem Header="Project #1"></TreeViewItem>
                                                    <TreeViewItem Header="Project #2"></TreeViewItem>
                                                    <TreeViewItem Header="Project #3"></TreeViewItem>
                                                    <TreeViewItem Header="Project #1"></TreeViewItem>
                                                    <TreeViewItem Header="Project #2"></TreeViewItem>
                                                    <TreeViewItem Header="Project #3"></TreeViewItem>
                                                    <TreeViewItem Header="Project #1"></TreeViewItem>
                                                    <TreeViewItem Header="Project #2"></TreeViewItem>
                                                    <TreeViewItem Header="Project #3"></TreeViewItem>
                                                    <TreeViewItem Header="Project #1"></TreeViewItem>
                                                    <TreeViewItem Header="Project #2"></TreeViewItem>
                                                    <TreeViewItem Header="Project #3"></TreeViewItem>
                                                    <TreeViewItem Header="Project #1"></TreeViewItem>
                                                    <TreeViewItem Header="Project #2"></TreeViewItem>
                                                    <TreeViewItem Header="Project #3"></TreeViewItem>
                                                </TreeViewItem>
                                            </TreeView>
                                        </Grid>
                                    </controls:Layer.Content>
                                </controls:Layer>
                                <controls:Layer Level="2" Orientation="Column" Name="Toolbox">
                                    <controls:Layer.Content>
                                        <ListBox >
                                            <ListBoxItem>Button</ListBoxItem>
                                            <ListBoxItem>Label</ListBoxItem>
                                            <ListBoxItem>CheckBox</ListBoxItem>
                                            <ListBoxItem>ListBox</ListBoxItem>
                                        </ListBox>
                                    </controls:Layer.Content>
                                </controls:Layer>
                                <controls:Layer Level="3" Orientation="Column" Name="Toolbox Manager">
                                    <controls:Layer.Content>
                                        <ListBox >
                                            <ListBoxItem>Button</ListBoxItem>
                                            <ListBoxItem>Label</ListBoxItem>
                                            <ListBoxItem>CheckBox</ListBoxItem>
                                            <ListBoxItem>ListBox</ListBoxItem>
                                        </ListBox>
                                    </controls:Layer.Content>
                                </controls:Layer>
                                <controls:Layer Level="4" Orientation="Column" Name="Numbers">
                                    <controls:Layer.Content>
                                        <ListBox >
                                            <ListBoxItem>1</ListBoxItem>
                                            <ListBoxItem>2</ListBoxItem>
                                            <ListBoxItem>3</ListBoxItem>
                                            <ListBoxItem>4</ListBoxItem>
                                        </ListBox>
                                    </controls:Layer.Content>
                                </controls:Layer>
                                <controls:Layer Level="5" Orientation="Column" Name="Names">
                                    <controls:Layer.Content>
                                        <ListBox >
                                            <ListBoxItem>Ty</ListBoxItem>
                                            <ListBoxItem>Tayo</ListBoxItem>
                                            <ListBoxItem>Temitayo</ListBoxItem>
                                            <ListBoxItem>Lauren</ListBoxItem>
                                        </ListBox>
                                    </controls:Layer.Content>
                                </controls:Layer>
                                <controls:Layer Level="0" >
                                    <controls:Layer.Content>
                                        <ListBox
                                                HorizontalAlignment="Stretch"
                                                VerticalAlignment="Stretch"
                                                BorderBrush="Wheat">
                                            <ListBoxItem>Article #1</ListBoxItem>
                                            <ListBoxItem>Article #2</ListBoxItem>
                                            <ListBoxItem>Article #3</ListBoxItem>
                                            <ListBoxItem>Article #4</ListBoxItem>
                                        </ListBox>
                                    </controls:Layer.Content>

                                </controls:Layer>
                            </controls:LayeredGrid.Layers>
                        </controls:LayeredGrid>
                    </controls:Layer.Content>
                </controls:Layer>
                <controls:Layer Level="1" Orientation="Column" 
                         Name="Solution Explorer" ColumnLocation="Right">
                    <controls:Layer.Content>
                        <Grid >
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto"/>
                                <RowDefinition />
                            </Grid.RowDefinitions>
                            <ToolBar Grid.Row="0">
                                <ToggleButton >
                                    <Image Source="Images\Home-icon.png"/>
                                </ToggleButton>
                                <ToggleButton>
                                    <Image Source="Images\Next-icon.png"/>
                                </ToggleButton>
                                <ToggleButton>
                                    <Image Source="Images\Next-icon.png">
                                        <Image.LayoutTransform>
                                            <RotateTransform Angle="180"/>
                                        </Image.LayoutTransform>
                                    </Image>
                                </ToggleButton>
                            </ToolBar>
                            <TreeView 
                                Grid.Row="1"
                                >
                                <TreeViewItem Header="Solution Explorer">
                                    <TreeViewItem Header="Project #1"></TreeViewItem>
                                    <TreeViewItem Header="Project #2"></TreeViewItem>
                                    <TreeViewItem Header="Project #3"></TreeViewItem>
                                    <TreeViewItem Header="Project #1"></TreeViewItem>
                                    <TreeViewItem Header="Project #2"></TreeViewItem>
                                    <TreeViewItem Header="Project #3"></TreeViewItem>
                                    <TreeViewItem Header="Project #1"></TreeViewItem>
                                    <TreeViewItem Header="Project #2"></TreeViewItem>
                                    <TreeViewItem Header="Project #3"></TreeViewItem>
                                    <TreeViewItem Header="Project #1"></TreeViewItem>
                                    <TreeViewItem Header="Project #2"></TreeViewItem>
                                    <TreeViewItem Header="Project #3"></TreeViewItem>
                                    <TreeViewItem Header="Project #1"></TreeViewItem>
                                    <TreeViewItem Header="Project #2"></TreeViewItem>
                                    <TreeViewItem Header="Project #3"></TreeViewItem>
                                </TreeViewItem>
                            </TreeView>
                        </Grid>
                    </controls:Layer.Content>
                </controls:Layer>
                <controls:Layer Level="2" Orientation="Column" 
                             Name="Explorer" ColumnLocation="Right">
                    <controls:Layer.Content>
                        <Grid >
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto"/>
                                <RowDefinition />
                            </Grid.RowDefinitions>
                            <TreeView 
                                Grid.Row="1"
                                >
                                <TreeViewItem Header="Explorer">
                                    <TreeViewItem Header="Project #1"></TreeViewItem>
                                    <TreeViewItem Header="Project #2"></TreeViewItem>
                                    <TreeViewItem Header="Project #3"></TreeViewItem>
                                    <TreeViewItem Header="Project #4"></TreeViewItem>
                                    <TreeViewItem Header="Project #5"></TreeViewItem>
                                    <TreeViewItem Header="Project #6"></TreeViewItem>
                                    <TreeViewItem Header="Project #7"></TreeViewItem>
                                    <TreeViewItem Header="Project #8"></TreeViewItem>
                                    <TreeViewItem Header="Project #9"></TreeViewItem>
                                    <TreeViewItem Header="Project #10"></TreeViewItem>
                                    <TreeViewItem Header="Project #11"></TreeViewItem>
                                    <TreeViewItem Header="Project #3"></TreeViewItem>
                                    <TreeViewItem Header="Project #1"></TreeViewItem>
                                    <TreeViewItem Header="Project #2"></TreeViewItem>
                                    <TreeViewItem Header="Project #3"></TreeViewItem>
                                </TreeViewItem>
                            </TreeView>
                        </Grid>
                    </controls:Layer.Content>
                </controls:Layer>
                <controls:Layer Level="3" Orientation="Column" Name="Toolbox">
                    <controls:Layer.Content>
                        <ListBox >
                            <ListBoxItem>Button</ListBoxItem>
                            <ListBoxItem>Label</ListBoxItem>
                            <ListBoxItem>CheckBox</ListBoxItem>
                            <ListBoxItem>ListBox</ListBoxItem>
                        </ListBox>
                    </controls:Layer.Content>
                </controls:Layer>
                <controls:Layer Level="4" Orientation="Column" Name="Toolbox Manager">
                    <controls:Layer.Content>
                        <ListBox >
                            <ListBoxItem>Button</ListBoxItem>
                            <ListBoxItem>Label</ListBoxItem>
                            <ListBoxItem>CheckBox</ListBoxItem>
                            <ListBoxItem>ListBox</ListBoxItem>
                        </ListBox>
                    </controls:Layer.Content>
                </controls:Layer>
                <controls:Layer Level="5" Orientation="Column" Name="Numbers">
                    <controls:Layer.Content>
                        <ListBox >
                            <ListBoxItem>1</ListBoxItem>
                            <ListBoxItem>2</ListBoxItem>
                            <ListBoxItem>3</ListBoxItem>
                            <ListBoxItem>4</ListBoxItem>
                        </ListBox>
                    </controls:Layer.Content>
                </controls:Layer>
                <controls:Layer Level="6" Orientation="Column" Name="Names">
                    <controls:Layer.Content>
                        <ListBox >
                            <ListBoxItem>Ty</ListBoxItem>
                            <ListBoxItem>Tayo</ListBoxItem>
                            <ListBoxItem>Temitayo</ListBoxItem>
                            <ListBoxItem>Lauren</ListBoxItem>
                        </ListBox>
                    </controls:Layer.Content>
                </controls:Layer>
                <controls:Layer Level="1" Orientation="Row" Name="Text Manager">
                    <controls:Layer.Content>
                        <ListBox  
                            MinHeight="60"
                                >
                            <ListBoxItem Content="{Binding Title}"></ListBoxItem>
                        </ListBox>
                    </controls:Layer.Content>
                </controls:Layer>
                <controls:Layer Level="0" >
                    <controls:Layer.Content>
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto"/>
                                <RowDefinition/>
                                <RowDefinition/>
                                <RowDefinition/>
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto"/>
                                <ColumnDefinition Width="Auto"/>
                                <ColumnDefinition/>
                            </Grid.ColumnDefinitions>
                            <Grid 
                                Grid.Column="0"
                                Grid.Row="0" 
                                Grid.RowSpan="4"
                                Background="White"></Grid>
                            <GroupBox   
                                Grid.Row="1" 
                                Grid.Column="0"
                                Background="White"
                                VerticalAlignment="Stretch"
                                VerticalContentAlignment="Stretch"
                                Header="Recent Projects"></GroupBox>
                            <GroupBox
                                Grid.Row="2" 
                                Grid.Column="0"
                                Background="White"
                                Header ="Getting Started"
                                ></GroupBox>
                            <GroupBox
                                Grid.Row="3" 
                                Grid.Column="0"
                                Background="White"
                                Header="Headlines"></GroupBox>
                            <GridSplitter 
                                Width="2" 
                                Grid.Row="0"
                                Grid.Column="1" 
                                Grid.RowSpan="4"
                                Background="Transparent"
                                HorizontalAlignment="Left" />
                            <ListBox
                                Grid.Column="2"
                                Grid.Row="0"
                                Grid.RowSpan="4"
                                HorizontalAlignment="Stretch"
                                VerticalAlignment="Stretch"
                                BorderBrush="CadetBlue">
                                <ListBoxItem>Article #1</ListBoxItem>
                                <ListBoxItem>Article #2</ListBoxItem>
                                <ListBoxItem>Article #3</ListBoxItem>
                                <ListBoxItem>Article #4</ListBoxItem>
                            </ListBox>
                        </Grid>
                    </controls:Layer.Content>

                </controls:Layer>
            </controls:LayeredGrid.Layers>
        </controls:LayeredGrid>
    </DockPanel>
</Window>

Points of Interest

There are a few things that I did not implement in the custom control, but would make sense to probably implement if this code is to be used other than as an example. I will list out a few of the changes that should be made in case someone finds the code useful.

  1. The Level as implemented expects the user to take care of placing the correct numbers to Level (i.e., there can't be gaps, the numbering is expected to be sequential). This would need to be changed.
  2. I have not tested how the custom control will act when Layers are added and removed on the go via code, however it is easy to see that a collection change event handler in the custom control will be needed to handle such cases.
  3. The style used in the custom control is defined within the custom control, this defeats the aim of having a custom control as users are unable to template the style of the control.

The above three points are probably the most necessary changes I believe would have to be done to make the control adequate for general use.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionExcellent Article Pin
Member 1393678529-Apr-22 3:49
Member 1393678529-Apr-22 3:49 
QuestionI need to change width of background width on resize the dock panel Pin
Member 1316359024-Mar-21 0:11
Member 1316359024-Mar-21 0:11 
QuestionNice article! How can i pinned by default? Pin
sheraziii1-Jul-16 14:36
sheraziii1-Jul-16 14:36 
AnswerRe: Nice article! How can i pinned by default? Pin
sheraziii9-Aug-16 16:30
sheraziii9-Aug-16 16:30 
GeneralRe: Nice article! How can i pinned by default? Pin
sheraziii9-Aug-16 16:37
sheraziii9-Aug-16 16:37 
QuestionThank you for this! Single Column example and code perhaps? Pin
Ibizanhound8-Apr-15 11:04
Ibizanhound8-Apr-15 11:04 
QuestionRegarding Dock window Pin
Member 996405227-Feb-15 6:26
Member 996405227-Feb-15 6:26 
QuestionHow to display Panes same as visual studio Pin
Ravi K G11-Dec-14 18:42
Ravi K G11-Dec-14 18:42 
Questionwhy can not i run the code Pin
Oweulysus4-Mar-14 15:37
Oweulysus4-Mar-14 15:37 
GeneralMy vote of 5 Pin
leiyangge23-Jun-13 22:53
leiyangge23-Jun-13 22:53 
GeneralMy vote of 5 Pin
Prafulla Hunde28-Feb-13 20:51
Prafulla Hunde28-Feb-13 20:51 
QuestionProblems loading source Pin
PaulMorris29-Oct-12 5:29
PaulMorris29-Oct-12 5:29 
AnswerRe: Problems loading source Pin
Ty Anibaba6-Nov-12 4:24
Ty Anibaba6-Nov-12 4:24 
GeneralRe: Problems loading source Pin
PaulMorris6-Nov-12 6:39
PaulMorris6-Nov-12 6:39 
GeneralMy vote of 5 Pin
Sanjay K. Gupta30-Aug-12 18:37
professionalSanjay K. Gupta30-Aug-12 18:37 
GeneralRe: My vote of 5 Pin
Ty Anibaba4-Sep-12 2:58
Ty Anibaba4-Sep-12 2:58 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.