Introduction to Component Building by Ray Konopka

By: David Intersimone

Abstract: This article describes the steps involved in creating a new VCL component and focuses on several key aspects of the VCL architecture upon which all components are built.

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

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 1

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

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:

  1. Specifying the uses clause
  2. Declaring the component class
  3. Implementing component methods
  4. 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

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

Ray Konopka rkonopka@raize.com
Raize Software Solutions, Inc. http://www.raize.com

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



Server Response from: ETNASC02