0% found this document useful (0 votes)
218 views55 pages

Delphi Informant Magazine (1995-2001)

The document discusses Anders Hejlsberg leaving Borland to work at Microsoft, but assures readers that the Delphi team remains strong under new leadership and that the future of Delphi is secure as work on Delphi 97 continues. It also briefly mentions a new C++ product from Borland that applies the Delphi approach to C++ development and could significantly boost Borland if successful.

Uploaded by

reader-647470
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
218 views55 pages

Delphi Informant Magazine (1995-2001)

The document discusses Anders Hejlsberg leaving Borland to work at Microsoft, but assures readers that the Delphi team remains strong under new leadership and that the future of Delphi is secure as work on Delphi 97 continues. It also briefly mentions a new C++ product from Borland that applies the Delphi approach to C++ development and could significantly boost Borland if successful.

Uploaded by

reader-647470
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 55

January 1997, Volume 3, Number 1

A New Spin on Delphi


Delphi Graphics Programming

ON THE COVER
7 A New Spin on Delphi — Peter Dove and Don Peer
Misters Dove and Peer begin a series on graphics programming by
developing the TGMP component. The series will follow it from inception,
through development, to a fully-functional 3D rendering component.

FEATURES
14 Informant Spotlight — Danny Thorpe
A member of the Delphi R&D team, Mr Thorpe begins a two-part series
that explores virtual methods and polymorphism.

19 DBNavigator — Cary Jensen, Ph.D.


INI files or the Windows 95 registry? Dr Jensen offers an overview
of how to use either — or both.

25 OP Tech — Ray Lischner


Undocumented — until now; Mr Lischner shares some of the internal
messages of the Delphi VCL.

34 In Development — Mark Ostroff


Object names getting unwieldy? Mr Ostroff shares a set of Delphi
naming conventions that can make your programming life easier. Cover Art By: Tom McKeith

38 Delphi at Work — David Faulkner DEPARTMENTS


Offering his TBarCode component, Mr Faulkner demonstrates creating
2 Symposium by Jerry Coffey
Code 39 bar codes.
3 Delphi Tools
6 Newsline
42 Case Study — Don Bauer
55 File | New by Richard Wagner
How good is Mr Bauer’s TWorldMap component? NASA found it useful.

REVIEWS
45 InfoPower 2.0 — Product Review by Bill Todd
49 SysTools for Delphi — Product Review by Alan C. Moore, Ph.D.

1 January 1997 Delphi Informant


Symposium
I will be leaving Borland by the end of [October 1996] to take a job at Microsoft. This has not been an easy decision to
make, but I have now been with Borland for 13 years, and I feel that it is time for me to try some new challenges.
— Anders Hejlsberg, Delphi co-architect (from CompuServe forum, BDelphi32)

After Anders
No — you didn’t miss anything; there just wasn’t a December issue of Delphi Informant. And there
was no gap in your monthly Delphi coverage; it was strictly a logistical move to help DI get wider dis-
tribution and appear sooner each month on newsstands. So relax; with any luck, we’ll get this maga-
zine thing down soon.

On to the main topic: As you’ve proba- of you worried about Delphi’s future, I As you know by now from its debut
bly heard by now (this is written on want to assure you that the product is at Comdex, Borland has given the
October 21st, 1996), Anders Hejlsberg in the hands of an incredibly compe- “Delphi treatment” to C++. This
has left for Microsoft. Hejlsberg has tent team of people for whom I harbor one’s been kept under tight wraps and
been with Borland since its inception. the deepest respect. Back in the old doesn’t have a shipping name as of
He created the now-legendary Turbo Turbo Pascal days it was possible for this writing. It masquerades under
Pascal product that revolutionized the one person to write and maintain an various names such as Ebony and
desktop computer industry with the first entire product. This is no longer the Pronto, but what’s important is what
integrated development environment. case. Delphi was built by a team, and I the product represents to the industry
have full confidence in the team’s abili- and to Borland.
News of Anders’ departure hit me like a ty to develop and deliver new versions
thunderbolt. On the several occasions of Delphi. In fact, the Delphi team at Delphi boosted academia-bound
I’ve met Anders or heard him speak, I this point is almost twice the size it Pascal into the stratosphere. Given
was invariably struck by his affability was when we shipped 1.0 in early ’95. the preponderance of C++ in the pro-
and — well — brilliance. It was com- And Delphi97 is going to be a great gramming world — as Urlocker says
forting that my all-time-favorite devel- product [with] multi-tier database “some folks think more naturally in
opment environment was in his skillful access and COM/ActiveX support.” C++” — Ebony could make Delphi
hands. What now would happen to the look like a moderate success. Things
Delphi R&D team? Would others fol- Urlocker is also encouraging: “The look bad for Borland right now, but I
low Anders to Microsoft? Would they architectural work that Anders covers is can remember a darker period. In the
just scatter to the four winds? complete for Delphi97 and we’re in winter of 1994-95, Borland was rely-
beta. Anders’ departure won’t affect the ing on the flagging sales of Paradox
From all accounts, Hejlsberg had per- ship date or features going forward. and dBASE, and had pinned all hopes
sonal reasons for moving on; he’s not so Chuck Jazdzewski will move up from on a Pascal-based product no one had
much “leaving Borland” as beginning a co-architect to chief architect. Chuck’s heard of.
new endeavor. According to Zack been here longer than I have and
Urlocker, Director of Product worked closely with Anders for eight Delphi Informant wishes you well
Management for Borland and a member years. In fact, he designed the VCL and Anders; you’ve given us an incredible
of the Delphi R&D team: “We’re cer- the UI builder that’s such a powerful tool. Gotta run though — I have this
tainly sad to see Anders leave, but it was aspect of Delphi. He’s also played an Delphi project I’m working on ...
a personal decision on his front. After important role on Latté and our
being here 13 years — since college — upcoming C++ product. So again, even
he’s decided to try something different. though we’ll miss Anders, I think the
The rest of the team is here to stay.” Delphi team is in very good shape to
ship a very impressive release. There is a Jerry Coffey, Editor-in-Chief
The most important thing to readers whole crew of folks working on
of this magazine, however, is the health Delphi97 many of whom have been Internet: [email protected]
of Delphi without Hejlsberg. involved since the very beginning of the CompuServe: 70304,3633
Fortunately, the view of the product project and have a strong vision for Fax: (916) 686-8497
being in the hands of one man is where we are taking it in this next Snail: 10519 E. Stockton Blvd.,
naive. Again, from Anders: “For those release and beyond.” Ste. 100, Elk Grove, CA 95624

2 January 1997 Delphi Informant


Delphi Eagle Research Announces Version 2.0 of VB2D Translator
Eagle Research Inc. of San
T O O L S Francisco, CA has released
version 2.0 of its Visual Basic
New Products (VB) to Delphi translator.
and Solutions VB2D 2.0 translates applica-
tions created in VB 3.0 or
4.0 to 32-bit Delphi 2.
VB2D 2.0 also features
online reporting, conversion
of most database code,
automatic replacement of
common VBXes, and better
code output.
According to Eagle
Research, Delphi’s new
variant, string, and curren-
cy data types make it easier but instead of connecting Price: Standard Edition, US$150;
to create Delphi code from to the BDE, they connect and Professional Edition, US$450.
New Delphi Book
VB applications. directly to Microsoft’s JET Both versions include a 30-day
Delphi 2 Developer’s Guide, For VB projects that use Database Engine. money-back guarantee.
2nd Edition
Xavier Pacheco & Steve Teixeira Access or other databases VB2D is offered in stan- Contact: Eagle Research Inc.,
SAMS Publishing via the JET Database dard and professional edi- 360 Ritch St., Ste. 300,
Engine, VB2D replaces tions. The Professional San Francisco, CA 94107
standard VB data-aware Edition includes a copy of Phone: (415) 495-3136
controls with Eagle’s JETset Eagle’s JETset product, Fax: (415) 495-3638
controls for Delphi. additional reporting capa- E-Mail: [email protected]
JETset controls are bilities (such as side-by-side Web Site: http://www.xeaglex.com
derived from standard listing of VB and Delphi
Delphi data-aware controls, code), and source code.

ExceleTel Releases TeleTools-Delphi 1.04


ISBN: 0-672-30914-9
Price: US$59.99
ExceleTel Inc. of Raleigh, access to telephony record and E-Mail: [email protected]
(1,322 pages, CD-ROM) NC has released TeleTools- playback features. Web Site: http://www.zaccatalog.com
Phone: (800) 428-5331
Delphi 1.04. TeleTools-
Delphi is a set of VCL mod- Price: TeleTools-Delphi, US$99. Contact: ExceleTel Inc.,
ules that provides native Contact: ZAC Catalog, 1090 Kapp 5142 Simmons Branch Trail, Ste. 100,
access to telephony functions Drive, Clearwater, FL 34625 Raleigh, NC 27606
for Delphi’s IDE. Phone: (800) 463-3574 or Phone: (919) 233-2232
Using a component to (813) 298-1181 Fax: (919) 233-2230
access Windows telephony Fax: (813) 461-5808 E-Mail: [email protected]
(TAPI) functions, TeleTools
enables developers to add
caller ID, dialing, call log-
ging, and screen pops. After
the TAPI component is
placed on a form, a develop-
er can access 22 properties,
9 events, 18 methods, and
additional TAPI functions.
Future releases of ExceleTel
products include TeleTools-
OCX, as well as the second
series of TeleTools products:
TeleTools2-Delphi and
TeleTools2-OCX. The
TeleTools2 series will add
.WAV functionality with

3 January 1997 Delphi Informant


Delphi Tamarack Associates Releases Rubicon for Delphi
Tamarack Associates of
T O O L S Palo Alto, CA has released
Rubicon for Delphi, a data-
New Products base search engine.
and Solutions The Rubicon technology
allows the end-user of an
application to perform
searches using wildcards;
apply AND, OR, and NOT
logic to the search; and nar-
row the search without
regard to the underlying
database or field structure.
Rubicon encapsulates this
search technology in a set of
three native Delphi VCL
components that build
indexes, update indexes, and
New Delphi Book
execute searches, respectively. Rubicon reduces the com- Price: US$99, includes free updates
Programming Delphi Custom Rubicon performs all plexity associated with search- and support via e-mail. Rubicon may
Components
Fred Bulback searches at keyed index-like ing normalized tables and also be ordered through CompuServe
M&T Books speeds by building a single tables containing BLOB data. shareware registration ID 11536.
Rubicon table that indexes all All components are compati- Contact: Tamarack Associates,
the words in the source ble with Delphi 1 and 2. 868 Lincoln Ave., Palo Alto, CA 94301
table(s) and their locations Trial versions of Rubicon are Phone: (415) 322-2827
(most Rubicon searches never available from Tamarack’s Web Fax: (415) 322-2827
read the source table[s]). site, as well as the Delphi and E-Mail: [email protected]
Reads and writes against this BDelphi CompuServe forums Web Site: http://www.tamaracka.com
table are minimized by built- (filenames: RUBICON.ZIP
in compression technology. and RBCNDEMO.ZIP).

ISBN: 1-55851-457-0 IntegrationWare Ships Speed Daemon for Delphi v1.1


Price: US$39.95
(420 pages, CD-ROM) IntegrationWare, Inc. of to generate function tim- Price: US$59
Phone: (212) 886-1068 Deerfield, IL has released ings. This modified version Contact: IntegrationWare, Inc.,
Speed Daemon for Delphi can then be recompiled. The Deerfield Tech Center, 111 Deer Lake
v1.1, a source code profiler. entire process is guided by a Road, Ste. 109, Deerfield, IL 60015
It provides an analysis engine wizard-like interface. Phone: (888) 773-1133 or
for optimization and perfor- Speed Daemon works (847) 940-1133
mance tuning. with DLLs and OLE Fax: (847) 940-1132
Using Speed Daemon, devel- automation servers devel- Web Site: http://www.integra-
opers can monitor the effi- oped in Delphi, and can be tionware.com
ciency of key sections of code. used on single-threaded and
Given any Delphi 1 or 2 pro- multi-threaded applications.
ject, the utility produces statis-
tics for each function. These
include the number of times a
function is called, the total
time spent executing the func-
tion relative to other pieces,
and average time per call.
Speed Daemon’s output can
be printed or viewed onscreen.
Speed Daemon doesn’t
require any changes to a
project or source code. It
parses the source code and
adds any required constructs

4 January 1997 Delphi Informant


Delphi SkyLine Debuts ImageLib Corporate Suite Document Imaging Package
SkyLine Tools of North
T O O L S Hollywood, CA debuted
ImageLib Corporate Suite
New Products Version 1.0. This version of
and Solutions the ImageLib software
includes the features of
ImageLib’s Combo Package
and ImageLib@the edge,
image manipulation, and
correction package.
The ImageLib Corporate
Suite package incorporates
all the formats of the
ImageLib Combo (.PCX,
.PNG, .TIF [baseline],
.JPG, .BMP, .WMF, et al.)
plus ISIS and TWAIN scan- wave, and transition effects. Skyline Tools is planning to
ning support, multimedia To these features, the suite release a new Web kit that will
formats, a video frame grab- adds multipage scanning, contain a progressive display
ber, thumbnails, and BLOB low-level scanning, as well for .GIF, .PNG, and .JPG, as
support. as enabling users to read well as animated .GIFs.
Also included are point- and write the following
and-click image correction TIFF formats: TIFF III, Price: US$499
effects such as brightening, CITT; TIFF IV, CITT; Contact: SkyLine Tools,
contrast, gamma correction, Multipage TIFF; Packbits; 11956 Riverside Dr., Ste. 107,
color reduction, and rota- and LZW. Other formats North Hollywood, CA 91607
tion package. included are Photo-CD Phone: (818) 766-4561
The ImageLib Corporate (Kodak), .IMG, in addition Fax: (818) 766-9027
Suite features special effects to .PCX and .EPS (read E-Mail: [email protected]
such as mosaic, page curl, only). Web Site: http://imagelib.com

Hurricane Software Announces Multi-File Search Utility


In Blue Springs, MO, is shipping WinGREP, a Other features include
Hurricane Software, Inc. multi-file search utility Windows 95 long file-
that allows programmers name support, the ability
to locate text strings in to save, load, and print
source code files. search result sets, a button
WinGREP features a col- bar to help create regular
lapsible tree of search expressions, UNIX text
results that expands to file support, online Help,
reveal the matches for a and install/uninstall.
filename. WinGREP also ships with
WinGREP also includes the Hurricane Editor, a
a Quick-Preview window. multi-file text editor that
As results are highlighted features word wrapping,
in the tree, the Quick- and search and replace.
Preview window shows
several lines of text Price: US$39
around the search match. Contact: Hurricane Software, Inc.,
WinGREP can automat- 2401 SE 7th St.,
ically synchronize an edi- Blue Springs, MO 64014
tor or IDE with the Phone: (816) 373-9252
results of a search. It will E-Mail: [email protected]
open the file and position or [email protected]
to the line of the match. Web Site: http://www.hurricanesoft.com

5 January 1997 Delphi Informant


News Borland Cuts Staff; Expects Second Quarter Loss
Scotts Valley, CA —
Borland has reduced its cor-
and realigning program.
Paul Emery, vice president
of approximately US$36 mil-
lion for the quarter ending
porate headcount by approx- and chief financial officer, said Sept. 30, 1996.
L I N E imately 15 percent or 125 Borland’s cost reduction mea- According to Borland, the
individuals, as part of its sures are expected to produce quarter’s losses were due to
January 1997 new worldwide restructuring annual savings from US$15 slower than expected transi-
million to US$17 million. tion of its sales, marketing,
ICG Relocates, In addition, Borland and development efforts in
Discontinues announced it expects to moving from desktop markets
CompuServe Forum report a loss of US$.32 to into departmental and corpo-
Elk Grove, CA — US$.36 per share on revenues rate technologies.
Informant Communications
Group, Inc. (ICG) has relo- Borland Announces Interim President and CEO
cated its corporate headquar- Scotts Valley, CA — Borland Most recently, Lynn has
ters to 10519 E. Stockton has named Whitney G. Lynn served as a consultant in
Blvd., Ste. 100, Elk Grove, acting President and Chief the integration process sur-
CA, 95624-9703, effective Executive Officer. Lynn suc- rounding Borland’s pending
December 1, 1996. The ceeds William F. Miller, who acquisition of Open
L e a r n i n g Tr e e ICG telephone number returns to his role as Environment Corp.
International Offers
Delphi Courses
remains (916) 686-6610 and Chairman of the Board. Additionally, Borland
Learning Tree International is the company’s fax number Miller had been the acting announced replacements for
hosting two Delphi courses in
January. The first, Object
remains (916) 686-8497. CEO of Borland since the res- Paul Gross, departing Senior
Oriented Analysis and Design, In addition, ICG has ignation of Gary Wetsel. Vice President of Research
will be held January 6-10
at the Learning Tree Education
announced it will discontinue Borland is continuing to and Development. Heading
Center in Washington, D.C. its CompuServe Forum (GO search for a permanent enterprise development is
Delphi Application
Development, scheduled for
ICGFORUM) beginning CEO, and Lynn will serve Jothy Rosenberg, a Borland
January 21-24, will be held at January 1, 1997. The compa- until a new CEO is named. development vice president.
the Learning Tree Education
Center in Los Angeles, CA.
ny will place all of its cus- Lynn is a technology indus- Jeff Rudy, also a Borland
For more information, contact tomer support services and try veteran with a broad development vice president,
Learning Tree International at
(310) 417-9700 or
efforts into its Web site at range of executive and leads the Scotts Valley research
http://www.learningtree.com. http://www.informant.com. management experience. and development efforts.
The ICG Web site contains
code listings and applications ICG Announces Delphi Informant and
referenced in all Informant Oracle Informant on CD-ROM
publications, and will also Elk Grove, CA — Informant Works: 1996 and
contain subscription support Informant Communications Oracle Informant Works:
and threaded discussion areas. Group, Inc. (ICG) has 1996 may be placed with
announced the release of ICG by calling (800) 88-
Borland Ships Java-Enabled InterBase SQL Delphi Informant and Oracle INFORM (in the US), or
Database Server Informant magazines on (916) 686-6610; or by fax-
Scotts Valley, CA — Borland tion distribution and appli- CD-ROM. Both titles will ing (916) 686-8497.
has released InterClient for cation development. offer duplicate versions of all Each title is available for
the InterBase cross-platform InterClient also targets Web- articles as they appeared in US$49.95, plus US$5 ship-
SQL database server. oriented VARs and consul- print during 1996, with ping and handling (US$15
InterClient, written in Java, tants creating Web sites and simplified keyword access for international customers).
provides JDBC-compliant applications. through a word index.
connectivity for InterBase. InterClient is one compo- Each CD includes the code, Errors & Omissions
Built on JavaSoft’s Java nent of Borland’s Internet supporting files, screen shots, Reference to downloadable code
Enterprise-API set and its and Intranet initiatives. Their accompanying Dan Miser’s article,
and graphics for the stories “The INISource Component,” was
JDBC protocols, InterClient RAD environment for Java, listed. Readers can print any inadvertently omitted from the
streamlines distributed trans- code-named Latté, will help page within the CD. October issue of Delphi Informant.
action processing at both the developers extend the value This code is available for download
A 16-page color booklet from the Informant Web site at
Java client and database serv- of InterClient with its Java ships with each title. It fea- http://www.informant.com, file
er. It eliminates installation object component model. tures installation instruc- name DI9610DM.ZIP.
and maintenance for compa- For more information, visit tions, contents, and addi- We apologize for any inconvenience
nies committed to Web-cen- Borland’s Web site at tional information. this omission may have caused.
tric technologies for informa- http://www.borland.com. Orders for Delphi

6 January 1997 Delphi Informant


On the Cover
Object Pascal / Delphi 2

By Peter Dove and Don Peer

A New Spin on Delphi


Delphi Graphics Programming: Part I

elphi is rapidly gaining respect as a games and graphics programming


D language. It offers a graphical interface, rapid application develop-
ment (RAD), and the use of components at design time. More importantly,
Delphi offers true compiler technology — speed.

This article begins a series about Delphi dent bitmaps, sprites, palettes, optimization,
graphics programming. In this series, we’ll pointers, memory management, inline assem-
develop a component, TGMP, following it bler, and reading polygon data files.
from inception, through development, to a
fully-functional 3D rendering component. If you are a C or C++ programmer, you’ll find
Our journey will take us through some basic that writing this kind of component illus-
topics such as properties, events, and proper- trates how to get Delphi to do all those things
ty editors. Eventually we’ll visit more exotic you may have taken for granted. Regardless,
and advanced topics, such as device-indepen- this discussion will bring you closer to discov-
ering what makes Delphi such a great prod-
unit Unit1; uct for games and graphics development.
interface
To summarize what we’ll create throughout
uses this series, a brief description of the TGMP
Windows, Messages, SysUtils, Classes, Graphics,
Controls, Forms, Dialogs;
rendering engine is in order. TGMP will enable
you to create 3D worlds based on polygonal
type data, allowing users to move through and
TGMP = class(TComponent)
private
observe the world from all positions and
{ Private declarations } angles. (The game Doom is an example of a
protected rendering engine.) The TGMP engine will
{ Protected declarations }
public
also include shading based on light source
{ Public declarations } positions, clipping, collision detection, texture
published mapping, and Gourard shading.
{ Published declarations }
end;
Now let’s set our minds into 3D mode and
procedure Register; dive into the project.
implementation
Let the Games Begin
procedure Register;
begin
Begin by selecting File | Close All from
RegisterComponents('Graphics', [TGMP]); Delphi’s main menu. This will ensure that all
end; items currently open are closed. Next, select
Component | New. In the Component
end.
Expert, enter TGMP for the Class Name,
Figure 1: The template unit created by Delphi. TComponent for the Ancestor type, and

7 January 1997 Delphi Informant


On the Cover

{ Place this code in the public section }


constructor Create(AOwner : TComponent) ; override;

{ Place this code in the implementation section }


constructor TGMP.Create(AOwner : TComponent);

begin
{ We must call the inherited Create of the TComponent
from which we derived this class }
inherited Create(AOwner);

{ Create the bitmap canvas }


FBackBuffer := TBitmap.Create;

{ Get a pointer to the Form's canvas }


FFrontBuffer := TForm(AOwner).Canvas;

{ Get the height and width of the window }


Figure 2: The cube rendered in wireframe.
ViewHeight := TForm(AOwner).Height;
ViewWidth := TForm(AOwner).Width;
Graphics for the Palette Page. (Because we want TGMP to
{ Set the bitmap's height }
be a non-visible component you can drop onto a form, it’s FBackBuffer.Height := ViewHeight;
derived from the TComponent class.) FBackBuffer.Width := ViewWidth;

{ Set up viewport }
After you have entered these values, select OK, and Delphi HalfScreenHeight := ViewHeight div 2;
will create a template unit (see Figure 1). This is the template HalfScreenWidth := ViewWidth div 2;
unit from which TGMP will evolve.
{ Get the handle of the window }
FWindowHandle := TForm(AOwner).Handle;
Let’s start with something simple. We’ll create TGMP to be a
component capable of generating a wireframe cube and pyra- { Set the viewing distance }
ViewingDistance := 200;
mid. Figure 2 shows how the cube will appear rendered in wire-
frame. The background color is black and is therefore the color { Set the Z distance }
of the canvas on which we are drawing. (Black is the usual Z_Distance := -50;
end;
choice for background color, although you can select any color.)

The main routines that must be developed for our wireframe Figure 3: Overriding the Create constructor.
rendering component are line-drawing and screen-clearing
routines, 3D to 2D projection routines, and rotational calcu- FBackBuffer: TBitmap;
lations. The Graphic Design Interface (GDI) functions FFrontBuffer: TCanvas;
encapsulated within TCanvas make line-drawing and screen- ViewWidth, ViewHeight: Integer;
FWindowHandle: THandle;
clearing routines easy to write. (3D to 2D projection routines HalfScreenWidth, HalfScreenHeight,
and rotational calculations are covered later in this article.) ViewingDistance: Integer;
We’ll begin by adding some data members to the component.
At this point we need: By placing this code in the private section, we can restrict
1) something to draw on and something to hold the back- access to data members through well-defined methods.
ground color; (Think of the private section as the innards of a microwave.
2) variables to hold our viewing distance and variables for You wouldn’t go fiddling with those innards unless you were
the screen height and width; and a qualified microwave engineer. Access to the microwave is
3) a means to keep the handle of the window that receives through a simple pushbutton user interface, equating to the
the TGMP component. public and published methods.)

Instead of using a device context, we’ll use TBitmap as the Next we need to initialize the declared data members. To do
backbuffer, because it will be compatible with the current this, we’ll override the Create constructor as shown in Figure 3.
color depth and it encapsulates the idea of a device context. The constructor is called to create the object. The Create
This means we can set the height and the width of the method contains the code that allocates the memory and gov-
bitmap, and leave TBitmap to deal with any necessary erns the look of the component. The override keyword indi-
resource allocation. TBitmap also has a Canvas property, so cates that we want to add to the inherited Create constructor.
copying between the bitmap canvas and form canvas will be Note that Delphi allows you to cast AOwner as another object
easy. This backbuffer also allows us to draw on a hidden sur- (TForm), an example of Delphi’s polymorphism.
face, which is then copied to the screen all at once, creating a
smoother animation effect. To do this, place the following Note also that the parameter AOwner is in the Create construc-
code in the private section of the TGMP component: tor. AOwner is the owner of the component, the object respon-

8 January 1997 Delphi Informant


On the Cover
sible for freeing the component before it frees itself. The owner same as the value in the form file, the value in the form
will be the form on which the component is placed. This actu- file isn’t altered.
ally turns out to be quite useful because we can get the win-
dows size and other information using Windows API calls, Declaring a default value doesn’t mean the value will be auto-
sending the window handle as a parameter. matically assigned to the property; rather, you should provide an
initial value for the property in the class’ constructor. We will
We also want to destroy the TBitmap object, and release the take this route with the ZDistance property to ensure that the
device context when the object comes to the end of its scope. default distance is at least minus 50. If this was not the case and
Thus, we need to override the destructor Destroy: the default value was zero, the user would be unable to see the
object. The component would then behave as if users were view-
{ Place this in the public section of TGMP } ing the program with their noses against the screen. The follow-
destructor Destroy; override;
ing code sets the property value and performs the initialization:
{ Place this in the implementation section }
destructor TGMP.Destroy; { Set the FDistance variable }
begin procedure TGMP.SetDistance(Distance : Integer);
{ Free the TBitmap } begin
FBackBuffer.Free; FDistance := Distance;
inherited Destroy; end;
end;
{ Initializing the ZDistance variable }
constructor TGMP.Create(AOwner : TComponent);
Creating Properties begin

TGMP is now initializing and de-initializing correctly. Next, ...


we should check if there are any variables that we want users
to be able to set visually. Initially, two variables in TGMP fall { Set the Z distance }
ZDistance := -50;
into this category: BackColor and ZDistance. BackColor sets end;
the background color and ZDistance sets the viewing distance
from the screen to the object. Basic 3D Procedures
All our variables are created and initialized, but they are of
This article assumes the reader is familiar with Delphi prop- little use until we create some methods to do the 3D work
erties. However, some discussion of basic implementation for our component. Here are the methods we’ll use:
theory is appropriate. To do this, place the following code in
the private section of the TGMP code: { Place in the private section }
procedure DrawLine3D(x1,y1,z1, x2,y2,z2 : Single);
procedure DrawLine2D(x1, y1, x2,y2 : Integer);
FColor : TColor; procedure SetDistance(Distance : Integer);
FDistance : Integer;
{ Place in the public section of TGMP }
procedure ClearBackPage;
Then place the following code into the published section of procedure RenderNow(var Object3D : TObject3D);
the TGMP code: procedure FlipBackPage;
procedure Rotate(x,y,z, angle);
procedure ChangeObjectColor( var Object3D : TObject3D;
property BackColor : TColor read FColor write FColor;
Color : TColor);
property ZDistance : Integer read FDistance
write SetDistance default -50;
Most of the methods are self-explanatory; however some require
A property definition starts with the keyword property fol- a little explanation. FlipBackPage copies the picture you were
lowed by the name of the property. This is followed by a rendering in memory (on our temporary bitmap) and paints it
colon, the type of the property, and the read/write keyword. on the form that holds the component. Use the DrawLine3D
The read keyword tells Delphi from where it should read the method to pass it the 3D coordinates of your line; it works out
value for BackColor, and the write keyword tells Delphi how what that should resemble on a 2D screen, which is then drawn
and where it should record the value a user may enter. The using the DrawLine2D method. The last method in the public
name of a method, rather than a variable, can follow the read section has a type that we have not yet addressed. The
and write keywords. This will allow some testing or screening TObject3D is going to be our object structure. Its declaration is:
of the data entered by the user.
TPoint3D = record
x,y,z : Single;
The ZDistance property varies slightly in declaration from end;
the BackColor property. ZDistance has read and write
TLine3D = record
statements, but the write keyword uses a procedure, Start, End : TPoint3D;
SetDistance, to save the value for the property. ZDistance end;

also uses the keyword default with a value of minus 50. TObject3D = record
This is a bit of a misnomer — it doesn’t really establish a LineStore : array [0..100] of TLine3D;
NumberLine : Integer;
default value for the property, but rather determines if the Color : Tcolor;
value is stored in the form file. If the default value is the end;

9 January 1997 Delphi Informant


On the Cover
Some 3D Theory procedure TGMP.DrawLine3D(x1,y1,z1, x2,y2,z2 : Single);
var
Currently, the object structure is simplistic. This will ScreenX1, ScreenX2, ScreenY1, ScreenY2 : Integer;
change radically over the next few articles. First, however, begin
let’s go over a few 3D math theories. ScreenX1 :=
HalfScreenWidth + Round(X1 * ViewingDistance / Z1);
ScreenY1 :=
The first mathematical concept we’ll cover is rotation of a Round(HalfScreenHeight - Y1 * ViewingDistance / Z1);
point in 3D space. In this component, all rotations are per- ScreenX2 :=
formed around the point 0,0,0. This means that if you HalfScreenWidth + round(X2 * ViewingDistance / Z2);
ScreenY2 :=
wanted to rotate a sphere on that point, the center of the Round(HalfScreenHeight - Y2 * ViewingDistance / Z2);
sphere must be at 0,0,0. If not, the sphere would orbit DrawLine2D(ScreenX1, ScreenY1, ScreenX2, ScreenY2);
around the point 0,0,0. Sometimes this is the desired effect. end;

For now, however, the object is defined and rotated about You’ll also notice we have two constants: HalfScreenHeight
its local coordinate system. Be aware that more than one and HalfScreenWidth. Because we want objects to appear at
coordinate system exists: the center of the screen, we need to add half the screen height
Local Coordinate System refers to the coordinates of the to the Y value and half the screen width to the X value.
objects’ vertices themselves.
World Coordinate System refers to the position of the Finally, we’ll create a variable of type TObject3D, fill it with
objects’ vertices in a virtual world. 3D data, assign it a color, and we’re ready to go. Listing One,
Camera Coordinate System is the final position of the beginning on page 11, shows the code for the TGMP unit,
objects’ vertices after being transformed through the with some added data members. These members are filled in
camera matrix. This transformation positions all the the Create constructor to determine the size of the window to
objects as they would be if they were seen through a render to. After this code has been added to the initial unit
camera at a given position and angle of rotation. template, save the unit as GMP.PAS.

These coordinate systems will be covered in greater detail in You are now ready to install the component into Delphi’s
future articles. component library. To do this, select Component | Install to
display the Install Components dialog box. Select the
Here are the formulas for rotation of an object through the X, GMP.PAS file, add it to the component list, and select OK to
Y, and Z axis: recompile. Delphi will return the Component Palette with a
new Graphics page containing the TGMP component.
Rotation around Z:
Our First Application
NewX := X * Cos(Angle) - y * Sin(Angle) Now that you have installed the component, we can put it to
NewY := X * Sin(Angle) + y * Cos(Angle)
work and create our wireframe application.
Rotation around X: In our first application, we’ve created two arrays that hold the
NewY := Y * Cos(Angle) - Z * Sin(Angle)
data for the cube and pyramid objects. This is to set the founda-
NewZ := Y * Sin(Angle) + Z * Cos(Angle) tion for reading polygon data files to create the 3D objects. The
arrays are declared in the type section of the application code:
Rotation around Y:
TPyramidArray = array [0..47] of Integer;
TCubeArray = array [0..71] of Integer;
NewZ := Z * Cos(Angle) - X * Sin(Angle)
NewX := X * Cos(Angle) + Z * Sin(Angle)
In the public section of the source code for the wireframe
Next we need to know how to convert a 3D line onto a 2D application, notice the two variables of type TObject3D and a
screen. This is a simple problem to solve. Simply divide X pointer to TObject3D, CurrentObject. We are using
and Y by the Z value. However, doing this leads to a CurrentObject to keep track of the object the user selected.
“zoomed” perspective, so we need to add the idea of When the user selects a new object, we assign the current
viewing distance. You get the extreme zoomed effect object pointer to the object selected.
because the algorithm assumes your nose is pressed against
the screen. You’ll notice in the Timer1.Timer procedure that we use the
pointer in the following format: CurrentObject^. By plac-
Try holding an object close to your face and you’ll see the ing the carat after the pointer CurrentObject, we are referring
zooming effect. By increasing the viewing distance, you to the object rather than its address. This enables us to have
reduce the zoom. a generic place holder. Otherwise, we would need to test
which object is selected, and have two sets of the code you
The following code converts from 3D to 2D with respect to see in the Timer1.Timer procedure (one set of code for each
viewing distance: object we create). Typically, you would have to use a case

10 January 1997 Delphi Informant


On the Cover
The files referenced in this article are available on the Delphi
Informant Works CD located in INFORM\97\JAN\DI9701DP.

Peter Dove is a Technical Associate with Link Associates Limited and a partner in
Graphical Magick Productions. He can be reached via the Internet at
[email protected].

Don Peer is a Technical Associate for Greenway Group Holdings Inc. (GGHI) and a
partner in Graphical Magick Productions. He can be reached via the Internet at
[email protected].

Begin Listing One — The GMP Unit


unit gmp;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics,
Controls, Forms, Dialogs;
Figure 4: The type of graphic your TGMP rendering engine will
type
be capable of generating by the fourth article of this series.
TPoint3D = record
x,y,z : single;
end;
statement to accomplish this, but as you can see, pointers
can be an eloquent solution to long-winded programming. TLine3D = record
StartPoint, EndPoint : TPoint3D;
end;
Listing Two on page 13 shows the first program that demon-
strates the use of TGMP. The application loads a TObject3D TObject3D = record
variable and animates the object in wireframe mode. LineStore : array [0..100] of TLine3D;
NumberLine : Integer;
Color : Tcolor;
Conclusion end;
Our initial development of the TGMP rendering component
TPObject3D = ^TObject3D;
provides a solid foundation on which to add additional 3D
math and rendering procedures. To give you an indication of TGMP = class(TComponent)
where we’re headed with this series, Figure 4 shows the type private
{ Private declarations }
of graphic your TGMP rendering engine will be capable of FBackBuffer : TBitmap;
generating later in this series. FFrontBuffer : TCanvas;
FColor : TColor;
ViewWidth, ViewHeight : Integer;
We’ll also be covering the issue of code optimization in some FWindowHandle : THandle;
depth later in this series. In fact, some of those optimizations HalfScreenWidth, HalfScreenHeight,
will include code that has been presented this month. It ViewingDistance : Integer;
FDistance : Integer;
might be a good mental exercise to look over the example procedure DrawLine3D(x1,y1,z1, x2,y2,z2 : Single);
program and determine where you would optimize this code. procedure DrawLine2D(x1, y1, x2,y2 : Integer);
procedure SetDistance(Distance : Integer);
protected
Although our first application is basic, the component has been { Protected declarations }
initialized in such a manner that future articles can build upon public
it rather than re-design it from the ground up with each new { Public declarations }
constructor Create(AOwner : TComponent) ; override;
rendering process. This will become apparent in our next article destructor Destroy; override;
as we add solid matter to the wireframe objects created here. ∆ procedure ClearBackPage;
procedure RenderNow(var Object3D : TObject3D);
procedure FlipBackPage;
procedure Rotate(x,y,z, angle : Single;
References: var Object3D : TObject3D);
LaMothe, A., Black Art of 3D Game Programming procedure ChangeObjectColor(var Object3D : TObject3D;
Color : TColor);
[Waite Group Press, 1995]. published
Lampton, Christopher, Flights of Fantasy { Published declarations }
[Waite Group Press, 1993]. property BackColor : TColor read FColor write FColor;
property ZDistance : Integer read FDistance write
Lyons, Eric R., Black Art of Windows Game Programming SetDistance default -50;
[Waite Group Press, 1995]. end;

11 January 1997 Delphi Informant


On the Cover

procedure Register; EndPoint.Z * Sin(Angle);


NewZ := EndPoint.Y * Sin(Angle) +
implementation EndPoint.Z * Cos(Angle);
EndPoint.y := Newy;
procedure TGMP.SetDistance(Distance : Integer); EndPoint.z := Newz;
begin end;
FDistance := Distance; if Y <> 0 then
end; begin
NewZ := StartPoint.Z * Cos(Angle) —
procedure TGMP.RenderNow(var Object3D : TObject3D); StartPoint.X * Sin(Angle);
var NewX := StartPoint.X * Cos(Angle) +
A : Integer; StartPoint.Z * Sin(Angle);
begin StartPoint.z := NewZ;
FBackBuffer.Canvas.Pen.Color := Object3D.Color; StartPoint.x := NewX;
for A := 0 to Object3D.NumberLine - 1 do NewZ := EndPoint.Z * Cos(Angle) —
with Object3D.LineStore[A] do EndPoint.X * Sin(Angle);
DrawLine3D(StartPoint.x, StartPoint.y, StartPoint.z, NewX := EndPoint.X * Cos(Angle) +
EndPoint.x, EndPoint.y, EndPoint.z ); EndPoint.Z * Sin(Angle);
end; EndPoint.z := NewZ;
EndPoint.x := NewX;
procedure TGMP.ChangeObjectColor(var Object3D : TObject3D; end;
Color : TColor); end;
begin end;
Object3D.Color := Color;
end; procedure TGMP.DrawLine2D(x1, y1, x2,y2 : Integer);
begin
procedure TGMP.DrawLine3D(x1,y1,z1, x2,y2,z2 : Single); FBackBuffer.Canvas.MoveTo(x1, y1);
var FBackBuffer.Canvas.LineTo(x2, y2);
Screenx1, Screenx2, Screeny1, Screeny2 : Integer; end;
begin
Screenx1 := HalfScreenWidth + procedure TGMP.ClearBackPage;
Round(X1*ViewingDistance/(Z1+(ZDistance))); begin
FBackBuffer.Canvas.Brush.Color := FColor;
Screeny1 := Round(HalfScreenHeight-Y1*ViewingDistance / FBackBuffer.Canvas.FillRect(Rect(0,0,ViewWidth,
(Z1+(ZDistance))); ViewHeight));
end;

Screenx2 := HalfScreenWidth +
Round(X2*ViewingDistance/(Z2+(ZDistance)));
Screeny2 := Round(HalfScreenHeight-Y2 * procedure TGMP.FlipBackPage;
ViewingDistance/(Z2+(ZDistance))); var
DrawLine2D(Screenx1, Screeny1, Screenx2, Screeny2); ARect : TRect;
end; begin
ARect := Rect(0,0,ViewWidth,ViewHeight);
procedure TGMP.Rotate(x,y,z, angle : Single; var FFrontBuffer.CopyRect(ARect, FBackBuffer.Canvas, ARect);
Object3D : TObject3D); end;
var
P : Integer; constructor TGMP.Create(AOwner : TComponent);
NewX, NewY, NewZ : Single; begin
begin { We must call the inherited create of the Tcomponent
for P := 0 to Object3D.NumberLine - 1 do from which we derived this class }
with Object3D.LineStore[P] do begin inherited Create(AOwner);
if Z <> 0 then
begin { Create the bitmap canvas }
NewX := StartPoint.X * Cos(Angle) — FBackBuffer := TBitmap.Create;
StartPoint.y * Sin(Angle);
NewY := StartPoint.X * Sin(Angle) + { Get a pointer to the Form's canvas }
StartPoint.y * Cos(Angle); FFrontBuffer := TForm(AOwner).Canvas;
StartPoint.X := NewX;
StartPoint.y := NewY; { Get the height and width of the window }
NewX := EndPoint.X * Cos(Angle) — ViewHeight := TForm(AOwner).Height;
EndPoint.y * Sin(Angle); ViewWidth := TForm(AOwner).Width;
NewY := EndPoint.X * Sin(Angle) +
EndPoint.y * Cos(Angle); { Set the bitmaps height }
EndPoint.X := NewX; FBackBuffer.Height := ViewHeight;
EndPoint.y := NewY; FBackBuffer.Width := ViewWidth;
end;
if X <> 0 then { Set up viewport }
begin HalfScreenHeight := ViewHeight div 2;
NewY := StartPoint.Y * Cos(Angle) — HalfScreenWidth := ViewWidth div 2;
StartPoint.Z * Sin(Angle);
NewZ := StartPoint.Y * Sin(Angle) + { Get the handle of the window }
StartPoint.Z * Cos(Angle); FWindowHandle := TForm(AOwner).Handle;
StartPoint.y := Newy;
StartPoint.z := Newz; { Set the viewing distance }
NewY := EndPoint.Y * Cos(Angle) — ViewingDistance := 200;

12 January 1997 Delphi Informant


On the Cover

{ Set the Z distance } {$R *.DFM}


ZDistance := -50;
end; procedure TForm1.Timer1Timer(Sender: TObject);
begin
destructor TGMP.Destroy; with GMP1 do begin
begin ClearBackPage;
{ Free the Tbitmap } Rotate(1,1,0,0.1, CurrentObject^);
Fbackbuffer.Free; RenderNow(CurrentObject^);
inherited; FlipBackPage;
end; end;
end;
procedure Register;
begin procedure TForm1.FormShow(Sender: TObject);
RegisterComponents('Graphics', [TGMP]); var
end; LineCount, ArrayCount : Integer;
begin
end. for LineCount := 0 to 7 do begin
End Listing One ArrayCount := LineCount * 6;
Pyramid.LineStore[LineCount].StartPoint.x :=
Begin Listing Two — The Article1 Unit PyramidPoints[ArrayCount];
unit Article1; Pyramid.LineStore[LineCount].StartPoint.y :=
PyramidPoints[ArrayCount + 1];
interface Pyramid.LineStore[LineCount].StartPoint.z :=
PyramidPoints[ArrayCount + 2];
uses Pyramid.LineStore[LineCount].EndPoint.x :=
Windows, Messages, SysUtils, Classes, Graphics, PyramidPoints[ArrayCount + 3];
Controls, Forms, Dialogs, ExtCtrls, Gmp, Menus; Pyramid.LineStore[LineCount].EndPoint.y :=
PyramidPoints[ArrayCount + 4];
type Pyramid.LineStore[LineCount].EndPoint.z :=
TPyramidArray = array [0..47] of Integer; PyramidPoints[ArrayCount + 5];
TCubeArray = array [0..71] of Integer; end;
Pyramid.NumberLine := 8;
TForm1 = class(TForm) Pyramid.Color := clYellow;
GMP1 : TGMP; for LineCount := 0 to 11 do begin
Timer1 : TTimer; ArrayCount := LineCount * 6;
MainMenu1 : TMainMenu; Cube.LineStore[LineCount].StartPoint.x :=
Object1 : TMenuItem; CubePoints[ArrayCount];
Cube1 : TMenuItem; Cube.LineStore[LineCount].StartPoint.y :=
Pyramid1 : TMenuItem; CubePoints[ArrayCount + 1];
Cube.LineStore[LineCount].StartPoint.z :=
procedure Timer1Timer(Sender: TObject); CubePoints[ArrayCount + 2];
procedure FormShow(Sender: TObject); Cube.LineStore[LineCount].EndPoint.x :=
procedure Pyramid1Click(Sender: TObject); CubePoints[ArrayCount + 3];
procedure Cube1Click(Sender: TObject); Cube.LineStore[LineCount].EndPoint.y :=
public CubePoints[ArrayCount + 4];
Pyramid : TObject3D; Cube.LineStore[LineCount].EndPoint.z :=
Cube : TObject3D; CubePoints[ArrayCount + 5];
CurrentObject : TPObject3D; end;
end; Cube.NumberLine := 12;
Cube.Color := clYellow;
const CurrentObject := @Cube;
PyramidPoints : TPyramidArray =
Cube1.Checked := True;
( 0, -10, 0, 10, 10, 10,
end;
10, 10, 10, -10, 10, 10,
-10, 10, 10, 0, -10, 0,
procedure TForm1.Cube1Click(Sender: TObject);
0, -10, 0, 10, 10, -10,
begin
10, 10, -10, -10, 10, -10,
if (Cube1.Checked = True) then
-10, 10, -10, 0, -10, 0,
Exit;
-10, 10, 10, -10, 10, 10,
10, 10, -10, 10, 10, 10, ); Cube1.Checked := True;
Pyramid1.Checked := False;
CubePoints : TCubeArray = CurrentObject := @Cube;
(-10, 10, 10, 10, 10, 10, end;
10, 10, 10, 10, -10, 10,
10, -10, 10, -10, -10, 10, procedure TForm1.Pyramid1Click(Sender: TObject);
-10, -10, 10, -10, 10, 10, begin
-10, 10, -10, 10, 10, -10, if (Pyramid1.Checked = True) then
10, 10, -10, 10, -10, -10, Exit;
10, -10, -10, -10, -10, -10, Pyramid1.Checked := True;
-10, -10, -10, -10, 10, -10, Cube1.Checked := False;
10, 10, 10, 10, 10, -10, CurrentObject := @Pyramid;
10, -10, 10, 10, -10, -10, end;
-10, 10, 10, -10, 10, -10,
-10, -10, 10, -10, -10, -10, ); end.

var End Listing Two


Form1: TForm1;
implementation

13 January 1997 Delphi Informant


Informant Spotlight
Delphi 2 / Object Pascal

By Danny Thorpe

Virtual Methods,
Inside Out
Virtual Methods and Polymorphism: Part I

olymorphism is perhaps the cornerstone of object-oriented program-


P ming (OOP). Without it, OOP would have only encapsulation and
inheritance — data buckets and hierarchical families of data buckets — but
no way to uniformly manipulate related objects.

Polymorphism is the key to leveraging your methods (i.e. methods declared with virtual,
programming investments to enable a relatively dynamic, or override), and “virtual”
small amount of code to drive a wide variety of denotes the specific term that refers only to
behaviors, without requiring carnal knowledge methods declared with the virtual directive.
of the implementation details of those behav- For example, most polymorphism concepts
iors. However, before you can extend existing and issues apply to all virtual methods, but
Delphi components, or design new, extensible there are a few noteworthy items that apply
component classes, you must have a firm only to virtual methods.
understanding of how polymorphism works
and the opportunities it provides. Review: Syntax of Virtual Methods
Here’s a review of the two kinds of virtual
True to its name, polymorphism allows methods and four language directives used to
objects to have “many forms” in Delphi, and declare them:
a component writer typically uses a mix of all Virtual methods come in two flavors: vir-
these forms to implement a new component. tual and dynamic. The only difference
In this article, we’ll closely review the imple- between them is their internal implemen-
mentation and use of one of Delphi’s poly- tations; that is, they use different tech-
morphism providers, the virtual method, and niques to achieve the same results.
some of its more peculiar sand traps and Calls to virtual methods are dispatched
exotic applications, e.g. its part in making more quickly than calls to dynamic
.EXEs smaller. (Dynamic methods, message methods.
methods, and class reference types are Seldom-overridden virtual methods
Delphi’s other polymorphism providers, but require much more storage space for their
are outside the scope of this article.) compiler-generated tables than dynamic
methods.
This article assumes you are familiar with The keywords, virtual and dynamic,
Delphi class declaration syntax and general always introduce a new method name
OOP principles. If you’re a bit rusty with into a class’ name space.
these concepts, you should first refer to the The override directive redefines the
Delphi Language Reference. Also note that in implementation of an existing virtual
this article, “virtual” denotes the general method (virtual or dynamic) that a class
term that applies to all forms of virtual inherits from an ancestor.

14 January 1997 Delphi Informant


Informant Spotlight
The override method uses the same dispatch mechanism If we start with a variable P of type TBaseGadget, we can
(virtual or dynamic) as the inherited virtual method it assign to it an instance of a TBaseGadget; or an instance of
replaces. one of its descendants, such as a TKitchenGadget or
The abstract directive indicates that no method body is asso- TOfficeGadget. Recall that Delphi object instance variables
ciated with that virtual method declaration. Abstract declara- are pointers to the instance data allocated from the global
tions are useful for defining a purely conceptual interface, heap, and that pointers of a class type are type compatible
which is in turn useful for maintaining absolute separation with all descendants of that type. We can then call meth-
between the user of a class and its implementation. ods using the instance variable P:
The abstract directive can only be used in the declaration
var
of new virtual (virtual or dynamic) methods; you can’t P : TBaseGadget;
make an implemented method abstract after the fact. begin
A class type that contains one or more abstract methods is an P := TBaseGadget.Create;
P.NotVirtual(10); { Call TBaseGadget.NotVirtual }
abstract class. P.ThisIsVirtual(5); { Call TBaseGadget.ThisIsVirtual }
A class type that contains nothing but abstract methods P.Free;
(no static methods, no virtual methods, no data fields) is end;

called an abstract interface (or, in C++ circles, a pure virtu-


al interface). (In the interest of brevity, I’ll fold the execution traces into com-
ments in the source code. You can step through the sample code
Polymorphism in Action to verify the execution trace.)
What do virtual methods do? In general, they allow a
method call to be directed, at run time, to the appropriate If P refers to an instance of TKitchenGadget, the execution
piece of code, appropriate for the type of the object instance trace would resemble the code in Figure 2. Nothing remark-
used to make the call. For this to be interesting, you must able here; we have one call to a static method going to the
have more than one class type, and the class types must be version defined in the ancestor type, and one call to a virtual
related by inheritance from a common ancestor. method going to the version of the method associated with
the object instance type.
Figure 1 shows the three classes we’ll use to explore the execu-
tion characteristics of polymorphism: a simple base class named You may deduce that the inherited static method, NotVirtual,
TBaseGadget that defines a static method named NotVirtual and is called because TKitchenGadget doesn’t override it. This obser-
a virtual method, ThisIsVirtual; and two descendant classes, vation is correct, but the explanation is flawed, as Figure 3
TKitchenGadget and TOfficeGadget, that override the shows. If P refers to an instance of TOfficeGadget, you may be
ThisIsVirtual method they inherit from TBaseGadget. a little puzzled by the result.
TOfficeGadget also introduces a new static method named
NotVirtual and a new virtual method named NewMethod. Static method calls are resolved by variable type. Although
TOfficeGadget has its own NotVirtual method, and P refers to
type
an instance of TOfficeGadget, why does TBaseGadget.NotVirtual
TBaseGadget = class get called instead? This occurs because static (non-virtual)
procedure NotVirtual(X: Integer);
procedure ThisIsVirtual(Y: Integer); virtual;
end; var
P : TBaseGadget;
TKitchenGadget = class(TBaseGadget) begin
procedure ThisIsVirtual(Y: Integer); override; P := TKitchenGadget.Create;
end; P.NotVirtual(10); { Call TBaseGadget.NotVirtual }
P.ThisIsVirtual(5); { Call TKitchenGadget.ThisIsVirtual }
TOfficeGadget = class(TBaseGadget); P.Free;
function NewMethod: Longint; virtual; end;
procedure NotVirtual(X,Y,Z: Integer);
procedure ThisIsVirtual(Y: Integer); override; Figure 2: Execution with an instance of TKitchenGadget.
end;

Figure 1: Three classes to explore polymorphism. var


P : TBaseGadget;
begin
Identical names in different classes aren’t related. P := TOfficeGadget.Create;
Declaring a static method in a descendant that happens to P.NotVirtual(10); { Call TBaseGadget.NotVirtual }
have the same name as a static method in an ancestor is
{ The compiler will not allow the following two lines:
not a true override. Other than same-name similarity, no P.NotVirtual(1,2,3); "Too many parameters"
relationship exists between static methods declared in a P.NewMethod; "Method identifier expected" }
descendant and static methods declared in an ancestor
P.ThisIsVirtual(5); { Call TOfficeGadget.ThisIsVirtual }
class. Your brain makes an association, but the compiler P.Free;
does not. For instance, TBaseGadget has a NotVirtual end;
method, and TOfficeGadget has a disparate method, also
named NotVirtual. Figure 3: Execution with an instance of TOfficeGadget.

15 January 1997 Delphi Informant


Informant Spotlight
method calls are resolved at compile time according to the type Ancestors set the standard. Why do we care about the
of the variable used to make the call. For static methods, what nearsightedness of ancestral classes? Why not simply use
the variable refers to is immaterial. In this case, P ’s type is the matching variable type when you create or manipulate
TBaseGadget, meaning the NotVirtual method associated with an object instance? Sometimes this is the simplest thing to
P ’s declared type is TBaseGadget.NotVirtual. do. However, this “simplest” solution falls apart when you
begin talking about manipulating multiple classes that do
Notice that NewMethod defined in TOfficeGadget is out of almost the same things.
reach of a TBaseGadget variable. P can only access fields and
methods defined in its TBaseGadget object type. Ancestral class types set the minimum interface standard
through which we can access a set of related objects.
New names obscure inherited names. Let’s say P is declared Polymorphism is the use of virtual methods to make one verb
as a variable of type TOfficeGadget. The following method (method name) produce one of many possible actions
call would be allowed: depending on the context (the instance). To have multiple,
possible actions, you must have multiple class types (e.g.
P.NotVirtual(1,2,3)
TKitchenGadget and TOfficeGadget) each potentially defining
a different implementation of a particular method.
However, this method call:

P.NotVirtual(1) To be able to make one call that could cover those multiple
class types, the method must be defined in a class from which
would not be allowed, because TOfficeGadget.NotVirtual all the multiple class types descend — in an ancestral class such
requires three parameters. as TBaseGadget. The ancestral class, then, is the least common
denominator for behavior across a set of related classes.
TOfficeGadget.NotVirtual obscures the TBaseGadget.NotVirtual
method name in all instances and descendants of TOfficeGadget. For polymorphism to work, all the actions common to the
The inherited method is still a part of TOfficeGadget (proven by group of classes need to at least be named in a common
the code in Figure 3); you just can’t get to it directly from ancestor. If every descendant is required to override the ances-
TOfficeGadget and descendant types. tor’s method, the ancestral method doesn’t need to do any-
thing at all; it can be declared abstract.
To get past this, you must typecast the instance variable:
If there is a behavior that is common to most of the classes in
TBaseGadget(P).NotVirtual(1) the group, the ancestor class can pick up that default behavior
and leave the descendants to override the defaults only when
If P were declared as a TOfficeGadget variable, necessary. This consolidates code higher in the class hierarchy,
P.NewMethod would also be allowed, because the compiler for greater code reuse and smaller total code size. However, pro-
can “see” NewMethod in a TOfficeGadget variable. viding default behaviors in an ancestor class can also complicate
the design issues of creating flexible, extensible classes, since
Descendant >= ancestor. An instance of a descendant type what is done by ancestors usually cannot be entirely undone.
could be greater than its ancestor type in both services and data.
However, the descendant-type instance can never be less than Polymorphism lets ancestors reach into descendants.
what its ancestors define. This makes it possible for you to use a Another aspect of polymorphism doesn’t appear to involve
variable of an ancestral type (e.g. TBaseGadget) to refer to an instance pointer types at all — at least not explicitly.
instance of a descendant type without loss of information.
Consider the code fragment in Figure 4. The
Inheritance is a one-way street. With a variable of a particular TBaseGadget.NotVirtual method contains an unqualified call to
class type, you can access any public symbol (field, property, or ThisIsVirtual. When P refers to an instance of TKitchenGadget,
method) defined in any of that class’ ancestors. You can assign
an instance of a descendant class into that variable, but cannot procedure TBaseGadget.NotVirtual;
access any new fields or methods defined by the descendant begin
class. The fields of the descendant class are certainly in the ThisIsVirtual(17);
end;
instance data that the variable refers to, yet the compiler has no
way of knowing that run-time situation at compile time. var
P: TBaseGadget;

There are two ways around this “nearsightedness” of ancestral begin


class types: P := TKitchenGadget.Create;
Typecasting — The programmer assumes a lot and forces P.NotVirtual(10); { Call TBaseGadget.NotVirtual }
P.Free;
the compiler to treat the variable as a descendant type. end.
Virtual methods — The magic of virtual will call the
method appropriate to the type of the associated instance, Figure 4: Polymorphism allows ancestors to call into descen-
determined at run time. dants.

16 January 1997 Delphi Informant


Informant Spotlight

Figure 5: The structure of the Virtual Method Table, and its relationship to the object instance.

P.NotVirtual will call TBaseGadget.NotVirtual. Nothing new, so method call. You’ve specified that you want only the
far. However, when that code calls ThisIsVirtual, it will execute TBaseGadget.ThisIsVirtual method called, so the compiler
TKitchenGadget.ThisIsVirtual. Surprise! Even within the depths does exactly what you tell it to do. Dispatching this as a
of TBaseGadget, a non-virtual method, a virtual method call is virtual method call may call some other version of that
directed to the appropriate code. method, which would violate your explicit instructions.
Except in special circumstances, you don’t want this in
How can this be? The resolution of virtual method calls your code because it defeats the whole purpose of making
depends on the object instance associated with the call. A ThisIsVirtual virtual.
pointer to the object instance is secretly passed into all
method calls, surfacing inside methods as the Self identifier. The Virtual Method Table
Inside TBaseGadget.NotVirtual, a call to ThisIsVirtual is actu- A Virtual Method Table (VMT) is an array of pointers to all
ally a call to Self.ThisIsVirtual. Self, in this context, operates the virtual methods defined in a class and all the virtual
like a variable of type TBaseGadget that refers to an instance methods the class inherits from its ancestors. A VMT is creat-
of type TKitchenGadget. Thus, when the instance type is ed by the compiler for every class type, because all classes
TKitchenGadget, the virtual method call resolves, at run time, descend from TObject and TObject has a virtual destructor
to TKitchenGadget.ThisIsVirtual. named Destroy. In Delphi, VMTs are stored in the program’s
code space. Only one VMT exists per class type; multiple
How is this useful? An ancestral method — virtual or not — instances of the same class type refer to the same VMT. At
can call a sequence of virtual methods. The descendants can run time, the VMT is a read-only lookup table.
determine the specific behavior of one or more of those vir-
tual methods. The ancestor determines the sequence in Structure of the VMT. As shown in Figure 5, the first four
which the methods are called, plus miscellaneous setup and bytes of data in an object instance are a pointer to that class
cleanup code. The ancestor, however, does not completely type’s VMT. The VMT pointer points to the first entry in the
determine the final behavior of the descendants. The descen- VMT’s list of four-byte pointers to the entry points of the
dants inherit the sequence logic from the ancestor, and can class’ virtual methods. Since methods can never be deleted in
override one or more of the steps in that sequence. But, the descendant classes, the location of a virtual method in the
descendants don’t have to reproduce the entire sequence VMT is the same throughout all descendant classes. Thus,
logic. This is one of the ways OOP promotes code reuse. the compiler can view a virtual method simply as a unique
entry in the class’ VMT. As we’ll see shortly, this is exactly
Fully-qualified method calls are reduced to static calls. As a how virtual method calls are dispatched. Thinking of virtual
footnote, consider what happens if TBaseGadget.NotVirtual methods as indexes into an array of code pointers will also
contains a qualified call to TBaseGadget.ThisIsVirtual: help us visualize how method name conflicts are resolved by
the compiler.
procedure TBaseGadget.NotVirtual;
begin
TBaseGadget.ThisIsVirtual(17);
The VMT does not contain information indicating how
end; many virtual methods are stored in it or where the VMT
ends. The VMT is constructed by the compiler and accessed
Although ThisIsVirtual is a virtual method, a fully-quali- by compiler-generated code, so it doesn’t need to make notes
fied method call will compile down to a regular static to itself about size or number of entries. (This does, however,

17 January 1997 Delphi Informant


Informant Spotlight
make it difficult for BASM code to call virtual methods.) The VMT pointer is always stored at offset 0 (zero) in the
Optimization note. A descendant of a class with virtual meth- instance data. In this example, the method being called is the
ods gets a new copy of the ancestor’s VMT table. The descen- third virtual method of a class, including inherited virtual
dant can then add new virtual methods or override inherited methods. The first virtual method is at offset 0, the second at
virtual methods without affecting the ancestor’s VMT. For offset 4, and the third at offset 8.
example, if the ancestor has a 12-entry VMT, the descendant
has at least a 12-entry VMT. Every descendant class type of Conclusion
that ancestor, and all descendants of those descendants, will That’s it — all the magic of virtual methods and polymor-
have at least 12 entries in their individual VMTs. phism boils down to this: the indicator of which virtual
method to invoke on the instance data is stored in the
All these VMTs occupy memory. For most programs, this instance data itself.
won’t be a problem, but extraordinarily large class types
with thousands of virtual methods and/or thousands of Next month, we’ll conclude our series with a discussion of
descendants could consume quite a bit of memory, both in abstract interfaces and how virtual methods can defeat and
RAM and .EXE file size; dynamic methods are much more enhance “smart linking.” See you then. ∆
space efficient, but incur a slight execution speed penalty.

Now let’s examine the mechanics behind the magic of virtual This article is adapted from material for Danny Thorpe’s book,
method calls. Delphi Component Design [Addison-Wesley, 1996].

Inside a virtual method call. When the compiler is compiling


your source code and encounters a call to a virtual method
identifier, it generates a special sequence of machine instruc-
tions that will unravel the appropriate call destination at run
time. The following machine code snippets assume compiler
optimizations are enabled, and stack frames are disabled:

// Machine code for statement P.SomeVirtualMethod;


Danny Thorpe is a Delphi R&D engineer at Borland. He has also served as techni-
{ Move instance data address (P^) into EAX } cal editor and advisor for dozens of Delphi programming books, and recently com-
MOV EAX, [EBP+4]
pleted his book, Delphi Component Design, on advanced topics in Delphi pro-
{ Move instance’s VMT address into ECX }
MOV ECX, [EAX]
gramming. When he happens upon some spare time, he rewrites his to-do list
{ Call address stored at VMT index 2 }
manager to ensure that it doesn’t happen again.
CALL [ECX + 08]

18 January 1997 Delphi Informant


DBNavigator
Delphi 1 / Delphi 2

By Cary Jensen, Ph.D.

INI, the Registry, or Both?


Three Approaches to Creating MRU Lists

any applications require configuration and initialization information


M to be stored on a per-user basis; for example, to retain a list of the
files a user has accessed most recently. Similarly, some applications permit
a user to select a custom bitmap to display as a background.

The standard technique for saving this type The structure of an INI file is simple. Each
of information is to use INI files in contains one or more sections. The name of
Windows 3.1x, and the Registry in each section appears within brackets. Each
Windows 95 and Windows NT. This section contains zero, one, or multiple keys.
month’s installment offers an overview of The name of each key appears on a separate
how to save this information, focusing on line, and is separated from its value by the
the creation of a “most recently used” “equal” sign ( = ). In Figure 1, for example,
(MRU) file list as an example. the INI file contains a section named
Library. Within this section is a key
Using INI Files named SearchPath. The value of this key
In Windows 3.1x, the standard technique for is C:\DELPHI\LIB.
storing user-specific information is to employ
an INI (initialization) file. While typically Delphi provides a unit named IniFiles that
stored in the Windows directory, it can also defines the TIniFile class. This class encapsu-
be stored in the same directory as the EXE lates all the basic calls you need to work with
file (as long as the EXE isn’t stored in a INI files. You create an instance of the
shared directory). Delphi 1, for example, TIniFile class (i.e. an object of type IniFile)
stores information concerning its installation, by calling its Create constructor. This method
as well as user preferences and settings, in a has the following syntax:
file named DELPHI.INI. A portion of this
file, located in the \WINDOWS directory, is constructor Create(const FileName: string);

shown in Figure 1.
You pass a single string parameter when you
call Create. This parameter identifies the name
[Library] of an INI file that will be either opened or
SearchPath=C:\DELPHI\LIB
ComponentLibrary=C:\DELPHI\BIN\COMPLIB.DCL created (e.g. 'TestINI.INI'). If the specified
SaveLibrarySource=0 INI file does not exist, calling Create creates it.
MapFile=0 Otherwise, Create opens the INI file. If the
LinkBuffer=0
DebugInfo=0 file name you supply includes a DOS path,
the INI file will be created or opened in the
[Gallery] directory you specify. If you omit the path,
BaseDir=C:\DELPHI\GALLERY
GalleryProjects=1 the Windows directory is used by default.
GalleryForms=1
Once you’ve created an INI file, you’re ready
Figure 1: A portion of the DELPHI.INI file. to read and write keys to it, using the

19 January 1997 Delphi Informant


DBNavigator
4) You must add four methods to your form’s type definition:

procedure LoadMRU;
procedure UpdateMRU(const FileName: string);
procedure WriteMRU;
procedure DisplayMRU;

5) You must add a constant named MaxMRUS to your


form’s unit. Assign to this constant the integer associated
with the maximum number of items in your MRU list.
For example, if you permit a maximum of four items,
your const statement will look like this:

const
MaxMRUS = 4;

Figure 2: This application’s MRU list is stored in an INI file.


6) Add a var declaration to your form’s unit, as follows:
WriteString, WriteBool, and WriteInteger methods. This is the var
syntax of the WriteString method: MRUS: TStringList;
MRUSChanged: Boolean;
procedure WriteString(const Section, Ident, Value: string); Ini: TIniFile;

When calling WriteString, you pass three string parameters. 7) Implement the new methods described in step 4. An
The first is the name of the section you’re writing to, the sec- example of these methods implemented for a TForm1
ond is the name of the key, and the third is the value you’re class is shown in Listing Three on page 23.
assigning to the key.
Using these methods is straightforward. Call LoadMRU and
The corresponding read methods are ReadString, ReadBool, DisplayMRU from the form’s OnCreate event handler. Each time
and ReadInteger. The following is the syntax of ReadString: a new file is opened, call UpdateMRU. Finally, call WriteMRU
from the form’s OnDestroy event handler. You should also explic-
function ReadString( const Section, Ident, itly free both the IniFile object and the StringList object from
Default: string): string; within the OnDestroy event handler. Figure 3 shows examples of
how these event handlers might look.
When you call ReadString, you pass the name of the section
and the key that you want to read. In addition, you pass a Using the Registry
third argument whose value ReadString retains if the specified While Windows 3.1x makes extensive use of INI files for stor-
section and key do not exist. ing client-specific information, Windows 95 and Windows NT
encourage the use of the Registry for this purpose. The Registry
These methods are employed in the project named INI.DPR, is a centralized information database used as a repository for all
shown in Figure 2. This project demonstrates how to imple- client information by Windows 95 and Windows NT. You can
ment a generic MRU list for a Delphi menu. Using this
example code, however, requires some preparation. procedure TForm1.FormCreate(Sender: TObject);
Specifically, you must perform the following steps: begin
1) Add the IniFiles unit to the uses clause for the form. This MRUS := TStringList.Create;
LoadMRU;
clause can appear in either the interface or the implemen- DisplayMRU;
tation section of the unit. end;

procedure TForm1.FormDestroy(Sender: TObject);


2) Create a MainMenu object that includes one begin
TMenuItem object for each of the MRU values in the WriteMRU;
MRUS.Free;
list. These TMenuItem objects must use the naming end;
convention MRU1, MRU2, MRU3, and so forth. For
procedure TForm1.Open1Click(Sender: TObject);
example, if you plan to permit a maximum of four begin
items in your MRU list, your MainMenu must include if OpenDialog1.Execute then
begin
TMenuItem objects with the names MRU1, MRU2, Form2 := TForm2.Create(Self);
MRU3, and MRU4. Form2.Caption := OpenDialog1.FileName;
UpdateMRU(OpenDialog1.FileName);
end;
3) You must add a separator to the menu immediately pre- end;
ceding the MRU-related menu items. Furthermore, this
TMenuItem must be named MRUDiv. Figure 3: Event handlers for the example MRU application.

20 January 1997 Delphi Informant


DBNavigator
One of the major advantages of the TRegIniFile unit is that it
permits you to create a single source file compilable by either
Delphi 1 or 2 — if you’re willing to include a few condition-
al compiler directives. For example, you can replace each
TIniFile variable declaration with a conditional compiler
directive that will create a TIniFile variable under Delphi 1,
or a TRegIniFile variable under Delphi 2.

The following code segment demonstrates how this should look:

var
{$IFDEF Win32}
Figure 4: REGEDIT.EXE allows you to view the Registry Editor. Ini: TRegIniFile;
{$ELSE}
Ini: TIniFile;
view the Registry Editor using REGEDIT.EXE (see Figure 4), {$ENDIF}
an application located in the \WINDOWS folder.
Furthermore, you must also use conditional compiler direc-
The Registry consists of keys, subkeys, values, and data. This tives when calling the Create constructor for the declared
information is depicted hierarchically, meaning that a key variable, as well as for the uses clause. For example, the fol-
may contain subkeys, and those subkeys may also contain lowing code calls the TIniFile constructor under Delphi 1,
subkeys. Typically, the lowest subkey on a branch will have and the TRegIniFile constructor under Delphi 2:
one or more values displayed in the right panel of the
Registry Editor dialog box. Within this right panel, the name {$IFDEF Win32}
of the value appears in the left column, and the data associat- Ini := TRegIniFile.Create('mru.ini');
{$ELSE}
ed with that value appears in the right column. Ini := TIniFile.Create('mru.ini');
{$ENDIF}
The Registry offers many advantages over INI files:
The Registry does not have an inherent size limit; INI An example of a project that uses these techniques is shown
files are limited to 64KB. in Figure 5. This project is named INIREG.DPR. Figure 6
The Registry is structured hierarchically, giving you more shows how the Registry appears at run time after compiling
flexibility in how information is stored, and making spe- the INIREG application under Delphi 2.
cific values easier to locate.
The Registry can be administered remotely by a system
administrator (or your Delphi code, for that matter).
A single Registry can store information about multiple
users.

Delphi 2 has two Object Pascal classes for working with the
Registry. These are TRegIniFile and TRegistry. Both are
defined in the Registry unit.

Using the TRegIniFile Class


The TRegIniFile class is designed to permit applications writ-
ten with the TIniFile class to be quickly and easily converted
to Registry use. All methods and properties of the TIniFile
class are present in the TRegIniFile class. This permits you to
Figure 5: Though nearly identical to INI.DPR, INIREG.DPR uses
upgrade an application simply by changing all TIniFile class
an INI file when compiled in Delphi 1, and the Registry when
references to TRegIniFile. compiled by Delphi 2.

The Create method of TRegIniFile either opens or creates a Using the TRegistry Class
subkey in the HKEY_CURRENT_USER root key, using the The TRegIniFile class is extremely useful when creating applica-
file name as the key name. For example, the code in Listing tions that must be used in both 16- and 32-bit environments, as
Three creates an INI file named MRU.INI. If you change the well as for quickly porting applications to a 32-bit environment.
TIniFile references to TRegIniFile, and replace the IniFiles It’s limited, however, in that it permits you to add subkeys only
unit with the TRegistry unit, this code will create a key directly under the HKEY_CURRENT_USER root key.
named MRU.INI under the HKEY_CURRENT_USER root
key. Furthermore, instead of creating sections using the write For more complete control, you should use the TRegistry class,
methods, subkeys to MRU.INI will be created. In addition, which permits you to add subkeys and values to any key within
keys written to a section become values within the subkey. the Registry. While the TRegistry class contains a large number

21 January 1997 Delphi Informant


DBNavigator
will open or create a key named MRUS under the current
key, and will make it the new current key.

CreateKey is used (again, as you might guess) to create new


keys. CreateKey is different from OpenKey in that it doesn’t
make the specified key the current key. As with the preceding
statements, you can use either an absolute or a relative
address to specify the key.

Finally, ValueExists tests whether a specified value exists under


the current key. Unlike TIniFile or TRegIniFile objects, which
Figure 6: This is how the Registry Editor appears after running permit you to read from a specified section and key even if
INIREG.DPR compiled with Delphi 2. the key doesn’t exist, the TRegistry class will generate an
exception. Consequently, you’ll generally use ValueExists to
of methods, most of the work you’ll do can be accomplished test the existence of a value before attempting to read from it.
with a select few. Among the most valuable are Create, KeyExists,
OpenKey, CreateKey, and ValueExists. The write and read meth- The read and write methods for TRegistry are also different
ods (such as WriteString and ReadString) are essential as well. from the associated methods of the TIniFile class. For exam-
ple, this is the syntax of the TRegistry.ReadString method:
Create is used to initialize a TRegistry descendant. Unlike
function ReadString(const Name: string): string;
the Create constructor for the TIniFile and TRegIniFile
classes, TRegistry.Create requires no arguments. By default,
the root key when you create a Registry object is To use this method, pass the name of a value under the cur-
HKEY_CURRENT_USER. You can change the root key rent key. ReadString returns the data associated with that
by using the RootKey property of the TRegistry class. value. Again, if the specified value doesn’t exist, an excep-
tion is generated.
KeyExists tests whether a particular key exists. The key
whose existence you’re testing is relative to the root key The write methods in the Registry class are somewhat more
when you use an absolute address, and is relative to the similar to their TIniFile counterparts than are the read meth-
current key when using a relative address. Absolute address- ods. For example, this is the syntax of the
es begin with a backslash; relative addresses do not. To test TRegistry.WriteString method:
whether HKEY_CURRENT_USER\MRU.INI\MRUS
procedure WriteString(const Name, Value: string);
exists, you can use the following statement, regardless of
which is the current key:
The first parameter you pass to WriteString is the name of a
if RegVar.KeyExists('\MRU.INI\MRUS') then value under the current key. If the named value doesn’t
... already exist, it will be created. The second is the data to
write to the specified key. In addition to the syntactical differ-
However, the statement: ences between the read and write methods of TRegistry and
those in the TIniFile class, the TRegistry class supports meth-
RegVar.KeyExists('MRU.INI\MRUS') ods for reading and writing data types other than just string,
Boolean, and integer. Read and write methods are included
evaluates to True only when a key named MRU.INI\MRUS for binary, datetime, currency, and float data, among others.
exists under the current key.
The use of the TRegistry class is demonstrated in the project
OpenKey makes the named key the current key; also, if you REGISTRY.DPR. This project is quite similar to the INI.DPR
pass a Boolean True as the second parameter in the call to and INIREG.DPR projects discussed earlier. The primary dif-
OpenKey, it will create the specified key if that key doesn’t ference revolves around the declaration of the Registry variable,
already exist. When using OpenKey, just as when using as well as the implementation of the four MRU methods. This
KeyExists, you can give an absolute key name that begins with code is shown in Listing Four, beginning on page 23.
a backslash, or a relative key name. For example, calling:
Figure 7 shows how the Registry looks after running
OpenKey('\Software\YourCompany\ThisApp',True)
REGISTRY.DPR.
opens or creates a key named HKEY_CURRENT_USER\-
Conclusion
SOFTWARE\YOURCOMPANY\THISAPP, and makes it the
Delphi provides a number of options for saving user-specific
current key. By comparison, calling:
information from one execution of your application to the
OpenKey('MRUS',True) next. For Windows 3.1x applications, use the TIniFile class to

22 January 1997 Delphi Informant


DBNavigator

MI := TMenuItem(FindComponent(Concat(
'mru',inttoStr(i+1))));
MI.Visible := True;
MI.Caption := MRUS.Strings[i];
Inc(i);
end;
MRUDiv.Visible := MRU1.Visible;
end;

procedure TForm1.UpdateMRU(const filename: string);


var
i: Integer;
begin
MRUSChanged := True;
if MRUS.IndexOf(filename) = -1 then
{ Add filename to top, move all down }
begin
Figure 7: The REGISTRY.DPR project writes to the Registry key MRUS.Insert(0,filename);
HKEY_CURRENT_USER\SOFTWARE\YOURCOMPANY\THISAPP\MRUS. { Remove last item if MRU list already full }
if MRUS.Count > MAXMRUS then
MRUS.Delete(MAXMRUS-1);
write to INI files. For applications that must be compiled end
else
under both Delphi 1 and 2, you can use the TRegIniFile
MRUS.Move(MRUS.IndexOf(filename),0);
class. Finally, when creating 32-bit applications with Delphi DisplayMRU;
2, use the TRegistry class. ∆ end;

procedure TForm1.WriteMRU;
var
The files referenced in this article are available on the Delphi Ini: TIniFile;
i: Integer;
Informant Works CD located in INFORM\97\JAN\DI9701CJ.
begin
if MRUSChanged then
begin
Ini := TIniFile.Create('mru.ini');
Cary Jensen is President of Jensen Data Systems, Inc., a Houston-based database try
i := 0;
development company. He is author of more than a dozen books, including Delphi
while (i < (MRUS.Count)) and (i < MaxMRUS) do begin
In Depth [Osborne/McGraw-Hill, 1996]. He is also Contributing Editor to Delphi Ini.WriteString('MRUS',Concat('mru',
Informant. You can reach Jensen Data Systems at (713) 359-3311, or via inttoStr(i+1)),MRUS.Strings[i]);
CompuServe at 76307,1533. Inc(i);
end;
finally
Ini.Free;
end;
Begin Listing Three — Example MRU Implementation
end;
procedure TForm1.LoadMRU;
end;
var
Ini: TIniFile;
End Listing Three
i: Integer;
Value: string;
begin
Begin Listing Four — Demonstrating the TRegistry Class
var
Ini := TIniFile.Create('mru.ini');
{ Hold the MRUs during the application }
try
MRUS: TStringList;
{ Load MRUs into Tstrings }
{ Flag for writing MRUs from OnDestroy for form }
for i := 1 to MaxMRUS do
MRUSChanged: Boolean;
begin
Reg: TRegistry;
Value := Ini.ReadString(
procedure TForm1.LoadMRU;
'MRUS',Concat('MRU',IntToStr(i)),'');
var
MRUS.Add(value);
i: Integer;
end;
Value: string;
{ Remove blank MRUs }
begin
while MRUS.IndexOf('') <> -1 do
Reg := TRegistry.Create;
MRUS.Delete(MRUS.IndexOf(''));
try
finally
Reg.OpenKey('\Software\YourCompany\ThisApp\MRUS',True);
Ini.Free;
{ Load MRUs into Tstrings }
end;
for i := 1 to MaxMRUS do begin
end;
if Reg.ValueExists(Concat('MRU',IntToStr(i))) then
begin
procedure TForm1.DisplayMRU;
Value := Reg.ReadString(
var
Concat('MRU',IntToStr(i)));
MI: TMenuItem;
MRUS.Add(value);
i: Integer;
end
begin
else
i := 0;
Break;
while (i < (MRUS.Count)) and (i < MaxMRUS) do
end;
begin

23 January 1997 Delphi Informant


DBNavigator

finally
Reg.Free;
end;
end;

procedure TForm1.DisplayMRU;
var
MI: TMenuItem;
i: Integer;
begin
i := 0;
while (i < (MRUS.Count)) and (i < MaxMRUS) do begin
MI := TMenuItem(FindComponent(Concat('mru',
inttoStr(i+1))));
MI.Visible := True;
MI.Caption := MRUS.Strings[i];
Inc(i);
end;
MRUDiv.Visible := MRU1.Visible;
end;

procedure TForm1.UpdateMRU(const filename: string);


var
i: Integer;
begin
MRUSChanged := True;
if MRUS.IndexOf(filename) = -1 then
{ Add filename to top, move all down }
begin
MRUS.Insert(0,filename);
{ Remove last item if MRU list already full }
if MRUS.Count > MAXMRUS then
MRUS.Delete(MAXMRUS-1);
end
else
MRUS.Move(MRUS.IndexOf(filename),0);
DisplayMRU;
end;

procedure TForm1.WriteMRU;
var
i: Integer;
begin
if MRUSChanged then
begin
Reg := TRegistry.Create;
try
Reg.OpenKey('\Software\YourCompany\ThisApp\MRUS',
True);
i := 0;
while (i < (MRUS.Count)) and (i < MaxMRUS) do begin
Reg.WriteString(Concat('mru',
inttoStr(i+1)),MRUS.Strings[i]);
Inc(i);
end;
finally
Reg.Free;
end;
end;
end;

procedure TForm1.FormCreate(Sender: TObject);


begin
MRUS := TStringList.Create;
LoadMRU;
DisplayMRU;
end;

procedure TForm1.FormDestroy(Sender: TObject);


begin
WriteMRU;
MRUS.Free;
end;
End Listing Four

24 January 1997 Delphi Informant


OP Tech
Delphi 1 / Delphi 2

By Ray Lischner

Stream of Consciousness
The VCL’s Internal Component Messages

he components in the VCL send messages among themselves to com-


T municate events, changes in state, and other information. When you
write a custom component, you can usually inherit the correct message
handlers from one of Delphi’s control classes, such as TCustomControl or
TGraphicControl. In some cases, however, you might need to handle these
internal messages differently. This article describes some of the internal
messages the VCL uses.

The component messages are generated by set the Color property of a control, it broad-
the VCL in response to Windows messages casts the Cm_ParentColorChanged message
and user actions. The default action of a com- to its children so they can respond to the
ponent when it receives many of the VCL change, if needed. Delphi’s pre-defined com-
component messages is to broadcast that mes- ponents, for example, check the ParentColor
sage to all its child components. These mes- property, and if it is True, change the com-
sages originate with a Windows control that ponent’s Color to match the Parent control’s
receives a standard Windows message. new Color.

For example, a form receives a The messages described here are only some of
Wm_WinIniChange message, and broadcasts the internal component messages that Delphi
a Cm_WinIniChange message to its children, defines. Note that not all Windows messages
which in turn broadcast that message to their have corresponding component messages.
children, until it’s been sent to every child on
the form (see Figure 1). If you want your component to intercept,
say, a Wm_Compacting message sent to the
This enables you to write components that form, you cannot use a component message,
can respond to standard Windows messages but must set up an application message
without interfering with the form, which is hook, which is a completely different topic.
important when you’re writing a reusable
component. The component can silently do Many of the component messages don’t have
its work, responding to messages the form specific argument types, so you can use
automatically sends; the application pro- TMessage. Others do have specific types, such
grammer never needs to be concerned with as TCmDialogChar for the Cm_DialogChar
these details. message. If a message type is defined, the
descriptions here show the message type.
Other Cm_ messages are generated by VCL
components to broadcast to their child com- Some messages require that a result be stored
ponents, informing the children of a change in the message argument’s Result field. The
in the parent’s state. For example, when you value of the result depends on the message.

25 January 1997 Delphi Informant


Op Tech

Application Form Panel Edit

Windows sends
Wm_WinIniChange Application
to Application’s dispatches
message queue Wm_WinIniChange
to main form Form sends
Cm_WinIniChange
to Self

Form sends Child controls


Cm_WinIniChange broadcast
to all children Cm_WinIniChange
to their children

Controls take appropriate


action, e.g. reset
date-time format settings

Application waits
for next message

Figure 1: Sending the Cm_WinIniChange message.

Cm_AppKeyDown Message the main form, even if the child has its own menu. If you
The Cm_AppKeyDown message is sent from a control to the went to the trouble of adding a menu bar to a child form, you
Application object in response to a Cn_KeyDown or probably want the child form to handle its own menu short-
Cn_SysKeyDown event. The control first checks whether the cut messages. To do this, you must intercept the
key event is a shortcut for a pop-up menu. If not, the con- Cm_AppKeyDown and the Cm_AppSysCommand messages.
trol sends the Cm_AppKeyDown event to the application, The former handles the accelerator shortcuts and the latter
and the application handles the message by checking handles the caption shortcuts. Because the Cm_AppKeyDown
whether the key-down event is a shortcut key for the main message is sent only to the Application object, you cannot
form. If so, the main form handles the event, and the mes- write a handler for it.
sage Result is set to 1. If Result is zero, the main form didn’t
handle the event, which leaves the control that originated You can, however, write a message hook to intercept the mes-
the message to handle the Cn_KeyDown event. In other sage. The message hook can pretend the message has been
words, this message is how Delphi forwards a menu shortcut handled, thereby preventing the main form’s menu from ever
key from a child form to the main form. seeing the shortcut key. That lets each form handle its own
key stroke events. Figure 2 shows one way to hook the
For this message, a shortcut key is the value of a menu item’s Cm_AppKeyDown and Cm_AppSysCommand messages to pre-
ShortCut property. When the menu item Caption contains an vent the main form from receiving them.
ampersand (&), the letter after the ampersand is also a short-
cut key, but it’s treated differently. In this article, the latter The Cm_AppKeyDown message, like other key stroke messages,
shortcuts are called caption shortcuts and the former are called uses the TWmKeyDown message type. The CharCode field is
accelerator shortcuts. the virtual key code, and the KeyData field holds the modifier
keys, repeat count, and other key data. Figure 3 shows a
In Delphi 2, you don’t need to handle or send the description of the constituent parts of the KeyData field.
Cm_AppKeyDown message. If a child form has a menu bar, it
handles its own menu shortcuts, and if it doesn’t, it forwards Cm_AppSysCommand Message
the shortcut keys to the main form. In Delphi 1, however, you The Cm_AppSysCommand message is sent in response to a
must handle this message if you want a child form to have its Wm_SysCommand message, but only when the sending form
own menu bar, separate from the main form’s menu bar. is not the main form, the form is not minimized, the com-
In Delphi 1, a child form forwards its menu shortcut keys to mand is Sc_KeyMenu, and the key is not a space or a hyphen

26 January 1997 Delphi Informant


Op Tech
type
function TMainForm.AppKeyDownHook(var Msg: TMessage): TCmButtonPressed = record
Boolean; Msg: Cardinal;
begin GroupIndex: Integer;
case Msg.Msg of Sender: TSpeedButton;
Cm_AppkeyDown : Result := True; Result: LongInt;
Cm_AppSysCommand : Result := True; end;
else
Result := False;
end; Figure 4: TCmButtonPressed type, for use when handling
end; Cm_ButtonPressed events.

procedure TMainForm.FormCreate(Sender: TObject); (wParam) and the sender button (lParam), but no message
begin type is defined. For your convenience, Figure 4 shows the
Application.HookMainWindow(AppKeyDownHook)
end; TCmButtonPressed type, which you can use when handling
this message.
Figure 2: How to intercept and discard the Cm_AppKeyDown
and Cm_AppSysCommand events. If you derive a new class from TSpeedButton, you can handle
this message if you want to implement different behavior
when a speed button is pressed. You can also handle this event
type for non–speed button components, perhaps to reflect in a dif-
TKeyDataFlag = ( kdExtended, kdUnused1, kdUnused2, ferent manner which speed button is currently depressed.
kdInternal1, kdInternal2, kdAltDown,
kdWasDown, kdReleased);
TKeyDataFlags = set of TKeyDataFlag; The result is ignored for this message.
TKeyData = record
RepeatCount: Word;
ScanCode:
Flags:
Byte;
TKeyDataFlags;
Cm_ColorChanged Message
end; When a control’s Color property changes, it sends the
Cm_ColorChanged message to itself. The default message
handler invalidates the control, so Windows will repaint it.
Figure 3: TKeyData record for interpreting key event data.
A control with children broadcasts the
Cm_ParentColorChanged message to its children so they can
(-). In other words, this message is sent when the user presses respond to the color change, if needed.
a caption shortcut key.
TWinControl also copies the color to its Brush.Color prop-
In Delphi 1, you must intercept this message when you want erty, and TForm copies the Color to its Canvas.Brush.Color
a child form to have its own menu bar, separate from the property. If you derive a class from a different base class
menu of the main form. Figure 2 shows how to do this. (such as TGraphicControl ) that publishes the Color proper-
Otherwise, there is no reason to handle or send this message. ty, you might want to handle the Cm_ColorChanged mes-
sage. Remember to call the inherited message handler to
In Delphi 1, a form sends the Cm_AppSysCommand to the ensure the control is refreshed and all child controls are
application, which forwards it to the main menu. The mes- properly notified.
sage type is TWmSysCommand. In Delphi 2, a control first
sends the Cm_AppSysCommand message to its parent form; Because this message takes no arguments and returns no
the parent form forwards it to the application only if the result, you should use TWmNoParams for the message type.
form is an MDI child, has no menu, or has a main menu
whose AutoMerge property is set to True. This lets a child Cm_ControlListChange Message
with its own menu handle the shortcut key. The lParam A window control sends the Cm_ControlListChange mes-
argument is a pointer to the original message’s TMessage sage to itself when its control list changes; that is, when a
record. The parent form passes this TMessage record to the control is added or removed. A TDBCtrlPanel component
Application object, which forwards it to the main form. updates its database links when it receives this message.

Cm_ButtonPressed Message The Cm_ControlListChange message is sent before a control is


A speed button sends this message to its siblings to enforce the added to the list, so a message handler can examine the con-
group semantics; that is, to ensure that only one button in a trol and raise an exception to prevent it from being added.
group is pressed at any given time. When a speed button is When a control is removed, the Cm_ControlListChange mes-
pressed, it broadcasts the Cm_ButtonPressed message to all its sage is sent after the control is removed.
sibling controls (not just the TSpeedButton components).
The message type is TWmControlListChange. The Control
By default, TSpeedButton components handle this message so field refers to the control being added or removed. The
that all buttons in the same group can set themselves to the Inserting field is True when the control is to be added and
Up state. The message arguments are the group index False when the control is being removed.

27 January 1997 Delphi Informant


Op Tech
The Cm_ControlListChange message and the TDBCtrlPanel
library Demo;
component are not included in Delphi 1.
uses WinTypes, Forms;
Cm_Ctl3DChanged Message
The Cm_Ctl3DChanged message is similar to { The application passes its Application.Handle to
Initialize, so the DLL can communicate with the main
Cm_ColorChanged, but corresponds to the Ctl3D property. application. }
procedure Initialize(Handle: HWnd); export;
begin
Cm_CursorChanged Message Application.Handle := Handle;
When a control’s Cursor property changes, it sends the end;
Cm_CursorChanged message to itself. The default implementa-
...
tion, in TWinControl, checks whether the cursor is currently
over the control, and if so, immediately changes the cursor. You exports
probably don’t need to change this behavior for any Windows Initialize;

control. For a component derived from TGraphicControl, how- end.


ever, you might decide to implement similar behavior.
Figure 5: Initializing an Application object in a DLL.
This message takes no arguments and returns no result, so
you should use TWmNoParams as the message type. uses Forms;

Cm_DesignHitTest Message procedure Initialize(Handle: Hwnd); external DemoDLL;

As the name implies, Delphi sends the Cm_DesignHitTest ...


message to a component at design time to determine whether
Initialize(Application.Handle);
the cursor position is in a design area. If your component
needs to handle mouse events at design time, you can set the ...
Result to a non-zero value.
Figure 6: Initializing a DLL from the main application.
For example, the various grid components handle column
and row resize events at design time. When the mouse is over The DialogHandle property lets you use non-Delphi mode-
a resize bar, the grid component sets the Result to 1 in less dialog boxes with Delphi. You assign the window handle
response to a Cm_DesignHitTest event. This tells Delphi to of the dialog box to the application’s DialogHandle property,
pass mouse events to the component, rather than handling and the Application forwards dialog messages to the dialog
these events itself (for resizing and moving components, and box. In particular, the TFindDialog and TReplaceDialog com-
for using the component editor). ponents rely on the DialogHandle property.

In most cases, your component doesn’t need to handle this mes- When using the Application object in a DLL, be sure to
sage. If you’re writing a component that must respond to mouse assign the main application’s handle to the
events at design time, you can supply a message handler. If you Application.Handle property. This allows the Application
decide that the mouse cursor is not in a design area, call the object in the DLL to forward the Cm_DialogHandle mes-
inherited message handler. The inherited handler checks for sage to the Application object in the main application.
child controls that might need to handle this message. Figure 5 shows an example of an Initialize procedure in a
DLL, which takes a window handle as an argument,
The message type is TCmDesignHitTest. The cursor position assigning it to Application.Handle.
is given by the XPos and YPos fields, or by the Pos field, which
is of type TPoint. The Keys field gives the state of the V To use the DLL’s Initialize procedure, you must pass
and C modifiers, and tells you which mouse button is Application.Handle as its argument. This is shown in Figure 6.
pressed, in the same manner as TWmMouseMove (as a bit
mask). Call the KeysToShiftState function to convert this bit Because the Cm_DialogHandle message is used internally
mask into a TShiftState set, which is easier to use. by the TApplication class, there is no reason for you to send
or handle it. Instead, use the Application.DialogHandle
Cm_DialogHandle Message property. This message is defined only in Delphi 2.
In Delphi 2, an Application object in a DLL sends the
Cm_DialogHandle message to its counterpart in the applica- Cm_Drag Message
tion, which responds by setting or returning its The Cm_Drag message communicates drag events between
DialogHandle property. When wParam equals 1, the controls. The DragMessage field specifies what kind of drag
Application object returns the current value of event is taking place (Enter, Leave, Move, Drop, Cancel, or
DialogHandle; otherwise, it sets DialogHandle to the value Find Target), and the DragRec field points to a TDragRec
of the lParam field. This lets Delphi keep a single record, which contains a reference to the Source and Target
DialogHandle for an application and all DLLs. objects and the cursor position.

28 January 1997 Delphi Informant


Op Tech
The TCmDrag message type declares the DragMessage and The result is non-zero if the target accepts the drag source, or
DragRec fields. The Cm_Drag message is defined only in zero if rejected.
Delphi 2. In Delphi 1, you can override the DragDrop and
DragCanceled methods, but to customize any other drag-and- dmDragMove Drag Message. During a drag operation,
drop behavior, you must handle the mouse events explicitly. when the mouse cursor moves over a control, the control
Borland has addressed this limitation by adding the receives a Cm_Drag message with the dmDragMove drag
Cm_Drag message and by adding virtual methods to message. The default response is to call the DragOver
TControl in Delphi 2. method. The preferred way to handle the dmDragMove
message is to override the DragOver method. If you do so,
By inheriting from the standard Delphi components, your remember to call the inherited method, which calls the
components automatically inherit the proper behavior for the OnDragOver event handler.
Cm_Drag message. In most cases, you can customize any spe-
cific behavior by overriding the drag-and-drop methods: The result is non-zero if the target accepts the drag source, or
DragOver, DragCanceled, DoStartDrag, DoEndDrag, and zero if rejected.
DragDrop. In rare cases when you need greater control over
drag events, you can override the Cm_Drag message handler. dmFindTarget Drag Message. When the mouse moves during
The following sections describe the appropriate response for a drag operation, Delphi must identify the component under
each drag message. the mouse cursor. It starts with the window control under the
mouse cursor, and sends it the Cm_Drag message with the
dmDragCancel Drag Message. If the user finishes a drag- dmFindTarget drag message. The control responds by return-
and-drop operation by dropping on a target that does not ing an object reference, i.e. the component that is the drag
accept the drag source, the intended target receives a target. Your custom component can handle this message by
Cm_Drag message with the dmDragCancel drag message. The forwarding drag operations to a different control or by return-
cursor position in the drag record is set to (0,0). The default ing nil to refuse the drag operation. In most cases, however,
behavior is to do nothing when receiving the dmDragCancel you will want to keep the default behavior. When setting the
drag message. This message returns no result. result, there is no convenient message type defined, but you
can use TCmDragFindTarget, as shown in Figure 7.
dmDragDrop Drag Message. When the intended target of a
drop operation accepts the drop, it receives a Cm_Drag type
message with the dmDragDrop drag message. The default TCmDragFindTarget = record
Msg: Cardinal;
behavior, upon receiving the dmDragDrop message, is to call Source: TObject;
the DragDrop method. The preferred way to handle a Unused: LongInt;
dmDragDrop message is to override the DragDrop method. Result: TControl;
end;
If you do so, remember to call the inherited method, which
calls the OnDragDrop event handler. This message returns
Figure 7: TCmDragFindTarget type, for use when handling
no result.
Cm_Drag events.

dmDragEnter Drag Message. During a drag operation, when


the mouse cursor enters a control, the control receives a Cm_EnabledChanged Message
Cm_Drag message with the dmDragEnter drag message. The When a control’s Enabled property changes, it sends the
default response is to treat the dmDragEnter message as a normal Cm_EnabledChanged message to itself. Upon receiving this
dmDragMove message, i.e. to call the DragOver method. The message, most components invalidate themselves to force a
preferred way to handle the dmDragEnter message is to override repaint. A Windows control enables or disables its window
the DragOver method. If you do so, remember to call the inher- and gives up the input focus when disabled. If you write a
ited method, which calls the OnDragOver event handler. component that needs to do something different when the
Enabled property changes value, then you can handle this
The result is non-zero if the target accepts the drag source, or message. Remember to call the inherited event handler.
zero if rejected.
This message takes no arguments and returns no result, so
dmDragLeave Drag Message. When the mouse cursor leaves you should use TWmNoParams as the message type.
a control during a drag operation, the control receives a
Cm_Drag message with the dmDragLeave drag message. The Cm_FocusChanged Message
default response is to treat the dmDragLeave message as a After changing the focused control, a form sends the
normal dmDragMove message, i.e. to call the DragOver Cm_FocusChanged message to itself, which broadcasts the mes-
method. The preferred way to handle the dmDragLeave mes- sage to all child components. The control that received the
sage is to override the DragOver method. If you do so, input focus is stored in the Sender parameter, which is of type
remember to call the inherited method, which calls the TWinControl. The message type is TCmFocusChanged, which
OnDragOver event handler. declares the Sender field. The message handler returns no result.

29 January 1997 Delphi Informant


Op Tech
Most likely, your component would override the DoEnter and
type
DoExit methods to handle focus changes, but there might be
TCmGetDataLink = record
situations in which you want your component to reflect focus Msg: Cardinal;
changes that occur elsewhere on the form. In that case, you Unused1: Integer;
Unused2: LongInt;
can handle the Cm_FocusChanged message. Remember to call
Result: TFieldDataLink;
the inherited method to ensure that child components are end;
notified of the focus change, too.
Figure 8: TCmGetDataLink type, for use when handling
Cm_FontChange Message Cm_GetDataLink events.
When a form receives the Wm_FontChange message, it sends
Cm_FontChange to itself. Upon receiving Cm_FontChange, a type
window broadcasts the message to its children. A control TCmHintShow = record
Msg: Cardinal;
handles Cm_FontChange by updating its font and broadcast-
Unused: Integer;
ing the Cm_ParentFontChanged message. There is rarely any HintInfo: PHintInfo;
reason to handle this message yourself, unless your compo- Result: LongBool;
end;
nent manages a list of installed fonts.
Figure 9: TCmHintShow type, for use when handling
Note that Cm_FontChange is very different from
Cm_HintShow events.
Cm_FontChanged. Windows sends the Wm_FontChange mes-
sage when the list of installed fonts changes, usually because position, size, and color of the hint window. When
the user installed or removed a font. responding to the Cm_HintShow message, the control can
change any of these fields.
This message takes no arguments and returns no result, so
you should use the TWmNoParams message type. Cm_InvokeHelp Message
The Cm_InvokeHelp message is sent by an Application
Cm_FontChanged Message object in a DLL when it has no associated Help file. The
The Cm_FontChanged message is similar to main application handles this message by calling its
Cm_ColorChanged, but corresponds to the Font property. InvokeHelp method, which asks Windows to open a Help
Notice the difference between Cm_FontChanged and file. In other words, Delphi uses the Cm_InvokeHelp mes-
Cm_FontChange. sage to forward Help requests from a DLL to an applica-
tion. The wParam and lParam arguments are passed to
Cm_GetDataLink Message WinHelp as the Command and Data arguments. No results
When a TDBCtrlPanel updates its data links, it sends the are returned, so you can use TMessage for the message type.
Cm_GetDataLink message to each of its child database con-
trols. A database component, in response to this message, When using the Application object in a DLL, be sure to
returns a reference to its data link object. assign the main application’s handle to the Application.Handle
property. This allows the Application object in the DLL to
Although there is no class defined for this message, you forward the Cm_InvokeHelp message to the Application object
can use the TCmGetDataLink record shown in Figure 8. in the main application, as illustrated in Figures 5 and 6.
Notice that the Result field is not an integer, but a
TFieldDataLink object. The wParam and lParam fields are Ordinarily, your components, applications, and libraries don’t
not used. The Cm_GetDataLink message is specific to need to send or handle this message, because you can call the
Delphi 2. TApplication.InvokeHelp method.

Cm_HintShow Message Cm_IsToolControl Message


In Delphi 2, the Application object sends the In Delphi 2, an OLE container sends the Cm_IsToolControl
Cm_HintShow message to a control before displaying its message to a control to learn whether it’s a toolbar or similar
hint. If the control returns a True response, then the appli- control that must remain when an OLE object is activated in
cation doesn’t display the hint window. The default place. This message is sent only to controls whose Align prop-
response is to return a False result, thereby letting the erty is alLeft, alRight, alTop, or alBottom. If the control returns
application display the hint window. Note that if the con- a zero result, then it remains; if a non-zero result is returned,
trol’s ShowHint property is False, the application doesn’t the control is hidden while the OLE object is active.
attempt to show a hint, and the control never receives the
Cm_HintShow message. By default, a control returns a zero result. A TCustomPanel
object returns a non-zero result if its Locked property is False.
Although there is no message type, Figure 9 shows the In other words, most aligned controls remain visible during
TCmHintShow record, which you can use. The HintInfo an OLE in-place activation, but to keep a panel visible, you
field points to a THintInfo record, which contains the must set its Locked property to True.

30 January 1997 Delphi Informant


Op Tech
returns no result. Figure 12 depicts a simple form with the
type
mouse moving across it. Figure 13 shows the resulting event
TCmIsToolControl = record
Msg: Cardinal; diagram.
Unused1: Integer;
Unused2: LongInt;
There is no message type, but you can declare
Result: LongBool;
end; TCmMouseLeave to be the same as TCmMouseEnter, as
shown in Figure 11.
Figure 10: TCmIsToolControl type, for use when handling
Cm_IsToolControl events. Cm_ParentColorChanged Message
When a control’s Color property changes, it notifies its
type child controls by sending the Cm_ParentColorChanged mes-
TCmMouseEnter = record sage. When a control’s ParentColor property becomes True,
Msg: Cardinal;
Unused: Integer; it sends the Cm_ParentColorChanged message to itself to
Sender: TControl; update its Color property to match that of its parent. Upon
Result: LongInt; receiving this message, a control checks its ParentColor
end;
TCmMouseLeave = TCmMouseEnter; property, and if True, sets its Color property to match that
of its Parent. When a component is added to a form,
Figure 11: TCmMouseEnter type, for use when handling Delphi sends this message to ensure the component’s Color
Cm_MouseEnter events. property is initialized correctly.
There is no message type defined for this message. You can
use the TCmIsToolControl record, shown in Figure 10. This In most cases, you don’t need to handle or send this mes-
message is not defined for Delphi 1. sage, because Delphi does so automatically. This message
takes no arguments and returns no result, so you should
Cm_MouseEnter Message use TWmNoParams as the message type.
The Application object sends the Cm_MouseEnter message to a
control when the mouse enters that control’s bounds. If a con- Cm_ParentCtl3DChanged Message
trol is capturing all mouse input, then the Cm_MouseEnter The Cm_ParentCtl3DChanged message is similar to
message is sent only when the mouse enters the capture con- Cm_ParentColorChanged, but corresponds to the Ctl3D property.
trol. The Application object doesn’t send Cm_MouseEnter
unless it has no other messages to process, so your component Cm_ParentFontChanged Message
must not rely on receiving this message in a timely manner. The Cm_ParentFontChanged message is similar to
Cm_ParentColorChanged, but corresponds to the Font property.
Delphi controls do not handle this message, except to for-
ward it to the parent control. The lParam argument is the Cm_ParentShowHintChanged Message
message sender, or nil if the sender is the Application object. The Cm_ParentShowHintChanged message is similar to
This message returns no result. For a diagram of the Cm_ParentColorChanged, but corresponds to the ShowHint
Cm_MouseEnter message, see Cm_MouseLeave. property.
There is no message type, so you can use the TCmMouseEnter
type shown in Figure 11. Cm_Release Message
A form’s Release method posts the Cm_Release message to
Cm_MouseLeave Message itself. When the form receives this message, it frees itself by
The Application object sends the Cm_MouseLeave message calling the Free method. Note that by posting the message
to a control when the mouse leaves that control’s bounds. instead of sending it, the form ensures all pending messages
If a control is capturing all mouse input, then the will be handled properly. This ensures an orderly cleanup
Cm_MouseLeave message is sent only when the mouse before closing the window.
leaves the capture control. The Application object doesn’t
There is little reason to handle this message. Instead, you
send Cm_MouseLeave unless it
can write an OnClose or OnDestroy event handler, or over-
has no other messages to
ride the form’s destructor, whichever is more convenient.
process, so your component
There is also no reason to send this message. Instead, call
must not rely on receiving this
the Release method.
message in a timely manner.

Delphi controls do not handle This message takes no arguments and returns no result, so
this message except to forward it you should use TWmNoParams as the message type.
to the parent control. The
lParam argument is the message Cm_ShowHintChanged Message
sender, or nil if the sender is the Figure 12: Moving the The Cm_ShowHintChanged message is similar to
Application object. This message mouse on a form. Cm_ColorChanged, but corresponds to the ShowHint property.

31 January 1997 Delphi Informant


Op Tech

Application OK Button Cancel Button Panel Form

User moves mouse


over Panel
Windows sends Wm_MouseMove to Panel

Application sends Panel forwards


Cm_MouseEnter Cm_MouseEnter
to Panel to Parent (Form)
User moves mouse
over the OK Button
Windows sends Wm_MouseMove toOK Button

Application sends Panel forwards


Cm_MouseLeave Cm_MouseLeave
to Panel to Form

Application sends
Cm_MouseEnter
to OK Button OK Button forwards
Cm_MouseEnter
to Panel Panel forwards
Cm_MouseEnter
User moves mouse to Form
over Cancel Button
(Wm_MouseMove
not shown)

Application sends
Cm_MouseLeave
to OK Button OK Button forwards
Cm_MouseLeave
to Panel Panel forwards
Cm_MouseLeave
to Form

Application sends
Cm_MouseEnter
to Cancel Button Cancel Button forwards
Cm_MouseEnter
to Panel Panel forwards
Cm_MouseEnter
to Form

Figure 13: Event diagram for Cm_MouseEnter and Cm_MouseLeave.

32 January 1997 Delphi Informant


Op Tech
Cm_SysColorChange Message is to broadcast the message to its children. Your compo-
When a form receives a Wm_SysColorChange message, it nent can handle this message if it needs to respond to
sends the Cm_SysColorChange message to itself and clears changes in the user’s environment.
the graphics caches (pens, brushes, and canvases). The
default behavior of a control when it receives the The message type is TWmWinIniChange, which defines
Cm_SysColorChange message is to broadcast the message to the Section field as a PChar variable. The Section is the
its children. name of the section that changed in WIN.INI, or nil if
more than one section changed.
Remember that Windows sends the Wm_SysColorChange
message when the user changes the system color scheme. Conclusion
Every form repaints itself with the new color scheme, but In this article, you learned about some of the messages that
if your component saves any color information (such as a Delphi components send to each other. These messages are
bitmap background), you should handle this message to an integral part of the VCL; when you write custom compo-
refresh your component’s information. nents, you need to be aware of these messages so you know
which ones the component must handle and which messages
This message takes no arguments and returns no result, but it must send.
the message type TCmSysColorChange is defined, which is the
same as TWmNoParams. In many cases, your component inherits the correct behav-
ior from the standard Delphi control classes, such as
Cm_TextChanged Message TCustomControl. There are times, however, when a com-
When a component’s Text or Caption property changes, it ponent must implement non-standard or uncommon
sends the Cm_TextChanged message to itself. Many con- behavior. For example, a clock component might need to
trols invalidate themselves upon receiving this message. If be notified when the system time changes. Windows sends
you derive your component from a custom component, the Wm_TimeChange message to the form, and the form
you can let the base class handle this message. broadcasts the Cm_TimeChange message to all its controls.
Thus, when you implement a clock component, you must
However, if you create a new component that publishes the know how to handle the Cm_TimeChange message, which
Text or Caption property, you might need to handle this was covered in this article.
message to call Invalidate. If your component takes addition-
al measures when its text changes, you can write an event Some messages are sent to or from the Application object,
handler, but remember to call the inherited handler, too. such as the Cm_AppKeyDown message. In Delphi 1, this
message is especially important when you are writing an
This messages takes no arguments and returns no result, application where each form has its own menu bar. Unless
so you should use TWmNoParams as the message type. you handle this and the Cm_AppSysCommand messages, all
keyboard shortcuts are forwarded to the main form’s menu
Cm_TimeChange Message bar, so the individual forms can’t implement their own
When a form receives a Wm_TimeChange message, it sends menu bars correctly. You learned how to intercept these
the Cm_TimeChange message to itself. The default behavior messages, so any form can have its own menu bar. ∆
of a control when it receives the Cm_TimeChange message is
to broadcast the message to its children. If your component
uses the system date or time, you must handle this message. This article is adapted from material for Ray Lischner’s
Secrets of Delphi 2 [Waite Group Press, 1996], available
This message takes no arguments and returns no result, but for $49.99 from your local bookstore or by calling (800)
the message type TWmTimeChange is defined, which is the 428-5331. Secrets of Delphi 2 contains a description of
same as TWmNoParams. every internal VCL message.

Cm_WinIniChange Message
When a form receives a Wm_WinIniChange message, it Ray Lischner is a software author and consultant for Tempest Software
sends Cm_WinIniChange to itself. The default behavior of (http://www.tempest-sw.com). His current focus is object technology, especially in
Delphi, Java, and Smalltalk. You can reach him at [email protected].
a control when it receives the Cm_WinIniChange message

33 January 1997 Delphi Informant


In Development
Delphi 1 / Delphi 2

By Mark Ostroff

What’s in a Name?
Naming Conventions for Delphi Projects

elphi’s focus on RAD development enables a developer to drop a few


D components onto a form, write a few lines of code, and have a work-
ing prototype of the application with little or no effort.

In fact, Delphi might make it too easy. effective naming system. The larger the
Soon you can have a bewildering number number of objects in your project, the
of components in your application with more important the naming of those
useful names such as Edit1, Query2, and objects becomes. Otherwise, you’ll have
DataSource5. Delphi’s default object names more and more difficulty finding the cor-
are only good as place holders until you rect object to select in the Object
develop a workable naming convention. Inspector’s pull-down lists. A workable
naming convention is also important when
Why Design a Naming Convention? you start working on a project with a team
Without some forethought, the list of com- of developers, because you might not be
ponent names you choose could become as the only developer creating object names.
confusing as the names Delphi assigns.
Many of us begin naming our Delphi Figure 1 shows the result of creating a new
objects based on their functionality — project using the SDIApp template from
names such as ExitButton, DeptField, and Delphi’s Object Repository. Notice that no
DateSpin. apparent organization exists for this list of
objects. Because the names are ambiguous
This convention seems to make sense for a and inconsistent, it’s hard to tell which object
while. However, as you use your objects, it some of the names refer to.
soon becomes apparent that you need a bet-
ter way of organizing their names. Do FileMenu and MainMenu refer to sepa-
rate objects of type TMainMenu? Do
Organization becomes important. Delphi’s ExitItem and FileMenu both refer to
Object Inspector lists your components in TMenuItem objects? These aren’t idle ques-
alphabetical order. This feature is only use- tions. As your projects grow, you will
ful in large projects when you have an remember what type of component you
want to work with, but you might not
remember its name.

Side-effects of poor object naming. The


lack of a good object naming convention
affects more than selecting objects from the
Object Inspector. Delphi automatically
names the event handlers that respond to
Figure 1: Lack of a workable naming convention causes an unorganized list of user events. These method names are
objects in the Object Inspector. derived from the name of the target object

34 January 1997 Delphi Informant


In Development
type abbreviation make it
clear what action each button
is meant to perform.

This type abbreviation prefix


scheme is actually used in
Delphi, and all enumerated Figure 3: Use of two-letter
types use this prefix conven- prefixes to designate legal
tion. For example, Figure 3 values for color schemes.
shows the use of a two-letter prefix to designate legal val-
ues for color selections.

Delphi provides over 100 types of components. The use of a


three-letter prefix for object names seems to provide enough
information for meaningful type abbreviations.
Figure 2: Side-effects of assigning an existing method to the
OnClick event of another object.
Why case is important. The above examples all use a
for which they are defined. Therefore, disorganized object lower-case object type abbreviation. The type abbreviation
names also make it difficult to select the proper event han- is followed by the object’s functional description in proper
dler to reuse in other objects. case (i.e. the first letter of each word or phrase is capital-
ized). This particular case selection is deliberate. Once
Figure 2 shows what happens when you try to assign an again, it follows the convention used within Delphi.
existing method to the OnClick event of another object.
The naming inconsistency of the objects carries over to the The initial goal of sorting object names is achieved by
naming of the event handlers. While FormCreate is fairly placing the type abbreviation first. The use of lower-case
clear, what object association does the ShowHint method followed by proper-case enables the developer to ignore
have? You’d have to look at the code to find out. the type and focus on the object’s description. This case
organization helps when selecting a specific object from a
Why doesn’t a name such as ExitButton work? Most peo- grouped list of similar objects.
ple start by using names that begin with the functionality
and end with the component type. This convention elimi- The type prefixes. The table in Figure 4 is a suggested list of
nates event handler naming inconsistencies. Unfortunately, object type prefixes you can apply to your object names. It
it doesn’t help when you’re looking for an object of a cer- covers all the components that ship with Delphi. This list is,
tain type, but can’t remember what abbreviation you used of course, not cast in concrete. Adjust the prefixes to suit
for its functionality. You need to use something that lever- your tastes and add prefixes for new object types that you cre-
ages the Object Inspector’s auto-sorting of names. ate or purchase. The only hard and fast rule is to keep your
object type prefixes consistent.
A System That Works
What we need is a naming convention that follows the nat- Explaining Some of the Suggestions
ural thought processes of the human mind, and then takes Some of the distinctions between the prefixes listed may
advantage of Delphi’s features to make even large projects seem a bit arbitrary. Why lump some components under
easier to work with. Because most people have trouble the same prefix, while others are out on their own? In gen-
remembering names, but not types of names, the object eral, components used in similar ways are placed under the
naming convention should result in a list that is sorted by same prefix. Both the Edit and DBEdit components are
type, then description. We also want some visual method employed in much the same way, so both use the edt pre-
whereby the developer can easily focus on the functional fix. The Memo, DBMemo, and RichEdit objects also have
description after the desired type has been found. similar functions, yet they differ quite a bit from Edit
objects. So they get their own prefix, mmo.
Pre-pending the object type. Getting Delphi to sort the
object names by type is easy. Simply place the type first — Some of the more “colorful” prefixes were created to avoid
rather than last — in the object’s name. For example, a collisions. The QuickReport components are an example.
collection of buttons might be named btnClose, Sometimes you must be imaginative to create a set of unique
btnDirChange, and btnFileOpen. The pre-pending of the three-letter combinations.
object type places all objects of the same type along with
their object-related event handlers in any Object Inspector Component types you don’t often use might be good candi-
pull-down list. The functional descriptions that follow the dates for prefix consolidation. You could lump all the

35 January 1997 Delphi Informant


In Development
Prefix Object Type Prefix Object Type
bar ProgressBar, StatusBar, TrackBar mnu MainMenu
bat BatchMove msk MaskEdit
btn Button, BitBtn, SpeedButton nav DBNavigator
bvl Bevel nbk Notebook, TabbedNotebook
cal Calendar nnt NNTP (Internet Network Newsgroup Access)
cbx ComboBox, DBComboBox ole OLEContainer
chk CheckBox, DBCheckBox out Outline
dbs Database pbx PaintBox
dcc DDEClientConv pge PageControl
dci DDEClientItem pnl Panel
dir DirectoryOutline pop POP (Internet Post Office Protocol to receive E-Mail)
dlg Dialogs (Open, Save, Font, Color, Print, pum PopUpMenu
PrinterSetup, Find, Replace) qrb QRBand
drv DriveComboBox qrc QRCalc
dsc DDEServerConv qrd QRSysData
dsi DDEServerItem qrg QRGroup
dtm DataModule qrl QRDetailLink
dts DataSource qrm QRMemo
edt Edit, DBEdit, SpinEdit qrp QuickReport
fcb FilterComboBox qrv QRPreview
frm Form qrs QRShape
ftp FTP (Internet File Transfer Protocol) qrt QRLabel, QRDBText
gge Gauge qry Query
grd ColorGrid, DrawGrid, DBGrid, DBControlGrid, rad RadioButton
StringGrid rgp RadioGroup, DBRadioGroup
grf ChartFX, VCFirstImpression, GraphicsServer rpt Report
grp GroupBox sbr Scrollbar
hdr HeaderControl, Header sbx Scrollbox
hot Hotkey shp Shape
htm HTML (Internet Web Browser) sht VCFormulaOne
htt HTTP (Send, Receive or Search HTML Documents smt SMTP (Internet Simple Mail Transport)
ibe IBEventAlerter spl VCSpeller
img Image, DBImage spn SpinButton, UpDown
lbl Label, DBText ssn Session
lst ListBox, DBListBox, DirectoryListBox, FileListBox, stp StoredProc
ImageList tab TabControl, Tabset
luc DBLookupComboBox, DBLookupCombo tbl Table
lul DBLookupListBox, DBLookupList tcp TCP (Internet Data Exchange … like a telephone)
lvw ListView tmr Timer
med MediaPlayer tre TreeView
mmo Memo, DBMemo, RichEdit udp UDP (Internet Data Broadcast … like a radio)
mni MenuItem ups UpdateSQL
Figure 4: Suggested object type prefixes.

Internet Solution Pack components under the prefix web. If Data object naming. Look at the DataModule displayed in
you rarely use DDE, you might want to use the prefix dde Figure 5. Assigning a DataSet to the dtsContacts DataSource
for all four DDE component types. object is simplified by this naming convention. It’s easy to see
that this DataModule contains a number of Query, Stored
Some Examples Procedure, and Table objects.
The effects on the ease of development are best seen in an
example. In Figure 2, we saw that poor object naming can Menu object naming. Note that this DataModule also con-
make method selection difficult. tains an application standard PopUpMenu named

36 January 1997 Delphi Informant


In Development
What about file names? File naming conventions weren’t
much of an issue in Delphi 1. Nearly every .PAS file was a
form. However, Delphi 2 added the ability to create
DataModules.

At a glance, this issue seems


Prefix File Type
easy to resolve. Simply use
the same three-letter prefixes fm Form
you used for Form and dm DataModule
DataModule objects. Figure 7: File type prefixes.
However, Delphi doesn’t allow
you to save a unit with the same name as an object within
that unit.

You only need two designations for file names, one for Forms
and another for DataModules. Simply use a two-letter prefix
when you save the file (see Figure 7).

Conclusion
Try this naming convention in your next project. After an
adjustment period, I think you’ll find your Delphi develop-
Figure 5 (Top): An example of a workable naming convention. ment will proceed even more rapidly than before. As your
Figure 6 (Bottom): Easing method selection.
project grows, you’ll also realize more efficiencies.

pumStdPopUp. Because PopUpMenus are more amenable to This article builds on the work of two groups. The initial
reuse, it makes sense to include standard ones in a effort was started by Borland. Many of the concepts and
DataModule. naming conventions covered here are used by Borland for
developing its in-house applications.
It also makes sense to give them their own type prefix.
PopUpMenus are used differently than MainMenus, and Additional work was performed by the Delphi Study Group,
their type prefix should reflect this, rather than lumping sponsored by Informant Communications Group on its
them together with other menus. CompuServe forum. Additional thanks go to Tom Arnold
and the other members of the group for consolidating much
Menus also have unique naming convention requirements. of the discussion about type prefixes. ∆
Typically you’ll want to perform one set of actions on a
menu object itself. You’ll want to execute another kind of
Mark Ostroff has over 18 years experience in the computer industry. He began by
logic in response to events that occur for MenuItems with- programming minicomputer medical research data acquisition systems, device
in that menu object. interfaces, and process control database systems in a variety of 3GL computer lan-
guages. He then moved to PCs using dBASE and Clipper to create systems for the
Thus, the prefix list in Figure 4 suggests mnu and pum for US Navy, as well as for IBM’s COS Division. He also volunteered to help create the
MainMenu and PopUpMenu objects, respectively, and mni original Paradox-based “InTouch System” for the Friends of the Vietnam Veterans’
Memorial. Mark has worked for Borland for the past six years as a Systems
for use with MenuItems themselves. The clarity this adds Engineer, specializing in database applications.
to selecting event handlers is shown in Figure 6.

37 January 1997 Delphi Informant


Delphi at Work
Delphi / Object Pascal

By David Faulkner

TBarCode
A Custom Bar Code Component

biquitous, even annoying, bar codes are nevertheless extremely


U handy, and can relieve much data entry tedium. Their Delphi imple-
mentation was therefore just a matter of time. And — well — it’s time!

This article presents TBarCode, a compo- potentially expensive, and a lot of trouble
nent that displays bar codes which can be compared to the ease with which TBarCode
read into a computer using an optical scan- was assembled.
ner as the input device. TBarCode imple-
ments the Code 39 specification, but you The Specification
should be able to extend it to display any Code 39 maps each of a set of pre-defined
bar code format. Figure 1 shows TBarCode printable characters to a set of nine bars. A
in action. bar can be wide or narrow, black or white,
meaning both the black bars and the spaces
Before creating this component, I experi- between are meaningful.
mented with bar code fonts that display
coded symbols for each character, i.e. set- There’s no specific width assigned to a bar;
ting TLabel ’s Font property to a bar code instead, only the ratio of the bars is speci-
font effectively creates a bar code. While fied. The wide bars must be 2.2 to 3.0
this approach worked, it meant I had to times as wide as the narrow bars. The larg-
worry about licensing the font and er ratio is usually favored, as it results in
installing it on users’ machines. This was fewer read errors.

Code 39 supports 44 printable characters:


upper-case letters A through Z
the numbers 1 through 9
the symbols -, ., $, /, +, %, and the
blank character

(The * character is also supported, but it’s


used as a start and stop character, and cannot
be used as a data item.)

Each character is assigned a unique series of


nine bars. The first and last bar are always
black; the remaining bars alternate between
black and white. For example, the series for
“A” is WNNNNWNNW; that is, a wide black bar
followed by a narrow white bar followed by a
narrow black bar, etc. Figure 2 shows the bar
Figure 1: The TBarCode component at work. code for this character.

38 January 1997 Delphi Informant


Delphi at Work
Descend
The first step in creating a component is to decide from
which component to descend. In TBarCode’s case,
TCustomLabel is the perfect choice. TBarCode is simply a
label that paints itself in an encoded format. TCustomLabel
provides a canvas on which to draw the bar code, and has
Figure 2: The series of bars for the letter “A”. published properties, such as Left, Top, Width, and Height,
that we don’t have to code.
The series for the rest of the character set is in the source
code listing as BarCodeTable. Every character is encoded Additionally, TCustomLabel has a number of fully functional
with nine bars, three of which are wide, thus the name properties that are hidden from the user because they are pro-
Code 39 (Code 3 of 9). tected. By simply publishing the Alignment and Caption
properties, we round out the required properties.
A white space is inserted between each character so the reader
can distinguish the end of one character and the beginning of Create
another. The white space’s width isn’t specified, but it’s gener- Next, the Create method is overridden to provide some rea-
ally set to the width of a narrow bar. sonable defaults:

A legal Code 39 bar code requires a start and stop char- constructor TBarCode.Create(AOwner: TComponent);
begin
acter — the * character is encoded as NWNNWNWNN. Thus inherited Create(AOwner);
the shortest Code 39 bar code is three characters, ControlStyle := ControlStyle - [csOpaque];
although the bar code-reading hardware usually strips off Alignment := taCenter;
AutoSize := False;
the * characters. This requirement also makes it easy to Height := 50;
visually check if a bar code is Code 39. If the first nine Width := 175;
bars match the last nine bars and the bars are two dis- end;

tinct widths, you can be fairly certain you are looking at


a Code 39 bar code. If the Height and Width properties are not set, they take
on the default Height and Width of their ancestor,
The left and right edges of the entire bar code must be TCustomLabel. This causes problems, because the default
surrounded by enough white space for the optical scanner Width of a label is not wide enough for TBarCode to dis-
to distinguish between the end of one bar code and the play the bar code interpretation of its default caption,
beginning of another. The white space must be at least .25 BARCODE1. The Paint method won’t print a bar code if
inches, or 10 times the width of a narrow bar, whichever is the whole caption doesn’t fit, so the user would have an
greater. The TBarCode component doesn’t consider this; invisible component. Setting the Width property to 175
instead, it expects the user to place the component in an assures that a default bar code will be visible on the screen
area with sufficient white space. when the user places TBarCode on a form.

The Component A minor disadvantage to deriving TBarCode from


Many excellent articles on component building are avail- TCustomLabel (compared to TGraphicControl ) is that
able. So here we’ll concentrate on the code unique to TCustomLabel implements the AutoSize feature. Of course,
TBarCode. TCustomLabel gets the width wrong for TBarCode because it
expects the caption to be painted in the current font. Because
TBarCode’s component declaration is one of the simplest we aren’t publishing the AutoSize property, the Create method
you’ll see: sets AutoSize to False, so we can forget about it.

type Removing the csOpaque ControlStyle is also important to the


TBarCode = class(TCustomLabel)
drawing of the bar code. According to Delphi’s online Help,
private
procedure CaptionChanged(var Message: TMessage); adding csOpaque to the ControlStyle property means that the
message CM_TEXTCHANGED; control hides any items behind it, making it unnecessary to
draw them.
protected
procedure Paint; override;
By removing csOpaque, we’re telling Windows to redraw the
public
background before calling TBarCode’s Paint procedure.
constructor Create(AOwner: TComponent); override;
Redrawing the background erases any existing bar codes from
published previous paints, so we don’t need to code an erase routine.
property Caption;
Additionally, when printing white bars, the Paint routine
property Alignment;
end; doesn’t need to print anything at all; instead, it relies on the
background color to show through.

39 January 1997 Delphi Informant


Delphi at Work
Auto Update when an invalid message is entered, and raise an exception if
You have probably noticed that when editing the Caption it happens at run time. However, it’s easier and less intrusive
property of a label in the Object Inspector, the Label on the to convert any lower-case characters to upper case and filter
form is updated with each key stroke. Two mechanisms can out any illegal characters.
make this happen.
Since the CaptionChanged procedure is called each time the
The first mechanism is through the creation of a custom Caption is changed, the validation is done within the
Property Editor, say TMyCaptionEditor, with the CaptionChanged procedure. Note that although some charac-
paAutoUpdate attribute: ters will be discarded, they still appear in the Object
Inspector’s in-place editor until the user exits. This is because
type TMyCaptionEditor = class(TStringProperty) the in-place editor does not refresh itself from the Caption
public
function GetAttributes: TPropertyAttributes; override;
property after each change.
procedure SetValue(const Value: string); override;
end; Painting Concerns
... As you can see from the component definition, the Paint pro-
cedure is overridden so we can paint a bar code instead of a
function TMyCaptionProperty.GetAttributes:
TPropertyAttributes;
text caption. The code for the Paint procedure is:
begin
Result := [paMultiSelect,paAutoUpdate]; procedure TBarCode.Paint;
end; begin
BarCodePaint(Caption,Canvas,0,0,Width,Height,Alignment);
end;
paAutoUpdate tells Delphi to call the SetValue procedure after
each change is made to the MyCaption property, instead of wait-
ing until an entire edit session is posted. Within the SetValue There’s not much to it; in fact, all it does is call BarCodePaint.
procedure, you can then cause a component to be repainted. While this appears inefficient, it’s always a good idea to design
This works fine, but the second mechanism is simpler. visual components in this manner. By doing so, the compo-
nent can be “painted” on a printer as well as on the screen.
Delphi defines a set of Component Messages that are listed
in the online Help. One of the messages, Cm_TextChanged, Because BarCodePaint is declared in the interface section of
is triggered whenever the Text property of a component is the Barcode unit, it can be called by other units. For exam-
changed (regardless of the Property Editor associated with ple, the PrintClick event prints the form’s bar code:
that property). By attaching code to this message to repaint procedure TForm1.PrintClick(Sender: TObject);
the bar code, the TBarCode will exhibit the same var
AutoUpdate behavior as a TLabel. ScaleX,ScaleY : Integer;
begin
Printer.BeginDoc;
What’s puzzling about this mechanism is that the user is
changing the Caption property of TBarCode, not the Text ScaleX := WinProcs.GetDeviceCaps(
Printer.Handle,LOGPIXELSX) div 96;
property. In fact, TBarCode doesn’t even appear to have a
ScaleY := WinProcs.GetDeviceCaps(
Text property. Because it’s descended from TCustomLabel, Printer.Handle, LOGPIXELSY) div 96;
TBarCode has all the properties of TCustomLabel, includ-
BarCodePaint(BarCode1.Caption,Printer.Canvas,
ing the Text property. This is a protected property, so the
BarCode1.Left*ScaleX,
Object Inspector and your code don’t have access to it. BarCode1.Top*ScaleY,
But Delphi does have access to it, and magically keeps “in BarCode1.Width*ScaleX,
sync” with the Caption property. BarCode1.Height*ScaleY,
BarCode1.Alignment);

You can prove this by descending a component from Printer.EndDoc;


TCustomLabel and publishing the Text property. As you end;

edit the Caption property in the Object Inspector, the Text


property is automatically updated (the reverse is also true). I find it simplifies component development if I make Paint
Thus, whenever the Caption property is changed, a routines available to other units and have those routines
Cm_TextChanged message is triggered, which in the case of accept a Canvas parameter so the components can be easily
TBarCode, calls the CaptionChanged procedure. printed. Because BarCodePaint is coded in this way, only one
CaptionChanged causes the bar code to repaint, effectively line of code was necessary to implement a QuickReport ver-
implementing the AutoUpdate feature. sion of this component.

Validating Input BarcodePaint


As mentioned, the Code 39 specification only supports 44 BarcodePaint is the workhorse of the component, but it’s
characters, so it’s necessary to validate any input the user sup- still quite simple. It begins by checking the input, setting
plies. It would be reasonable to give the user an error message up the canvas, then adding the following code:

40 January 1997 Delphi Informant


Delphi at Work
for x := 1 to Length(Caption) do begin
Index := Pos(Caption[x],cValidCode39Characters);
if Index = 0 then
Continue;
for y := 1 to 9 do
PaintABar(BarCodeTable[Index][y],odd(y),Canvas,
LeftEdge,Top,Height,NarrowWidth,WideWidth);
Inc(LeftEdge,NarrowWidth);
end;

The interesting part here is that the cValidCode39Characters Figure 3: A Code 39 bar code created by TBarCode.
constant is not only used to validate the input; it’s also a
map into BarCodeTable. For example, the character “A” is Other Goodies
the 11th character in the cValidCode39Characters string and In addition to TBarCode, you should check out the down-
corresponds to the 11th element in the BarCodeTable array. load file. It contains TDBBarCode, a data-aware version of
TBarCode; TQRBarcode, a QuickReport compatible ver-
BarCodeTable is a two-dimensional array whose first ele- sion; and TQRDBBarCode, a QuickReport data-aware ver-
ment is 1,1 for conveyance (as compared to 0,0). Each row sion. Both 16- and 32-bit resource files are included, so it
of BarCodeTable contains a series of nine Ws and Ns which can be compiled in Delphi 1 or 2.
specify the pattern of wide and narrow bars for the corre-
sponding character. Conclusion
I’ve included the sample program, shown in Figure 3. It
PaintABar lets you enter a value at run time and see the resulting bar
PaintABar’s job is to paint a single bar at the passed coordi- code. The sample program also prints the bar code for you
nates. The code that paints the bar is: so you can test it against your optical scanner.

For more information and specifications for other bar codes,


if ThisBarIsBlack then
aCanvas.Rectangle(LeftEdge,Top,
check out http://www.hp.com:80/AccessGuide/TI_ahp.html
LeftEdge+CurrentWidth,Top+Height); and follow the link to Bar Code Symbologies. ∆
Inc(LeftEdge,CurrentWidth);

The files referenced in this article are available on the Delphi


If the bar being printed is white, PaintABar does not paint Informant Works CD located in INFORM\97\JAN\DI9701DF.
anything, but still advances the left edge. TBarCode can
get away with not painting anything because it removed
csOpaque from the ControlStyle. This means that when
painting on a screen, Windows will erase the previously
painted bar code, and when painting on a printer’s canvas, David R. Faulkner is President of Software Development at Island Community
Lending in Honolulu, HI. He is co-author of Using Delphi: Special Edition (QUE,
the white spaces will let the paper show through (see 1995) and can be reached at (808) 572-5524 or [email protected].
Figure 3).

41 January 1997 Delphi Informant


Case Study
By Don Bauer

TWorldMap
Seeing the World in a New Way

hile creating an application for the United States Marine Corps,


W Stanley Associates’ Senior Technical Manager, Don Bauer, and
Senior Analyst, Dan Adams, wanted to add a means to graphically view
training schedules relative to training location. Unfortunately, time and
money constraints wouldn’t allow Stanley Associates to simply buy conven-
tional map software. Additionally, the Marine Corps Training Exercise and
Employment Planning (MCTEEP) project didn’t warrant developing the map
at the government’s expense.

With this in mind, Don read Programming as he began development in September


Windows with Borland C++ by William 1995, were straightforward — he wanted
Roetzheim [Ziff-Davis Press, 1992] which to create a component in which users only
contained, as an example, a primitive map needed to know the longitude and latitude,
system — no other functionality was provid- and the online Help would walk them
ed. Don decided to build a map component through the rest. The original features
on his own, and if successful, market it for would include a map, zooming, and points.
future release.
Delphi seemed the natural choice to Don, as
The Development Process earlier that year, he, along with four other
Don’s ultimate goal was to create a low- programmers, converted MCTEEP v1.0
cost, royalty-free, easy-to-use GEO inter- from PowerBuilder to Delphi in less than
face that wasn’t encumbered with the com- four months. The switch to Delphi resulted
plexities of typical GIS software. His goals, in much-improved capability, tremendous
performance gains, and the ability to easily
modify and improve the code.

Don approached the map development


process incrementally, finalizing each step of
the process to ensure that the original goals
were met. As development proceeded, he
added functionality that fit in well with the
design intent of the component.

The development process wasn’t always


easy. The biggest setbacks came from work-
ing with the Windows 3.1 graphic design
interface (GDI). It was a constant struggle
to keep within the bounds of performance
and resource usage with the 16-bit version,
Figure 1: A capture from the Earth Observation Aid being used because the GDI not only affected display,
on the Mir space station. but printing as well. When working with

42 January 1997 Delphi Informant


Case Study

APPLICATION PROFILE
The TWorldMap component is totally configurable by the
developer. Its functionality includes built-in zoom, built-in
adjustable latitude/longitude grid, a custom point object, cus-
tom line object, clickable polygons, custom printing, and cre-
ating bitmaps. Available in both 16- and 32-bit versions,
TWorldMap is a royalty-free, easy-to-use solution for many
day-to-day geo-presentaion needs.

Target Audience: Anyone needing a simple, fast, easy-to-use,


customizable map interface added to their application.

Contact: Don Bauer


Phone: (202) 396-6970
Fax: (202) 396-6914
Voice Mail/Pager: (703) 616-6166
Figure 2: Galileo, an astrology program written by Dirk Paessler.
Web Site: http://www.stanleyassoc.com/worldmap/wmap.htm
E-Mail: [email protected]

large polygons in the Windows 3.1 GDI, things stopped


working for no apparent reason, making development diffi-
cult. To overcome this, Don and David Steel, a Senior
Windows Programmer, systematically determined the limi-
tations of the GDI and built the map component’s display
and printing capabilities to work within them.

The Beta Cycle


Despite the setbacks, the beta cycle began moving the com-
ponent toward its current form. The beta testers gave Don
feedback, and wherever possible, the changes were imple-
mented. This crucial process resulted in a stable, feature-rich
component. TWorldMap version 1.0 began shipping in
March of 1996 as a 16-bit component. Currently, Figure 3: The EPA Clean Air Act model using TWorldMap.
TWorldMap is available in both 16- and 32-bit versions with Custom Line Object — The component maintains an inter-
parallel capabilities under a combined codebase. nal list of lines that are redrawn anytime the map is redrawn.
Support for lines has also been included to allow connecting
Capabilities points on the map to show routes, service webs, etc. If a line
TWorldMap’s capabilities include: passes through the current display area, it is drawn.
Built-in Zoom — The three ways to control the map are: Custom Printing — The component supports two types
set the EnableZoom Property to True and click your of printing — full page or user-defined location and size
mouse button, drag a rectangle on the map, and it resizes — on an existing print job. Additionally, the map will
automatically; use the ZoomByFactor method and zoom print in color and supports plotters.
the map, centered on the latitude/longitude value sent; or Create Bitmaps — Create bitmaps of your map screens
set custom coordinates and redraw the map. for inclusion in your presentations and word processing
Custom Point Object — The Custom Point object allows documents. Simply pick a file name and dimensions, and
the developer to add, delete, update, or find points on the the component will create a bitmap representation of
map. An OnPointClicked event passes all point informa- your current map, including all details.
tion to the calling program, enabling actions to be based
upon clicking a point on the map. Points support various Customers
geometric shapes as well as bitmaps to be displayed. TWorldMap has many customers who use the component in
Live Clickable Polygons — Each polygon (based upon different ways: NASA and the Earth Observation Program, as
level of detail) can be systematically included or excluded illustrated in Figure 1, are currently using the component
for use in the OnPolyClick event. The component passes onboard the Mir space station in software that uses the world
the polygon object to the calling program. The descrip- map to display points of interest and link them to satellite
tion can be extracted and displayed or used to query a photographs taken on previous missions (see the sidebar
database to provide amplifying information. “Earth Observation Aid” on page 44); a trucking company in

43 January 1997 Delphi Informant


Case Study

EARTH OBSERVATION AID


The Earth Observation Group approached the Field
Deployable Trainer (FDT) project, a joint team of NASA and
United Space Alliance employees, to add an application to a
laptop computer already on the Mir space station. Currently,
the Earth Observation Group is conducting a scientific experi-
ment on Mir that involves taking a series of pictures over time
of many locations on Earth from Mir. The group asked for a
map in which you could select one of their sites and see a pic-
ture of that site previously taken from space.

The FDT team planned on using a bitmap image for the map.
However, while waiting for the pictures to use in the application, Figure 4: A phone sales distribution system written by Gary
Holbrook.
the team saw an advertisment for TWorldMap. The team thought
it would provide a better interface, if the component could be
the Environmental Protection Agency (EPA) uses TWorldMap
obtained in time; only three weeks remained until the flight soft-
ware needed to be delivered, and it isn’t normally possible to pur- for its Clean Air Act health benefit model (see Figure 3); the
chase software that quickly for a government project. They called US Marine Corps uses it for its scheduling system; a commer-
Don to determine if NASA had a license, or if there was some cial marketing company is using the component to color states
way to use the code. It turned out that another group at the by sales revenue (see Figure 4); and the US Army is integrat-
Johnson Space Center, working on a Shuttle emergency landing ing TWorldMap into the Automated BattleBook system to
site program, had the software, and Don let the team tag on. show the world’s reserve equipment depots.

It took less than a week to write the program using the Looking Ahead
TWorldMap component. The program includes a scale bar, Custom systems are created by Don for customers who need
variably spaced latitude/longitude grid bars, and site informa- specific capabilities, and all added capabilities are made avail-
tion that displays when the cursor approaches a marked site,
able to registered users.
which is why it took as long as it did. TWorldMap made the
application much better.
Don currently has a few major clients that are waiting for the
The FDT team has received nothing but favorable comments map to support different projections, live scrolling, infinite
from NASA staffers, including astronauts, who have seen the zoom-in, and, of course, a native link to database tables; these
application. Additional features are planned for the next version. are his current areas of focus. ∆

— David B. Chiquelin
Lead Programmer, Field Deployable Trainer project
United Space Alliance

Canada uses the component to show truck routes; three com- Don Bauer is Director of Software Development, East Coast for Marotz, Inc. (a
Delphi Development Company), and is currently converting the Navy’s Fleet
mercial astrology systems use TWorldMap (see Figure 2); in Modernization Program Management Information System to Delphi 2. He can be
Newport Beach, CA, a statistics company uses TWorldMap to reached at [email protected].
show salary and benefit distribution within North America;

44 January 1997 Delphi Informant


New & Used
By Bill Todd

InfoPower 2.0
Making Life Easier for Database Developers

lot of great new features have been added to Woll2Woll Software’s


A InfoPower 2.0, but one thing hasn’t changed: InfoPower is still the
“must have” addition to Delphi for anyone developing database applica-
tions. If you’ve never used InfoPower, you may want to browse the sidebar
“An InfoPower Primer” (on page 47) before reading on.

Picture Masks Clicking the ellipsis button in the Picture


One of the more exciting and useful new Mask edit control of this dialog box leads to
features in version 2.0 is the use of the Lookup Picture Mask dialog box shown
Paradox-style picture masks for controlling in Figure 3. This dialog box lists a number
what users can enter in a field. This is a of useful pictures that have already been
welcome replacement for the very limited built for you; just select the one you want.
edit mask capability built into Delphi.
Figure 1 shows the picture characters used If you need to design a custom picture,
with InfoPower. click the Design Mask button in the Select
Fields dialog box (again, see Figure 2) to
There are several ways to enter pictures when display the Design Picture Mask dialog
you use InfoPower components, but perhaps box in Figure 4.
the easiest is by clicking on the PictureMask
property of a TwwTable component to dis- For example, suppose you need a picture
play the dialog box shown in Figure 2. that will let users enter either a valid US
five-digit or nine-digit ZIP code or a
Character Description Canadian postal code in a field. Start by
typing the picture:
# Any digit.
? Any letter, either upper or lower case. {#&# &#&,#####[-####]}
& Any letter. Lower-case letters are converted
to upper case.
~ Any letter. Upper-case letters are converted into the Picture Mask field. Now click the
to lower case. Verify Syntax button to verify that your pic-
@ Any character. ture is syntactically correct, then type sam-
! Any character. Letters are converted to ple values into the Sample Value field to
upper case.
ensure the picture works as you intended.
; Treat the next character as a literal, not a
mask character.
* Repeat count. *# means any number of If you will use this picture again, click the
numbers. *5# means five numbers. Save Mask button to add it to the database
[] Anything in square brackets is optional. of pictures displayed in the Lookup Picture
{} Group of alternatives. {Y, N, U} means the
Mask dialog box shown in Figure 3. This is
user must enter either Y, N, or U.
just one example of a mask that’s impossi-
Figure 1: The picture characters used with InfoPower. ble to create in Delphi without InfoPower.

45 January 1997 Delphi Informant


New & Used

Figure 4: If no picture fits the bill, InfoPower 2.0 lets you “roll
your own.”

the user enters an invalid value. Using these events, you can
determine if the value entered by the user is indeed invalid. If
the value is invalid, you can display an error message that
identifies the invalid field when the user tries to post the
record. Because the OnInvalidValue event identifies the
offending field, you can also move focus to that field and
change its color.

Filter Dialog
Figure 2 (Top): The Select Fields dialog box provides an easy
TwwFilterDialog gives end users an easy way to filter or query
way to enter pictures. Figure 3 (Bottom): Simply select the pic- a dataset on multiple fields. To use it, just drop the
ture that best fits your needs. FilterDialog component on your form, set its DataSource
property, and provide a button or menu choice to call its
Another good example of the superiority of InfoPower’s Execute method. Calling Execute displays the dialog box
pictures over Delphi’s edit masks is the use of the picture: shown in Figure 5.

#[#]/#[#]/##[##] You can specify one of two ways to select records. If the dataset
being searched is a TwwTable or TwwQBE, then a BDE filter is
for a simple date. Not only does this picture allow you to applied. If the dataset is a TwwQuery, you can either filter the
enter either a one- or two-digit month or day, it also lets query result set, or let the FilterDialog modify the WHERE
you enter either a two- or four-digit year. clause of the query and re-execute it to select the records. You
can set the caption of the dialog box, as well as choose the
If you’ve ever tried to use a Delphi edit mask for a date, you fields for which the user can enter selection criteria.
have probably discovered that it puts the two literal characters
(the slashes) into the field as soon as it gets focus; you can’t The View Summary button allows users to see the field or
eliminate them if you then decide to leave the field blank. fields for which they have entered selection criteria. The
InfoPower doesn’t insert the literals until, when typing your By Value and By Range tabs let users enter a single value
value, you reach the point at which they appear. If you high- to search for, or a range of values. Entering a single value
light the field and delete the value, the literal characters dis- lets users choose to search for an exact match, a record
appear as well, allowing you to easily leave a field blank. that starts with the search value, or a record that contains
the search value anywhere in the field. Users can also
As with all other InfoPower features, pictures work identi- select a case-sensitive search.
cally in both Delphi 1 and 2. Also, they apply whether
you assign a value to a field in code, or the data is entered Wait — There’s More
by the user through a form. InfoPower 2.0 includes three other new components:
1) The TwwIntl component is a non-visual control that
By setting the AllowInvalidExit property to True, you can allows easy internationalizing of applications that use
allow a user to exit a field that contains an invalid value. InfoPower. It provides a central location where you can
You can also control whether the pictures are used for change the captions, hints, and button styles used by all
interactive editing by setting the UsePictureMask property. of InfoPower’s built-in, end-user dialog boxes.
2) The TwwDBSpinEdit lets users easily increment or decre-
InfoPower controls also include OnCheckValue and ment the value in a numeric or date field using the
OnInvalidValue events to let you control what happens when mouse or the up- and down-arrow keys. You can set the

46 January 1997 Delphi Informant


New & Used

AN INFOPOWER PRIMER

For those who haven’t had the pleasure, here’s a short rundown of
existing features in InfoPower version 1.0. All are available to users of
Delphi 1 or 2.

A Table component that fully supports Borland Database Engine


(BDE) filters. This includes the ability to change the filter criteria
on-the-fly at run time; a Pack method for both Paradox and dBASE
tables; and a wwFindKey method that works faster with SQL tables
Figure 5: Calling Execute displays this dialog box. than the Delphi 1 FindKey method.

A QBE component that fully supports QBE queries. This includes


minimum, maximum, and increment values. Also, you
an AnswerTable property to let you easily save your result set to disk,
can use this control without binding it to a data source. as well as an AuxiliaryTables property so you can have the query cre-
3) TwwDBEdit is a replacement for Delphi’s TDBEdit con- ate Paradox-style KeyViol, Changed, Inserted, and Deleted tables in
trol. The TwwDBEdit provides support for InfoPower the user’s private directory.
picture masks. In addition, it automatically detects date
fields and lets users enter the current date by pressing An enhanced data-aware grid component. This lets you:
s. display a cell as a checkbox, combo box, lookup combo box,
or custom dialog box,
An Even More Powerful Grid display the text of a memo field in the grid,
One of the stars of InfoPower 1.0 was the TwwDBGrid double-click a memo field in the grid and display a pop-up
component, which provided many features not found in memo editor, and
the Delphi TDBGrid component. InfoPower 2.0 enhances display multiple tables in a single grid and define non-scrollable
the grid by letting you embed a checkbox, combo box, columns in the grid.
spin edit, bitmap, lookup combo box, or custom edit box
in a field of the grid. Handling long text strings is now High-performance search controls. These include incremental, exact
easier because you can scale a cell to double or triple its match, “starts with,” and sub-string searching.
normal height and word-wrap text in the cell.
A customizable pop-up memo field editor. This is used to edit
Picture edit masks are fully supported in a grid, as is memo fields displayed in any control.
selecting multiple records. You can also cause J to
A DBComboBox component. This is equivalent to the Delphi
behave as F and move focus to the next cell. Another
DBComboBox component except that F, V+F, J, and E
great enhancement is the PerfectFit property. If set to True,
work as expected.
it automatically sizes the grid so that no space is left
between the last row and the bottom of the grid.
A DBComboDialog component. This features an ellipsis button and
an event triggered when the user clicks the button, allowing you to
Lookup Combo Power for display custom dialog boxes to help users edit a field in a table.
Client/Server Developers
The use of lookup combo boxes presented performance prob- A DBLookupCombo component. This lets you:
lems for client/server developers because the lookup dataset display any number of fields in the drop-down list,
had to be a Table component; as a result, opening each table display column separators in the drop-down list,
and filling the BDE cache could go slowly. The only solution display column headings in the drop-down list,
was to load a combo box from a query’s result set. InfoPower control whether the drop-down list grows to the left or to the right,
2.0 solves this problem by letting you use a SQL or QBE control which column sorts the drop-down list, and
query as the source for the lookup data; you don’t have to allow users to incrementally search the drop-down list by typing —
write a single line of code. in the field display — a description instead of a code, even though
the code is stored in the table.
The new TwwDBLookupCombo not only can be placed in
a cell in the InfoPower grid, but can also be placed in the You can use this component without binding it to a data table.
new TDBCtrlGrid in Delphi 2. Also, you can use a detail
A DBLookupComboDialog component. This includes all the fea-
table as the lookup source, and multiple lookup combo
tures of the DBLookupCombo component except that, instead of a
components can now be attached to a single lookup table.
drop-down list, this control displays a dialog box with the lookup
table displayed in a customizable grid.
No More Codes
Some of the most annoying things to deal with when dis-
— Bill Todd
playing data in a grid are fields that contain codes such as

47 January 1997 Delphi Informant


New & Used
part numbers, job codes, and code at any time to let users edit the text in a memo field.
customer numbers. In Delphi Now you can attach additional buttons to the memo edi-
1, the only solution is to cre- tor dialog box to call a spell checker or perform any other
ate calculated fields, then function you need.
write code to look up the val- InfoPower 2.0 includes,
ues and display them next to among others, these welcome The TwwLocateDialog component, which lets users search for
additions to Delphi: Paradox-
the codes in the grid. style picture masks, simplified a value in any field of a dataset, now allows users to search on
dataset filtering and querying, calculated and lookup fields.
and an even more powerful
Unfortunately, this does not data-aware grid component.
help users who must enter If you’re a database develop- Conclusion
er, InfoPower 2.0 is a must-
data and may not remember have tool. If you develop database applications and don’t use
the codes. The enhanced InfoPower, you’re making your life a lot harder than it
Woll2Woll Software
TwwDBComboBox comes to 2217 Rhone Dr., needs to be. Not only does InfoPower offer a wide array of
the rescue by allowing you to Livermore, CA 94550 features unavailable in any version of Delphi, it also offers
Phone: (800) 965-2965 or
display a description instead of (510) 371-1663 the same feature set for both Delphi 1 and 2. This makes
the code in both the field Fax: (510) 371-1664 maintaining a common code base for both the Win16 and
E-Mail: Internet:
itself and in the drop-down [email protected] or Win32 platforms much easier. ∆
list, while still storing the code CIS: 76207,2541
Web Site:
in the underlying table. http://www.woll2woll.com
Price: InfoPower 2.0,
US$199; source code is
TwwDBLookupCombo has available for an additional
done this since version 1.0 in US$99. Upgrade from
previous version, US$99.
instances when the lookup
data is stored in a table; but
now you have the option to show descriptions instead of Bill Todd is President of The Database Group, Inc., a Phoenix-area consulting and
codes when the lookup information is not in a table. development company. He is co-author of Delphi: A Developer’s Guide [M&T
Books, 1995], Creating Paradox for Windows Applications [New Riders
Publishing, 1994], and Paradox for Windows Power Programming [QUE, 1995];
Other Enhancements a member of Team Borland; and a speaker at every Borland Developers
If you deal with memo fields, one of the handiest compo- Conference. He can be reached at (602) 802-0178, or on CompuServe at
nents in InfoPower is the TwwMemoDlg. This provides a 71333,2146.
pop-up memo field editor that you can call from your

48 January 1997 Delphi Informant


New & Used

By Alan C. Moore, Ph.D.

SysTools for Delphi


A Low-Level Delphi Supermarket

ost third-party Delphi products fall into one of two categories: com-
M ponent libraries or utilities. The first is self-explanatory; the latter
helps programmers develop, debug, and profile applications, and create
custom components.
SysTools for Delphi is different; it contains no ibility with Win/Sys or take full advantage of
components or utilities. It is however, a large Delphi’s new features. TurboPower opted for
library of low-level routines that can help with the latter, writing SysTools almost from
the inner workings of applications. Produced scratch in Delphi (with a significant amount
by TurboPower Software Company, SysTools of assembly code). SysTools works with either
is based on an earlier TurboPower product, Delphi 1 or 2. While many of its methods
Win/Sys Library for Borland Pascal and C++. and properties have the same names and para-
While most tools in the earlier library are meters as in Win/Sys Library, some do not.
included, some differences exist (see Figure 1).
SysTools works with either 16- or 32-bit
A Comparison of Win/Sys and SysTools operating systems. In 16-bit programs,
In transforming the Win/Sys Library into most SysTools routines are similar to those
SysTools for Delphi, TurboPower had to in Win/Sys; however, this isn’t the case with
decide whether to provide full reverse compat- 32-bit programs. Some Win/Sys units aren’t
available, including WsTimer (timing and
Purpose of Unit Win/Sys Library SysTools delay procedures), WsDPMI (DPMI
access), and WsHeap (Heap analysis).
String Manipulation WSString STString
TurboPower also has a new utility, Memory
STStr? (S/L/Z)
Sleuth (available separately), that includes
Date/Time Manipulation WSDate STDate
Low Level Operating WSInLine STUtils tools to analyze and report various prob-
System Routines WSDos lems such as memory leaks and resource
Bit Set Manipulation WSBitSet STBits allocation errors in 32-bit programs.
String Dictionary WSPchDct STDict
Double Ended Queue WSQueue STDQue The classes comprising the SysTools library
Large Array and WSLarray STLarr can be divided into three groups: string and
Large Matrix (2 dim) STMatrix numerical data manipulation, container class-
Collection Classes WSColl STColl es, and a low-level interface to the Windows
Linked List WSList STList operating system. With the exception of the
Virtual Array (not implemented) STVarr container classes (which are discussed in one
Tree Class WSTree STTree chapter), each class is explained in its own
Sorting Engine WSSort STSort chapter of the manual which defines all the
Timer WSTimer (not implemented) key properties and methods using clear
BCD Arithmetic Methods (not implemented) STBCD examples. Each chapter also includes a sam-
Registry/INI File Access (not implemented) STRegIni ple application demonstrating its features and
Figure 1: Differences between the tools in Win/Sys Library and capabilities. Now we’ll look at the features,
SysTools for Delphi. uses, and limitations of the main class.

49 January 1997 Delphi Informant


New & Used
String Methods included as an example
SysTools fully supports the three types of strings used in (see Figure 2).
Delphi 2: length-byte (old-style Pascal), null-terminated, and
ANSI strings. Each string-type has a separate unit (i.e. STStrS, Container Classes
STStrZ, and STStrL) with all methods duplicated in each All SysTools’ container
unit. There are routines to parse and manipulate filenames classes descend from
such as AddBackSlash, DefaultExtention, and JustName; and TPersistent, so all are
string parsing/formatting routines to count, wrap, and locate streamable. Overriding
words (a substring surrounded by delimiters) in a string. TPersistent’s Assign
method, many of the
The Boyer-Moore algorithm, a powerful search method, is classes provide a means to
fully implemented; searches can be case sensitive or insensi- convert data to another
tive. SysTools’ Soundex algorithm enables you to test for container class, including
words that sound alike. Also included are text formatting TSTList, TSTDQue,
primitives to trim blanks, center text, and so on. When you TSTLArray, TSTLMatrix, Figure 2: Accepting input in deci-
mal format, SysTools’ BCD
combine these methods with those in Delphi, the string- and the two collections.
Calculator shows the result in BCD’s
handling tools are well represented. As you would expect, internal format.
methods to read from
Date/Time Routines and write to files or streams of different types are included.
If you own or have seen TurboPower’s component library,
Orpheus, you’re probably familiar with many of the date and The container classes fall into two general groups: those
time manipulation methods. SysTools and Orpheus share a based on pointers, such as linked lists, trees, and collections;
common unit, STDate, which includes the basic date and and those based on untyped variables (i.e. arrays). Therefore
time manipulation tools while using additional, autonomous you can store any kind of data in each. You can also adjust
units to handle international issues and date/time string data element size and the number of elements at run time.
manipulation. The unit provides support for Julian dates (a All of these classes descend from/or use one or two base class-
compact numeric representation used for any date from es, TSTContainer and sometimes TSTNode. You can use these
1/1/1600 to 12/31/3999). Dates can be represented in a vari- classes to derive your own container classes.
ety of ways using picture masks such as hh:mm:ss. There are
also useful date/time testing functions, including ValidDate As with many other classes in this library, all the container
and IsLeapYear, along with many conversion methods such as classes are “thread-safe.” In other words, if you want to use
DayOfWeek, MonthToString, TimeStringToSTTime, and them in Delphi 2 applications which take advantage of
RoundToNearestMinute. Many contain error checking. Win32’s multi-tasking capabilities, a safety net is provided.
InternationalDate and InternationalLongDate are useful func- This safety net includes critical sections, which must finish
tions that return the appropriate picture masks as stored in their work before another thread can become active. Among
the [intl] section of WIN.INI. other scenarios, this prevents two threads from attempting to
read from and write to the same data within too close a time-
BCD Math frame, potentially corrupting that data. Altogether there are 10
Binary Coded Data (BCD) is a high-precision, floating-point container classes. Let’s take a brief look at each.
class useful in financial or accounting applications. As
explained in the manual, BCD was “originally defined in Bit Sets, Lists, and Queues
Turbo Pascal 3.0 [as] an array of bytes providing 18 signifi- If you’re writing an application that uses a large number of
cant digit accuracy and a range of 1E-63 to 1E+63.” Both the Boolean switches, and/or memory is a critical factor, the
original and SysTools’ BCD implementation store amounts TSTBits class may fulfill your requirements. Its resizable bit
directly in the familiar base, avoiding conversion to and from sets can be set, cleared, toggled, or tested. Both of the list
binary floating point types. Thus, the class is intuitive to classes, TSTList and TSTDQue, provide methods for adding,
work with. This new BCD class can have as many as 36 sig- deleting, moving, or testing for items. Among other unusual
nificant digits and range from 1E-64 to slightly less than capabilities, you can step through (iterate) a list and join or
1E+64. Most of the math functions required for financial separate lists.
applications are included (up to power functions); however
some of those that might be needed in scientific applications Arrays of Global Proportions:
(trigonometric sine, cos, etc.) are not. Sorted and Unsorted Collections
Using the Windows global heap, SysTools’ two large array classes
The BCD class includes methods to convert to and from expand beyond the dimensional limits of Delphi’s arrays (partic-
Delphi strings, integers, and real types. Reverse compatibility ularly in 16-bit mode.) There is, however, a caveat in 32-bit
with TurboPower’s Object Professional (DOS Turbo Pascal) mode: operations involving large arrays will be slower than
Library is also provided, as long as the latter’s transcendental Delphi’s native arrays, despite the former’s optimization. While
functions are not used. A BCD Calculator application is no separate class for 3D arrays is included, an example program

50 January 1997 Delphi Informant


New & Used
is included that demonstrates how to create
one by having each element of a large array
point to a large matrix. An example pro-
gram employing this technique is shown in
Figure 3.

SysTools’ two collections are sparse


arrays of pointers in which most of the
pointers are assumed to be empty. When
you instantiate a collection, you set a Figure 3: While
page size that determines the initial size not supported
of the collection. During this process directly, you can
create 3D arrays
you are faced with the speed versus size with SysTools.
dilemma. Large pages enable faster
Figure 4: SysTools’ TSTTree class has many inserting, deleting,
access while small pages are bulkier, becoming closer in char-
and searching capabilities.
acter to linked lists.

Much of the functionality of the Borland Pascal 7 TCollection is


included in these classes with the exception of the FirstThat and
LastThat functions. Interestingly, these methods are also absent
from the new incarnation of TCollection in Delphi 2. These col-
lection classes include new methods to test the efficiency of a
collection, to pack it, to iterate through it, and to clear it with a
single call. The familiar methods At, AtInsert, and IndexOf are
present, along with the Count and Items properties.

A Dictionary and a Tree


TSTDictionary is a string dictionary, and is not all that different
from a dictionary on your book shelf. You look up objects by
referring to their key strings, analogous to the terms or words in
Figure 5: The sample sorting application with randomly gener-
a language dictionary. The objects themselves can be anything:
ated strings before being sorted.
strings, data records, or numerical data. The class uses hashing
algorithms to search for data and is particularly efficient when Now let’s discuss input and output of text files. The STText
all the data fits into RAM. TSTDictionary shares many basic unit provides a high-level interface to ASCII (text) files. The
methods with other container classes including add, delete, four procedures and functions are TextFileSize, TextFlush,
clear, find, and iterate. While you can join two string dictionar- TextPos, and TextSeek. These provide a convenient set of
ies in this class and in TSTList, you can join — but not split — methods to facilitate working with text files.
a single string dictionary into two (as you can with TSTList).
Windows Operating System Interface
TSTTree provides a balanced, binary search tree. Its size is In the operating system domain, SysTools has considerably
only limited by available memory. The documentation fewer procedures and functions than Win/Sys. TurboPower
emphasizes that TSTTree includes capabilities of both a string omitted some because they didn’t apply to all the operating sys-
dictionary and the sorting engine (which we’ll discuss soon). tems on which SysTools can be used. However, new routines
The nodes of the tree are stored in sorted order; order and were added to provide better access to the operating system.
balance are maintained during inserts and deletes. Figure 4
shows a sample application that demonstrates the capabilities The STUtils unit includes routines for setting, filling, clearing,
of the TSTTree class. Having discussed each container class in testing, or exchanging values in various data types, including
some detail, let’s look at SysTools’ Sorting Engine. SetLongFlag (sets one or more bits in the parameter Flags) and
FillStruct (fills a given region of memory with values of a speci-
The Sorting Engine and ASCII Text File Handling fied size, i.e. not just byte-size as in FillChar). Others include
SysTools’ sorting unit can be applied to a variety of data types, ClearByteFlag, LongFlagIsSet, ExchangeLongInts, and SetMediaID.
including records and arrays. It uses a non-recursive quicksort
algorithm, along with some characteristics of a merge sort. As Low-level file handling routines are included to retrieve
with the container classes, it’s thread-safe. It can theoretically the number of file handles remaining; read, write, or
hold up to two billion elements; however, the total size used delete volume labels; and check valid drive or directory
by the elements is limited to available memory. A sample sort- status. In the process of conversion, many Win/Sys rou-
ing application creates a list of random strings, enabling you tines were not implemented, including those relating to
to sort them (see Figure 5). DOS. Other functions have been superseded by the func-

51 January 1997 Delphi Informant


New & Used

procedure CreateConfigEntry;
begin
CFG.Free;
CFG := nil;
if not DirectoryExists(ConfigTest.InstallPath) then
MkDir(ConfigTest.InstallPath);
{$IfDef Win32}
CFG := TSTRegIni.Create(RICUser, False);
CFG.CurSubKey := 'Software';
CFG.CreateKey('MyApp');
{$Else}
if FileExists('C:\Windows\MyApp.Ini') then
DeleteFile('C:\Windows\MyApp.Ini');
CFG := TSTRegIni.Create('C:\Windows\MyApp.Ini',True);
{$EndIf}
CFG.CreateKey('User Info');
CFG.CurSubKey := 'User Info';
CFG.WriteString('User', ConfigTest.UserName);
Figure 6: The example program, RIEdit, allows you to view and CFG.WriteDateTime('Config_Changed',
edit .INI files or the Windows Registry. ConfigTest.CreateTime);
CFG.CreateKey('Program Options');
CFG.CurSubKey := 'Program Options';
tionality of Delphi components (e.g. Number of Drives by CFG.WriteString('Prog_Directory',
ConfigTest.InstallPath);
the TDriveCombo component). Win/Sys’ ExistFile function
CFG.WriteBoolean('Program Option Enabled',
now has a Delphi equivalent (FileExists in the SysUtils ConfigTest.ProgOptEnabled);
unit); and various methods such as ExtractFileExt and CFG.Free;
end;
ExtractFileName functions have been replaced by Delphi’s
filename parsing functions. Figure 7: InstallPath, UserName, CreateTime, and
ProgOptEnabled hold the application’s configuration data.
Registry and .INI Files
The Windows Registry (.REG) and .INI files are used in
Assembly Code in Delphi
Windows 3.x, Windows 95, and Windows NT. While .INI
One of the bonuses in SysTools is a large collection of
files were the preferred way to store information in Windows
routines demonstrating the different ways to use assembly
3.x, the Registry has assumed this role in Windows 95.
code. Two units have the bulk of ASM code. The STBase
unit contains a number of strict assembler functions. The
Since .INI files are ASCII text files, they are easy to edit.
other unit, STUtils, includes assembly code within
.REG files, which are binary, pose more problems.
Delphi methods and functions. There is even inline code
However, SysTools’ TRegIni class provides all the tools nec-
for some of the 16-bit primitives (inline is no longer sup-
essary to write to or read from .REG and .INI files. You
ported in Delphi 2, but was used in Delphi 1). Let’s look
can use practically the same code to work with both types.
at two examples.
The example application for this unit, RIEdit, mimics the
Windows 95 RegEdit utility. It transparently opens and
At the start of the STUtils unit is a series of 16-bit proce-
shows either the Registry or a particular .INI file (see
dures and functions written in inline code for Delphi 1.
Figure 6). Data types you can write to or read from a .REG
Most are low level. How low? Here is the 16-bit version of
or .INI file include strings, Boolean, datetime types, inte-
MakeWord, a function to construct a word out of two bytes:
gers, floating point, and so on.
function MakeWord(H, L : Byte) : Word;
A configuration example. To demonstrate the power of this { Construct a word from two bytes }
{$IFNDEF OS32}
class, I’ve written a small demonstration program that sets a inline(
few configuration options after testing for a serial number. It $58/ { pop ax ;low byte into AL }
consists of a project file and four unit files for each of the $5B/ { pop bx ;high byte into BL }
$88/$DC); { mov ah,bl ;high byte into AH }
four forms: CGFTest, SerialDg (a Serial Number testing dia- {$ENDIF}
log box), EntDlg (Entry Dialog Box), and ShowCFG (which
allows both viewing and editing of existing .REG or .INI Now let’s take a look at the 32-bit version for Delphi 2:
files). Admittedly, the program is much simpler than
SysTools’ example program. However, it demonstrates the function MakeWord(H, L : Byte) : Word;
begin
power of these tools. Result := (Word(H) shl 8) or L;
end;
Following is the method that writes to a .REG or .INI file.
CFG is the variable that refers to the file being written to. Among other things, this comparison demonstrates the opti-
InstallPath, UserName, CreateTime, and ProgOptEnabled hold mization built into Delphi 2. It’s no longer necessary to write
the application’s configuration data. Note the compiler direc- as much assembly code to optimize. Few of the 32-bit exam-
tives that enable the appropriate changes for Delphi 1 or 2 ples contain assembly code. The Delphi 2 version of the
(see Figure 7). WriteVolumeLabel function is lean compared to Delphi 1:

52 January 1997 Delphi Informant


New & Used
function WriteVolumeLabel (const VolName: string;
Drive: AnsiChar) : Cardinal;
Conclusion
const SysTools for Delphi is a truly
RootMask = 'x:\'; multi-faceted library. Compared
var
Temp : string; to its predecessor, Win/Sys
Root : string; Library, it’s a major step forward, SysTools for Delphi is a large library of
begin Delphi classes for manipulating strings of
Temp := VolName;
as Delphi was from Turbo Pascal. all kinds, 10 container classes, and low-
Root := Drive + ':\'; One of the more important new level routines to interface the Windows
if Length(Temp) > 11 then operating environment, including the
features in SysTools is its ability to
SetLength(Temp, 11); Registry. It includes example programs,
if Windows.SetVolumeLabel( PAnsiChar(Root), handle the three types of strings in full source code, and additional console
PAnsiChar(Temp)) then Delphi: length-byte, ANSI string test programs. The manual is well orga-
Result := 0 nized, informative, and very well written.
else (introduced in Delphi 2), and SysTools is an excellent collection of
Result := GetLastError; null-terminated. It has an impres- Delphi building tools and an excellent
end; learning resource.
sive set of container classes, a mar-
velous sorting engine, a suite of TurboPower Software Company
While this example contains five lines of code, the 16-bit version low-level tools, and a BCD math P.O. Box 49009
contains 16 lines of Pascal code and 46 lines of assembly code Colorado Springs, CO 80949
class. It also provides a class that Phone: (800) 333-4160 or
(see Listing Five on page 54). With these excellent examples of can directly read from and write (719) 260-9136
source code, this library is a great resource and learning tool. Fax: (719) 260-7151
to any .INI file or the Windows E-Mail: Internet: [email protected]
Registry (16- and 32-bit). Best of or CIS: 76004,2611
The TurboPower Legacy all, SysTools is compatible with
Web Site: http://www.tpower.com
Price: SysTools for Delphi, US$149;
Typical of the written documentation accompanying other both 16- and 32-bit programs upgrade from Win/Sys Library, US$59;
TurboPower libraries, the SysTools manual is excellent: all key compiled in Delphi 1 and 2 upgrade from any other TurboPower
product, US$119.
properties and methods are fully explained; carefully selected respectively. No serious Delphi
examples of code are included in each chapter (concentrating programmer should be without this marvelous library. ∆
on the issues raised by the classes themselves); and many
insights into Windows programming in general, and 32-bit The files referenced in this article are available on the Delphi
development in particular, are provided. Informant Works CD located in INFORM\97\JAN\DI9701AM.

As mentioned earlier, the company’s tradition of including full


source code at no additional cost continues. In addition to the
many example programs, TurboPower has included the console
programs used to test these classes. Known problems are always
articulated, and excellent support is provided via e-mail, online, Alan Moore is a Professor of Music at Kentucky State University, specializing in music
and phone. You can also download minor upgrades free of composition and music theory. He has been developing education-related applica-
charge. Having used TurboPower tools for the past 10 years, I tions with the Borland languages for more than 10 years. He has published a num-
ber of articles in various technical journals. Using Delphi, he specializes in writing
can state with conviction that SysTools for Delphi continues custom components and implementing multimedia capabilities in applications, partic-
the TurboPower tradition. I wish every programming tool ven- ularly sound and music. You can reach Alan on the Internet at [email protected].
dor could reach this level of excellence.

53 January 1997 Delphi Informant


New & Used

Begin Listing Five — STUTILS.PAS 1.00 rep movsb


From SysTools for Delphi, Copyright (c) TurboPower Software, mov ax,ss
Co. 1996 mov ds,ax
mov ah,17h
int 21h
{ 16-bit version of function for Delphi 1 } mov ErrorCode,al
function WriteVolumeLabel(const VolName : string; jmp @@ExitPoint
Drive : AnsiChar) : Cardinal;
var @@NoLabel:
XFCB : XFCBRec; lea di,XFCB
DTA : DTABuf; push ss
SaveDTA : Pointer; push di
MediaID : MediaIDType; add di,8
DriveNo : Byte; mov cx,ss
VName : array[0..10] of AnsiChar; mov ds,cx
MediaBlockExists : Boolean; mov es,cx
ErrorCode : Byte; lea si,VName
begin cld
SaveDTA := GetDTA; mov cx,11
SetDTA(DTA); rep movsb
FillChar(XFCB, SizeOf(XFCB), 0);
FillChar(XFCB.FileSpec, 11, '?');
XFCB.Flag := $FF; call DosFCBCreate
XFCB.AttrByte := 8; mov ErrorCode,al
DriveNo := Ord(UpCase(Drive)) - (Ord('A') - 1); cmp al,0
XFCB.DriveCode := DriveNo; ja @@ExitPoint
MediaBlockExists := (GetMediaID(Drive,MediaID) = 0);
FillChar(VName, SizeOf(VName), ' '); lea di,XFCB
Move(VolName[1],VName, push ss
MinWord(Length(VolName),SizeOf(VName))); push di
asm call DosFCBClose
push ds mov ErrorCode,al
mov ax,ss
mov ds,ax @@ExitPoint:
mov es,ax pop ds
lea dx,XFCB end;
mov ah,11h
int 21h if MediaBlockExists and (ErrorCode = 0) then
or al,al begin
jnz @@NoLabel Move(VName[0], MediaID.VolumeLabel,
SizeOf(MediaID.VolumeLabel));
lea di,DTA ErrorCode := SetMediaID(Drive, MediaID);
mov dx,di end;
add di,18h
cld SetDTA(SaveDTA^);
mov cx,ss Result := ErrorCode;
mov ds,cx end;
lea si,VName End Listing Five
mov cx,11

54 January 1997 Delphi Informant


File | New
Directions / Commentary

Business Objects: Moving beyond Vaporware?

he term business object has a certain ethereal quality to it. Although touted for years by object-
T oriented (OO) proponents, questioning its utility isn’t necessarily wrong. Most developers have
found it hard to sink their teeth into business objects and use them in real-life applications. Last year,
I talked about emerging trends in object-oriented programming. This month, we’ll take a closer look
at one of those trends — business objects — and determine to what extent you can take advantage
of them in Delphi.

What are business objects? Perhaps the should be able to gain a return on the ented language, so the ability to create
simplest way to understand what busi- investment in future applications. business object classes is not an issue.
ness objects are is to contrast them with However, objects instantiated at run
what I call system objects. System objects Business objects simplify the process of time are stored only in memory; all
are reusable components that can be making rule changes. After developing a information related to an object is lost
used across a variety of business appli- user interface component (such as an when you close an application. Thus,
cations. The TTable component, along edit box), how often will you need to in order for business objects to be use-
with all other components in the make changes to it? While some ful, you must be able to store them
Delphi Visual Component Library, is a changes are expected, it’s unlikely that persistently between sessions.
good example of a system object. In many properties or methods of this Unfortunately, out of the box, Delphi
contrast, business objects encapsulate visual component will need to be modi- has little to offer when it comes to
business logic specific to a business fied or added. Contrast this edit box object storage. Because Delphi is pri-
entity (such as a customer, student, or with a typical business object. As the marily targeted as a client/server devel-
invoice) or a process (such as a purchase companies these business objects imi- opment tool, its database access is
or ATM withdrawal). System objects tate are constantly changing, there will focused exclusively on being a good
are horizontal, whereas business objects be an ongoing need to make changes to client for back-end relational database
are — by their nature — vertical crea- a business object. Because of encapsula- systems. Also, Delphi data classes don’t
tures, specific to a particular line of tion, a properly designed business support persistent storage of objects,
business, e.g. a business object devel- object can be modified rather quickly, so you really have no choice but to do
oped for a bank probably won’t have without causing a ripple effect across an it on your own. In an upcoming
tangible benefits for an HMO. application — as long as its external File | New, we’ll look at the four
interface remains constant. options you have for creating a custom
Why use business objects? When devel- business object storage solution.
oping applications, the greatest amount of Business objects separate business rules
energy is usually spent on the business- from the rest of the application. In Are you using business objects in
specific parts of the application. Given client/server systems, a fundamental Delphi? If so, contact me at
this investment, it’s clear that business issue has always been determining [email protected] and let me
rules are the heart of most applications. where the business rules of the applica- know how you’re using them. I’ll com-
Thus, encapsulating these rules into busi- tion should be stored: within the pile this information and share it with
ness objects has several advantages: client’s user interface, or as stored pro- all of you. ∆
cedures on the server. From an OO
Business objects provide the ability to standpoint, the solution is to partition — Richard Wagner
build a reusable component architecture. the application, adding a business logic
Reuse is touted as one of the funda- layer between the presentation and
mental motivations behind OO database levels. Such an application Richard Wagner is the Chief Technology
methodologies. As with any Delphi architecture becomes more extensible Officer of Acadia Software in the Boston,
component you create, a well-built and easier to maintain. MA area. He welcomes your comments
business object can be reused in multi- at [email protected] or
ple applications. Therefore, after a com- Can you use business objects in on the File | New home page at
pany has developed business objects, it Delphi? Delphi features an object-ori- http://www.acadians.com/filenew.htm.

55 January 1997 Delphi Informant

You might also like