Let's Get Docked
by Ray Konopka
July/August 1999, Vol. 10, No. 2
Delphi's docking machinery is both broad and deep, but unfortunately
the documentation is neither. Here are the high points, and some docking
wisdom born of hard-won experience.
Well, I'm finally gonna do it. If you are a regular reader of this
column, you know that for the past few issues, I've been avoiding the
topic of docking support in Delphi 4. Well, I have no more excuses, and in
this article, we will take a closer look at adding docking support to our
Delphi applications. However, before we do that, I want to revisit a topic
I covered earlier this year-initializing custom action classes.
Custom Action Resources
Back in the March/April issue, I described how to create your own
custom action classes. In the process, I commented on how the standard
actions that come with Delphi have no constructors, and at that time I did
not have an explanation for this. Without constructors I had no idea how
the action properties were getting initialized. I suggested that the
Action List Editor could be initializing property values, but followed
that with, "this would be a really bad design." Since writing
that article I have found out how action properties are initialized-you're
not going to believe this.
You may recall from the earlier article that before you can use a
custom action class in an action list, the new action class must be
registered with Delphi, which is accomplished by calling the RegisterActions
procedure. This procedure is defined in the ActnList unit and looks
like the following:
procedure RegisterActions( const CategoryName: string;
const AClasses: array of TBasicActionClasses;
Resource: TComponentClass );
You may also recall that the Delphi documentation is very unclear
regarding the purpose of the Resource parameter. In the example that I
presented I simply passed TListBox using the logic that the action
classes I created were associated with list boxes.
From the declaration above, the Resource parameter is a
reference to a TComponent descendant. It turns out that the default Action
Editor associates this resource class with the action classes
passed in the AClasses parameter. When the editor creates a new
instance of one of these action classes at design-time, the resource class
is used to initialize the action's properties. How this happens is
interesting to say the least.
According to Delphi R&D, the Resource parameter should normally
refer to a TDataModule descendant (yes, a data module) that
contains a TActionList component, which in turn should contain sample
instances of each action specified in the AClasses parameter. The
properties of each sample action are then set to the action's default or
initial values. For example, Figure 1 shows the data module and action
list used to initialize the standard actions that are defined in the StdActns
unit. The EditCopy1 action is currently selected in the Action
Editor and the Object Inspector thus shows the initial values that have
been specified for the TEditCopy action class.
Figure 1: Using a DataModule to initialize custom action classes.
When a developer uses the Action Editor to create an instance of your
custom action class, the editor checks the list of registered actions to
determine the resource class. If one is found, an instance of the resource
class is created. Recall that the resource class is actually a data
module. As a result, the default values for the sample actions defined in
the data module are stored in the data module's associated DFM file. By
creating an instance of the data module, the default property values are
read in from the DFM file.
With the data module resource created in the background, the Action
Editor then searches the data module for an action that matches the action
class that is being created. For example, if the developer instructs the
Action Editor to create a new TEditCopy standard action, the editor
searches for the first action of type TEditCopy, which from Figure
1 we can see is EditCopy1. If a matching action is found, the
property values from the default action are copied to the newly created
Certainly an interesting way to initialize property values, isn't it?
Before you make your decision consider the following. In order to create
the sample actions on the data module in which to specify the default
values, the action classes must already be registered in Delphi.
Therefore, before you can create your resource class, you must first
register your custom action passing nil as the Resource parameter to RegisterActions.
After defining the data module resource, you can then unregister your
custom action and then re-register it, but this time specifying your new
data module descendant in the Resource parameter.
Frankly, I just don't see the benefit in all of this. In my March/April
column, I achieved the same functionality by simply overriding the
constructor of my custom actions. Not only are the extra steps described
above not necessary, it makes much more sense to me to initialize actions
in a constructor. After all, actions are components, and it is standard
practice to initialize components in their constructors.
So, which approach should you use to initialize your custom actions?
I'm still waiting to hear a compelling reason for using a data module
resource. Until I do I will continue to initialize my custom action
classes in a constructor.
By far the most common type of docking found in applications these days
is toolbar docking. Furthermore, there are two ways of docking toolbars
and fortunately for us Delphi supports both of them.
The first way is to allow toolbars to be repositioned within a general
toolbar area. Delphi supports this type of docking with the new TControlBar
component located on the Additional palette page. For example, drop a TControlBar
onto your form and set its Align property to alTop. Plus,
you will usually want to make sure the AutoSize property of the
control bar is set to True. This causes the component to automatically
resize itself so that it remains as small as possible while still
encompassing all controls dropped onto it.
Next, we need something to drag around. You can drop any control onto a
ControlBar, but typically you will drop one or more toolbars.
Switch to the Win32 tab and click on the Toolbar component and then
click on the control bar. This will create a toolbar that has a small
grabber bar on its side. The grabber bar allows you to drag the toolbar
anywhere within the control bar's bounds. To remove the groove that
appears at the top of the toolbar, simply change the toolbar's EdgeBorders
property to an empty set.
The second way of docking toolbars involves dragging a toolbar off of
the control bar. This results in the toolbar being displayed in a floating
window. In order to support this type of dragging and docking, you will
need to change the DragMode property of the desired toolbar to dmAutomatic
and the DragKind property to dkDock.
When the toolbar is dragged off of the control bar and displayed in a
floating window, the title bar of the window displays the contents of the
toolbar's Caption property. As a result, you should change this
property to reflect a more meaningful name.
Dockable Menus (Revisited)
In the May/June issue, after postponing full coverage of docking for
yet another issue, I did provide a small tip regarding the construction of
dockable menus like those used in the Delphi 4 IDE. To briefly recap, the
menu in the Delphi 4 main window is really just a TToolbar
component that is configured in a particular way. Although it is possible
to manually configure the toolbar so that it mimics a menu, the easiest
way to accomplish this is to download the TMenuBar component from
http://www.borland.com/devsupport/delphi/downloads/index.asp. It is listed
toward the end of the Delphi 4 list of downloads.
After downloading and installing this component into Delphi 4, it is
very easy to create docking menus. Unfortunately, the component does not
come with any documentation and although easy to use, there are several
issues that you need to be aware of.
Since TMenuBar is just a custom version of TToolbar, we
create dockable menus using the same technique described above for
creating dockable toolbars. Next, drop a TMainMenu component onto
the form and populate the menu items. When you are finished, you will
notice that the form displays the menu as a normal menu. To eliminate
this, simply clear the Menu property of the form. Next, set the
Menu property of the menu bar component to reference the main menu. Now
the menu bar component displays each submenu as a button.
When using the TMenuBar component, you will not be able to
select any of the menu items at design-time from the menu bar component.
In order to specify event handlers for the menu items, you need to use the
menu editor. Also note that if you use the menu editor to make changes to
the menu, such as changing a menu name or adding a new menu, the menu bar
will not be updated when you close the menu editor. To update the menu
bar's display, you need to clear the menu bar's Menu property and reassign
it to the menu component. Figure 2 shows a sample form, which is using the
Figure 2: TMenuBar makes it easy to create docking menus.
In order for the buttons on the TMenuBar to behave like
drop-down menus, the system must have version 4.72 or later of
ComCtl32.dll installed. This DLL is from Microsoft and implements the
Windows Common Controls of which the toolbar is a part. The TToolbar
component in Delphi is simply a VCL wrapper around the common toolbar
control. Without version 4.72 or later of ComCtl32.dll available, all of
the menu buttons will be the same width and will appear as standard
non-flat buttons. In addition, older versions of the DLL do not convert
ampersands in the caption into access characters. By the way, located in
the \Info\Updates directory on the Delphi 4 CD is a simple setup program
that installs the correct version of the ComCtl32.dll.
There is one more additional issue with the TMenuBar component.
Specifically, it does not handle merging menus of child forms in an MDI
application. Hopefully, borland.com will address this issue in the near
Dockable Tool Windows
The Delphi 4 IDE utilizes both dockable toolbars and dockable menus.
The IDE also utilizes a third style of docking. For example, by default
the Code Explorer window is docked to the left side of the Code Editor and
the Messages view is docked to the bottom of the Code Editor. In fact, any
of the tool windows that can be displayed in Delphi can be docked to the
left, bottom, or right side of the Code Editor window.
In addition, the tool windows in Delphi can be docked on top of one
another. For example, select the View|Debug Windows|Breakpoints menu item
and then drag the Breakpoint window to the center of the object inspector.
You will notice that the docking outline rectangle is centered within the
bounds of the Object Inspector. This indicates that if the tool window is
docked in this position, both the Object Inspector and the Breakpoints
window will be docked to the same floating form but each tool window will
be located on a separate page in a page control.
At this point, I normally show a demo application that illustrates the
subject matter and then spend the next several paragraphs reviewing the
code. In this issue, I'm doing things a little different. First, you may
have already noticed that there is no source code example associated with
this article. One reason for this is because most of the changes that need
to be made in order to support docking can be accomplished by simply using
the Object Inspector.
However, the primary reason for not providing a sample program is
because Delphi 4 already comes with a fairly good demo program (DockEx.dpr)
located in the Demos\Docking directory. In fact, the demo contains some
really cool docking code (with comments). The only problem is that on the
surface the demo appears to be very superficial. My intention here is to
highlight the important pieces.
The main form of the DockEx program contains a TCoolBar
component, which is Microsoft's common control version of TControlBar.
The CoolBar contains two TToolbar components used in the
same manner I described above. As a result, I'll skip this part of the
Along the left edge of the main form you will find two components: the LeftDockPanel
and the VSplitter. The panel is a dock site for any of the tool
windows that are displayed by the application. The splitter allows the
user to resize the width of the panel. What's interesting is that the
panel does not have its AutoSize property set to True. According to
the online help, setting AutoSize to True is suppose to
automatically resize the width of the panel according to controls that are
docked in it. However, this approach does not work. For some reason the
panel does not resize itself when a control is docked inside it.
Instead of using the AutoSize property, the demo program handles
a few of the events associated with the dock site. First, when the user
drags a form over the LeftDockPanel, the OnGetSiteInfo event
is generated. The demo program handles this event to determine if the
selected form can actually be docked onto the panel. Next, the OnDockOver
event is handled allowing the demo program to alter the size of the
docking rectangle. When the user releases the mouse button to drop the
form on the dock site, the OnDockDrop event is generated.
The DockEx program also supports docking tool window onto tool
window. For example, when you run the program, you can dock the green
window on top of the purple window. When this happens, a new floating
window with a page control is created and each tool window (green and
purple) is placed on a separate page. It is also possible to dock the
purple window next to the green window so that they both appear in the
same floating window without having the page control.
The tool windows themselves are instances of TDockableForm. The
most important method in this class is CMDockClient, which is
responsible for creating the correct docking host when a dockable form is
dropped onto another dockable form. If the client form is dropped in the
center of the dock site form, then a new TTabDockHost window is
created and both the client form and the dock site form are put on
separate pages. If the client form is dropped at one of the edges of the
dock site form then a new TConjoinDockHost window is created and
both tool windows are tiled inside the new dock site.
I encourage you to take a closer look at this demo. There are lots of
well-written comments that explain the process in detail. Unfortunately, I
just don't have the room to do that here.
Okay. So now we know how to add docking toolbars and windows to our
applications, but what about persistence? That is, how do we maintain a
user's docking selections between program runs? For example, suppose a
user decides to drag a toolbar off of the control bar so that it is
floating in a separate window. When the user runs the program again, the
toolbar should start out floating in a window and not docked onto the
This problem can be broken up into two tasks. The first task is to
record the docking state for all dockable controls in the application. At
first this sounds like a lot of work, but generally you should not have
that many dockable controls. You can also use your dock sites to help out.
For instance, each dock site has a DockClients property along with
a DockClientCount property. Using these two properties you can
determine which controls are docked in a particular dock site.
At the control level, you will probably want to look at the LRDockWidth,
TBDockHeight, UndockWidth, and UndockHeight
properties. LRDockWidth specifies the width of the control from the
last time it was docked horizontally. TBDockHeight specifies the
height of the control from the last time it was docked vertically. UndockWidth
and UndockHeight specify the width and height of the control from
the last time it was floating, respectively.
When you program starts, you will need to scan through the settings you
saved from the previous run and then create and move the controls in an
appropriate manner. The most challenging aspect of this is how to dock a
control at startup that was originally a floating window. This is
accomplished by creating the floating window first and then using the ManualDock
method to programmatically dock a control to a specific dock site.
Docking support in Delphi 4 is quite powerful, indeed. However, I am
not particularly fond of the grabber bars that are displayed when a client
is docked as shown in Figure 3. The bars are fine for toolbars, but for
forms and other controls, I would prefer to see a small caption describing
the contents of the docked control.
Figure 3: Delphi's default docking manager.
Fortunately, Delphi provides the means to alter this behavior, although
it is not a trivial process. The trick is to create a custom docking
manager. The default docking manager, which is implemented in the TDockTree
class, is responsible for displaying the grabber bars for docked controls.
By creating a custom docking manager and instructing a component to use
it, we can customize the appearance of docked controls. For example,
Figure 4 shows a sample form utilizing the custom manager that comes with
Figure 4: An example of a custom docking manager.
Unfortunately, I don't have enough room to describe the details of
creating your own custom docking manager. The basic technique is to create
a descendant of TDockTree and override the appropriate methods. For
example, to replace the grabber bars with tiny captions, you override the PaintDockFrame
However, creating a descendant of TDockTree is only part of the
solution. You must instruct your dock site component to use your new
docking manager. This can be accomplished in two ways. The first is to
create an instance of your docking manager and assign it to the DockManager
property of the dock site. However, this usually only works for very basic
Generally, you will create a docking manager for a specific type of
component. In this case, you will need to create a descendant of the
component you wish to use as your dock site. You then override the dock
site's CreateDockManager method and return an instance of your new
custom docking manager class.
Words of Wisdom
Before wrapping up this article, I would like to offer some free
advice-use docking sparingly. Just because Microsoft supports docking
toolbars in their applications doesn't mean that you must support them in
yours. Docking can be quite confusing to inexperienced users and can
easily result in extra technical support calls. The point is that you must
weigh the added flexibility docking provides against the added complexity
it adds to your application. Like virtually everything in programming it
all comes down to tradeoffs.
On the Drawing Board
By now you have probably heard that Delphi 5 is coming soon. Next time,
we will take a look at what the latest incarnation of our favorite
development tool has to offer.v
Copyright © 1999 The Coriolis Group, Inc. All rights reserved.