Floating Windows and Docking
Everyone expects an MDI interface to sport dockable windows and toolbars-floating controls that can be latched into place or left hovering above your application. Unfortunately, designing this type of interface is surprisingly awkward. Windows (and previous application frameworks) do not provide native support for most of these features. Instead, the developer has to resort to some creative coding to implement a solution. As a side effect, docked and floating windows never look exactly the same in any two Windows applications-even if those two applications are both written by Microsoft programmers. Every solution has drawbacks and advantages.
Floating Toolbars
By default, when you create a toolbar it is automatically docked to the top of the form. However, it doesn't need to be-as with any .NET control, you can set the Dock property to modify this behavior.To create a toolbar that can float, you need to add some additional logic to the mouse events that detect if the toolbar is clicked and "pulled" downward or rightward. In the next example, the toolbar is disconnected after 20 pixels of a movement in either direction. Then other event-handling logic comes into play, which allows the control to be dragged around the surface of the MDI container.Here's the code that needs to be added to the Form class to support this design. It's similar to the dragging code developed in Chapter 4, as it combines MouseDown, MouseMove, and MouseUp event handlers to manage the process of moving the floating control.
private bool draggingToolbar;
private Point draggedFrom;
private void toolBar1_MouseDown(object sender,
System.Windows.Forms.MouseEventArgs e)
{
draggingToolbar = true;
draggedFrom = new Point(e.X, e.Y);
toolBar1.Capture = true;
}
private void toolBar1_MouseMove(object sender,
System.Windows.Forms.MouseEventArgs e)
{
if (draggingToolbar)
{
if (toolBar1.Dock == DockStyle.Top)
{
// Check if the dragging has reached the threshold.
if (draggedFrom.X < (e.X - 20) || draggedFrom.Y < (e.Y - 20))
{
draggingToolbar = false;
// Disconnect the toolbar.
toolBar1.Dock = DockStyle.None;
toolBar1.Location = new Point(10, 10);
toolBar1.Size = new Size(200, 100);
toolBar1.BorderStyle = BorderStyle.FixedSingle;
}
}
else if (toolBar1.Dock == DockStyle.None)
{
toolBar1.Left = e.X + toolBar1.Left - draggedFrom.X;
toolBar1.Top = e.Y + toolBar1.Top - draggedFrom.Y;
}
}
}
private void toolBar1_MouseUp(object sender,
System.Windows.Forms.MouseEventArgs e)
{
draggingToolbar = false;
toolBar1.Capture = false;
}
Figure 10-11 shows the toolbar in its "floating" state.

Figure 10-11: Creating a floating toolbar
A key technique in this example is the use of the Control.Capture property. By setting this property to true, the code ensures that mouse events will be received, even if the mouse moves off the control. This allows the user to drag the toolbar from its docked position by clicking at the bottom of the control and dragging down off the surface of the toolbar. This step wasn't required for our simple dragging examples in Chapter 4, because the control was never fixed. Instead, it always moved to keep under the mouse pointer.There's still one aspect missing from the draggable toolbar: it doesn't automatically reattach itself. To accomplish this, you simply need to add some code in the MouseMove event that checks how close the toolbar is to top of the form. If it is within a given threshold, the toolbar should return to its fixed position.Here's the rewritten mouse move code that allows automatic docking to the top or left sides.
private void toolBar1_MouseMove(object sender,
System.Windows.Forms.MouseEventArgs e)
{
if (draggingToolbar)
{
if (toolBar1.Dock == DockStyle.Top)
{
// (Code for undocking omitted.)
}
else if (toolBar1.Dock == DockStyle.None)
{
toolBar1.Left = e.X + toolBar1.Left - draggedFrom.X;
toolBar1.Top = e.Y + toolBar1.Top - draggedFrom.Y;
if (toolBar1.Top < 5)
{
draggingToolbar = false;
// Re-dock the control.
toolBar1.Dock = DockStyle.Top;
toolBar1.BorderStyle = BorderStyle.None;
}
else if (toolBar1.Left < 5)
{
draggingToolbar = false;
// Re-dock the control.
toolBar1.Dock = DockStyle.Left;
toolBar1.BorderStyle = BorderStyle.None;
}
}
}
}
These techniques are simple building blocks that can lead to a fairly sophisticated interface. You can experiment with the FloatingToolbar project, which is included with the samples for this chapter.
Unusual controls
The preceding example also paves the way to creating your own "coolbar" type of control that provides other .NET controls (like drop-down list controls) in a dockable toolbar. All you need to do is create a custom user control that contains a Panel control and the controls you want to use. You can then use that control with exactly the same docking code used for the toolbar example. Remember, in .NET any type of control can be dragged over the surface of an MDI container.
Tip
With several dockable controls, you'll want to rewrite the code to be more generic, so that it automatically works with the sender control, instead of assuming a specific control. You would also add docking information to a hash table collection, indexed under the control reference.
Dockable Windows
Dockable windows use a similar concept to floating toolbars, but require a little more finesse. The problem is that window movement is handled automatically by Windows, and can't be easily altered in your code.A typical approach to create a dockable window is to create a toolbox-border window, and then check its position in the Form.LocationChanged or Form.Move event handler. If you find that it is within certain range from one of the form sides, you could then manually move it to so that it sits flush against the border.Unfortunately, several problems arise with this approach. It's possible to react after the position of a window has changed, but it's not possible to prevent attempted changes (and thus "fix" a window into place). Generally, a docked window should only move if the user clicks and drags it beyond a specified threshold. However, if the user has configured the system settings to show window contents while dragging, there could be a significant amount of flicker as the window battles between the user's movement and the code's attempt to resist. It's for this reason that the Visual Studio .NET dockable windows (and those in earlier Visual Studio versions) never show their contents while dragging. All you see is a grey transparent outline, regardless of your system settings.
Dockable windows with owner-drawn controls
One of the ways that you can create fake dockable windows is to create your own owner-drawn control that attempts to look as much like a window as possible. This fake window can use the technique you applied with the floating toolbar, which allows you to drag it across the surface of an MDI container and automatically latch it onto a side. Best of all, you have complete control over when the window does and doesn't move. Unfortunately, you will have to take some effort to ensure that your control mimics a real window closely enough, and takes into account the current system colors and font. This painful approach is used in many modern Windows applications, although this chapter won't attempt it.
Dockable windows with timers
One of the key problems you'll face while trying to create dockable windows is the fact that forms don't fire MouseUp events when the user finishes dragging them (instead, mouse events are reserved for actions in the client area). Typically, you would use a MouseUp event handler to dock the control.You can code around this limitation with a little desperate ingenuity, by manually polling the state of the mouse when a dock is potentially about to take place. That's the technique used in your next example, which provides two forms: MDIMain and Floater, the dockable window.When the program first starts, MDIMain creates an instance of the Floater window and displays it. Note that the Floater is not a child window but an owned form. That means it can be moved anywhere on the screen, but will always appear above the MDIMain form. This is simply a design decision-the Floater window could also be created as a child window.
private void MDIMain_Load(object sender, System.EventArgs e)
{
Floater frmFloat = new Floater();
frmFloat.Owner = this;
frmFloat.Show();
}
Some of the most interesting code takes place in the Move event handler for the floating window. Here, the current position of the form is examined:
private Point dockTestAt;
private void Floater_Move(object sender, System.EventArgs e)
{
// Determine the current location in parent form coordinates.
Point mouseAt = this.Owner.PointToClient(this.Location);
// Determine if the floater is close enough to dock.
if (mouseAt.X < 5 && mouseAt.X > -5)
{
if ((Control.MouseButtons & MouseButtons.Left) == MouseButtons.Left)
{
dockTestAt = mouseAt;
// Show the dock focus rectangle.
((MDIMain)this.Owner).DrawDockRectangle = true;
// Reset the timer to poll for the MouseUp event.
tmrDock.Enabled = false;
tmrDock.Enabled = true;
}
}
}
If the floating window is within a predefined threshold of the left border of the owner form, the floating window instructs the owner to draw a dock cue: a grey rectangle in the spot where the dock will be performed. This is performed by setting the custom MDIMain.DrawDockRectangle property.
public bool DrawDockRectangle
{
get
{
return pnlDock.Visible;
}
set
{
pnlDock.Visible = value;
}
}
All this property does is hide or show a Panel control. The panel provides its own logic to draw a hatched border outline, as shown in Figure 10-12.

Figure 10-12: A dock cue
private void pnlDock_Paint(object sender,
System.Windows.Forms.PaintEventArgs e)
{
HatchBrush dockCueBrush = new HatchBrush(HatchStyle.LightDownwardDiagonal,
Color.White, Color.Gray);
Pen dockCuePen = new Pen(dockCueBrush, 10);
e.Graphics.DrawRectangle(dockCuePen,
new Rectangle(0, 0, pnlDock.Width, pnlDock.Height));
}
This isn't the only action that's taken when the floating window is in a valid dock position. The floating window also begins polling to check if the mouse button is released by enabling a timer.
// This code is triggered with every timer tick.
private void tmrDock_Tick(object sender, System.EventArgs e)
{
if (dockTestAt.X == this.Owner.PointToClient(this.Location).X
&& dockTestAt.Y == this.Owner.PointToClient(this.Location).Y)
{
if (Control.MouseButtons == MouseButtons.None)
{
// Dock in place.
tmrDock.Enabled = false;
((MDIMain)this.Owner).AddToDock(this);
}
}
else
{
// Mouse has moved. Disable this dock attempt.
tmrDock.Enabled = false;
((MDIMain)this.Owner).DrawDockRectangle = false;
}
}
If the form is moved, the polling is disabled, and the dock cue is hidden. If the mouse button is released while the form is still in the same place, docking is initiated using the MDIMain.AddToDock() method.
public void AddToDock(Form frm)
{
// Allow the form to be contained in a container control.
frm.TopLevel = false;
pnlDock.Controls.Add(frm);
// Don't let the form be dragged off.
frm.WindowState = FormWindowState.Maximized;
}
This is one of the most unusual pieces of code in this example. It works by adding the form to the Controls collection of another container, and maximizing it so that it can't be moved. This seemingly bizarre approach is possible as long as you disable the TopLevel property of the form. The docked window is shown in Figure 10-13.

Figure 10-13: A docked window
One benefit of this approach is that it is fairly easy to create a system with multiple dockable windows, just by adding a separate Panel for each form into one fixed Panel control. You can set the Dock property for each form-containing panel to Top or Fill, ensuring that they automatically adjust their sizes to accommodate one another. You could even add splitter bars so the user could alter the relative size of each panel, as demonstrated in Chapter 5.However, this simple example is a long way away from the docking intelligence of Visual Studio. To perfect this system requires a lot of mundane code and tweaking. For example, in Visual Studio .NET forms aren't pulled out of a docked position based on the amount they are dragged, but also the speed at which the user drags them. Thus, a quick jerk will dislodge a docked form, while a slow pull will leave it in place. Try it out on your own-you'll find a lot of thought has been put into this behavior. Unfortunately, docked windows are a nonstandardized area of Windows programming, and one where the .NET framework still comes up short.