If you are looking to add 'control docking' features to your winform application, this solution will allow you to practically make any control dockable/floatable in just a few lines of code!

Introduction
So you are looking to add 'control docking' features to your winform application? E.g., you want to have a floating toolbar or allow users to dock parts of your form at a different location at runtime? This tiny and lightweight control extender for C# 2.0 will help you enable these features.
I wrote this solution with the following purposes/constraints in mind:
- Docking/Floating behaviour must be easy to incorporate and use in new and existing winform projects.
- It may not pose too many constraints on the structuring of the form layout.
- The code must be lightweight. That is, have a small footprint and require few resources; i.e., may not be dependent on external, magic libraries.
- Dockable controls must support resizing.
- The code must be easy to use: steep learning curve!
- Use native C# where possible.
In order to support these constraints, I had to cut some features. E.g. this solution will not allow docking of single tab pages in tab controls. Also because of the basic programmatic interface, the client has little control over what happens with the floating/docking controls. So if you are looking for a more full blown (but also bloated and much heavier) solution that supports all types of docking, you might want to consider Weifen Luo's DockManager on source forge or commercial products.
Still interested in a low impact, easy to use, and lightweight approach? This solution will allow you to practically make any control dockable/floatable in just a few lines of code! So how does it work, you ask? Just keep on reading.
Using the Code
It's simple, first setup the project: create a new winform project, add the DockExtender.cs, Floaty.cs and Overlay.cs files to your project. Then, for the sake of the example, add a ToolStrip
control to your form.
Then add the ControlExtenders
namespace to the form and declare the DockExtender
object in the form. Then in the constructor of your form, create a new DockExtender
object and Attach
the (container) control to it that you want make dockable/floatable (the toolstrip
in this example). Once it is attached, the DockExtender class
will extend the behaviour of your attached control and will take care of the floating/docking behaviour for you. The following code will make a toolbar control dockable/floating:
using ControlExtenders;
...
DockExtender dockExtender;
...
dockExtender = new DockExtender(this);
IFloaty floaty = dockExtender.Attach(toolStrip1);
There, that's it! Two lines of code to make your first dockable/floatable control!
Drag and Resize
Basically, all you need is what I call a 'Container
' control. It represents the part of your form that can be made floatable/dockable. In order to drag the Container
control around with the mouse, you needs a 'Handle
', this is basically the Container
's caption/griphandle. Of course, if you supply the container itself as the handle, the whole container will become the handlebar (as in the example above)!
Now the beauty of this solution is that the 'Container
' control and 'Handle
' control can be of any type (as long as it is derived from ScrollableControl
and Control
respectively)! In my demo for simplicity's sake, I used a Panel
for the container and a Label
(hosted on the panel) for the handle. (Note: Obviously the handle looks rather plain, but it would be very easy to make it nicer looking. This is beyond the scope of this article though).
To make it more interesting, it would be very handy to allow a docked container control to be resized by the user. The DockExtender
supports this, yet there is one restriction. You can only use a Splitter
control (or a subclass of Splitter
) with the DockExtender
. E.g.:
IFloaty floaty = dockExtender.Attach(panel1, label1, splitter1);
Now when executing this code, and docking the panel1
on the form, you will notice that the splitter is automatically positioned on top of the panel, allowing the user to resize the panel.
Docking (Z-)order
By default, containers are docked on the inside of the docking host (i.e., the form). This means, that if other controls are already docked in the host, the container will be docked on the inside of the host (bring to front).
In some cases, you may not want to dock only on the inside, but rather on the outside. E.g., toolbars or menus are typically docked on the outside (sent to back). To allow this, we can use the returned floaty
object to set some additional properties: floaty.DockOnInside
:
IFloaty floaty = dockExtender.Attach(toolStrip1);
floaty.DockOnInside = false;
If the DockOnInside
is set to false
, the control will be set to the back, making it dock on the outside of the dockhost
.
Docking Event
So in this example, the toolbar will be docked on the outside. This raises a problem though, because this means that for instance the menubar will be docked below the toolbar which is not desired. In order to cope with this problem, a 'Docking
' event is raised when a control is docked. This allows the client to set the correct Z-Ordering of the non-dockable controls, such as the menubar, statusbar and the inner document.
To capture this event, you need to do the following:
IFloaty floaty = dockExtender.Attach(panel1, label1, splitter1);
floaty.Docking += new EventHandler(floaty_Docking);
...
void floaty_Docking(object sender, EventArgs e)
{
menuStrip1.SendToBack();
statusStrip1.SendToBack();
}
Now the menuStrip
and statusStrip
will always be positioned correctly with the correct Z-Order.
More Features
A few more trivial features are available that I will not explain in depth here. In order to have more control over the display of the floaty/container, you can programmatically Show()
and Hide()
a IFloaty
object, or use the dockManager's Show(container)
/Hide(container)
method to display the container control. Also, you can iterate through the list of registered DockExtender.Floaties
.
Sources and Demo
The sources project contains all sources of the DockExtender
and of the demo project, no external or magic libraries are needed! Feel free to use and adapt this code for your own purposes.
How Does It Work?
For those that are interested, I will explain here how the internals of the DockExtender
work. The DockExtender
consists of three main classes: 'DockExtender
', 'Floaty
' and 'Overlay
'. DockExtender
is more a management class that manages the floaties. For each container that is attached, a corresponding Floaty
object is created to which the container is attached.
The Floaty
does most of the work. The Overlay
class is used by the floaty
to display the designated docking areas. Basically, there are the following types of actions:
- Attach the container
- Go from docked state to floating state
- Select designated docking area
- Go from floating state to docked state
Attach the Container
- The state of the container is stored in the
DockState struct
. This allows to restore to the previous state if necessary. - The
Floaty
will attach mouse move event handlers to the handle and track the movement of the mouse when it is hovering over the handle.
From Docked to Floating
- Mouse move events are triggered by the handle.
- Resize the
floaty
form to the size of the docked control. - Send a Left-button-up windows message to the handle so it will stop firing mouse move events (requires Win API).
- Re-parent the control into the
floaty
and dock it as Filled
. - Position the
floaty
under the mouse cursor. - Hide the handle.
- Send a system command windows message to the
floaty
to start the size-move loop (requires Win API). - The mouse is now dragging the
floaty
.
Select Designated Docking Area
- Get the mouse position.
- Check over which attached forms/controls the mouse is hovering.
- Determine the maximum potential docking area.
- If the mouse is moving into a designated area, calculate the position and size of the overlay.
- Present the overlay control for the designated area.
- Dock the control.
From Floating to Docked
- Set the
DockState
parameters to the new docking area. - Re-parent the container into the designated control/form.
- Show the handle.
- Hide the
floaty
form. - Set the Z-Order of the container.
- Fire the
Docking
event.
Points of Interest
Using an 'extender' approach makes it so much easier to develop. It means you are no longer required to use specific controls or derive from them in order to extend their behaviour. This way, any control can be extended fast and easy without a lot of code modifications, and is therefore very well suited to be incorporated into existing projects that require these extended features.
Originally, the idea was to allow docking also inside containers, but this gave a number of new challanges to tackle, so I left it out of this demo by default. The code can however still be triggered by setting the floaty.DockOnHostOnly
to false
. Note that setting this property may result in undesired effects.
Just one personal note I want to make here is that I saw no other way than to use the SendMessage
API instead of pure C#. I prefer to write C# in pure C# and not resort to win APIs, but there you have it.
If you decide to use this code, feel free to post pointers and tips for improvements. Happy coding!
History
- 23rd February, 2007: Version 1.0 of the
DockExtender
published