Creating a Dockable Panel ControlManager Using C#, Part 3

Tuesday Dec 4th 2007 by Johann Schwarz

Share:

Continue your study of how to create your own dockable Forms.

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.

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?

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.

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.

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.

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.

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

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.

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.

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.

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.

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).

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.

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.

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 {
//...

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.

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.

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.

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.

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.)

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:

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.

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.

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.

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.

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:

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:

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:

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.

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.

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.

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).

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.

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.

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.