This paper describes the critical extra steps that must be taken in order to develop professional, commercial-quality components. Specific topics will include creating online component help, effectively designing packages for component distribution, supporting multiple versions of Delphi, and how supporting automatic one-step installation.
Once a component has passed testing, the component building process is often
considered complete, and the component distributed to end users. Although this
can be done (and all too often is done) the results can be detrimental to the
overall success of the component. This is especially true if you are writing
components that will be used by other developers, or if you plan on selling your
components commercially.
The truth is that the commercial component market simply will not tolerate
unprofessional components, and releasing a component without professional
features, such as online help, will not be accepted by this ever-growing market.
This paper shows you how to add the professional features necessary to make
your components suitable for commercial release. Note that this does not mean
that these techniques should only be used if you are creating a third-party
component library-on the contrary, these techniques are just as beneficial to
in-house software departments as they are to commercial customers.
So, what are these professional features? As mentioned above, a professional
component must provide online help accessible through the Delphi environment.
Because Delphi provides an extensible help system, and providing online help
support requires no changes to the component itself, there is no excuse not to
provide this feature.
Another feature for consideration is support for internationalization. This
does not necessarily mean creating international versions of your component.
Instead, it means taking the necessary steps that make this task easier if it
ever needs to be performed. Specifically, components that use literal strings
should consider using a string table resource.
It has become common practice for third-party developers to include the
source code for their components with the shipping library. I fully recommend
this, by the way. However, doing so presents a potential problem. This problem
may arise if the user decides to recompile your source code-to include debug
information, for example. Because you cannot be certain how Delphi will be
configured for each user, you should take special precautions to ensure that
your components compile correctly-or you'll suffer the headache of many support
calls.
And let's not forget about deployment and installation. Although installing
new components in Delphi is relatively easy, there are some techniques that can
be used to make the installation process run more smoothly.
Online Help
There are two ways in which a user can request context-sensitive help on a
component within the Delphi design environment. First, the user can press F1
while the Object Inspector has the focus. In this case, Delphi displays the help
associated with the currently highlighted property or event. The second way
occurs when the user presses the F1 key when a form has the focus. In this
instance, Delphi displays the help screen associated with the currently selected
component.
However, context-sensitive help is only available for the components,
properties, and events that Delphi knows about. For example, if we drop a new
custom component onto a form, select one of its properties in the Object
Inspector, and then press F1, Delphi displays the Topics Found dialog box with a
list of topics. Unfortunately, none of these topics will be correct because we
have not yet instructed Delphi where to find help for the new component. In
order to do this, we must first create the component help file.
Creating The Help Document
The first step in creating a component help file is to create a Rich Text
Format (RTF) file that contains the help information to be displayed. This RTF
file will be referred to as the help document for the component. This section is
not intended to describe the entire process of creating a Windows help file.
Instead, it focuses on the issues specific to building a component help
document.
For a step-by-step guide through the process of creating a general help file,
take a look at the Creating Windows Help file, CWH.HLP. It can be found in the
Delphi 1 Bin directory or with the source code included with this paper. It is
very well organized, and demonstrates many online help features.
There are two basic ways of creating the help document. First, any word
processor, like Microsoft Word or WordPerfect, that can handle RTF files can be
used. Second, a help-authoring tool like HelpScribble, DotHelp, ForeHelp or
RoboHelp can be used to create the document. The example presented in this paper
uses the word-processor approach to avoid describing product-specific features.
The help document is organized into a series of pages, with each page
representing a help screen. For example, selecting the Help|Contents menu item
in Delphi displays the Help Topics screen. Each page specifies several
footnotes. These footnotes convey information to the search engine and are not
displayed in the resulting help file. Table 1 lists the common footnote types.
Table 1: Footnotes processed by WinHelp.
| Symbol |
Identifies |
Purpose |
| # |
Context String |
Used to uniquely identify the topic. |
| $ |
Topic Title |
Used to specify title of a topic. This title is displayed in the Topics
Found and History windows. |
| K |
Visible Keywords |
Used to specify words or phrases associated with a topic. Each keyword
is listed on the Index page in the Help Topics dialog box. |
| A |
Invisible Keywords |
Used to specify words or phrases associated with a topic. A-keywords
never appear on the Index page in the Help Topics dialog box. |
Jumping to a related topic and displaying a popup window are two basic
features used quite extensively in component help files. Each feature relies on
a hotspot on which the user clicks to start the action. The two types of
hotspots are distinguished by how they are formatted. In particular, to create a
hotspot that jumps to another topic, apply double-underline formatting to the
text. Immediately after the hotspot text, you must specify the context string of
the topic to jump to, but the context string must be formatted as hidden text.
The same steps are used to create a hotspot that displays a popup window, but
instead of using a double-underline, the hotspot text is formatted with a single
underline.
Let's take a look at a real example. Figure 1 shows the first page of the
RkLaunch.rtf file, which contains the online help information for the RkLauncher
component. The figure has been annotated to indicate the various font styles and
sizes used throughout the page. The first page in a help document generally
presents an overview of the component and provides links to the component's
properties, methods, and events.

Figure 1: Any word processor that supports RTF files can be used
to create a help document.
In Delphi 1 and 2, these links are located beneath the title and appear as
hotspots to popup windows. The links in Delphi 3 and later also appear as
hotspots below the title, but instead of invoking a popup window, a separate
secondary window is displayed.
I used a slightly different approach in designing the help for this book. The
main page for each component contains a link to a secondary Class Inspector
window, which separates a component's properties, methods, and events into three
pages within the window. Once the Class Inspector is displayed, it remains
visible allowing the user to quickly jump from one item to another within the
component class. Figure 2 shows the Class Inspector in action.

Figure 2: The Class Inspector help window makes it easy to jump
between a component's properties, methods, and events.
The link to the Class Inspector is defined as a topic jump. This can be seen
in Figure 3, which shows the hidden context strings following the link. The same
technique is also used for creating links to items from within the component's
description. Notice that FileName in the description body is specified as a
topic jump to that property.

Figure 3: A topic jump consists of a label followed by a context
string, which must be formatted as hidden text.
Now let's take a closer look at the footnotes specified in Figure 1. The
lines on the left map each footnote symbol in the body to its corresponding
footnote string. The #-footnote specifies the context string for this topic to
be hlp_TRkLauncher. The $-footnote specifies the title of the topic, and the
K-footnote specifies two search strings, "TRkLauncher" and
"Launcher." These search strings will appear on the Index page in the
Help Topics dialog box. Therefore, the user can find help for this component by
searching for "TRkLauncher" or just "Launcher."
A-Footnotes
The A-footnote specified in Figure 1 deserves special attention. A-footnotes
were introduced in version 4 of the WinHelp engine, and are similar to
K-footnotes except they do not appear on the Index page of the Help Topics
dialog box. A-footnotes have a special purpose in Delphi-they are the mechanism
Delphi uses to support context-sensitive help within the IDE.
When the F1 key is pressed, Delphi instructs WinHelp to search for an
A-footnote that matches one of the patterns listed in Table 2. If more than one
match is found, WinHelp displays the Topics Found dialog box listing all of the
matches. Otherwise, the topic defining the A-footnote is displayed.
Table 2: A-footnote formats used by Delphi.
|
Format
|
Example
|
Use for
|
|
ClassName
|
TRkLauncher
|
Main topic page
|
|
ClassName_PropName
|
TRkLauncher_FileName
|
Component-specific property
|
|
ClassName_MethodName
|
TRkLauncher_Launch
|
Component-specific method
|
|
ClassName_EventName
|
TRkLauncher_OnFinished
|
Component-specific event
|
|
PropName_Property
|
Parameters_Property
|
General property
|
|
MethodName_Method
|
Execute_Method
|
General method
|
|
EventName_Event
|
OnClick_Event
|
General event
|
Following the guidelines in Table 2, Figure 1 illustrates how to define an
A-footnote for a component's main topic page. Likewise, Figure 4 shows that the
Parameters property topic page defines the A-footnote as "TRkLauncher_Parameters."

Figure 4: Each property, method, and event occupies its own page
within the help document.
I recommend using the component-specific A-footnote formats because they have
the best chance of being unique across all components and help files. This means
your users will be able to access your online help more quickly.
To provide a link to an item that exists in the Delphi help files-a property
for an ancestor class, for example-use the AL (ALink) macro. For example, the
following command is used to jump to the BorderWidth property of TCustomPanel:
!AL(TCustomPanel_BorderWidth,1)
See the online help for Help Workshop for more information on the AL macro.
Creating The Help File
After the help document is written, the help file (*.hlp) can be created.
This is accomplished by creating a help project file that specifies which help
documents will be contained in the help file. Since a single help file can
consist of any number of help documents, it's a good idea to create a separate
help document (*.rtf) file for each component.
Although you can create the help file by hand (after all, it's just a text
file), it's much easier to use the Help Workshop. Located in the Delphi
HelpTools directory, Help Workshop is a Microsoft utility program for creating
WinHelp version 4 help files. Figure 5 shows the DCDC.hpj project file being
edited in Help Workshop.

Figure 5: Help Workshop automates the mundane tasks of creating and
managing a help project.
Notice how each component help document (*.rtf) is specified in the [FILES]
section. Documents are added to this section by clicking on the Files button.
For a description of other features, refer to Help Workshop's online help. After
all help documents are added, click on the Save And Compile button to generate
the help file. Compiling the project in Figure 5 generates the DCDC.hlp file.
Creating The Contents File
In addition to creating help files, Help Workshop can create contents files
(*.cnt). When WinHelp opens a help file, it checks to see if a contents file of
the same name exists. If so, WinHelp creates a Contents page in the Help Topics
dialog box. The Contents page is like a table of contents for your help file,
but instead of specifying page numbers, the contents list provides topic jumps
into the help file.
Like a help project file, a contents file is a simple text file. But rather
than create the file manually, use the Help Workshop as illustrated in Figure 6.
When the DCDC.hlp file is opened in WinHelp, the Help Topics dialog box contains
a Contents page as shown in Figure 7

Figure 6: Use Help Workshop to build hierarchical contents files.

Figure 7: WinHelp adds a Contents page to the Help Topics dialog box
when it finds a contents file matching the name of the help file being opened.
We now have a compiled help file and a contents file. However, before this
new information can be displayed from within Delphi, we must tell Delphi where
to find it.
Installing Component Help
Merging our new help file with Delphi's involves two steps. Unlike the two
steps required in Delphi 1 and 2 (generating keywords and running HelpInst), the
steps required in Delphi 3 and later are much easier and, more importantly, can
be automated.
The first step is to add two entries under the HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsHelp
registry key. One for the help file, the other for the contents file. Each entry
specifies the name of the file and its location on the system. Table 3 shows the
values used for the DCDC.hlp and DCDC.cnt files.
Table 3: Registry entries for component help files.
|
File
|
String Value
|
|
DCDC.hlp
|
C:DCDCHelp
|
|
DCDC.cnt
|
C:DCDCHelp
|
The second step is to merge the new component help file with Delphi's. This
step is necessary in order for Delphi to provide context-sensitive help for your
components. Merging is accomplished by adding two lines to the Delphi3.cnt file.
For example,
:Include dcdc.cnt
:Link dcdc.hlp
or
:Include dcdc.cnt
:Index dcdc.hlp
When adding lines to a contents file, make sure the last line ends with a
CR-LF pair. Otherwise, the last command will not be executed.
The Include command adds the specified contents file to Delphi's contents
list. The effect of this statement is shown in Figure 8. The Link and Index
commands instruct the WinHelp engine to search for keywords in the specified
file in addition to the Delphi3.hlp file. The difference between the two is that
the Index command adds the specified file's keyword index to Delphi's index.
The Link command is used to merge component help files with the Delphi help
system to avoid exceeding the maximum index size limit.

Figure 8: Our new component help file is seamlessly integrated into
the Delphi help system.
After completing these two steps, context-sensitive help can be requested for
new custom components as illustrated in Figures 9 and 10.

Figure 9: Pressing F1 from within the IDE displays help for the
selected RkLauncher component.

Figure 10: Use the Class Inspector to jump to a particular property,
method, or event.
String Tables
String tables are one of the least-used resource types, which is a shame
because they are so easy to use in Delphi, and even easier to use in Delphi 3
and later. String tables store strings that are used in an application. Because
the strings are part of the resource file, they can be changed without having to
recompile the source code. In addition, moving literal strings from code to a
string table frees memory in the data segment.
I highly recommend storing error messages, prompts, and other text in string
tables. Besides the advantages given in the previous section, if you're
considering distributing your components internationally string tables must
handle language differences.
The Easy Way
Delphi 3 and later versions encourage the use of string tables by introducing
a new feature that makes it extremely easy to create string resources.
Traditionally, string tables had to be created using a tool other than Delphi.
Delphi 3 introduces the new resourcestring reserved word directly inside a unit
to specify string table entries.
As an example, consider the constructor for the TRkAddress component, which is
shown in Listing 1. In particular, notice the
literal strings being passed to the CreateLabel method. These strings are good
candidates to be converted to resource strings.
Listing 1: The TRkAddress constructor using literal strings.
constructor TRkAddress.Create( AOwner : TComponent );
begin
inherited Create( AOwner );
. . .
FLblFirstName := CreateLabel( 'First Name' );
FEdtFirstName := CreateEdit;
FLblLastName := CreateLabel( 'Last Name' );
FEdtLastName := CreateEdit;
FLblStreet := CreateLabel( 'Street' );
FEdtStreet := CreateEdit;
FLblCity := CreateLabel( 'City' );
FEdtCity := CreateEdit;
FLblState := CreateLabel( 'State' );
FCbxState := CreateCombo;
FLblZIP := CreateLabel( 'ZIP' );
FEdtZIP := CreateEdit;
. . .
end;
Listing 2 highlights the changes needed to convert from literal strings to
resource strings. As you can see, the resourcestring keyword is used in a
similar fashion to the const keyword. In fact, resource strings are used just
like string constants as demonstrated in the new calls to CreateLabel. Rather
than pass a literal string, the new resource string identifier is used.
Listing 2: The TRkAddress constructor using resource strings.
implementation
uses
Windows;
resourcestring
SAddrFirstName = 'First Name';
SAddrLastName = 'Last Name';
SAddrStreet = 'Street';
SAddrCity = 'City';
SAddrState = 'State';
SAddRkIP = 'ZIP';
constructor TRkAddress.Create( AOwner : TComponent );
begin
inherited Create( AOwner );
. . .
FLblFirstName := CreateLabel( SAddrFirstName );
FEdtFirstName := CreateEdit;
FLblLastName := CreateLabel( SAddrLastName );
FEdtLastName := CreateEdit;
FLblStreet := CreateLabel( SAddrStreet );
FEdtStreet := CreateEdit;
FLblCity := CreateLabel( SAddrCity );
FEdtCity := CreateEdit;
FLblState := CreateLabel( SAddrState );
FCbxState := CreateCombo;
FLblZIP := CreateLabel( SAddRkIP );
FEdtZIP := CreateEdit;
. . .
end;
By Convention: Constants representing string resource identifiers start with the letter S.
When the new version of the RkAddr unit is used by a package or an
application, Delphi automatically generates string table entries corresponding
to the strings specified in the resourcestring block. As a result, wherever a
resource string identifier is referenced, Delphi generates the code to load the
string value from the string table.
The Traditional Approach
As simple as the resourcestring approach is, it may be more appropriate to
use the old-fashioned approach of creating the resource file manually. The main
reason to do this is to gain access to a numeric identifier that can be used to
access the desired string. Consider the following
CreateLaunchError function shown in Listing 3. Notice that the LoadStr function
is used to return the desired string value. However, in this example, the string
is indexed using the numeric error code.
Listing 3: A new and improved CreateLaunchError method.
uses
ShellApi, LnchMsgs;
function CreateLaunchError( ErrCode : Integer ) : ELaunchError;
begin
Result := ELaunchError.Create( LoadStr( SLaunchOutOfMemory + ErrCode ));
Result.ErrorCode := ErrCode;
end;
The resourcestring approach described earlier cannot be used in this way
because each string constant is just that-a string, and therefore cannot be used
as an offset. Plus, the error codes are not contiguous. When resourcestring is
used, Delphi automatically assigns numeric values for each string in the table.
Typically, these values are contiguous, but because Delphi handles ID conflicts,
there is no way to predict the numeric value. In order for the approach
demonstrated in Listing 3 to work, we must be able to control which id matches a
particular string. Therefore, we must create a string table by hand.
Creating A String Table
A string table can be created in one of two ways. First, a Windows resource
editor, such as Resource Workshop, can be used to create the table. Secondly,
you can create a string table manually, whichbecause of the simple structure of
the string table resource type, is not an unreasonable alternative.
The format of a string table is quite simple. Each entry in the table
consists of a numeric identifier and an associated string. The identifier is
used to reference the particular string from within code. Because the integer
identifiers have less meaning than their string counterparts, it is common to
create a set of constants to represent each string resource identifier.
Listing 4 shows the source code for the LnchMsgs unit, which only consists of
an interface section that declares constants for each of the strings to be
placed in the string table. The starting value of 42000 is an arbitrary value
chosen to avoid conflicting with other string resource identifiers.
Listing 4: LnchMsgs.pas-This unit defines string resource identifiers.
unit LnchMsgs;
interface
{$R LNCHMSGS.RES} // Link in Win32 resource file
const
SLaunchOutOfMemory = 42000;
SLaunchFileNotFound = 42002;
SLaunchPathNotFound = 42003;
SLaunchSharingViolation = 42005;
SLaunchSeparateDataSeg = 42006;
SLaunchInsufficientMemory = 42008;
SLaunchIncorrectWindowsVer = 42010;
SLaunchInvalidEXE = 42011;
SLaunchIncorrectOS = 42012;
SLaunchForMSDos4 = 42013;
SLaunchUnknownType = 42014;
SLaunchLoadRealMode = 42015;
SLaunchNonReadOnlyDataSeg = 42016;
SLaunchCompressedEXE = 42019;
SLaunchInvalidDLL = 42020;
SLaunchWin32 = 42021;
SLaunchNoAssociation = 42031;
implementation
end.
Creating the LnchMsgs unit serves three purposes. First, it will be used by
the code modules that need to reference these strings; for example, the RkLaunch
unit. Second, the LnchMsgs.pas file can be included into the LnchMsgs.rc
resource file shown in Listing 5. This allows the resource file to use the same
constants, which ensures that the correct string is synchronized to a particular
ID. Third, the LnchMsgs unit is responsible for including the LnchMsgs.res file
into the current project. LnchMsgs.res is the compiled version of LnchMsgs.rc.
Listing 5: LnchMsgs.rc-String resource file for launch messages.
#include "lnchmsgs.pas"
STRINGTABLE
{
SLaunchOutOfMemory, "Out of memory or executable file is corrupt"
SLaunchFileNotFound, "File was not found"
SLaunchPathNotFound, "Path was not found"
SLaunchSharingViolation, "Sharing violation or network error"
SLaunchSeparateDataSeg, "A library required separate data segments for each task"
SLaunchInsufficientMemory, "Insufficient memory to start application"
SLaunchIncorrectWindowsVer, "Incorrect version of Windows"
SLaunchInvalidEXE, "File is not a Windows application"
SLaunchIncorrectOS, "Application was designed for a different operating system"
SLaunchForMSDos4, "Application was designed for MS-DOS 4.0"
SLaunchUnknownType, "Unknown executable file type"
SLaunchLoadRealMode, "Cannot load a real-mode application"
SLaunchNonReadOnlyDataSeg, "Cannot load a second instance of an executable file containing multiple, non-read-only data segments"
SLaunchCompressedEXE, "Cannot load a compressed executable file"
SLaunchInvalidDLL, "A dynamic-link library (DLL) file is invalid"
SLaunchWin32, "Application requires Windows 32-bit extensions"
SLaunchNoAssociation, "No association for specified file type"
}
Once the resource file is created, it needs to be compiled into a binary RES
file so that it can be referenced by the LnchMsgs unit and thus be included into
the current project using the RkLauncher component. If you are using Resource
Workshop, the RES file can be created when the RC file is saved by making sure
the RES Multi-save option is checked in the Preferences dialog box.
If you created the RC file manually, you will need to use the command line
resource compiler to create the corresponding RES file. For the LnchMsgs.rc file
the following command is entered at a DOS prompt:
C:> BRC32 -R LnchMsgs
BRC32 is the Borland Resource Compiler that comes with Delphi 3. It can be
found in the Delphi Bin directory. The -R option indicates that the LnchMsgs.rc
file should be compiled only. The BRC32 program is also capable of binding a
resource file to an existing executable file. For this example, we only need to
create the RES file because the binding will be handled by the Delphi compiler.
Once the LnchMsgs.res file is created, it needs to be linked into the
component unit using the constants. This is accomplished by using the {$R}
compiler directive as demonstrated back in Listing 4.
A Common Include File
I mentioned at the beginning of this paper that it has become common for
vendors to include the source code for their components. If you plan on doing
this, you should seriously consider using a common include file in all of your
component units. Listing 6 shows the include file used by all of the components
presented in this book. The purpose of this file is to set compiler directives
and conditional defines that govern how the components are compiled.
Listing 6: DCDC.inc-A common include file for components.
{===============================================================================
DCDC Include File
This file is included into each component unit and serves as a common
place to add conditional defines and compiler directives to be used by all
component units.
Copyright ) 1995-2000 by Ray Konopka
===============================================================================}
{$IFDEF WIN32}
{$DEFINE D2_OR_HIGHER}
{$ENDIF}
{$IFNDEF VER80}
{$IFNDEF VER90}
{$IFNDEF VER93}
{$DEFINE D3_OR_HIGHER}
{$IFNDEF VER100}
{$DEFINE BCB3_OR_HIGHER}
{$IFNDEF VER110}
{$DEFINE D4_OR_HIGHER}
{$IFNDEF VER120}
{$DEFINE BCB4_OR_HIGHER}
{$IFNDEF VER125}
{$DEFINE D5_OR_HIGHER}
{$ENDIF}
{$ENDIF}
{$ENDIF}
{$ENDIF}
{$ENDIF}
{$ENDIF}
{$ENDIF}
{$IFDEF VER93}
{$DEFINE BCB_COMPILER}
{$ENDIF}
{$IFDEF VER110}
{$DEFINE BCB_COMPILER}
{$OBJEXPORTALL ON}
{$ENDIF}
{$IFDEF VER125}
{$DEFINE BCB_COMPILER}
{$OBJEXPORTALL ON}
{$ENDIF}
{== Code Generation Directives ==}
{$F-} { Force Far Calls }
{$A+} { Word Align Data }
{$U-} { Pentium-Save FDIV }
{$K-} { Smart Callbacks }
{$W-} { Windows Stack Frame }
{== Runtime Errors ==}
{$IFOPT D+}
{$R+} { Range Checking - On - if compiled with Debug Information }
{$ELSE}
{$R-} { Range Checking - Off - if compiled without Debug Information }
{$ENDIF}
{$S-} { Stack Checking }
{$I+} { I/O Checking }
{$Q-} { Overflow Checking }
{== Syntax Options ==}
{$V-} { Strict Var-Strings }
{$B-} { Complete Boolean Evaluation }
{$X+} { Extended Syntax }
{$T-} { Typed @ Operator }
{$P+} { Open Parameters }
{$IFDEF D4_OR_HIGHER}
{$J+} { Writeable Typed Constants }
{$ENDIF}
{== Miscellaneous Directives ==}
{$C MOVEABLE DEMANDLOAD DISCARDABLE} { Code Segment Attribute }
{$G+} { Delphi 1: 286 Instructions / Delphi 3 & later: Imported Data }
{$N+} { Numeric Coprocessor }
{$Z-} { Word Size Enumerated Types }
{$IFDEF WIN32}
{$H+} { Long String Support }
{$ENDIF}
Using an include file is especially important when distributing the source
code along with the compiled versions of your components. If the user decides to
recompile your code, you cannot assume that the compiler settings on that
machine are the same as the ones you used to develop the component. Therefore,
by creating a common include file, no matter what the current compiler settings
are, your component units will always be compiled with the correct settings.
For these same reasons, a common include file is essential for multi-person
development teams. The include file guarantees that everyone builds the
components using the same options.
An interesting aspect of this include file is that it does not specify any
settings for the $D, $L, or $Y directives. This is because these directives are
associated with including debug information into the compiled unit. Since these
options have no impact on how the code is compiled, it is not necessary to
specify these. Besides, when a library is built for distribution, debug
information is usually turned off. However, the component user may opt to
recompile the source code and include debug information. The point is that the
common include file does not prevent the user from doing this.
Also notice that this is an include file and not a unit file. Therefore, the
Object Pascal include file directive $I is used to effectively embed the
contents of the DCDC.inc file into the current source file. As an example, the
following code fragment shows the first few lines of the RkPrgres unit:
{=========================================================================
RkPrgres Unit
Developing Custom Delphi Components
=========================================================================}
{$I DCDC.INC}
unit RkPrgres;
interface
uses
Windows, Classes, Graphics, Controls, Menus, ExtCtrls, RkCommon;
type
TRkProgressBar = class( TGraphicControl )
private
What if you have a component unit that needs to use a different compiler
directive? In situations like this, specify the new directive after the include
file statement. This way, you can essentially override any of the common
settings on a unit-by-unit basis.
Packaging
When you create a new custom component, it is very tempting to add the
component to the DclUsr50 package so that it can be quickly installed into
Delphi. This is fine for testing, but when it's time to
distribute your components to other developers, you'll want to supply custom
runtime and design packages for your components. To understand why, we need to
take a closer look at the DclUsr50 package.
DclUsr50 is defined as a design-only package, meaning that it can be loaded
by the IDE in order to install the components that reside within it. Note that
design packages are only used by the IDE-they are never distributed with an
application. When an application is "built with packages," the program
is compiled so that it utilizes runtime packages. This is a subtle, but
important point.
The predefined packages that ship with Delphi come in runtime/design pairs.
For example, the runtime package containing the database related components is
VclDb50. Its design counterpart is DclDb50. To distribute a database application
that uses packages, you also need to distribute the VclDb50.dpl package. If the
recipient of your application already has this package, then this step is not
necessary. This works because there is only one version of the VclDb50 package.
So what does this have to do with DclUsr50? Actually, quite a bit. DclUsr50
does not have a runtime counterpart, which is not surprising because every
developer will have a different set of components within that package. And
without a runtime package, all of the components inside DclUsr50 must be
statically linked into an application. For one or two components, this won't
have much of an impact, but for an entire library of components, installing them
into DclUsr50 defeats the purpose of runtime packages.
Yet, this is not the only reason to supply your own packages. As we will see,
installing a pre-built package is much easier than adding component units to
DclUsr50. In fact, the entire process can be automated.
Creating Custom Packages
There are four steps required to create a package. First, you must decide
which type of package to create. There are actually four possible choices, but
for component packaging, we can limit the selection to runtime-only versus
design-only. Runtime-only packages contain only the units necessary to implement
one or more components. A runtime package should never contain property editors,
component editors, or other design- specific code. This type of functionality
should be placed in a separate design package. I recommend creating the runtime
package first because, as we'll see, doing so simplifies the construction of the
design-time package.
Next, you must select a name for your package. This may sound like a trivial
task, but it is very important to select a name wisely. Recall that packages are
designed to be distributed to users. Therefore, package names should be unique
even across different versions of the same package.
The third step in creating a package is to determine the units to be
contained in the package. For runtime packages, you must specify the units that
implement the components to be distributed in the package. For example, the
Dcdc20 package contains the RkTabLst unit, which defines the TRkTabbedListBox
component class. Secondary units used by the component units must also be added.
For example, the IntList unit is added because it is used by the RkTabLst unit.
For design packages, you must specify design-related units, which includes units
that implement property editors, component editors, and registration units.
Any number of units can be added to a package. The only restriction is that
you should not add a unit that is already contained in another package. Doing so
prevents Delphi from being able to load both packages at the same time.
The fourth step is to determine which packages are required by the new
package. An example will help. Consider a runtime package that contains the
RkTrkBar unit. One of the units used by RkTrkBar is Classes. Because Classes is
contained in the Vcl50 package, Vcl50 is required by the new package. Design
packages often require their runtime counterparts, because the runtime version
contains all the component units used by the editors contained in the design
package.
The Package Editor
The easiest way to create a new package is to use the Package Editor. Start
by selecting File|New and then double-click the Package icon. You will then be
asked to enter a file name and description for the new package. By default, the
new package will be created in the current directory. To specify a new location,
click the Browse button. When entering the package name, it is not necessary to
specify a file extension; Delphi automatically adds a DPK extension to indicate
a package source file. Clicking on the OK button instructs Delphi to generate
the source file and open it up in the Package Editor.
At this point, the package is empty. But before we start adding units, we
should specify whether this is a runtime or design package. By default, new
packages created in the editor are defined as design-only. To change this, click
on the Options button to display the dialog box shown in Figure 11. Make the
appropriate selection on the Description page, and then close the Options dialog
box to continue editing the package.

Figure 11: To create a runtime package, you must change the usage
option from design to runtime.
Next, we need to add the units that will be contained in the package. Simply
click on the Add button and select the desired unit. You can also create a new
component from the Add dialog, but I don't recommend it because the component
resource file is not automatically added to the package at this point in the
process.. Figure 12 shows that the
Dcdc20.dpk runtime package contains all of the non-data-aware component units
presented in this book. Following Borland conventions, I've isolated the
data-aware components and placed them into a separate package; specifically,
DcdcDb20.dpk.

Figure 12: The main window of the Package Editor isolates the Contains
list from the Requires list by displaying it on a separate page.
After populating the Contains list, we need to specify the required packages.
Switch to the Requires page and click on the Add button to select a required
package. By default, the Vcl50 package is automatically added to the requires
list. If you don't add any required packages, when Delphi attempts to compile
the package it will inform you if any packages should be added. To save the
package source file, select File|Save or right-click in the editor and select
Save Package.
Next, you need to compile the package source file by clicking the Compile
button. Delphi creates two files when a package is successfully compiled. The
first is a package library file (*.dpl), which contains the machine code for the
components in the package. The second is a package symbol file (*.dcp), which
contains the symbol information for all of the units in the package. Both of
these files are necessary to compile an application that uses any of the
components contained in the package.
Preventing Unauthorized Access
With the runtime packages created, we can now shift our attention to the
design package. However, before we dive into details, we need to examine a
problem that may arise with the way the Component Expert generates a component
unit. The Component Expert automatically generates a
Register procedure at the end of the unit. Under Delphi 1 and 2, having the
Register procedure in the component unit poses no threat. In fact, it is quite
useful because it allows individual components to be installed.
Unfortunately, under Delphi 3 and later, leaving the Register procedure in your
component unit can have undesirable effects. Specifically, your components can
be installed by others even without your component unit or package!
This situation arises when someone uses your component in a property editor
or component editor, and then implicitly imports your unit into their design
package. A unit is implicitly imported into a package when the runtime package
containing the unit is not specified on the package's requires list.
When the property editor or component editor using your component is
installed, your component will also be installed because Delphi finds the
Register procedure defined in the component unit. This results in your component
appearing on the component palette even though your component unit and package
may not be present.
Fortunately, even though your component can be placed onto a form, the
application cannot be compiled because the user does not have the component unit
nor the package. But this is still certainly an undesirable effect.
There are two ways to prevent the above scenario from occurring. The first is
for property editor and component editor writers to avoid implicitly importing
your component units. The second is to remove the Register procedures from your
component units and place them in a registration unit. Because we have no
control over editor writers, the second option is preferred.
Registration Units
As its name suggests, the purpose of a registration unit is to register
components, property editors, and component editors with Delphi. Listing 7 shows
the source code for the DcdcReg unit, which is responsible for registering all
of the components presented in this book.
Listing 7: DcdcReg.pas-The registration unit for this book's
components.
{=========================================================================
DcdcReg Unit
Registration Unit for all components presented in "Developing Custom
Delphi 3 Components" by Ray Konopka.
In Delphi 3 and later:
Install the DcdcDcl.dpl design-time package.
Developing Custom Delphi 3 Components
Copyright ) 1995-1997 by Ray Konopka
=========================================================================}
unit DcdcReg;
interface
procedure Register;
implementation
uses
Forms, Classes, Controls, SysUtils, DsgnIntf, RkCommon,
RkBtn, RkPanel, RkLabel, RkLblEdt, RkTabLst, RkTabEdt, RkPrgres,
RkStatus, RkStsEdt, RkTrkBar, RkBWCC, RkAddr, RkNavPnl, RkLaunch,
RkLcrEdt, RkMail, RkLookup, RkDBStat, RkDBSpin, RkROGrid, RkAbout;
{========================}
{== Register Procedure ==}
{========================}
procedure Register;
begin
{== Register Components ==}
RegisterComponents( PalettePage,
[ TRkOkButton, TRkCancelButton, TRkPanel, TRkLabel,
TRkTabbedListBox, TRkProgressBar,
TRkStatusControl, TRkGlyphStatus, TRkClockStatus,
TRkKeyStatus, TRkTrackBar, TRkBwccCheckBox,
TRkBwccRadioButton, TRkAddress, TRkNavigatorPanel,
TRkLauncher, TRkMailMessage, TRkLookupDialog,
TRkDBStatusControl, TRkDBSpinEdit,
TRkDBReadOnlyGrid ] );
{== Register Property Editors ==}
RkStsEdt.Register;
RkTabEdt.Register;
RkLcrEdt.Register;
RkAbout.Register;
{== Register Component Editors ==}
RkLblEdt.Register;
end;
end.
Each component unit is added to the uses clause and a single call to
RegisterComponents within the Register procedure takes care of registering all
of the components. Notice that for each property editor and component editor,
the DcdcReg Register procedure simply calls the Register procedure defined in
the editor unit. Because property editors and component editors are only used at
design-time, there is no harm in keeping the registration code within the unit
implementing the editor.
Now that we have a registration unit, we can return our attention to the
design package. Instead of looking at the design package in the Package Editor,
let's take a look at the DcdcDcl.dpk source code in Listing 8. The package is
quite simple thanks to the registration unit and runtime packages created
earlier. The contains clause lists all of the design-time specific units
including the registration unit. The requires clause specifies the Dcdc20 and
DcdcDb20 runtime packages because the DcdcReg unit uses the units found in these
two packages.
Listing 8: DcdcDcl.dpk-The design package for this book's components.
package DcdcDcl;
{$DESIGNONLY}
{$DESCRIPTION 'Developing Custom Delphi 3 Components - Design'}
{$R 'DcdcReg.dcr'}
requires
vcl50,
Dcdc50,
DcdcDb50;
contains
DcdcReg,
RkAbout,
RkLblEdt,
RkLcrEdt,
RkStsEdt,
RkTabEdt;
end.
The Component Resource File
Take another look at Listing 8. In particular, look at the compiler
directives at the beginning. In addition to specifying the $DESIGNONLY directive
and a description, the DcdcReg.dcr component resource file is linked into the
package. Component resource files generally only contain
the bitmaps to be displayed on the component palette.
The Package Editor searches for a
matching DCR file whenever you add a unit to a package. However, the Package Editor does this for both runtime and design packages. But
linking palette bitmaps into a runtime package is pointless because the bitmaps
will go unused and simply waste space.
Therefore, instead of creating separate DCR files for each component unit, I
recommend creating a single DCR file containing all of the component bitmaps.
Recall that component resource files are nothing more than Windows RES files
with a different extension. Therefore, there is no problem in storing multiple
bitmaps as demonstrated in Figure 13, which shows the DcdcReg.dcr file loaded
into the Image Editor.
Notice that the name of the resource file is the same as the registration
unit. As a result, when the registration unit is added to the design package,
the component resource file is also added. Furthermore, since the registration
unit is not used in the runtime packages, the component bitmaps will not be
linked into them.

Figure 13: The Image Editor supports adding multiple bitmaps to a
component resource file.
Don't underestimate the importance of well designed bitmaps to represent your
components. The component bitmaps are the first impression users will have of
your components. Unprofessional bitmaps give the impression of unprofessional
components. If you build components for the commercial market, you may want to
get a professional graphics artist to design the bitmaps.
Deployment
The next issue we must face is how to deploy the components to the
consumer--that is, other Delphi developers. But before we address the
"how," we need to decide on "what" will be deployed. For
instance, is it sufficient to distribute only the DPL files for each package?
The answer to this is no, because although deploying the DPL files is
sufficient when distributing a compiled application to an end user, the DPL file
alone is not sufficient to compile a new application that uses any of the
components in the package. In this case, it is necessary to distribute the DCP
files as well. Table 4 lists various file types and describes when these files
should be deployed to other Delphi developers.
Table 4: Determining which files to distribute.
|
File Type
|
Extension
|
Reasons to Distribute
|
|
Runtime Package Library
|
*.bpl
|
These files are needed to support the use of the
runtime package version of your components. These files are also often used to
support design packages.
|
|
Runtime Package Symbols
|
*.dcp
|
These files are needed to compile a project that
uses the corresponding runtime package library.
|
|
Design Package Library
|
*.bpl
|
These files are needed to install your components
and design editors.
|
|
Design Package Symbols
|
*.dcp
|
Distribute these files if you want to allow others
to use your design editors in their own packages.
|
|
Compiled Units
|
*.dcu
|
These files are needed to compile a project that does not
use runtime packages. Do not distribute these files if you want to force
developers to use your runtime packages.
|
|
Form Files
|
*.dfm
|
Same rules as Compiled Units.
|
|
Source Files
|
*.pas
|
Source code is a convenience to developers. Distributing the
source code in place of the compiled units is a valid alternative.
|
Summary
This paper described the critical extra steps that must be taken in order to develop professional, commercial-quality components. Specific topics included creating online component help, effectively designing packages for component distribution, supporting multiple versions of Delphi, and how
to support automatic one-step installation.
Contact Information
About the Author
Ray Konopka, Raize Software, Inc.
Ray Konopka is the founder of Raize Software, Inc. and the chief architect for their Raize Components and CodeSite products. Ray specializes in Delphi component development and is a frequent speaker at developer conferences.
Paper originally presented at the 11th Annual Borland Conference, July 2000.