Introduction to Component Building
by Ray Konopka
Note: The following paper was presented at the 1999 Inprise/Borland Conference in Philadelphia Pennsylvania. Click on the
source code link to download the examples used in this paper.
This paper describes the process of building custom Delphi and C++Builder components.
In addition, key features of the Visual Component Library (VCL) will be covered. These key
features are extremely important to the component writer. However, before we move onto to
the process of creating a custom component, it is important to gain an understanding of
just what we are trying to create.
What is a Component?
A component is any object that descends from the TComponent class within the
Visual Component Library (VCL) class hierarchy. A more practical definition is that a
component is any piece of a software that you want to treat as a standard-issue
"black box" chunk of functionality at design-time.
This is an important concept, because although most components are visual user
interface controls, this is by no means a requirement. There are several standard
components that provide no visual user interface at all, such as the Timer. These are
called nonvisual components. The important point to remember is that a component can be
any piece of software, just as long as it fits into the component framework.
And that framework is the Visual Component Library. For a piece of software to be a
component, it must be defined as a class that exists somewhere within the VCL class
hierarchy. This object-oriented approach has several advantages. For instance, components
can serve as ancestors to new descendent components. This allows developers to create
custom components derived from other custom components. And you don't even need the source
code for the ancestor component to do it.
Reasons to Build Custom Components
There are four primary reasons for building custom components. They are as follows:
- To provide additional functionality
- To support reusability
- To increase productivity
- To promote consistency
Functionality
The first and most important reason for creating a custom component is to provide
functionality that the existing set of components does not provide. For example, you may
need a component that is similar to one of the existing components but it does not meet
all of your needs. Perhaps the existing control is not data-aware, or its appearance is
inconsistent with your user interface. It may in fact be that you need a component that is
totally different from any existing component.
Reusability
Delphi encourages reusability through its VCL component architecture. By making it
relatively easy to create custom components, Delphi encourages developers to view
applications as collections of reusable building blocks. One of the best ways to promote
reusability is to take isolated pieces of an application, such as a specialized edit field
or even a common dialog box, and convert them into components. These components can then
be used elsewhere in the same application or even in other applications with the simple
click of the mouse.
Productivity
Even though it is relatively easy to change property values using Delphi's Object
Inspector, changing four or five properties every time you drop a particular component on
the form can be tedious. As an example, consider an OK button. Yes, there is the Kind property
for the TBitBtn component, but I have my own bitmaps that I prefer to use as glyphs.
Furthermore, the default button size is too large for my taste. (By the way, Windows
95-style buttons do not use glyphs.) After dropping a Button component on the form, five
different properties must be set in order to create a standard OK button.
To eliminate the tedious task of setting these properties each time an OK button is
needed, you could create a custom button component that automatically sets the five
properties to the desired values. Therefore, whenever you need an OK button, all you need
to do is drop your custom version on the form. There is no need to modify the properties
because this is handled by the component itself.
The productivity gains realized in this way are directly proportional to the complexity
of the component. For example, if in addition to setting property values, suppose you
needed to specify some event handlers. As a component, this code is written only once, but
used many times.
Consistency
In addition to enhancing productivity, creating custom components aids in promoting
consistency among forms and even among applications. As an example, consider a development
shop where several different applications are being built. Further, suppose that each
application's main window will have a status bar. To promote consistency, a status bar
component could be created that specifies particular values for the Height, Align,
and Font properties. It may even specify standard status panes within the status
bar. As long as each application uses the status bar component, that aspect of the
interface would appear to be consistent across all applications produced in that shop.
How Building Components is Different
Although components and applications are built using the same development environment,
building components is quite different from building applications. The following list
highlights the major differences between building components versus applications:
- Components have different end users
- Component writing is a nonvisual process
- Component writing is highly object-oriented
- Components must follow more conventions
- Components must be flexible
- Components have three different interfaces
Components Have Different End Users
Components are not used by the same people that use applications. The end users of your
components will be other application developers, and they will expect your components to
behave in a certain way. Fall short of that expectation, and your components will not be
used, or worse, they'll come looking for you to help them out! In order to ensure
that you do not suffer this fate, be sure to become familiar with the components that come
with Delphi.
You will also find that during the development of a component, you will jump to the
other side of the computer screen and take on the role of application developer to review
your component. Figure 1 shows these two sides of component building.

Figure 1: Component users do not have the same perspective as component
writers.
Playing the role of component user serves as a safety check. For example, it's often
very useful to step back and ask yourself, "Would I use this component?" Often,
the answer will be yes. (Let's face it, you developed it, so it must be good,
right?) If you answer no, then it's time to reconsider the component's design. However, be
careful with this approach. As a rule, never be your own sole critic. It's always best to
get some feedback from other developers before making a design decision.
Component Writing is a Non-Visual Process
Unlike application development in Delphi, building custom components is generally a
nonvisual process. For example, component building is not form-based, so the Form Designer
is not used. Likewise, the Object Inspector only displays information pertaining to
components already registered with the Delphi development environment, so this is another
visual tool that is not applicable. Instead, components are created entirely by writing
Object Pascal code.
Fortunately, all is not lost. Although the visual design features of Delphi are not
used when building a custom component, the same development environment is. Aside from the
obvious use of the Code Editor, component development makes extensive use of the
integrated debugger and the ObjectBrowser. Of course, let's not forget online help!
For completeness, I should clarify that there are two situations in which the Form
Designer will be used during the development of a component-creating a dialog
component and creating a custom property or component editor. In both cases, the dialog
box is designed and created in the Form Designer. You still have to write some nonvisual
code in order to display the dialog at the appropriate time, but at least you don't have
to design the form using the Code Editor.
Component Writing is Highly Object-Oriented
Since components are actually instances of Object Pascal classes, creating them
requires a greater knowledge of object-oriented programming than is required for creating
Delphi applications. Unlike application developers, component writers must have a solid
understanding of key object-oriented programming techniques such as encapsulation, inheritance,
and polymorphism.
Building a new component always involves deriving a new class from an existing
component type. In essence, the Visual Component Library is extended through inheritance.
Furthermore, by descending from an existing class in the hierarchy, a significant amount
of functionality is immediately available to the new component type.
However, the most noticeable change between building components and applications is
that while applications change values of properties and call methods of components, the
component writer must write the methods and properties that define the full behavior of
the component in question.
Components Must Follow More Conventions
As a nonvisual process, building components is a more traditional programming task,
similar in many respects to traditional programming in Borland Pascal and Turbo Pascal.
There are also more conventions that should be followed. Of course, because they are
conventions and not rules, you are not forced to follow them-although it is highly
recommended that you do.
In particular, the conventions involved in component writing help to ensure
consistency. Remember that the end user of your component is an application developer-more
precisely, an application developer using Delphi. Therefore, your end users will have a
preconceived idea of how your (or anybody's) components will function within the Delphi
environment. For example, suppose that you are creating a new component and it registers a
new event in the Object Inspector. By convention, all event names should begin with the
word On. While it is perfectly legal to use any name you chose, your users will
expect to see the event names such as OnClick.
Components Must be Flexible
Conventions are not only associated with names, but with behaviors as well. In
particular, component users are accustomed to being able to do anything they want to a
component at any time. This means that you, as a component writer, need to make your
components flexible enough to support this. While this task is not particularly difficult,
it requires that the component be designed to support the necessary level of flexibility.
Components Have Three Different Interfaces
When developing an application, there is only one interface of concern-the runtime
interface. That is, the application only exists when it is being executed. Components,
on the other hand, have three different interfaces that must be handled. Like
applications, there is the runtime interface. The runtime interface defines how the
component can be used at runtime, namely the properties, methods, and events that are
available.
Unlike applications, components also have a design-time interface. To the
component user, this is the most important interface, because it defines how the component
behaves within the Delphi design environment. It determines which aspects of the component
are visible during design.
For the component writer, the most important interface is the developer interface,
which specifies all of the functionality present in the component. The developer interface
is a superset of both the runtime and design-time interfaces. It provides access to
implementation-specific elements of the component.
The VCL Hierarchy
For the application developer, the Visual Component Library (VCL) is a framework
consisting of a set of components that are used to construct applications. For the
component writer, the VCL represents an extensible class hierarchy containing a vast
amount of functionality that can be incorporated, through the mechanism of inheritance,
into custom components. The VCL is extensible in that new components become part of this
hierarchy and thus can serve as ancestors for still other components in the future.
The term Visual Component Library is a bit misleading because not all components
are visual. The VCL includes non-visual components as well. The confusion is further
compounded in that many classes that are not even components are considered to be part of
the VCL.
Figure 2 displays the base classes that form the structure of the VCL. At the top is TObject,
which is the ultimate ancestor for all classes in Object Pascal. Descending from it is TPersistent,
which provides the necessary methods to create streamable objects. A streamable
object is one that can, oddly enough, be stored on a stream. A stream is an object
that encapsulates a storage medium to store binary data (for example, memory or disk
files). Because Delphi implements form files using streams, TComponent descends
from TPersistent, giving all components the ability to be saved in a form file.

Figure 2: The base class structure of the Visual Component Library.
The TComponent class is essentially the top of the component hierarchy, and is
the first of the four base classes used to create new components. Direct TComponent
descendants are non-visual components. The TControl class implements the necessary
code for visual components. Notice in Figure 2 that there are two basic types of visual
controls: graphic controls and windowed controls. Each is represented by
its own hierarchy descending from TGraphicControl or TWinControl,
respectively. The main difference between these types of components is that graphic
controls do not maintain a window handle, and thus cannot receive the input focus.
Windowed components are further broken up into two categories. Direct descendants of TWinControl
are typically wrappers around existing controls that are implemented within Windows and,
therefore, already know how to paint themselves. The standard Windows controls, like the
edit field, are direct descendants of TWinControl. For components that require a
window handle but do not encapsulate an underlying Windows control that provides visualization
(that is, the ability to repaint itself), the TCustomControl class is provided.
The TActiveXControl class was introduced in Delphi 3.0 and allows developers to
create ActiveX controls. Technically, ActiveX controls are not part of the Visual
Component Library, as illustrated by the position of TActiveXControl in Figure 2.
However, TActiveXControl descendants typically contain a native component
reference. The component reference provides the control's functionality, while TActiveXControl
provides the COM interface so that the component can function as an ActiveX control.
The four shaded classes back in Figure 2 represent the base classes from which you will
derive new custom components. The following sections take a closer look at just what these
classes provide.
TComponent
The TComponent class provides the properties and methods that enable components
to be managed by the Form Designer. Besides the Name and Tag properties, TComponent
introduces a couple of properties useful to component writers. One of the more useful is ComponentState,
which holds a set of values that indicate the current state of the component. The possible
values are described in Table 1.
Table 1: Elements of the TComponentState type.
Element |
Description |
| csAncestor |
The component was introduced in an ancestor form. This flag
is only set if csDesigning is also set. |
| csDesigning |
The component is being manipulated in design mode by the Form
Designer. |
| csDestroying |
The component is being destroyed by its owner. |
| csFixups |
Since Delphi supports form linking, it is possible for a
component to reference a component on another form that has not yet been loaded. This flag
is set until all forms and data modules are loaded. |
| csLoading |
The component is being loaded from a form file or stream. |
| csReading |
The component is reading its property values from a form file
or stream. |
| csUpdating |
The inherited component is being updated to reflect changes
in an ancestor form. This flag is only set if csAncestor is also set. |
| csWriting |
The component is writing its property values to a form file
or stream. |
A common usage of the ComponentState property is to determine whether the
component is being manipulated at runtime or design-time. For example, the Image component
uses this property to determine if a dashed border should be drawn around the control. The
following fragment from the TImage.Paint method shows that by checking if csDesigning
is in the ComponentState set, the dashed border is only drawn at design-time.
procedure TImage.Paint;
begin
if csDesigning in ComponentState then
begin // Draw Dashed Border only at Design-Time
with inherited Canvas do
begin
Pen.Style := psDash;
Brush.Style := bsClear;
Rectangle( 0, 0, Width, Height );
end;
end;
. . . // Continue Drawing the Image within Bounds of Component
end;
As a result of form inheritance being introduced in Delphi 2.0, the ComponentStyle
property was added to the TComponent class. This property represents a set of style
attributes that govern how a component behaves. In particular, the set can hold two
possible styles: csInheritable and csCheckPropAvail.
The csInheritable style indicates that the component can be inherited by a
descendent form type. For a form to be inheritable (that is, serve as an ancestor to
another form), all components on the form must have the csInheritable style set. If
any of the components do not include this style, then Delphi will display an error when
you attempt to create a new form descendant. The TComponent class sets the csInheritable
style by default in its constructor, and for most circumstances, you will not need to
change it. However, it may be necessary to remove this style because of how the component
is implemented. For example, the TDatabase component class removes this style
because there can only be one Database component per database in an application. If this
style is not removed, then a form and its ancestor could be used in the same application,
thereby causing the Database component on each form to reference the same physical
database.
I mentioned above that the ComponentStyle property was added to TComponent
because of form inheritance. Well, that's not totally correct. The csInheritable
style was added for this reason, but the csCheckPropAvail style was added because
of Delphi 2.0's support for OLE Controls (OCXs). The csCheckPropAvail style
indicates that a component must check its properties for readability. OLE controls must
set this style because the Object Inspector cannot directly determine if a property is
readable, and therefore displayable. You never have to worry about using this style
because it is automatically included in the TOleControl class, which Delphi uses in
creating the wrapper class for all imported OCX controls. Besides, native components
should not use this style because the readability test is very time consuming.
The TComponent class also introduces the concept of ownership that is
propagated throughout the VCL. There are two properties that support ownership: Owner
and Components. Every component has an Owner property that references
another component as its owner. Likewise, a component may own other components. In this
case, all owned components are referenced in the Components array property. A
component's constructor takes a single parameter that is used to specify the new
component's owner. If the passed-in owner exists, then the new component is added to the
owner's Components list.
Aside from using the Components list to reference owned components, the most
important service this property provides is automatic destruction of owned
components. This is an important point even for application developers. As long as the
component has an owner, it will be destroyed when the owner is destroyed. For example,
since TForm is a descendant of TComponent, all components owned by the form
are destroyed and their memory freed when a form is destroyed. Of course, this assumes
that all of the components on the form clean themselves up properly when their destructors
are called.
Now let's turn to the methods of TComponent, one of the most important is the Notification
method. Whenever another component is inserted into or removed from a component's Components
list, the component notifies all of the other components on the list of the change by
calling their Notification methods. It's a bit confusing, so here's another way to
look at it. TForm is a descendant of TComponent and therefore has a Components
property. Whenever you drop a new component on a form, the new component is added to the
form's Components list. When this happens, the form notifies the other members of
the list (that is, the other components on the form) that a new component was added. The
form does this by calling the Notification method of each component on the list. A
component overrides the Notification method to ensure that any references to other
components remain valid.
Providing a Notification method is especially important for design-time
operation. Consider the form shown in Figure 3, which contains two components: Label1
and Edit1. Label1 references Edit1 through its FocusControl
property. When Label1 is selected, the Object Inspector displays the focused
control's name by accessing the FocusControl.Name property (that is,
"Edit1"). Now, suppose Edit1 is deleted from the form. If Label1
is not notified of this change, its FocusControl property becomes invalid
because it is pointing to the memory location that used to be occupied by Edit1.
Therefore, if Label1 is selected again, an access violation will occur when the
Object Inspector tries to access FocusControl.Name. Fortunately, the TLabel
component does provide a Notification method to prevent this. The method is
defined in the TCustomLabel class from which TLabel descends.

Figure 3: Connecting an edit field to a label using the FocusControl property.
Since the Notification method is called whenever any component is inserted or
removed from a form, the label's Notification method, shown below, tests if the
component being removed is the one referenced by the label's FocusControl property.
(FFocusControl is the internal data storage for the property.) If so, then the
reference is set to nil and the access violation is avoided.
procedure TCustomLabel.Notification( AComponent : TComponent;
Operation : TOperation );
begin
inherited Notification( AComponent, Operation );
if ( Operation = opRemove ) and ( AComponent = FFocusControl ) then
FFocusControl := nil;
end;
The notification scheme provided by TComponent and the Notification
method works very well for components that reside on the same form. However, Delphi 2.0
introduced form linking, which allows a component on one form to reference a component
located on another form. In this scenario, the Notification method is not
sufficient to ensure that the component reference remains valid. This is because the Notification
method only works for components that belong to the same Components list.
With form linking, this may not be true.
Not surprisingly, version 2.0 of Delphi extended the notification scheme to handle this
situation. The TComponent class now maintains a separate notification list called FFreeNotifies.
When a component is destroyed, its destructor iterates through FFreeNotifies and
calls the Notification method of each component on the list.
So, how does a component get on another component's FFreeNotifies list? This is
accomplished by using the new FreeNotification method. The following code fragment,
again from TCustomLabel, illustrates the technique.
procedure TCustomLabel.SetFocusControl( Value : TWinControl );
begin
FFocusControl := Value;
if Value <> nil then
Value.FreeNotification( Self );
end;
The SetFocusControl method is called when a new control is assigned to the FocusControl
property. If the control is not nil, the referenced control's FreeNotification
method is called. By passing Self as the argument to FreeNotification, the
referenced control's FFreeNotifies list will contain the current instance of the
label. Therefore, if the referenced control is ever deleted, the label's Notification will
be called even if the other control resides on a different form.
There is one more method defined in TComponent that is of particular interest to
component writers. The Loaded method is a virtual method called immediately after
all the property values of a component are read in from a form file. Since the call to Loaded
occurs before the form and component are displayed, you can perform initialization
steps without worrying about causing excessive repaints. The MediaPlayer component has a
fine example of overriding the Loaded method.
procedure TMediaPlayer.Loaded;
begin
inherited Loaded;
if ( not ( csDesigning in ComponentState ) ) and FAutoOpen then
Open;
end;
The MediaPlayer's version of Loaded first calls its inherited Loaded
method. This should always be done when overriding the Loaded method. This ensures
that any inherited properties are correctly initialized. In addition, the TComponent.Loaded
method is responsible for updating the ComponentState property by removing the csLoading
flag from the set. Next, the method checks if the component is being loaded at runtime and
if the AutoOpen property is set to True. If both conditions are met, the
MediaPlayer tries to open the media file.
TControl
Although not one of the shaded classes, the TControl class provides a majority
of the properties, methods, and events used by all visual components in Delphi. For
example, Table 2 shows some of the properties and events that are introduced in TControl.
Table 2: Properties and events defined in TControl.
Properties |
|
| Position properties |
Left, Top, Width, Height, Align |
| Client area properties |
ClientRect, ClientWidth, ClientHeight |
| Appearance properties |
Visible, Enabled, Font, Color |
| String properties |
Caption, Hint, Text |
| Mouse properties |
Cursor, DragCursor, DragMode |
Events |
|
| Left mouse button events |
OnClick, OnDblClick |
| General mouse events |
OnMouseDown, OnMouseMove, OnMouseUp |
| Drag-and-drop support |
OnStartDrag, OnDragDrop, OnDragOver, OnEndDrag |
Very few of these properties and events are declared with the published
directive. This allows descendent classes to determine which properties and events will
appear in the Object Inspector. This is a common theme in the VCL because although a
property can be made more visible in a descendent class by redeclaring it, but it cannot
be made less visible.
There are many classes that implement the properties, methods, and events of a
component but do not publish their properties and events. They leave this task to
descendent classes. The TControl class is an example of this. Likewise, there are
numerous Custom classes in the VCL that behave similarly. For example, the TCustomEdit
class provides all of the properties and methods to support the edit field control, but
very few of the properties and events are made visible. The TEdit class redeclares
the properties and events to be published.
Going back to TControl, there are a couple of properties introduced in this
class that are important to component writers. First of all, the TControl class
introduces the notion of parent controls in the VCL. The term parent used
here is a Windows-specific term. Although similar to owner, a control's parent is
the window (not the component) that contains the control. Therefore, parents must be TWinControl
objects or descendants, because a window handle is necessary in order to contain other
controls.
The second property of interest to the component writer is the ControlStyle
property. This property indicates the various styles applicable only to visual components.
The ControlStyle set can contain any number of the flags specified in Table 3. The ControlStyle
set is usually manipulated in the Create constructor of a component.
Table 3: Style flags for use in the ControlStyle property.
Style Flag |
Description |
| csAcceptsControls |
The control becomes the parent of any controls dropped onto
it at design-time. Only applicable to windowed controls. |
| csCaptureMouse |
The control captures mouse events. That is, MouseUp events
are sent to the control even if the mouse was released outside the bounds of the control. |
| csClickEvents |
When the mouse is pressed and released on the control, an
OnClick event is generated. |
| csDesignInteractive |
The control allows left mouse button events to be processed
by the control at design-time. |
| csDisplayDragImage |
The control will show the complete drag image when an object
is dragged over the control. |
| csDoubleClicks |
When the mouse is double clicked on the control, an
OnDblClick event is generated. |
| csFixedWidth |
The width of the control is not effected by scaling. |
| csFixedHeight |
The height of the control is not effected by scaling. |
| csFramed |
The control has a frame. Needed for Ctl3D effects. |
| csNoDesignVisible |
Prevents the component from being updated at design-time when
the Visible property is False. |
| csNoStdEvents |
The control does not generate the standard mouse and keyboard
events. It is used by OLE Controls. |
| csOpaque |
The control hides any items behind it, as opposed to being
transparent. |
| csReplicatable |
The control can be replicated by the TDBCtrlGrid. |
| csSetCaption |
The control's Caption or Text properties are set to match the
Name property. Only occurs if Caption/Text has not been explicitly set. |
The TControl class also implements many methods used by visual components. For
example, the event dispatch methods (for example, Click, MouseUp) supporting
all of the events listed previously are introduced in TControl. It is very common
for custom components to override these dispatch methods to provide custom handling of
events without disturbing the delegation model.
TGraphicControl
The TGraphicControl class is the base class for components that do not need to
receive the input focus or serve as a parent to other controls. Both of these tasks
require a window handle, which is not available in this class-and without a window handle,
graphic controls are Windows resource friendly. Even though the resource limits have been
increased in Windows 95 and NT, they are still finite, and it is better design to allocate
a window handle only when necessary.
Even though graphic controls do not have a window handle, this does not mean the user
cannot interact with the component-graphic controls are still able to respond to mouse
events. This is made possible by the control's parent. The parent of a control must be a TWinControl
(for example, a Form or Panel), and TWinControl components respond to mouse
messages by determining if the mouse event occurred within the bounds of any of its child
controls. If so, then a component message is sent to the child control containing the
information about the mouse event.
By default, TGraphicControl objects have no visual manifestation. However, a
virtual Paint method and Canvas property are provided for descendants. The Paint
method is called whenever the control needs to be painted, and the Canvas property
is used as a "surface" for the actual drawing of the control.
Warning: Although it is possible to set a graphic control's ControlStyle to
include csAcceptsControls, it is not valid to do so. Since a graphic control does
not have a window handle, when you attempt to drop a component on top of it, Delphi will
attempt to insert the dropped component into the window of your graphic control.
Since this is not possible without a window handle, you will experience a severe access
violation.
TWinControl
The TWinControl class is used as the base class for creating components that
encapsulate existing window controls that perform their own painting. This includes the
standard Windows controls like edit fields and checkboxes, as well as custom controls
implemented in dynamic link libraries. Since ActiveX controls are implemented as DLLs, the
component wrapper class that Delphi generates when installing an ActiveX control descends
from TWinControl.
As mentioned earlier, the TWinControl class provides the Handle property,
which is a reference to the underlying control's window handle. In addition to this
property, the TWinControl class implements the properties, methods, and events that
support keyboard events and focus changes. These additions are summarized in Table 4.
Table 4: Properties, methods, and events for the TWinControl class.
Properties |
|
| Focus properties |
TabStop, TabOrder |
| Appearance properties |
Ctl3D, Showing |
Methods |
|
| Event dispatch methods |
DoEnter, DoExit, KeyDown, KeyPress, KeyUp |
| Focus methods |
CanFocus, Focused |
| Alignment methods |
AlignControls, EnableAlign, DisableAlign, Realign |
| Window methods |
CreateWnd, CreateParams, CreateWindowHandle, RecreateWnd,
DestroyWnd |
Events |
|
| Focus events |
OnEnter, OnExit |
| Keyboard events |
OnKeyDown, OnKeyPress, OnKeyUp |
The are two methods in TWinControl that deserve a closer look. The CreateParams
and CreateWnd virtual methods are often overridden by component writers. Both
methods are called during the creation of the underlying Windows control. Whenever the
window needs to be created, CreateWnd is called. CreateWnd first calls CreateParams
to initialize a window-creation parameter record, and then calls CreateWindowHandle
to create the actual window handle using the parameter record. CreateWnd then
adjusts the size of the window and finally sets the control's font.
A component writer will typically override the CreateParams method when the
window handle to be created needs to be created using additional Windows style settings.
The method takes a TCreateParams record as its single parameter. As an example, the
TBitBtn component overrides this method to specify that BitBtn windows be created
with the bs_OwnerDraw style.
procedure TBitBtn.CreateParams( var Params : TCreateParams );
begin
inherited CreateParams( Params );
Params.Style := Params.Style or bs_OwnerDraw;
end;
CreateWnd, on the other hand, will typically be overridden when some initialization
code that depends on the existence of the window handle must be executed. For example, the
TCustomEdit component class, which is the direct ancestor of the TEdit
class, overrides CreateWnd so that it may send the em_LimitText Windows
message to the underlying edit window. This message sets the maximum length of text
allowed by the edit field. Do not access the Handle property before calling the
inherited CreateWnd method because before this method is called, the underlying
window does not yet exist, and the Handle property will reference a different or
even nonexistent window.
procedure TCustomEdit.CreateWnd;
begin
. . .
inherited CreateWnd;
. . .
SendMessage( Handle, EM_LIMITTEXT, FMaxLength, 0 );
. . .
end;
TCustomControl
The TCustomControl class is a combination of the TWinControl class and
the TGraphicControl. By descending from TWinControl, TCustomControl
inherits the ability to manage a window handle and all the features that go with it.
However, it is similar to TGraphicControl in that it provides a virtual Paint
method with an associated Canvas property. The TCustomControl class is used
as a base for components that encapsulate a window handle and must provide their own
painting routines.
The Building Process
All of the material covered thus far is important to component building. Consider the
previous material as tools of the trade. The remainder of this paper will focus on the
process of constructing a new component. Although the example component is simple, it
provides an excellent means of demonstrating the steps involved in the process. The
remainder of this paper covers the steps necessary to construct, test, and register a
component in Delphi.
Regardless of complexity, the process described here can be used when developing any
component. Figure 4 provides a graphical view of the steps involved in building a custom
component. The process begins with some initial setup, namely the creation of a directory
to hold the component unit and a test application. Components are built inside Delphi
units. They are structured very much like a Delphi form unit with the class declaration
for the component appearing in the interface section and the actual method definitions
appearing in the implementation section. The fact that a component unit and a form unit
are structured similarly is no coincidence. Remember that TForm is a descendant of TComponent,
and when you create a new form in Delphi, you are in essence creating a new form
component.

Figure 4: The process of building a custom component.
The next step is to create the component unit. As Figure 4 shows, this can be performed
either manually or by using the Component Expert. The Component Expert actually does more
than simply create the unit. It also generates placeholders for all of the basic elements
required in a component unit. These placeholders include a partially filled uses clause,
an empty class declaration, and a Register procedure. The dashed box in Figure 4
corresponds to the tasks that must be performed to produce the same output as the
Component Expert.
After the basic elements of the unit are specified, either manually or by using the
Component Expert, the next step is to fill out the class declaration and write the
supporting methods. Once the coding tasks are completed, the component can be tested.
Since components have two distinct interfaces, runtime and design-time, testing is
performed in two steps. Runtime testing can commence as soon as coding is finished, but
design-time testing can only occur once the component has been registered with Delphi and
appears on the component palette.
A Building Site
The first step in building a new component is to locate a building site. By this I mean
creating a new directory for the component unit and its associated test program. To start,
the directory will hold just the component unit file, but during the testing stage, the
directory will contain all of the files associated with a separate Delphi project.
At first, you may be tempted to place all of your component units in a single
directory. This is certainly possible, but after developing three or four component units
with test projects for each, the directory becomes so cluttered with files that it becomes
difficult to manage. Using a separate directory structure simply helps to organize all the
files associated with multiple components. It also prevents "namespace
collisions" between files that are part of different projects but have the same
names.
Creating the Component Unit
The source code for a component resides in a Delphi unit. While it is possible to place
any number of components into a single unit, generally only similar kinds of controls are
placed in the same unit. Keeping the number of components that a unit contains to a
minimum has all the same benefits that stem from modular program design. This can be
especially important when several developers are building the components in parallel.
There are two ways to create a component unit:
- Manually
- Using the Component Expert
As its name implies, the Component Expert simplifies the task of creating a component
unit. Actually, creating the unit is not what the Component Expert specializes in. The
Component Expert does more than create the unit file. It generates a syntactically correct
Delphi unit that contains a basic implementation of a component. This unit could then be
immediately installed onto the component palette.
The Expert does not generate any functionality. It simply provides a framework
or foundation around which to build the component. We'll cover the Component Expert
shortly. But first, the next section will demonstrate how to perform all of the steps that
the Component Expert does automatically.
Manual Labor
Actually creating the unit file is a simple matter of selecting the File|New menu item
in Delphi to display the New Items dialog box, and then selecting the Unit object from the
New page. The real work of creating a working component unit can be broken down into four
basic tasks:
- Specifying the uses clause
- Declaring the component class
- Implementing component methods
- Writing a Register procedure
The uses clause must specify at least the Classes unit, but typically, you will also
need to include the Controls and Forms units. If your component descends from an existing
visual component, the unit where that component is declared will also need to be
specified. Other common units to include are Messages, Windows, Graphics, and SysUtils.
Our button component will need the Classes, Controls, and Forms units. And since this
new component will be a direct descendant of the TButton class, the StdCtrls unit,
which contains the TButton class declaration, must be added as well.
The next task, declaring the component class, is a crucial step in the process because
it defines the different interfaces the component will have. Of course, before we can
declare the interfaces of the component, we need to give the component, and its class, a
name. Like most aspects of component building, there are conventions for naming
components.
Naming Conventions
The name you choose for your component class will dictate how the component will be
referenced in the Delphi environment. If the class starts with a T, which it should
by convention, Delphi strips off the first character and uses the remaining string as the
name of the component. If the class name does not start with a T, then the
entire class name is used. The resulting name is displayed as a hint when the cursor is
positioned over the component in the component palette. Likewise, this same name with a
numeric suffix (for example, Button4) is used as the default name for each
component of this type that you drop on a form.
In addition to T, it has become commonplace to use an additional prefix when
naming components. The prefix serves as an identifying string indicating the author of the
component, for example, the author's initials. But the prefix does not have to represent a
single person. It may be the name or abbreviation of a company, or even a product name.
Table 5 shows some examples of names used in some component packages currently on the
market.
Table 5: Samples of prefixes used in commercial component products.
Sample Component |
Prefix |
Company |
Product |
| TRzCheckList |
Rz |
Raize Software Solutions. |
Raize Components for Delphi |
| TOvcEditor |
Ovc |
TurboPower Software |
Orpheus |
| TwwTable |
ww |
Woll2Woll Software |
InfoPower |
Component prefixes also tend to be more practical. In order for Delphi to install a
component, its component name must be unique. Therefore, if you purchase two component
packages that each contain a TVirtualListBox component, Delphi will only allow one
of those components to be installed. This problem is further compounded by individuals
building their own custom components which can cause additional namespace conflicts.
In addition to selecting the component name, a name for the unit file must also be
selected. Do not underestimate the importance of this task. To avoid ambiguity, it is best
to have unique file names. For example, it would not be wise to use the unit name of
Buttons for the component presented in this paper, because Delphi already has a unit of
this name. As a result, like component names, unit names are commonly prefixed with an
identifier string. Keeping the unit files unique is especially important when installing
multiple components from many different sources.
Back to Work
Now that we have a name for our button component, we can get back to declaring the TRkOKButton
class. Before diving into naming conventions, it was mentioned that the class declaration
is where the different interfaces of the component are declared. Specifically, from a
user's point of view, the public and published sections of the class declaration are the
most important, because they define the runtime and design-time interfaces,
respectively.
Properties, methods, and events declared in the public section make up the runtime
interface. These items can only be called or referenced when the application using the
component is running. Properties and events declared in the published section are also
available at runtime, but more importantly are available through the Object Inspector at
design-time as well. Notice that methods are always declared as public and are thus
limited to runtime usage.
The Component Expert does not fill in the sections of the component class that it
generates. It simply generates the four different sections of the class. Therefore, it is
sufficient for now to simply declare the TRkOkButton class in the interface section
of the unit as an empty class. The properties, methods, and events of the component will
be added later. The following code fragment shows the current state of the RkBtn unit:
unit RkBtn;
interface
uses
Classes, Controls, Forms, StdCtrls;
type
TRkOkButton = class( TButton )
end;
implementation
The last task performed by the Component Expert lies in generating the Register
procedure. The Register procedure is used by Delphi to install the components that
reside in the unit. Since the Register procedure is called from outside the unit,
its procedure heading must appear in the interface section of the unit. The actual
procedure appears in the implementation section.
The Register procedure uses the RegisterComponents procedure to register
all of the components that are defined in the unit. The first parameter specifies in which
tab of the component palette the new components will appear. If the tab does not exist, it
is created. The second parameter is a set of component types that are to be registered.
Listing 1 shows the RkBtn unit complete with a Register procedure. This unit could
now be installed onto the component palette. The call to RegisterComponents
indicates that the RkOkButton component will be installed onto the Samples tab of the
component palette.
Listing 1: Stripped Down RkBtn Unit
unit RkBtn;
interface
uses
Classes, Controls, Forms, StdCtrls;
type
TRkOkButton = class( TButton )
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents( 'Samples', [ TRkOkButton ] );
end;
end.
Using the Component Expert
The Component Expert performs all of the steps described in the previous section
automatically, thereby making it a useful tool when building a new component unit. This
section describes how to use the Component Expert. Figure 5 shows the Component Expert
dialog box, which is displayed when the Component|New Component menu item is selected.

Figure 5: The Component Expert
The Component Expert requires the following four pieces of information:
- The name of the new component class
- The ancestor class from which the new component will descend
- A tab name on the component palette
- A name for the new component unit
First, enter the name to be used for the component class. Recall that component class
names must be unique and that the Component Expert will not let you enter the name of a
class that is already registered in Delphi. After entering the name to be used for the
component class, the ancestor class must be chosen. Either type in the name of the class
from which your new component will descend, or use the drop-down list. The list contains
all of the components currently registered in Delphi and is generally safer than entering
the name by hand because you won't commit any typing errors.
The last step is to select the component palette page where your new component will
appear once it is registered with Delphi. The drop-down list contains all of the current
pages in the palette. If you would like to create a new page, just enter the new name in
this field. Once all of the data is entered, press the OK button to instruct Delphi to
create the new unit. For the values shown in Figure 5, the Component Expert generates the
source code shown in Listing 2.
Listing 2: Component Template from Component Expert
unit RkBtn;
interface
uses
SysUtils, Windows, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls;
type
TRkOkButton = class(TButton)
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
published
{ Published declarations }
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TRkOkButton]);
end;
end.
Notice that the uses clause is populated with more units than the minimum
required to compile the unit under construction. By adding many of the common units to the
uses clause, most of the functionality that will be added to a component will be
available from this list of units. Fortunately, adding extra units to the uses
clause does not waste resources. Because of Delphi's smart linking, only those
program elements that are referenced somewhere in the source code are actually linked into
the final executable.
Although the Component Expert is a great tool for getting started with a component, it
can only be used to create a new component unit. It cannot add components to
existing unit files. This has to be done manually.
Customizing the Component
Once the framework for the component is created, the next step in the overall process
is to write the actual code that will customize the bare framework and thereby make it a
whole new component. This includes defining the properties, methods, and events of the
component as well as implementing the necessary support methods in the implementation
section of the unit.
This part of the process will vary widely, depending on the type of component that you
are creating. For simply overriding default values, all that is required is that the
constructor for the component be overridden. Listing 3 shows the RkOkButton component
complete with a class declaration and an overridden constructor.
Listing 3: The RkOkButton Component
unit RkBtn;
interface
uses
Classes, Controls, Forms, StdCtrls;
type
TRkOkButton = class( TButton )
public
constructor Create( AOwner : TComponent ); override;
published
property Default default True;
property ModalResult default mrOK;
end;
procedure Register;
implementation
constructor TRkOkButton.Create( AOwner : TComponent );
begin
// Don't forget to call the ancestor's constructor
inherited Create( AOwner );
Caption := 'OK';
Default := True;
ModalResult := mrOK;
end;
{========================}
{== Register Procedure ==}
{========================}
procedure Register;
begin
RegisterComponents( 'Samples', [ TRkOkButton ] );
end;
end.
Declaring the Constructor
Let's start with the class declaration. All components inherit a virtual constructor
called Create from the TComponent class. Even if the constructor is
overridden in a descendant class because it's declared as virtual, its parameter list
cannot be changed. The constructor takes a single argument, which is a reference to the
component that will own the new component. Recall that the owner is responsible for
destroying all of the components on its Components list.
When overriding default values, the only method that must be implemented is the
constructor. Listing 3 shows that the Create constructor is declared in the public
section of the class. This is done so that the Form Designer has access to the
constructor. In fact, the Create constructor for a component is always placed in
the public section.
In addition to declaring the constructor in the correct section, you must remember to
add the override directive to its declaration. If the override directive is
missing, the component will still compile, but when the component is created at
design-time by the Form Designer, the wrong constructor will be called. Omitting the override
directive breaks the virtual hierarchy chain between the constructors, and when the Form
Designer creates a new component, it utilizes polymorphism to determine the correct
constructor to call. But since the virtual chain is broken, the TButton.Create
constructor will be called instead of TRkOkButton.Create.
Redeclaring New Property Defaults
When overriding default values for a component, it is beneficial to redeclare the
properties that are being overridden and specify the new default value. The default
directive is used for ordinal properties to determine if the current property value gets
stored in the form file. When providing a new default value for an inherited property, use
the default directive in the following way:
property Height default 26;
While it is not required to redeclare the property with a new default value, it is more
efficient to do so. When a form is saved, the properties whose current values differ from
their default values are stored in the form file. When the component is loaded from the
form file, it is first created, which sets the properties to their default values. Then
the property values stored in the form file are loaded, and the component's current
property values are updated accordingly.
This process is inefficient if the constructor sets a property to the same value that
is stored in the form file. The property is thus set twice! When you override a property
value, unless you use the default directive, that property will always be in
the form file because the Form Designer will use the old default value to determine
if the property gets stored in the form file.
Implementing the Constructor
At this point, the only thing left to do is to implement the constructor. Again,
because the constructor is virtual, it should call the inherited constructor first. This
ensures that the component is properly created. When overriding default values, it is
especially important to do this because the inherited constructors are responsible for
setting the original default values that we are trying to override. If the inherited
constructor is called after setting the new default values, the original values
will be reused.
Testing the Runtime Interface
Just as in application programming, once the implementation code for the component has
been written, it's time to test it. Testing a component is a little different from testing
a complete Delphi application. The difference stems from the fact that components have two
separate, although similar, interfaces that need to be tested. When a component is used in
an application, the runtime interface of the component determines the behavior of the
component. However, when a component is dropped onto a form within the Delphi environment,
your component's behavior is dictated by its design-time interface. Both of these
interfaces need to be tested in order to create a quality component. It is quite possible
to create a component that behaves properly at runtime but improperly at design-time, and
vice-versa.
We'll first test the runtime interface of the component. We start with this one for
several reasons. First, it's much easier to test the runtime behavior of a component than
its design-time behavior. Second, for most components, the design-time interface of a
component is very similar to the runtime interface. Testing the runtime environment first
helps to ensure that the component works properly at design-time. And third, the
integrated debugger can be used for runtime debugging. This option is not available at
design-time because although Delphi was written in Delphi, you cannot use Delphi to debug
Delphi.
Creating the Test Application
To test our new RkOkButton component, we need to create a test application. This is a
primary reason for creating a separate directory for each component unit. The directory
contains the source file for the component and all the files associated with the test
project. Keeping the test application around also makes it easier to maintain your
components, because when it comes time to modify a component, you will already have the
test application already built into the same directory with the component proper.
So how will we test our new component when it doesn't appear on the component palette?
Instead of using the Form Designer to drop a component onto the form and then use the
Object Inspector to change some of its properties, we will dynamically create an instance
of the component when the form of our application is created. Dynamically creating a
component allows us to create the component without having to register it with Delphi.
Registration is the process by which Delphi becomes aware of the components that reside on
the component palette. Registration must be performed in order to test the design-time
behavior of a component, but for testing the runtime behavior it isn't necessary.
In the same directory where your component unit exists, create a new project. All you
will need to test a component is a single form. Dynamically creating the component is
summarized by the following steps:
1. Add the component unit to the form's uses clause.
2. Add a field in the form class that will be a reference to the component.
3. Create the component in the form's OnCreate event.
4. Set the Parent property of the component.
5. Set additional properties of the component as required.
Listing 4 contains the source code for the MainForm unit of the TestBtns project, which
will be used to test the RkOkButton component.
Listing 4: Test Project for RkOkButton
unit Mainform;
interface
uses
SysUtils, Windows, Messages, Classes, Graphics, Controls,
Forms, Dialogs, RkBtn, StdCtrls;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
BtnTest : TRkOkButton;
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
BtnTest := TRkOkButton.Create( Self );
BtnTest.Parent := Self;
BtnTest.Left := 100;
BtnTest.Top := 100;
end;
end.
The first step is straightforward enough. Since we will be referencing the type of the
new component, we need to include the new component unit in the form's uses clause.
The second step involves adding an object field to the public portion of the form class.
This object field will be used to reference the component that will be created, and thus
its type is that of the newly created component class. For the current test program, the BtnTest
field is declared of type TRkOkButton.
The remaining steps all involve the OnCreate event handler for the form. The
shortcut for creating the OnCreate event handler is to double click on an empty
area of the form. This creates the FormCreate method in which the BtnTest component
will be created.
The first task that must be performed within the FormCreate method is to create
an instance of the new component by calling the constructor of the component. The only
decision that needs to be made is what to specify as the owner of the component. Under
most circumstances, the owner will be the form. In the FormCreate method of Listing
4, the TRkOkButton constructor is passed the parameter Self indicating that
the main form will serve as the owner of the new component.
After the component is created, one of the most important tasks must still be
performed: setting the Parent property. Setting the Parent property puts the
current component on the parent's Controls list. A TWinControl
component, which is the only type of component that can accept controls dropped onto it,
uses the Controls list to establish the tab order and z-order among the controls it
owns. However, the most important use of the Controls list occurs when painting.
The parent component uses the Controls list to instruct all owned components to
paint themselves. Therefore, if a control does not exist on its parent's Controls
list, it will not appear when the application is executed.
Recall that only TWinControl descendants can accept controls. The
Panel and GroupBox are examples of components that can own other components. The TForm
class is also a descendant of TWinControl and thus behaves like the
others in that it can accept other components dropped onto it. For the purposes of testing
a new component, we want to mimic the action of dropping the component on the form.
Therefore, in the FormCreate method, the BtnTest.Parent property is changed
to Self, again referencing the form.
After the Parent property is set, it may be necessary to modify some of the
component's properties. This might include the position properties, Left and Top.
For testing the RkOkButton component, setting these three properties is all that is
necessary because we haven't added any other new features. For more complex components,
you may be required to set additional properties and even event handlers at this time.
Any runtime accessible property can be modified in the test application. The only real
restriction is that you must set the Parent property as described earlier before
setting any other properties. This is necessary because some property changes cause the
component to repaint itself, and if the component is not yet on the parent's Controls
list, changing these properties will not have the desired effect.
Installing a Component on the Palette
In order to proceed to the next level of testing, the component must be installed onto
the component palette. For a component to appear on the component palette, it must be
linked into a design-time package. Delphi provides a default design-time package for
custom components called DclUsr40.dpl. Figure 6 shows how this default package can be
selected from within the Component Expert.

Figure 6: Selecting the design-time package that will contain the new
component.
The Component Resource
There is an additional task that is performed by the install process. When a component
unit is installed, Delphi searches the directories listed in the search path for a file
that has the same name as the unit but with a .dcr extension. DCR stands for Delphi
Component Resource, and a DCR file is actually a Windows resource file. If a component
resource file is found for a component being installed, its contents are also linked into
the package. The main purpose of the component resource file is to specify the bitmaps
that are used to represent the components on the component palette.
The Image Editor that ships with Delphi can be used to create a DCR file. However, I
prefer to use Resource Workshop to create component resource files. Of course, any tool
that can build a compiled (.RES) resource file will suffice. You could even use a text
editor to create a .RC file that includes bitmaps that were created with Windows
Paintbrush. The BRC resource compiler could then be used to build the .RES file. Just
don't forget to change the extension of the file to .DCR.
Inside the component resource file, create a 24x24 pixel 16-color bitmap for each
component defined in the corresponding unit. In order for Delphi to link each bitmap to
its associated component, give each bitmap the same name as the component's class name.
The only difference is that the bitmap name is in all upper case.
The lower left pixel in the bitmap serves as the transparent color indicator. All other
pixels in the bitmap with this same color will appear transparent. The BitBtn and
SpeedButton components also treat bitmaps in this fashion. If you do not create a
component resource file for your new components, Delphi will use the bitmap of its
ancestor if it has one, or it will use a default bitmap.
Testing the Design-Time Interface
Once the component is installed on the palette, it can be used in a Delphi application.
Of course, it would be helpful to at least test its design-time behavior before doing so.
Testing the design-time features of a component can become quite involved, especially for
a component with a great many properties. For the RkOkButton component, there is not much
to test because we did not add any new published properties or events that would be
accessible through the Object Inspector. In this case, all we need to do is drop an
instance on a form and check if it is created with the new size values in force.
Adding Additional Components
Now that we have a working component, let's extend the RkBtn unit by adding a Cancel
button. Adding the TRkCancelButton class to the RkBtn unit must be done manually,
because the Component Expert can only be used to create a new unit--not to enhance an
existing one. Listing 5 shows the complete source code for the RkBtn unit that is included
on the companion disk. Both additional button components override their constructors and
redeclare the appropriate property values. The constructor for TRkOKButton sets the
Default property (not to be confused with the default directive) to True
and ModalResult to mrOk. Likewise, TRkCancelButton.Create sets the Cancel
property to True and ModalResult to mrCancel.
Listing 5: RkBtn.pas--The RkBtn Unit.
unit RkBtn;
interface
uses
Classes, Controls, Forms, StdCtrls;
type
TRkOkButton = class( TRkButton )
public
constructor Create( AOwner : TComponent ); override;
published
property Default default True;
property ModalResult default mrOK;
end;
TRkCancelButton = class( TRkButton )
public
constructor Create( AOwner : TComponent ); override;
published
property Cancel default True;
property ModalResult default mrCancel;
end;
procedure Register;
implementation
constructor TRkOkButton.Create( AOwner : TComponent );
begin
inherited Create( AOwner );
Caption := 'OK';
Default := True;
ModalResult := mrOK;
end;
constructor TRkCancelButton.Create( AOwner : TComponent );
begin
inherited Create( AOwner );
Caption := 'Cancel';
Cancel := True;
ModalResult := mrCancel;
end;
{========================}
{== Register Procedure ==}
{========================}
procedure Register;
begin
RegisterComponents( 'Samples', [ TRkOkButton, TRkCancelButton ] );
end;
end.
Conclusion
With the material covered at the beginning of this paper and a firm understanding of
the process of building a component, you are now ready to enter the wonderful world of
custom component building. One final note: although much of the focus of this paper was on
the process, the components presented do represent a certain class of components that can
be created. Overriding defaults is a very viable reason to create custom components. The
process of overriding default values can be applied to any component by following the
process outlined in this paper. More important, however, is the fact that creating any
type of component in Delphi follows this same process.
Contact Information
This paper is an adaptation of material originally presented in Developing Custom Delphi 3 Components by Ray Konopka, published by Coriolis Group Books.
Ray Konopka is the founder of Raize Software Solutions, Inc., and the chief architect for their Raize Components and CodeSite products. Ray is also the author of Developing Custom Delphi 3.0 Components and the popular Delphi by Design column in Visual Developer Magazine. Ray specializes in Delphi component development and is a frequent speaker at developer conferences.
rkonopka@raize.com
|