Creating a Dockable Panel ControlManager Using C#, Part 3

In Part 1, you learned how to create the DockableForm. In Part 2, you learned to move and size the DockableForm and you designed a docking manager that controls and coordinates all docking cycles. You also learned about the general design pattern needed to achieve your goal once you have finished this article series. If you haven't read the first two parts of this series, you can find them at the following links: Dockable Panel ControlManager Using C#, Part 1 and Dockable Panel ControlManager Using C#, Part 2.

In this part of the series, you will do the first preparations you need to complete the docking process, but this still doesn't cover the docking process itself. Just a word of caution : There are a lot of mathematical calculations in this part, and because some of the calculations might be difficult to understand, I will try to make it easier for you by explaining them via the use of drawings and pictures. Now, it's time to start.

Actions Needed Before Docking

When moving a DockableForm over an MDI Form, buttons appear that show where and how to dock. The position of these buttons depends on where you can place the dockable form. Figure 1 shows this process. Here, you are docking your first dockable form, and because of that, your "indicator" buttons show you where you can drag & place your dockable form (left, right, top, or bottom).

Figure 1: DockingButtons for placing the first DockableForm.

If you have already docked some DockableForms in your MDI, this might look differently, as in Figure 2, where you can see that your docking buttons appear at a totally different place.

Figure 2: DockingButtons after some DockableForms are placed on the screen.

The positions of these buttons must also change whenever the MDI Form is resized, so what you obviously need to do is to do some calculations where the buttons should be placed. The problem of a possible change of the MDI and the DockableForms size isn't really serious because you simply need to calculate the actual size(s) and actual position(s) of each Form. These buttons are the buttons needed for the very basic docking methods, which are the normal standard methods such as left, right, top, and bottom. But, you also have more advanced docking possibilities. You want to be able to dock two or more DockableForms to the left, right, top, or bottom as well; or to have them in a tabbed view and all the tabbed Forms docked in one of your basic methods. Figure 3 shows one of these versions of docking.

Figure 3: The DockingButtons when we are using one of the advanced docking methods.

The first thing you may notice in Figure 3 is that you obviously have different DockingButtons pictures and they also differ in their size. You have four pictures for the basic docking methods and five for the advanced docking methods. Three of them you can see in Figure 3, but there are two more if you are using advanced docking on the left or right.

Creating a Dockable Panel ControlManager Using C#, Part 3

You don't need to draw these icons yourself because they are included in the accompanying zip file, but if you want to draw them yourself, here you can see them all. Each button has its pendant, so you have to provide for all states of all buttons. This is an easy method to get them blinking when the docking panel is moved over these buttons. You simply use a timer and change the corresponding picture every tick as long as the mouse is dragging a DockableForm over one of this buttons. This way, you show that this button is just the current one. You may think now that these buttons have to do some specific work to get the docking done. No, sorry; they haven't. In reality, they are nothing more than orientation points on the screen; they need the mouse location to select a specific docking procedure. Here is what you do with these buttons: When the mouse button is released, and your mouse position is within the space of one of these pictures during the MouseUp Event, you choose a specific docking method depending on over which of these Buttons the left MouseButton was released. So, the buttons are nothing other than orientation points on the screen needed to inform your user that he/she is really just over one of these buttons and he/she would "activate" a docking procedure when the mouse button is released.

[AllDockingButtons.JPG]

Figure 4: All the needed button pictures to control the docking position.

Another thing that needs to be done before docking is showing the where the DockableForm would be placed when you drop the DockableForm over a specific selected button. This is done via the use of a transparent form, as shown in Figure 5:

[TransparentForm.JPG]

Figure 5: The transparent green form shows where you would dock.

[FlowChartDraggingForm.JPG]

Figure 6: Logical Flow Chart.

So, this is how this is basically done. There is only one explanation missing. What in the world is the Docking Controller?

Creating a Dockable Panel ControlManager Using C#, Part 3

The DockingController

I wanted to use existing controls as much as possible to do the work. Docking to the left, right, top, and bottom is pretty easy, so why not do simply that? As you already know, if you are, for example, docking two panels to the left, they are not placed in the top left and bottom left because the first one is placed on the left and the next one will be placed to the left of the first control. And now, you again are using a dirty trick. You are not docking the DockableForm, and you also are not docking the DockablePanel (which you will see on the screen instead of the DockableForm the whole time). Instead, you are using another UserControl named DockingControler. This control is docked in any one of the simple docking methods: left, right, top, or bottom—or wherever you want.

[DockingControler1.jpg]

Figure 7: The DockingControlers are doing the basic docking process.

The DockablePanel, however, is added to the DockingControlers controls Collection and depends on how many DockablePanels are already situated on a specific DockingControler. It will be placed into a SplitContainer control at that location, or added to a TabPage of a TabControler (both dynamically created depending on the actual needs). So, the DockingControler is the control that does the simple docking on the one hand, and administrates the amount of DockablePanels added to it, and all the work needed for the screen presentation of the panels it contains. The needed information to show these panels on the expected position is sent to it by the events fired by the DockablePanel as a result of where the left Mousebutton was released.

So, your current scenario looks like Figure 8:

[DockingControler2.jpg]

Figure 8: The Dockable panels are attached to the SplitContainer.

If you want to add more than two dockable panels to one DockingControler, you simply need to use a TabControl instead of the SplitContainer.

The exact way it works is the following:

When you want to dock a DockablePanel to a specific place using Basic Docking, you still would not dock the DockablePanel itself this way; you would use a DockingControler and place the DockablePanel into a SplitContainer. Because you do Basic Docking, you are just docking to a new dock position on the MDI form. There is no DockControler at this stage, so you need to create one, as shown in Figure 7. On this DockingControler, you create a SplitContainer, detach the DockablePanel from its carrier (the DockableForm), and add it to the first of its two possible frames (refer to Figure 8), and you collapse the other frame so the DockablePanel fills the full size of Frame 1. To get the DockablePanel the exact same size of the frame you are docking, you need to use System.Windows.Forms.DockStyle.Fill.

Note: The DockingControler is the same whether you are docking or using Basic Docking, to the left or to the top. The only difference is when you are docking to the left; then, the SplitContainers' Frames are separated by a horizontal divider. If you are docking to the top, the frames are on the left and on the right separated by a vertical divider. So, Frame1 is the top one when left or right Base Docking occurs, and the left one when Base Docking on top.

Now, do some advanced docking.

Creating a Dockable Panel ControlManager Using C#, Part 3

If we want to dock a second Panel into the same DockingControler, you are allowed to use Frame1 or Frame2 for this, depending on where you want to see it on the screen. If you use Frame1 again and your DockingControler is Base Docking on the left (like P0 in Figure 7), you will place the second DockablePanel on the upper position. If you are using Frame2, you get this control in the lower position and Frame2 is no longer collapsed.

Needless to say, if you want to place it in the upper position, you first have to change the position of the already existing DockablePanel from Frame1 to Frame2 before you can add the new panel to Frame1. The same design is used if you add a control to a DockingControler that is Basic Docked to the top, but this way you place your DockablePanel to the left or right Advanced Docking position.

I have explained left, right, upper, and lower of your Advanced Docking now, so there is only one Advanced Docking Style missing: CenterDocking.

In Part 1 of this article series, I explained that this docking controller was done for my personal needs, so it's a bit simplified compared with the one used in MS Visual Studio because I don't need all the features you have there. This means that in one DockingControler you may have only left, right, upper, or lower Advanced Docking or instead of them being CenterDocked.

CenterDocking removes all the Controls that already may have been placed in the SplitContainer and adds them to a TabControl instead.

On the other hand, if you undock panels from the TabControl, and there is only one panel on the left, you remove the TabControler and add the remaining Control to a SplitContainer again. This is done to have a clear design pattern all the time:

One DockablePanel is always placed in Frame1 of a SplitContainer, two are placed into a SplitContainer or a TabControler, and more than two panels always are placed into a TabControler. This is basically and roughly explained; it's what you are doing to get advanced docking done. Now, start to code it. The above explanations lead you directly to express the conditions of the DockablePanel and the DockingControler by the following enumerations.

//We will have to add later in this context

internal enum Hover { LeftPanel, RightPanel, TopPanel, BottomPanel,
                      FreeSpace };

// in the DockingManager.cs we already have
public enum DockType { None = 0, Top = 1, Bottom = 2, Left = 3,
                       Right = 4, Fill = 5, Upper = 6, Lower = 7,
                       LeftSide = 8, RightSide = 9, Center = 10, };

Now, create the DockingControler class that is derived from an UserControl and place the file in the namespace DockingControls. In this new Document, you will have an enumeration describing the DockingControler. After creating the class, add a field to store the ControlerType and define some of the regions you will have in that class.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using DockingControls.WinAPI;

namespace DockingControls{
   internal enum ControlerType {
      Vertical, Horizontal, Tabbed, New 
   };

internal partial class DockingControler: UserControl {
   #region Fields and Events
      private ControlerType _controlsType;

   #endregion
   #region ctor
      internal DockingControler() {
         InitializeComponent();
      }
   #endregion
      // The regions Properties, Methods, Delegates ...
      // follows here as needed
   }
}

As explained before, the DockingControler's actions are different depending on where it is docked and whether it is used for CenterDocking. The current usage of a specific DockingControler is expressed by the ControlerType enumeration. So, you need to add the following property to it.

#region Properties
internal ControlerType ControlsType {
   get {
      return _controlsType;
   }
}

#endregion

Before you code the whole class, you first need to create the Buttons class. So, add a Directory named Buttons and therein create a Form named DockButton in the DockButton.cs File.

Set the following properties for this button:

Name DockButton
AutoScaleMode None
AutoSizeMode GrowAndShrink
BackColor Fuchsia
FormBorderStyle None
MaximumSize 59;83
ShowInTaskbar False
ShowIcon False
Size 48;48
SizeGripStyle Hide
StartPosition Manual
TransparencyKey Fuchsia

Table 1: The DockButton Forms Properties

Add a PictureBox and a Timer to the form and adjust their properties like the following:

Name Surface
BackColor Fuchsia
Image (None)
Location 0;0
Size 59;83
Anchor Top,Left

Table 2: The PictureBox Properties

Name ImageTimer
Enabled False
Interval 300

Table 3: The Timer Properties

Control Name Event Delegate
DockButton Load DockButton_Load
ImageTimer Tick ImageTimer_Tick

Table 4: The Delegates to be added to this DockButton

Creating a Dockable Panel ControlManager Using C#, Part 3

Now, you need to add all the pictures shown in Figure 4 to the Resources of your DockingControls.

Double-click to the DockingControls Properties Folder and then to Pictures.resx. This will open the pictures resources and there use Add Resources-> Add existing Item. After you have inserted all the Images, you can go on coding the DockButton.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace DockingControls.Buttons {
   internal partialclass DockButton : Form {
   #region Fields and Events
     private bool _hover; 
     // is the mouse hoovering 
     //starting and stopping blinking 
     private bool _enableBlink;
     // the typeof this button 
     private DockType _typ;
     private Image _stdPic; // Standard Picture
     private Image _hooverPic; // Hoover Picture
     private Size _size;
   #endregion 
   #region ctor 
     internal DockButton(){ 
         InitializeComponent();
     }
   #endregion
   }
}

As you can see, you are using forms for these buttons. This way, they aren't in the Z-order of your panels and all the other controls, so you don't need to give permanent additional attention to that. In the ctor region, you add an additional constructor that is needed to create the exact type of button you want.

internal DockButton(DockType typ) {
   InitializeComponent();
   _typ = typ;
   switch (_typ) {
      case DockType.Left:
         _size = new Size(48,48);
         _stdPic = global::
            DockingControls.Properties.Pictures.DockingLeft;
         _hooverPic = global::
            DockingControls.Properties.Pictures.DockingLeftHoover;
         break;
      case DockType.Right:
         _size = new Size(48,48);
         _stdPic = global::
            DockingControls.Properties.Pictures.DockingRight;
         _hooverPic = global::
            DockingControls.Properties.Pictures.DockingRightHoover;
         break;
      case DockType.Top :
         _size = new Size(48,48);
         _stdPic = global::
            DockingControls.Properties.Pictures.DockingTop;
         _hooverPic = global::
            DockingControls.Properties.Pictures.DockingTopHoover;
         break;
      case DockType.Bottom :
         _size = new Size(48,48);
         _stdPic = global::
            DockingControls.Properties.Pictures.DockingBottom;
         _hooverPic = global::
            DockingControls.Properties.Pictures.DockingBottomHoover;
         break;
      case DockType.LeftSide:
         _size = new Size(59,45);
         _stdPic = global::
            DockingControls.Properties.Pictures.DockLeftSide;
         _hooverPic = global::
            DockingControls.Properties.Pictures.DockLeftSideHover;
         break;
      case DockType.RightSide:
         _size = new Size(59,45);
         _stdPic = global::
            DockingControls.Properties.Pictures.DockRightSide;
         _hooverPic = global::
            DockingControls.Properties.Pictures.DockRightSideHover;
         break;
      case DockType.Upper:
         _size = new Size(53,83);
         _stdPic = global::
            DockingControls.Properties.Pictures.UpperDock;
         _hooverPic = global::
            DockingControls.Properties.Pictures.UpperDockHover;
         break;
      case DockType.Lower:
         _size = new Size(53,83);
         _stdPic = global::
            DockingControls.Properties.Pictures.LowerDock;
         _hooverPic = global::
            DockingControls.Properties.Pictures.LowerDockHover;
         break;
      case DockType.Center:
         _size = new Size(59,83);
         _stdPic = global::
            DockingControls.Properties.Pictures.TabbedForms;
         _hooverPic = global::
            DockingControls.Properties.Pictures.TabbedFormsHover;
         break;
      default:
         break;
   }
   this.surface.Size = _size;
   this.ClientSize   = _size;
}

This way, you easily can create any one of the buttons, simply by using the needed type in its constructor. The buttons' blinking is done through the use of ImageTimer. This timer is started and stopped by calling the following methods.

#region
Methods

public void Mouse_Hovers() {
   ImageTimer.Enabled = true;
}


public void Mouse_HasLeft() {
   ImageTimer.Enabled = false;
   _hover = false;
      this.surface.Image = _stdPic;
}
#endregion

Creating a Dockable Panel ControlManager Using C#, Part 3

As you know, the Mouse Events are captured by DockablePanel because you are still dragging it around. When you hit a button's range, its timer needs to be enabled and disabled when you leave it. By using these methods, you only need to call these methods to start and stop buttons blinking.

As you see, you can do it in that simple way by using the following code in the ImageTimer Tick events delegate.

#region Delegates
private void ImageTimer_Tick(object
sender, EventArgs e){
   if (!_hover) {
      this.surface.Image = _hooverPic;
      _hover = true;
   }else{
      this.surface.Image = _stdPic;
      _hover = false;
   }
}
#endregion

But, by doing it this way, you will run into a problem of flickering pictures. Look at Figure 9.

[Timediagramm2.JPG]

Figure 9: Time diagram switching the Images on the button.

So you see, it's a bit trickier as you may have thought before.

Basically, as long as the timer ticks, you switch the pictures between the hovered design and the normal design. If you only change the ImageTimer's Enabled Property, it would be possible that you just are leaving the button's range when the hovered picture is shown and as the timer is stopped, it wouldn't change back. On the other hand, if you would simply stop the timer and reset the button's picture in the Mouse_HasLeft() method, as you see in above code, the button is changed instantly. This may lead to a shortened cycle of switching the picture on and off which may look like a short flicker (refer to Figure 9).

Instead, you can use a better way to get it done.

#region Methods

public void
Mouse_Hovers() {
   _enableBlink = true;
   ImageTimer.Enabled = _enableBlink;
}


public void
Mouse_HasLeft() {
   // nothing else then this
   _enableBlink = false;
}
#endregion
#region Delegates
private void ImageTimer_Tick(object sender, EventArgs e){
   if (!_hover) {
      this.surface.Image = _hooverPic;
      _hover = true;
   }else {
      this.surface.Image = _stdPic;
      _hover = false;
      if (_enableBlink == false){
         ImageTimer.Enabled = false;
      }
   }
}

//And in the DockButton_Load delegate we every time make sure
//the Standard Image is loaded

private void DockButton_Load(object sender, EventArgs e){
   this.surface.Image = _stdPic;
}
#endregion

This way, you only set the _enableBlink Field to false when you want to stop the timer. The timer goes on with its cycle and every time when it sets the Image back to standard, you check whethe _enableBlink was set to false in between. If so, it stops. So, you will see there is no longer any blinking cycle shortened because your time diagram now looks like Figure 10:

[Timediagramm1.JPG]

Figure 10: Time diagram of switching between images getting the DockButton blinking.

And that's the full code you need for the DockButtons.

Creating a Dockable Panel ControlManager Using C#, Part 3

Administrative Cycles

What follows will be quite complex interwoven code, so first do all the administrative cycles (just some upfront code needed to continue). After this, it will be much easier to follow the code.

During the whole docking process, you will need to create different types of events and their associate delegates. You also will need quite a few enumerations. So, you should create a separate file containing all the enumerations and the EventArgument Classes that you will need.

To do this, select the DockingControls project and add a file named DockingEnums.cs to it. Then, select the 'Classes' Folder and add a Document EventArgsClasses.cs to it. In DockingEnums.cs, you erase the automatically created class and add the following:

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;

namespace DockingControls {
   // leftSide means the left side of two panels both docked on
   // top or both docked on bottom
   // rightSide means the same but the panel is docked on top but
   // on right side
   // Upper means the upper place of two panels docked both to the
   // left or both to the right
   // Center means that panels are tabbed together
   internal enum buttonType {

      Left,
      Right,
      Top,
      Bottom,
      Center,
      Upper,
      Lower,
      LeftSide,
      RightSide,
      none
   };
   // 'Hits' means Mouse is entering the range of a Button
   internal enum HitState {
      hoverLeft,
      hoverRight,
      hoverTop,
      hoverBottom,
      hoverLeftSide,
      hoverRightSide,
      hoverCenter,
      hoverUpper,
      hoverLower,
      none
   };
   //public enum DraggingState { Docked, BeforeCapture, Moving,
   //                            Dropped };
   internal enum DraggingState {
      BeforeCapture,
      Captured,
      Moving,
      Dropped
   };
   internal enum Hover {
      LeftPanel,
      RightPanel,
      TopPanel,
      BottomPanel,
      FreeSpace
   };
}

To fire events transferring the adequate data, you need to create different EventArgument Classes. As mentioned earlier, you will use the EventArgsClasses.cs to create them. Open EventArgsClasses and add the following missing namespaces, after which it must look similar to the following.

using System;

using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;

namespace DockingControls.Classes {
   // add ControlDockedEventArgs class here
}

Now, you need to add ControlDockedEventArgs to this namspace. (The usage of different members of this class will be seen later.)

public class ControlDockedEventArgs : EventArgs {
   private DraggingState _state;
   private HitState _hit;
   private string _key;
   string _hoverBaseKey;

   internal DraggingState DockControlState {
      get {
         return _state;
      }
      set {
         _state = value;
      }
   }

   internal HitState Hitted {
      get {
         return _hit;
      }
      set {
         _hit = value;
      }
   }
   internal string HoverBaseKey {
      get {
         return _hoverBaseKey;
      }
      set {
         _hoverBaseKey = value;
      }
   }
   internal string DockingFormKey {
      get {
         return _key;
      }
      set {
         _key = value;
      }
   }
}

public class ShowControlEventArgs : ControlDockedEventArgs {
   bool _insidePanel;
   Hover _isOver;
   Point _mousePosition;
   internal Hover IsOver {
      get {
         return _isOver;
      }
      set {
         _isOver = value;
      }
   }

   public Point MousePosition {
     get {
         return _mousePosition;
      }
      set {
         _mousePosition = value;
      }
   }

    public bool InsidePanel {
      get {
         return _insidePanel;
      }
      set {
         _insidePanel = value;
      }
   }
}

As you can see, these simply are classes needed to transport the data. So, there isn't really anything difficult to understand about them. I have created them by typing the fields and then using the wizard to add the properties.

Because you will need EventArgsClasses in the DockablePanels class, add a DockingControls.Classes namespace to it.

Edit the beginning (top) of DockablePanel.cs to look like the following:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using DockingBasicControls;
using DockingControls.WinAPI;
using DockingControls.Classes;

namespace DockingControls.Forms {

//...

Lastly, add some Properties to DockAdministration.

// to the namespace we add

using System.Windows.Forms;

namespace DockingControls.Classes{

#region Fields
   private DockingManager _dockManager;    // should already exist
   private Rectangle _clientRectangle;
   private Point _clientScreenPos;
   // We need list to be able to go through all Controlers reading
   // their data for calculation ( size, type, position ...)

   private SortedList<string, DockingControler> DockControlers =
      new SortedList<string, DockingControler>();

// . . .
#endregion
#region Properties
internal Rectangle ClientRectangle {
   get {
      return _clientRectangle;
   }
   set {
      _clientRectangle = value;
   }
}

internal Point ClientScreenPosition {
   get {
      return _clientScreenPos;
   }
   set {
      _clientScreenPos = value;
   }
}
//...
#endregion

Creating a Dockable Panel ControlManager Using C#, Part 3

Starting the Docking Process

You will now add all the necessary code into all the appropriate classes, step by step. In preparing the docking process, the first effect you need to create is that the buttons will need to be visible on the screen just after the left Mousebutton is pressed on the titlebar of your DockablePanel. To do this, you first will need to revise the methods that are called by the DockablePanels Mouse Event Delegates. So, begin here and add the necessary code inside DockablePanels. Until now, you have had the following code there.

private void LeftMouseButtonPressed(MouseEventArgs e) {
   // store the actual mouseposition
   _mousePos.x = e.X;
   _mousePos.y = e.Y;
   // change it to global-Screen Coordinates so we have the
   // real position of the mouse instead of its position
   // in the header
   APICall.ClientToScreen(HeaderBox.Handle, ref _mousePos);
   // the internal notification that the mouse is just down
   _isMouseDown = true;
   // the global position of the DockablePanels Left-Top
   // corner is the global Position of the mouse reduced
   //by the position in the header
   _lastX = _mousePos.x - e.X;
   _lastY = _mousePos.y - e.Y;
}

When the mouse button is pressed on the Titlebar of the DockablePanel, you need to store the draggingState object of DockingPanel as Captured. Then, you are in a logical state of 'Captured'. Also, you need to store some data that you will need for calculations into the DockAdministration class (like the actual size and TopLeft of the MDI Windows Client Rectangle). This information is needed to calculate where the buttons will have to occur on the screen. Here is the changed code.

private void LeftMouseButtonPressed(MouseEventArgs e) {
   _mousePos.x = e.X;
   _mousePos.y = e.Y;
   this.BringToFront();
   APICall.ClientToScreen(HeaderBox.Handle, ref _mousePos);
   // now we need the position and size
   // of the MDI Windows Client Rectangle. Note the MDI
   // Window is the DockingManager Parent.
   _clientRectangle = _dockingManager.Parent.ClientRectangle;
   _isMouseDown = true;
   WinAPI.POINT TopLeft;
   TopLeft.x = _dockingManager.Parent.ClientRectangle.X;
   TopLeft.y = _dockingManager.Parent.ClientRectangle.Y;
   // Point is transformed to screenValues
   APICall.ClientToScreen(_dockingManager.Parent.Handle,
                          ref TopLeft);
   // Store the size and location in the rectangle
   // needed for calculation
   _admin.ClientScreenPosition = new Point(TopLeft.x, TopLeft.y);
   // we store the Client Rectangle into the Adminclass for further
   // calculations
   _admin.ClientRectangle = _clientRectangle;
   _lastX = _mousePos.x - e.X;
   _lastY = _mousePos.y - e.Y;
   // we note that the panels state is captured
   _draggingState = DraggingState.Captured;
}

This needs the following private Fields to be added to the DockablePanels.

#region Fields
// . . .
private DraggingState _draggingState;
private Rectangle _clientRectangle = new Rectangle();
private DockAdministration _admin;
/. . .
#endregion

In the LeftMouseButtonPressed(), you obviously need to have access to the DockAdministration class, which is represented in the DockablePanel by the object named _admin. The original object is part of the DockingManager class, so you need to address it. The best time to do that is just after setting the DockingManager Property, so simply add an Admin Property to the DockablePanels.

[Browsable(false)]
internal DockAdministration Admin {
   set {
      _admin = value;
   }
}

You also need to add the Admin property to the DockingManager class to exchange the correct data.

#region Properties

[Browsable(false)]
internal DockAdministration Admin{
   get {
      return _admin;
   }
}

And now, you just set these properties directly after the DockablePanels DockingManager property. This was done in the CreateNew() method in the DockableForm class.

public void CreateNew(){
//. . . some code
_dockPanel.DockingManager = this._dockManager;
// here we add this new line to exchange the data.
_dockPanel.Admin = this._dockManager.Admin;
// this is followed by the following
_dockManager.AddPanel(_dockPanel);
//. . .
}

Now, LeftMouseButtonPressed() is working and will add the needed data to the Admin class. As you know, just a few moments later, the next MouseMove Event will be fired and HeaderBox_MouseMove will call your MouseMoves() method that now has added an event to be fired only once: Exactly the first time when MouseMove was called after the leftMouseButton was pressed.

The way it runs, is demonstrated with the what you see in Figure 11:

[TimeTrack3.JPG]

Figure 11: MouseMove Event sequence.

private void MouseMoves(MouseEventArgs e){
   WinAPI.POINT mousePos;
   // read the actual mouseposition that will change during moving
   mousePos.x = (short)e.Location.X;
   mousePos.y = (short)e.Location.Y;
   // we calculate the global Position again
   APICall.ClientToScreen(HeaderBox.Handle, ref mousePos);
   Moving(mousePos);
   if (_draggingState == DraggingState.Captured) {
      ShowControlEventArgs ds = new ShowControlEventArgs();
      // in the moment we are firing the event
      // using an empty class
      OnShowDockControls(ds);
      _draggingState = DraggingState.Moving;
   }
}

Creating a Dockable Panel ControlManager Using C#, Part 3

Communication Between Classes

You already used this earlier, but I haven't really talked about this theme, so let me do it now because one of the main problems inexperienced programmers may encounter is how to communicate among classes, controls, or Forms. I don't want to show all the possibilities you have becausr this may fill a separate article, but I will talk about what you are using here and why.

The easiest method to exchange data between classes is simply to use their properties like you do all the time:

this._dockPanel.Admin = this._dockManager.Admin;

Here, the DockablePanel _dockPanel gets the reference address to the DockingManagers DockAdministration class Admin. This is done in the DockableForm class, as you know (see CreateNew() there). You use the get the Admin property of the DockingManager to get the needed data and give it to the DockablePanel using the set Property of this class. Simple and easy, isn't it? Wherever this transaction is done, both classes need to know at the time this code is done; that's all you have to know about. In the above code, both classes seem to be private members of this class at first glance, but if one of these classes were null, an exception would be thrown. So, you may use this method only when you are totally sure that the addresses of both classes are known at the time the data is exchanged. I'm normal use this method in cases like this example where you have only one DockingManager in the program and every DockableForm also has only one and the same DockablePanel, or in other situations that are based on that concept where specific Classes can be addressed easily.

[ExchangeingDataByProperties.jpg]

Figure 12: Data exchange using Properties only.

Another way you have used already and will use extensively now in this and the following sections is the usage of delegates. Delegates are references to methods. It looks like a method, but it is only a reference to it.

A delegate has a big advantage. If you call a Delegate, you call the method that is referenced by it. But, this doesn't need to be the same all the time. You may change the method used dynamically as long as the called method follows the pattern of the delegate. So, say the delegate defines the calling convention of a method like its parameter. Delegates can be used by events. You also can add more than one delegate to the same event; you only need to add or delete them. This leads you directly to the point you need. You can, wherever needed, send information to different classes at the same time. Delegates lead you into a wide, dynamic range of programming.

So, in your case you may have lots of instances of the DockablePanel but only one DockingManager and you additionally have some DockingControlers. The communication between them can only be done using delegates. For example, think of the above scene. Maybe you have three, four, or more DockablePanels on the screen and you want to move one of them. It really would get complex if every DockablePanel has its own method to do this. If somebody wanted to calculate all this directly in his DockablePanel, he would have to inform every DockablePanel about any movement and changes of all other DockablePanels, so it would have the data to calculate. This would mean connecting all the panels together and would lead at least to a complex jam of code that never would work. The only solution in all cases where classes are added or deleted dynamically, instances created of the same class and all that is the usage of Delegates and events. So, the only solution in this case scenario is that you throw an event to the DockingManager; this control does the job based on the data it gets from the DockablePanel and it already has from all other controls, because the DockingManager carries the DockAdministration object _admin that collects all the information and does the needed calculations for every single DockablePanel whenever needed. And this is way you do it:

[ExhangingDataByDelegate1.jpg]

Figure 13: Data exchange by use of delegates in step 1.

Defining a delegate and using its signature for declaring an event in the DockablePanel class:

[ExhangingDataByDelegate2.JPG]

Figure 14: Adding the DockablePanel to the DockingManager.

Creating a Dockable Panel ControlManager Using C#, Part 3

When adding a DockablePanel to the DockingManager, you add an object containing the method's address to the DockablePanels instance. The relevant code for this is. (Please don't code this because you will add the full code we need a bit later. This is only for explanatatory purpose.)

internal void AddPanel(DockablePanel dockPanel) {
AllDockPanels.Add(key, dockPanel);
dockPanel.ShowDockControls +=
   new ShowDockControlsEventDelegate(dockPanel_ShowDockControls);
}

[ExhangingDataByDelegate3.JPG]

Figure 15: One of the DockablePanels (In this case, P1) throws a ShowDockControls event.

So this is how it will work and now lets code it.

On Top of your DockablePanel.cs, you now can add the delegates definition and you also can define the DockControlActivities Event. The top of DockablePanel.cs should now look like the following:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using DockingBasicControls;
using DockingControls.WinAPI;
using DockingControls.Classes;

namespace DockingControls.Forms {
   #region Enumerations & Delegatedeclarations
      public enum BorderStyle { Fixed2D, Fixed3D,
                                Sizeable2D, Sizeable3D };
      public enum BorderRange { None, Left, Right, Top, Bottom,
                                LeftTopCorner, LeftBottCorner,
                                RightBottCorner, RightTopCorner };
      internal delegate void
         DockedFormClosingEventDelegate(string key);
      internal delegate void
         ShowDockControlsEventDelegate(ShowControlEventArgs e);
   #endregion

   internal sealed partial class DockablePanel : UserControl
   {
   #region events
   internal event DockedFormClosingEventDelegate
      PanelClosing;
   internal event ShowDockControlsEventDelegate
      ShowDockControls;
   #endregion
// ....

To fire the above event, you need to add the following method to the DockablePanels Methods. This is the standard method to get the event called only if a delegate is attached.

private void OnShowDockControls(ShowControlEventArgs ds) {
   if (this.ShowDockControls != null) {
      this.ShowDockControls(ds);
   }

}

Next, you need to add a delegate for this event in the DockingManager. To do this, you add the delegate at the same time you add the DockablePanel.

internal void AddPanel(DockablePanel dockPanel) {
   string key = "P" + _count.ToString();
   // each DockablePanel gets a new number
   // independing of some are maybe closed again.
   // we start with "P0" p for panel
   _count++;
   dockPanel.Key = key;
   AllDockPanels.Add(key, dockPanel);
   // !!! we add the new delegate just here !!!
   dockPanel.ShowDockControls +=
      new ShowDockControlsEventDelegate(dockPanel_ShowDockControls);
   dockPanel.PanelClosing +=
      new DockedFormClosingEventDelegate(dockPanel_PanelClosing);
   // now we attach the carrier to the DockablePanel;
   AttachCarrier(dockPanel);
}

And in the created delegate, you set:

#region Delegates (DockingManager)
//. . .
private void dockPanel_ShowDockControls(ShowControlEventArgs e) {
   ShowSinglePanelDockingControls(e);
}

#endregion

To calculate the positions of the buttons, you first inform the admin class which Buttons are used; then you calculate the positions of the buttons and the size the transparentForm, and last, you show the buttons. Additionally, you store the size of the InnerClientScreenWindow in the Dockingmanager This looks quite simple at this stage. Add the following:

#region Private Methods (DockingManager)
private void ShowSinglePanelDockingControls(ShowControlEventArgs e) {
   _admin.SetStandardButtonSize();
   _admin.CalculateDockingButtonPositions(e);
   _innerScreenClientRectangle = _admin.InnerClientScreenRectangle;
   _actDockFormSize = _admin.ActDockPanelSize;
   ShowDockingButtons();
}

As you see, all the potential problems are moved to the DockAdministration class where you will calculate all this information. Now, let me explain what the InnerClientScreenWindow is.

First, take a look at setting all the StandardButtons size values in the DockAdministration class, because you need these values for the following calculations. The Standard Buttons are those that you need to use for BaseDocking. In the DockAdministration class, you have to add some fields, so that you are able to store needed values and calculation results.

internal class DockAdministration {
#region Fields
   // BaseDocking Buttons (when mouse is in a free space,
   // not over a docked panel )
   Size buttonSize = new Size(48, 48);
   Size tabButtonSize = new Size(53, 59);    // the Center Button
   // the LeftSide or RightSideButton,
   // when mouse is over a panel
   Size horButtonSize = new Size(59, 45);
   // the Upper or lower Button,
   // when mouse is over a panel
   Size vertButtonSize = new Size(53, 83);
   //. . . the existing Fields followed by
   private Control[] LeftButtonStrips;
   private Control[] TopButtonStrips;
   private Control[] RightButtonStrips;
   private Control[] BottomButtonStrips;
      // A constant for calculation of Buttonposition:
   // Some pixels from Button to border
   private const int buttonOffset = 6;
   private Rectangle[] _buttonRanges;
   private Rectangle _innerClientScreenRectangle =
new Rectangle();

To be able to use these rectangle arrays, you have to initialize them in the Constructor. You must add the initialisation here, expanding the existing code to simply initialise the ButtonRanges rectangle Array with empty rectangles. They are enumerated using the buttonType enum you already have declared in the 'Administrative Cycles' section.

#region Ctor
internal DockAdministration(DockingManager dockManager) {
   _dockManager = dockManager;
   _buttonRanges = new Rectangle[] {
      new Rectangle(), new Rectangle(), new Rectangle(),
      new Rectangle(), new Rectangle(), new Rectangle(),
      new Rectangle(), new Rectangle(), new Rectangle()
   };
}
#endregion

Setting the StandardButtonSize means setting the values for left, right, top, and bottomButton, but you set the values for the DockingButtons you will need for AdvancedDocking to Empty. This way, you are sure that the unneeded Buttons don't matter in the calculations because their Size values are zero.

internal void SetStandardButtonSize() {
   _buttonRanges[(int)buttonType.Left].Size      = buttonSize;
   _buttonRanges[(int)buttonType.Right].Size     = buttonSize;
   _buttonRanges[(int)buttonType.Top].Size       = buttonSize;
   _buttonRanges[(int)buttonType.Bottom].Size    = buttonSize;
   _buttonRanges[(int)buttonType.Upper].Size     = Size.Empty;
   _buttonRanges[(int)buttonType.Lower].Size     = Size.Empty;
   _buttonRanges[(int)buttonType.LeftSide].Size  = Size.Empty;
   _buttonRanges[(int)buttonType.RightSide].Size = Size.Empty;
   _buttonRanges[(int)buttonType.Center].Size    = Size.Empty;
}

Next, you have to add the CalculateDockingButtonPositions() method. To do this Calculation, you first need to calculate the InnerClientScreenRectangale because you need this information to calculate the DockingButtonsPositions(). Now, let me explain what the InnerClientScreenRectangle is.

Creating a Dockable Panel ControlManager Using C#, Part 3

The InnerClientScreenRectangle

This is one of the main terms needed to design this DockablePanel Controler, s;o its really necessary to explain what I have meant by that. The easiest way to understand is simply to show it.

[InnerClientScreenRectangle.jpg]

Figure 16: The InnerClientScreenRectangle defined.

Putting it simply, the Rectangle is where an MDIChild Window would be placed when you add it to the application. You need to show your Buttons in the rectangle when you want to do some Base Docking action in our MDI Window, so its name is InnerScreenRectangle as long as its ocation is related to the MDI itself. It's named InnerClientScreenRectangle when its location is measured relative to the Desktops Upper left Corner. So, you calculate this:

internal void CalculateInnerClientRectangle() {
   int innerWidth = _clientRectangle.Width
   - CalcVertStripsTotalWidth()
   - CalcVertPanelsTotalWidth();
   int innerHeight = _clientRectangle.Height
   - CalcHorBarsTotalHeight()
   - CalcHorPanelsTotalHeight();
   int deltaLeft = CalcLeftStripsTotalWidth()
   + CalcLeftPanelsWidth();
   int deltaTop = CalcTopBarsTotalHeight()
   + CalcTopPanelsHeight();
   _innerClientScreenRectangle.Size =
      new Size(innerWidth, innerHeight);
   _innerClientScreenRectangle.Location =
      new Point(_clientScreenPos.X  + deltaLeft,
                _clientScreenPos.Y + deltaTop);
}

This can be understood if you look at Figure 17:

[InnerClientScreenRectangleSmall.JPG]

Figure 17: How to calculate InnerClientScreenRectangle.

As you easily can see, the size of the InnerClientScreenRecangles height is the height of the MDI's ClientRectangle reduced by the amount of all Visible MenuStrips, Toolstrips, and Statusstrips that are in set in a Horizontal Orientation and reduced by the amount of all DockingControls that are docked in DockStyle.Top or DockStyle.Bottom. To calculate this, you first need to analyse the MDI regarding all existing MenuStrips, ToolsStrips, and StatusStrips, their Size, Orientation, Position on Screen (left, right, top, bottom ) and if they are Hidden or Visible because you only need to count the visible ones. Mathematically, this is expressed in the example as:

InnerClientScreenRectangle.Height =
ClientRectangle.height - h_TS(0) -h_TS(1)-h_TS(2)- h_TS(3)
                       - h_P(1) - h_P(2)

Where h_TS(0) is the height of the MenueStrip, h_TS(1) is the height of the ToolStrip, and so on; you can see it in Figure 17. As you remember, you also have added four Toolstrips that you will use later when pinning and unpinning the panels. So, you also need to have them in your calculation, but in this case you need to check whether or not they are visible. In Figure 17, they are not, so they don't occur in your formula. To get a simple and clear mathematical design pattern, I use the following method. Basically, I have all Docking parts(not the Toolbars) that could influence my calculations reproduced in my DockAdministration class in their mathematical Values (Size, Location). If something is Invisible, for mathematical reasons the size is set to Empty; this mathematically creates values of zero. So, I can shrink down the number of needed 'If' statements. That's very simple because adding or subtracting zero doesn't matter, you see. By following this drawing, you will easily be able to follow all my calculations and the analysis code.

The analyses are needed to find out how many ToolStrips and MenuStrips are used and in which orientation they are placed on the screen. Because this may change during usage of a program by the user who may drag, set, or delete some of them, you need to check this every time before you do the InnerClientScreenRectangle calculation. The calculation of the vertical orientated ToolStrips is done in two steps: You calculate Strips which are Docked to left, and you calculate such which are docked to right. The LeftButtonstrips and RightButtonStrips controls array need to be filled before doing the calculation. But I'll show you that a bit later. First, here is the code for the calculation itself. Calculate the vertical-orientated ToolStrips:

private int CalcVertStripsTotalWidth() {
   return CalcLeftStripsTotalWidth() + CalcRightStripsTotalWidth();
}
private int CalcLeftStripsTotalWidth() {
   int amount= 0;
   amount = LeftButtonStrips.GetUpperBound(amount);
   if ( amount > 0){
      int width = 0;
      for (int i = 0; i < amount; i++){
         if (LeftButtonStrips[i] !=
            null && LeftButtonStrips[i].Visible == true){
               width += LeftButtonStrips[i].Width;
         }
      }
      return width;
   }
   return 0;
}
private int CalcRightStripsTotalWidth() {
   int amount = 0;
   amount = RightButtonStrips.GetUpperBound(amount);
   if (amount > 0){
      int width = 0;
      for (int i = 0; i < amount; i++) {
         if (RightButtonStrips[i] !=
            null && RightButtonStrips[i].Visible == true){
            width += RightButtonStrips[i].Width;
         }
      }
      return width;
   }
   return 0;
}

Creating a Dockable Panel ControlManager Using C#, Part 3

And now, the same for your vertical-orientated DockingControlers (like P0, P2, and P3 in Figure 16).

private int CalcVertPanelsTotalWidth(){
   return CalcLeftPanelsWidth() + CalcRightPanelsWidth();
}
internal int CalcLeftPanelsWidth(){
   int width = 0;
   foreach (KeyValuePair<string, DockingControler> kvp
            in DockControlers){
      DockingControler ctl = kvp.Value;
      if (ctl != null && ctl.Dock == DockStyle.Left && ctl.Visible){
         width += ctl.Width;
      }
   }
   return width;
}
internal int CalcRightPanelsWidth(){
   int width= 0;
   foreach (KeyValuePair<string, DockingControler> kvp
            in DockControlers){
      DockingControler ctl = kvp.Value;
      if (ctl != null && ctl.Dock == DockStyle.Right && ctl.Visible){
         width += ctl.Width;
      }
   }
   return width;
}

And now, you will do the same calculations for horizontal Layout Strips and Panels with horizontal Layout. As you see, dividing code in small logical parts makes understanding it quite easy.

private int CalcHorBarsTotalHeight()
{
return CalcTopBarsTotalHeight() + CalcBottomStripsTotalHeight();
}
private int CalcTopBarsTotalHeight() {
   int amount = 0;
   amount = TopButtonStrips.GetUpperBound(amount);
   if (amount > 0){
      int height = 0;
      for (int i = 0; i < amount; i++){
         if (TopButtonStrips[i] !=
            null && TopButtonStrips[i].Visible == true){
            height += TopButtonStrips[i].Height;
         }
      }
      return height;
   }
   return 0;
}
private int CalcBottomStripsTotalHeight(){
   int amount = 0;
   amount = BottomButtonStrips.GetUpperBound(amount);
   if (amount > 0){
      int height = 0;
      for (int i = 0; i < amount; i++){
         if (BottomButtonStrips[i] !=
            null && BottomButtonStrips[i].Visible == true)
         {
            height += BottomButtonStrips[i].Height;
         }
      }
      return height;
   }
   return 0;
}
private int CalcHorPanelsTotalHeight()
{
   return CalcTopPanelsHeight() + CalcBottomPanelsHeight();
}
internal int CalcTopPanelsHeight(){
   int height= 0;
   foreach (KeyValuePair<string, DockingControler> kvp
            in DockControlers){
      DockingControler ctl = kvp.Value;
      if (ctl != null && ctl.Dock == DockStyle.Top && ctl.Visible){
         height += ctl.Height;
     ; }
   }
   return height;
}
internal int CalcBottomPanelsHeight(){
   int height= 0;
   foreach (KeyValuePair<string, DockingControler> kvp
            in DockControlers){
      DockingControler ctl = kvp.Value;
      if (ctl != null && ctl.Dock == DockStyle.Bottom && ctl.Visible){
         height += ctl.Height;
      }
   }
   return height;
}

Now, you can calculate the InnerClientScreenRectangle and create the CalculateDockingButtonPositions() method. But, it's a bit useless at this time because you don't even have these buttons created, so why calculate its positions? You should do this now. The creation of these buttons is done in the DockingManager. You already have done a method there, where you created the ButtonStrips in Part 1 of these articles, so go back to this and add the needed code there. You had this there until now.

#region Public Methods ( DockingManager)
public void CreateAllElements() {
  // Buttonstrips to hide the panels
   LeftButtonStrip   = CreateButtonStrip(DockStyle.Left,   false);
   RightButtonStrip  = CreateButtonStrip(DockStyle.Right,  false);
   TopButtonStrip    = CreateButtonStrip(DockStyle.Top,    false);
   BottomButtonStrip = CreateButtonStrip(DockStyle.Bottom, false);
}

//. . .
#endregion

Before you can do the needed extensions of your code, on Top of DockingMangager.cs we add the namespace DockingControls.Buttons so your DockingButton class is known. In the fields region, you will have to add some private fields.

//. . .
using System.Windows.Forms;
using DockingControls.Forms;
using DockingControls.Classes;
using DockingControls.Buttons;

namespace DockingControls {
//. . .
public partial class DockingManager : UserControl{
//. . .
private DockButton LeftButton;
private DockButton RightButton ;
private DockButton TopButton;
private DockButton BottomButton;
private DockButton UpperButton;
private DockButton LowerButton;
private DockButton CenterButton;
private DockButton LeftSideButton;
private DockButton RightSideButton;
//. . . and after Dockadministration _admin we add
private Rectangle _innerClientScreenRectangle;
//. . .

Now, you are able to add the needed code to CreateAllElements() so it looks like this:

#region Public Methods ( DockingManager)

public void CreateAllElements(){
   // Buttonstrips to hide the panels
   LeftButtonStrip   = CreateButtonStrip(DockStyle.Left,   false);
   RightButtonStrip  = CreateButtonStrip(DockStyle.Right,  false);
   TopButtonStrip    = CreateButtonStrip(DockStyle.Top,    false);
   BottomButtonStrip = CreateButtonStrip(DockStyle.Bottom, false);
   // The Buttons to get docked
   TopButton       = new DockButton(DockType.Top);
   LeftButton      = new DockButton(DockType.Left);
   RightButton     = new DockButton(DockType.Right);
   BottomButton    = new DockButton(DockType.Bottom);
   UpperButton     = new DockButton(DockType.Upper);
   LowerButton     = new DockButton(DockType.Lower);
   CenterButton    = new DockButton(DockType.Center);
   LeftSideButton  = new DockButton(DockType.LeftSide );
   RightSideButton = new DockButton(DockType.RightSide );
}

//. . .
#endregion

And now, because you have created your buttons, you shouldn't forget to add the the analyses method to the DockAdministration:

#region Methods (DockAdministration)
// ...
internal void AnalyseMDIParentControls(){
   int amount         = _dockManager.Parent.Controls.Count;
   LeftButtonStrips   = new Control[amount];
   RightButtonStrips  = new Control[amount];
   TopButtonStrips    = new Control[amount];
   BottomButtonStrips = new Control[amount];
   if (amount > 0){
      int l, r, t, b;
      l = r = t = b = 0;    // left, right, top, and bottom elements
      for (int i = 0; i < amount; i++){
         Control c = _dockManager.Parent.Controls[i];
         if (c != null){
            Type tp = c.GetType();
            if (tp.Name == "ToolStrip" || tp.Name == "MenuStrip"
               || tp.Name == "StatusStrip"){
            switch (c.Dock){
               case DockStyle.Left:
                  LeftButtonStrips[l]= c;
                  l++;
                  break;
               case DockStyle.Right:
                  RightButtonStrips[r] = c;
                  r++;
                  break;
               case DockStyle.Top:
                  TopButtonStrips[t] = c;
                  t++;
                  break;
               case DockStyle.Bottom:
                  BottomButtonStrips[b] = c;
                  b++;
                  break;
            }
         }
      }
   }
}
//. . .
#endregion

As said, you have to call it before doing all the different calculations. So, the best place is to add it into the dockPanel_ShowControls() delegate just before calling the ShowSinglePanelDockingControls. This is because later in this article you also will have to call the MultiPanelDockingControls, which are called when using an advanced Dockingstyle (Upper Lower, LeftSide, RightSide, Center) and the analyses are needed in any case. So, go back to this delegate that you have created in this article in the DockingManager.

#region Delegates
private void dockPanel_ShowDockControls(ShowControlEventArgs e) {
   _admin.AnalyseMDIParentControls();
   ShowSinglePanelDockingControls(e);
}
//. . .
#endregion

Creating a Dockable Panel ControlManager Using C#, Part 3

Now, you are ready to create CalculateDockingButtonPositions() because you have done all the methods that need to be done when or before you can calculate these positions. Here is the code:

#region Methods (DockAdministration)
// ...
internal void CalculateDockingButtonPositions(
ShowControlEventArgs e) {
   if (e.InsidePanel) {
      // we will do this later
   }
   else {
      //in every other case we have this
      CalculateInnerClientRectangle();
      int topButtsHorLeft = innerClientScreenRectangle.Left
         + (_innerClientScreenRectangle.Width
         - _buttonRanges[(int)buttonType.Top].Width) / 2;
      int topButtsTop = _innerClientScreenRectangle.Top
         + buttonOffset;
      int rightButtsLeft = _innerClientScreenRectangle.Left
         + _innerClientScreenRectangle.Width
         - _buttonRanges[(int)buttonType.Right].Width
         - buttonOffset;
      int rightButtsTop = _innerClientScreenRectangle.Top
         + (_innerClientScreenRectangle.Height
         - _buttonRanges[(int)buttonType.Right].Height) / 2;
      int leftButtsTop = rightButtsTop;
      int leftButtsLeft = _innerClientScreenRectangle.Left
         + buttonOffset;
      int botButtsHorLeft = topButtsHorLeft;
      int bottButtsTop = _innerClientScreenRectangle.Top
         + _innerClientScreenRectangle.Height
         - _buttonRanges[(int)buttonType.Bottom].Height
         - buttonOffset;
      // Lets fill the found values into an aerea,
      // so we can access them
      _buttonRanges[(int)buttonType.Top].Location =
         new Point(topButtsHorLeft, topButtsTop);
      _buttonRanges[(int)buttonType.Left].Location =
         new Point(leftButtsLeft, leftButtsTop);
      _buttonRanges[(int)buttonType.Right].Location =
         new Point(rightButtsLeft, rightButtsTop);
      _buttonRanges[(int)buttonType.Bottom].Location =
         new Point(botButtsHorLeft, bottButtsTop);
   }
}
//. . .
#endregion

To access the calculated values, you add some properties to the Dockadministration class:

#region Properties (DockAdministration)
//. . .
internal Point LeftButtonPosition {
   get {
      return _buttonRanges[(int)buttonType.Left].Location;
   }
}
internal Point TopButtonPosition{
   get{
      return _buttonRanges[(int)buttonType.Top].Location;
   }
}
internal Point RightButtonPosition {
   get {
      return _buttonRanges[(int)buttonType.Right].Location;
   }
}
internal Point BottomButtonPosition {
   get{
      return _buttonRanges[(int)buttonType.Bottom].Location;
   }
}
//. . .
#endregion

Don't forget about all this. You are still going from point to point in your ShowSinglePanelDockingControls() method that you have postulated as the following. (Don't add it twice because you already have this; this is only to remember where you are in your design).

private void ShowSinglePanelDockingControls(
ShowControlEventArgs e) {
   _admin.SetStandardButtonSize();               // already done
   _admin.CalculateDockingButtonPositions(e);    // already done
   // still to do
   _innerClientScreenRectangle =
      _admin.InnerClientScreenRectangle;
   ShowDockingButtons();
}

You add the needed read-only Property to the DockAdministration class and then add a private field to the DockingManager. This way, the variable could be read out from the Dockadministration class and is available in the DockingManager.

#region Properties (DockAdministration)
//. . .
internal Rectangle InnerClientScreenRectangle {
   get {
      return _innerClientScreenRectangle;
   }
}
//. . .
#endregion

Now there is only one method missing: ShowDockingButtons().

#region private Methods (DockingManager )
//. . .
private void ShowDockingButtons() {
   TopButton.Location    = _admin.TopButtonPosition;
   LeftButton.Location   = _admin.LeftButtonPosition;
   RightButton.Location  = _admin.RightButtonPosition;
   BottomButton.Location = _admin.BottomButtonPosition;
   // we turn them visible now
   TopButton.Show();
   LeftButton.Show();
   RightButton.Show();
   BottomButton.Show();
   // Show them every time in Topposition
   StandardButtonsToTop();
}
//. . .
#endregion

Now, you are only one method missing; it's is a simple one. You need to bring the buttons to the top. Because it needs to be called from other DockingControls when undocking the panel, you need to set it as an internal method so you can use it from all your whole DockingControls classes. However, the person who uses these controls and only has the DockingControls DLL will not be able to access it, whereas public methods of the DockingManager or other DockingControls could be accessed by the programmer who uses these controls. You have to be careful in differentiating among public, internal, and private methods and properties.

#region Internal Methods (DockingManager )
internal void StandardButtonsToTop()
{
   LeftButton.TopMost   = true;
   RightButton.TopMost  = true;
   TopButton.TopMost    = true;
   BottomButton.TopMost = true;
}
//. . .
#endregion

[ShowDockingButtons.JPG]

Figure 18: The finished product.

Conclusion

Finally, you may compile it and give it a try. I think this is a good point to finish this article. In the next article, you will get these buttons vanishing again when Left MouseButton is released and blinking, when moving over them, showing the docking preview where you would dock, and which size the docked panel would get, and some more. If you have any questions to any part of this article series regarding design or if something needs some more explanation, feel free to ask or send me an email.



About the Author

Johann Schwarz

I first encountered computers in 1968, since then, I've written several programs in various Languages such as FORTRAN, C, C++, VB 6, and C#. Mostly I am a hobbiest, self taught programmer, but my computer and my programming is my second life

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: October 29, 2014 It's well understood how critical version control is for code. However, its importance to DevOps isn't always recognized. The 2014 DevOps Survey of Practice shows that one of the key predictors of DevOps success is putting all production environment artifacts into version control. In this webcast, Gene Kim discusses these survey findings and shares woeful tales of artifact management gone wrong! Gene also shares examples of how high-performing DevOps …

  • On-demand Event Event Date: December 18, 2014 The Internet of Things (IoT) incorporates physical devices into business processes using predictive analytics. While it relies heavily on existing Internet technologies, it differs by including physical devices, specialized protocols, physical analytics, and a unique partner network. To capture the real business value of IoT, the industry must move beyond customized projects to general patterns and platforms. Check out this webcast and join industry experts as …

Most Popular Programming Stories

More for Developers

RSS Feeds