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