0% found this document useful (0 votes)
166 views51 pages

The Paradox Files: The Data Is Out There

This document summarizes new products and tools for Delphi developers in April 1997. It discusses the release of version 2.0 of T-BASE for Windows by Videotex Systems, which adds ActiveX controls to their imaging library for Delphi. It also covers the announcement of OnGuard by TurboPower Software, which allows developers to create demonstration versions of their products for secure distribution. Additionally, it describes the new version 2.0 of IAC components released by Perconti Data Systems, a set of 19 INI-aware components for Delphi 3 that allow saving user configuration options.

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)
166 views51 pages

The Paradox Files: The Data Is Out There

This document summarizes new products and tools for Delphi developers in April 1997. It discusses the release of version 2.0 of T-BASE for Windows by Videotex Systems, which adds ActiveX controls to their imaging library for Delphi. It also covers the announcement of OnGuard by TurboPower Software, which allows developers to create demonstration versions of their products for secure distribution. Additionally, it describes the new version 2.0 of IAC components released by Perconti Data Systems, a set of 19 INI-aware components for Delphi 3 that allow saving user configuration options.

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/ 51

April 1997, Volume 3, Number 4

The Paradox Files


The Data Is Out There

ON THE COVER
6 The Paradox Files: Part I — Dan Ehrmann
Developers use Paradox tables every day, but little information circu- Cover Art By: Tom McKeith
lates about how the file format works. Coincidence — or cover-up?
Agent Ehrmann exposes what really happens when you add or
remove a field, create an index, etc.

FEATURES
11 Delphi at Work — Ian Davies 39 Inside OP — Gary Warren King
Back in the heyday of DOS, plain text on-screen was de rigueur. But Forms and dialog boxes often have interdependent field and display
Windows made anything less than rich text déclassé. Here’s a rich- properties whose friendliness can rapidly decay. Mr King shows how
text component to help keep your applications in fashion. to keep your UI responsive — by delaying events.

17 OP Tech — Bill Todd REVIEWS


How can you write code for any form, in any program, to manipulate 43 Multi-Edit for Windows
components in any way you need? With the Components arrays, for Product Review by Alan Moore, Ph.D.
which Mr Todd offers three practical uses.
49 Delphi 32-Bit Programming SECRETS
20 DBNavigator — Cary Jensen, Ph.D. Book Review by Richard Porter
Need to access the BDE, or control Paradox table settings? Dr Jensen 49 Delphi Database Development
explores the Session component — a database developer’s delight. Book Review by Warren Rachele

27 Sights & Sounds — Don Peer and Peter Dove DEPARTMENTS


It’s time to speed things up! This month, Misters Peer and Dove 2 Delphi Tools
employ DIBs to make faster, finer 3D graphics a reality in their
ongoing graphics series. 4 Newsline
51 File | New by Richard Wagner
34 Informant Spotlight — James Hofmann and Cathi Pickavet
Ah, the difference a year makes: We’ve added exciting new categories
to the Readers Choice Awards. Find out who dominated, and who’re
just getting their feet wet, as determined by you, the reader.

1 April 1997 Delphi Informant


Delphi Videotex Launches Version 2.0 of T-BASE for Windows
Videotex Systems, Inc. of images, as well as play accounting records, and
T O O L S Dallas, TX has released sound and video. Images are vehicle accident reports.
T-BASE 2.0 for Windows, scanned using TWAIN sup-
New Products an imaging library tool for port, enabling programmers Price: T-BASE for Windows 2.0 compo-
and Solutions Delphi, with added ActiveX to create applications that nents are priced separately from US$75
controls. can control any scanner to US$595. Special pricing is available
Using the newest version with a TWAIN driver. for registered users of earlier versions.
of T-BASE, developers can T-BASE supports images Contact: Videotex Systems, Inc.,
image-enable any Windows in .JPG, .PCX, .TGA, 11880 Greenville Ave., Ste. 100,
3.1, Windows 95, or .DCX, .GIF, .BMP, .DIB, Dallas, TX 75243
Windows NT application and .TIF (including Group Phone: (800) 888-4336 or
that is ActiveX compatible III/IV) file formats. (972) 231-9200
or can call DLLs. In addi- Using T-BASE 2.0, devel- Fax: (972) 231-2420
tion, controls and DLLs are opers can create such imag- E-Mail: software@videotexsystems.com
available in 16- and 32-bit ing applications as ID Web Site: http://www.videotex-
versions. badges, signature verifica- systems.com
Delphi Component Design
Danny Thorpe Version 2.0 can display, tions, commercial real estate
Addison-Wesley print, scan, and convert listings and floor plans,

TurboPower Software Announces OnGuard for Delphi


TurboPower Software Co. portions of the product. OnGuard ships with 16-
of Colorado Springs, CO has OnGuard enables develop- and 32-bit support for
announced OnGuard, the ers to choose from several Delphi 1 and 2. It is royal-
newest product in its line of protection mechanisms, ty free and includes full
professional libraries and including time-limited ver- source code.
tools for Delphi 1 and 2. sions or versions that are
OnGuard allows develop- limited to a particular num- Price: US$199
ISBN: 0-201-46136-6
Price: US$36.95
ers to create demonstration ber of executions. In addi- Contact: TurboPower Software Co.,
(348 pages, CD-ROM) editions of their products for tion, OnGuard lets devel- P.O. Box 49009, Colorado Springs, CO
Phone: (800) 822-6339 secure electronic distribution opers add network metering 80949-9009
over the Internet. After sam- to their programs, thereby Phone: (800) 333-4160 or
pling a downloaded pro- limiting the number of (719) 260-9136
gram, customers can contact simultaneous users of their Fax: (719) 260-7151
the developer to purchase a programs in a network E-Mail: info@tpower.com
key code that unlocks other environment. Web Site: http://www.tpower.com
The Essential Guide to
User Interface Design
Wilbert O. Galitz
John Wiley & Sons, Inc.

ISBN: 0-471-15755-4
Price: US$44.95
(626 pages)
Phone: (212) 850-6630

2 April 1997 Delphi Informant


Delphi Perconti Ships New INI-Aware Components for Delphi 3
Perconti Data Systems, IALabel, IACheckbox, ous versions can receive a
T O O L S Inc. of St. Petersburg, FL has IAComboBox, IARadio- free upgrade.
released IAC’s version 2.0, a Group, IAListBox, IAEdit-
New Products set of 19 INI-aware compo- Combo, IAMemo, IARadio- Price: IAC’s for Delphi version 2.0
and Solutions nents for Delphi 3. Button, IASRButton, IASR- (standard edition), US$69; IAC’s for
Although it’s designed for BitBtn, IASRSpeedButton, Delphi version 2.0, (professional edition
Delphi 3, IAC for Delphi IASaveDialog, IAOpen- with source code), US$99.
runs on all versions of Delphi. Dialog, IADBComboBox, Contact: Perconti Data Systems, Inc.,
It allows developers to save IASpinEdit, IAMaskEdit, 8601 Fourth Street North, Ste. 210,
user configuration options for and IAScrollBar. St. Petersburg, FL 33702
any system developed. Demonstration versions are Phone: (813) 576-7727
The set includes the fol- available from Perconti’s Fax: (813) 576-8033
lowing INI-aware compo- Web site. E-Mail: info@perconti.com
nents: IASource, IAEdit, All registered users of previ- Web Site: http://www.perconti.com

InstallShield Software Corp. Releases InstallShield5


InstallShield Software and other syntax elements allow developers to specify
Corp. of Schaumburg, IL are color-coded to help basic information about
has released InstallShield5 developers locate parts of the their applications and instal-
Professional. script when editing and lations. The Wizard then
Using this upgrade, develop- debugging. builds and compiles a pro-
ers can create Windows instal- InstallShield5 Professional ject, including a script,
lations through a visual inter- provides several wizards, installation dialog boxes, and
face. They can plan, script, including a Function more.
compile, debug, and build in Wizard, to assist developers Developers can also choose
the same multi-paned devel- in selecting a function and to create a Quick Build for
opment environment. specifying its parameters. testing purposes. In a Quick
The new IDE features a When creating the frame- Build, files are referenced,
built-in script editor win- work for a new installation but not included.
dow, which places each project, developers can use InstallShield5 Professional
installation project into plain the Project Wizard. It con- also allows developers to
view. Functions, keywords, tains eight dialog boxes that view and manage their file
groups and components
visually. No function calls
are required to copy files.
Developers can drag-and-
drop to specify and group
the files to be installed.
InstallShield5 Professional
provides multimedia sup-
port for its application
installations, including .AVI
video, .WAV and MIDI
sound, and 256-color
bitmap images.

Price: US$795
Contact: InstallShield Software Corp.,
900 National Parkway,
Schaumburg, IL 60173
Phone: (800) 374-4353
Fax: (847) 240-9138
E-Mail: info@installshield.com
Web Site: http://www.install-
shield.com

3 April 1997 Delphi Informant


News Oracle Licenses Borland’s Java and C++ Development Tools
Scotts Valley, CA — Borland
and Oracle Corp. have
compatibility between tech-
nologies.
in the second quarter of FY
1998, JBuilder provides visual
announced Oracle will Borland C++Builder is a component-based develop-
L I N E license Borland’s Java and new object-oriented develop- ment tools for cross-platform
C++ development technolo- ment tool that combines the development. JBuilder is a
April 1997 gies for use with Oracle C++ programming language development environment for
database systems and appli- with the RAD productivity projects such as Web-delivered
cation development tools. of Delphi. A beta version of applets, applications that
Under the terms of the C++Builder is currently require client/server database
license agreement, Oracle available to customers on connectivity, and enterprise-
will integrate and distribute Borland’s Web site at wide distributed computing
Borland’s C++Builder and http://www.borland.com. solutions.
JBuilder software tools with JBuilder is Borland’s upcom- “Oracle Licenses Borland’s Java
a number of Oracle’s existing ing Java application develop- and C++ Development Tools”
and future products, includ- ment tool. Scheduled to ship continued on page 5
ing Developer/2000,
Designer/2000, and Oracle Borland Announces Delphi/400 Client/Server
Power Objects (OPO). Suite for IBM’s AS/400
The Oracle-Borland agree-
Scotts Valley, CA — ized object classes and
ment is aimed at extending
Borland has announced ClientObject/400’s APPC
the tools available to Oracle
Delphi/400 Client/Server AS/400 connectivity,
customers for their Internet
Suite, a client/server devel- Delphi/400 Client/Server
and intranet application
opment tool for the IBM Suite delivers a secure and
development. As part of the
AS/400 hardware platform. fast Windows interface for
license agreement, Borland
The Delphi/400 Client/- the AS/400.
and Oracle development
Server Suite is based on In addition to this product
teams will work together to
Delphi Client/Server Suite launch, Borland announced
promote integration and
2.0 for Windows 95 and it has joined IBM’s AS/400
Windows NT, and AS/400- Partners in Development
[ calendar 1997 ] compatible connectivity and business program. Through
development technology this program, the two com-
March 8-10 Network World Unplugged, recently licensed by Borland panies will work to provide
10-14 Spring Internet World, Los San Jose Convention Center, from TCIS of Paris, France. Delphi-based software solu-
Angeles Convention Center, Los San Jose, CA. Contact DCI at
Angeles, CA. Visit http://www.- (508) 470-3870, or visit their This RAD tool combines a tions to the AS/400 com-
events.iworld.com. Web site at http://www.- visual component-based munity.
DCIexpo.com. design, an optimized 32-bit Borland plans to demon-
19-21 Internet World Asia ’97,
Putra World Trade Center, Kuala 8-11 Internet World Argentina native code compiler, and an strate Delphi/400 Client/-
Lumpur, Malaysia. Contact (800) ’97, Buenos Aires Sheraton open, scalable database archi- Server Suite at three IBM
632-5537 or (203) 226-6967, or Hotel, Buenos Aires, Argentina. tecture in an object-oriented trade shows this year: 1997
visit http://www.events.- Contact (800) 632-5537 or (203)
iworld.com. 226-6967, or visit http://www.- environment. IBM Business Partner
events.iworld.com. With the addition of the “Delphi/400”
25-27 DCI’s Internet Expo, ScreenDesigner/400 special- continued on page 5
London, England. Contact DCI at 8-11 Internet World Brazil ’97,
(508) 470-3870 or visit their Web Ahembi Convention Center, Sao
site at http://www.DCIexpo.com. Paulo, Brazil. Contact (800) 632- Searchable Delphi Knowledge Base
5537 or (203) 226-6967, or visit
April http://www.events.iworld.com. Now on the Web
1-3 DCI’s Web World, Westin
Marlboro, MA — Apogee allows keyword- and
Swan, Orlando, FL. Contact DCI 21-24 Borland Developers
at (508) 470-3870 or visit their Conference Europe, London, Information Systems has description-based searches of
Web site at http://www.- England. Contact Borland at released an online, interac- the database. It’s available
DCIexpo.com. (408) 431-1000.
tive version of the DTopics free on Apogee’s Web site at
2-4 Software Development, 23-25 Internet World Japan ’97, database created by Mike http://www.apogeeis.com.
Moscone Convention Center, Makuhari Messe, Tokyo, Japan. Orriss. DTopics currently Updates to the database
San Francisco, CA. Contact Contact (800) 632-5537 or (203)
includes over 700 entries will be made each time
Miller Freeman at (415) 905- 226-6967, or visit http://www.-
2784 or visit http://www.- events.iworld.com. regarding Delphi program- Orriss posts a new version
sd97.com ming topics. on CompuServe. For more
Developed in IntraBuilder, information call Apogee at
the new Web application (508) 481-1400.

4 April 1997 Delphi Informant


Southern California Delphi Developers’ Conference Scheduled
Irvine, CA — The Orange
County Delphi User Group
(OCDUG), in association
The conference runs from
8 a.m. to 5 p.m. at
Chapman University (287
but increases to US$149
after April 19. For more
information, visit the
News
with the Chapman University North Center Street, OCDUG Web
chapter of the ACM, Orange, CA 92666). Early site at http://www.ocdelphi.- L I N E
announced it will hold the registration costs US$99, org.
second annual Southern
April 1997
California Delphi Developers’ Borland Awards Software and Trademark for
Conference at Chapman
University in Orange on ReportSmith to Strategic Reporting Systems
Saturday, May 10, 1997. Scotts Valley, CA — sales of the ReportSmith
The conference will have Borland announced it has family of products. Borland
various educational tracks awarded a world-wide soft- provided technical support
aimed at both advanced and ware and trademark master for ReportSmith through
new Delphi developers. license for the ReportSmith January 31, 1997, and will
The keynote presentation product line to Strategic continue to make it avail-
features Chuck Jazdzewski, Reporting Systems, Inc. of able to Delphi Client/Server
co-developer and chief archi- Peabody, MA. Suite customers.
tect of Borland’s Delphi. As As of December 31, 1996, For more information,
with last year’s conference, Strategic Reporting assumed contact Strategic Reporting
the date was selected to coin- responsibility for world- Systems at (508) 531-0905
cide with the release of a wide service, maintenance, or e-mail rsmith@sea-
new version of Delphi. support, marketing, and systems.com.
The Borland Canada
Developer’s Conference
Oracle Licenses Borland’s Java and C++ Development Tools (cont.) Canadian developers are planning to
meet May 21-23 in Toronto to discuss
Oracle products for devel- computing environment. about Oracle products, call the latest in software technologies
opers include Designer/2000, It incorporates application (415) 506-7000 or visit and trends at the Borland Canada
Developer’s Conference.
Developer/2000, and OPO. partitioning, drag-and-drop Oracle’s Web site at For more information, call (800) 265-
Oracle’s Designer/2000, a reusability, and through the http://www.oracle.com. 1362, (416) 444-1071, e-mail web-
master@dbcenteral.com, or visit
repository-based modeling use of Java, ensures low cost http://www.dbcentral.com.
tool, provides systems life- deployment on the Web. Delphi/400 (cont.)
cycle support through busi- OPO provides a drag-and- Executive Conference;
ness process re-engineering, drop application develop- AS/400 User Group
systems analysis and design, ment environment for Conference; and AS/400
and the automatic generation BASIC programmers. It also User Group Conference
of client/server and Web eliminates the requirement Europe. The Delphi/400
applications. to code database interaction Client/Server Suite pricing
Developer/2000 is the first while providing support for starts at US$3,995.
Oracle tool to deliver scal- Windows, ActiveX, and For more information on
able deployment of database other desktop standards. Delphi/400, call (800)
applications in a network For more information 233-2444.

5 April 1997 Delphi Informant


On the Cover
Paradox / BDE

By Dan Ehrmann

The Paradox Files: Part l


The Table Format, Header, Family Files,
Data Blocks, and Table Levels

O nce there was a database program called Paradox. In the beginning,


it knew no distinction between Windows and DOS; there was just
Paradox, with its indivisible user interface, programming language, and file
format. But when Borland introduced Paradox for Windows in January of
1993, it separated Paradox into two components: the user interface and
the file format, connected by ODAPI, the Object Database API.

Over time, ODAPI became IDAPI, which Borland sold the desktop-database Paradox
in turn became the Borland Database to Corel, who will move it forward as part
Engine, or BDE. Paradox, the file format, of their Office product. Now the Paradox
took on a separate identity from Paradox, file format is simply one of the native for-
the desktop database tool. More recently, mats in the BDE, which is shipped with
every one of Borland’s developer tools,
including Delphi, C++, C++Builder,
Extension Description
JBuilder, and IntraBuilder.
.DB Table definition and all data except memo,
graphic, and BLOb fields. Many Delphi developers use Paradox
.MB Memo, graphic, and BLOb data.
tables every day, but remarkably little
information circulates in the Delphi com-
.PX The index for the table’s primary key. munity about how the Paradox file format
really works. The manuals certainly
.Xnn and .Ynn Each pair of files represents a separate secondary
explain almost nothing. Yet the Paradox
index. The naming convention for nn will be
explained in the third article of this series. file format is rich and robust, with many
features to serve even the most demanding
.VAL ValCheck, Table Lookup, and Referential Integrity desktop applications.
definitions.
Figure 1: Paradox file format extensions. This series of articles will explain how
Paradox tables are structured internally,
Extension Description and what really happens when you add or
.TV TableView settings (for Database Desktop and
remove a field from a table, create an
Paradox for Windows only). index, pack a table, present a password,
define referential integrity, or modify a
.FAM Linking file created with the .TV file (for Database record on a network.
Desktop and Paradox for Windows only).

.SET TableView settings in Paradox for DOS. Databases and Files


Microsoft Access defines a database as a single
.F and .F1-.F14 Forms attached to the table in Paradox for DOS. file with its own internal file system. Inside the
.R and .R1-.R14 Reports attached to the table in Paradox for DOS. typical .MDB file are tables, indices, relation-
ships, forms, reports, queries, and code.
Figure 2: Obsolete Paradox file format extensions. Paradox takes the opposite approach; a database

6 April 1997 Delphi Informant


On the Cover
is loosely defined as a subdirec-
tory containing related tables.
Elements of each table are
stored in separate files, which
are loosely linked together as a
family. You control the file
name, and the BDE controls
the extension. Figure 1 lists the
different extensions for the
Paradox file format.

(Older versions of the


Paradox file format supported
other files as part of the
table’s family. Figure 2 lists
the extensions used by older
versions of the Paradox file Figure 3: Structure of a Paradox table.
format and by the Database
Desktop shipped with Delphi. These files don’t need to be The header size in bytes.
included with your tables when you deliver an application to The record size in bytes.
a client.) The block size, being one of the following values: 1KB,
2KB, 4KB, 8KB, 16KB, or 32KB.
When you copy a table, you must copy all linked family mem- Whether the table is keyed or unkeyed.
bers as well. Otherwise, the family may become obsolete, rela- The number of fields in the table, from 1 to 255.
tive to the .DB file, creating all manner of problems when your If the table is keyed, the number of fields in the table’s
application needs to read information from a family file. primary key.
An array of field types and sizes, indexed by the field
The Table Format number.
Internally, the Paradox file format is known as a clustered-key, An array of field names, indexed by the field number.
VSAM-block, fixed-length record file format, meaning that: The number of data blocks in the table, a two-byte value
Data is stored in records that occupy a fixed length in the that can track up to 64KB blocks.
table, irrespective of the number of characters in each A pointer to the first data block, used to initialize the for-
field. For example, a 20-character alphanumeric field ward chain.
always occupies 20 characters inside the .DB file, even if A pointer to the last data block, used to initialize the
many values are shorter than 20 characters, or are blank. backward chain.
Records are organized into physical blocks, which can be The number of free blocks in the table, also a two-byte
accessed sequentially or randomly. Each block contains value.
one or more records. Records do not span blocks, often A pointer to the first free block.
resulting in empty space at the end of each block not The table language, used to define character translations
large enough to contain another record. and sort orders.
Blocks — and the records within each block — are orga- The master password — itself encrypted — that was used
nized so that records are physically stored in primary key to encrypt the data in the table. Note that the header is
sequence. This feature significantly improves access speed not encrypted, so it is always possible to read an encrypted
when the primary key is used. table’s structure and other information from the header.
An encrypted list of auxiliary passwords defined for the
Figure 3 shows the structure of a table. It begins with a table table, together with the table and field rights defined for
header of at least 2KB, and may be a larger multiple of 2KB, each password.
depending on the information to be stored there, including The current value of the table’s Autoincrement field (only
the following (most of which will be explained in this and one is allowed per table).
subsequent articles):
The table level. Some elements of the table’s header support Paradox for DOS
The Structure ID which is used to keep family members features (e.g. password rights for linked forms and reports,
synchronized. When the BDE opens one of the family whether the table maps to a remote table on a database serv-
files, it checks to see that the Structure ID in the header er). These areas of the header are ignored by the BDE.
of that file is the same as the ID in the .DB file. If they
are different, it means that the .DB was updated apart Data Blocks
from its family members. Whenever you restructure a When you create a table, no data blocks are allocated. The
table, the Structure ID is refreshed in all family members. table consists of the header only. When you then insert the

7 April 1997 Delphi Informant


On the Cover
first record, a data block is appended to the .DB file immedi- you might think that the maximum record size for a keyed
ately following the header. table would be (4096 - 6) / 3 = 1363 bytes.

Data blocks can be 1KB, 2KB, 4KB, 8KB, 16KB, or 32KB Instead, in a holdover from the days when 4KB was the
in size; the default is 2KB, but this can be changed in the largest supported block size, the BDE considers 1350 bytes as
BDE Administration program. (Note that the 1KB size is the maximum record size for a keyed table using a 4KB block
considered obsolete, and is normally not used.) Block size size. If your record is larger than 1350 bytes, the BDE uses an
does not vary within a table; when the table is created, the 8KB block for a keyed table.
BDE picks a block size and will use that size for every data
block appended to the .DB file. Insert and Delete Records
Let’s consider a keyed table with a record size of 204 bytes.
Coupled with the 64KB limit in the number of data blocks, (Don’t worry about how we arrived at this number; you’ll
the maximum table size (excluding header and free blocks) learn how to calculate record size in the next article.)
is shown in Figure 4. In the real world, the Paradox file for- Within a Paradox table, the BDE will use a 2KB block size,
mat grows unreliable, well before these theoretical limits are and will fit 10 records into each block, using 2040 bytes in
reached. For example, restructuring tables larger than 100 the block for data, with six bytes reserved at the beginning
or 200MB can take hours, especially when updating many of the block, and only two bytes of wasted space.
secondary indices.
Now let’s assume you insert four records into this table. The
The Paradox file format reserves six bytes at the beginning of BDE will fill the first four record slots in the block, and set
each block for three internal pointer series of two bytes each, the record counter to “4”, as shown in Figure 5. (The first
as follows: cell in each of the following figures is the count of active
The sequential number of the next logical block. This series records in that block. Active records are shaded, while avail-
of pointers is known as the forward chain. BDE computes able record slots are unshaded.)
the physical starting address of the block by multiplying the
block number by its size, and adding the size of the header. If you then delete record “C”, the BDE moves record “D”
The sequential number of the previous logical block. This up to occupy slot number 3 — formerly occupied by “C”.
series of pointers is known as the backward chain.
The space formerly used by “D” is not blanked out, so the
A counter to indicate how many active records are con-
original version of “D” still exists. But the record counter
tained within that block. Data records are always stored
is set to “3”, to indicate that only this many active records
at the front of the block, so this pointer, coupled with
remain in the block. This scenario is shown in Figure 6.
the record size stored in the header, tells the BDE exact-
ly how much of the block contains active data.
The next record inserted into the table will cause the obsolete
When a block is completely empty, it’s moved to the free- copy of “D” in record slot number 4 to be overwritten. For
block chain. This is a separate chain of available blocks example, if you insert a record after “A”, then “B” and “D”
used when the BDE must allocate another data block for are pushed over one record slot to maintain the keyed order
the table. Free blocks have the same three pointers at their shown in Figure 7.
beginning, although the “number of records” pointer is
always zero for a block in this chain. If you insert another six records into the table, the block
will be filled, as shown in Figure 8. When you insert the
The BDE will use the block size specified in the Paradox dri- eleventh record into the table, the BDE determines that the
ver section of your BDE configuration file, unless this block is current block is full, and that it must allocate another block.
too small for a single record — in the case of an unkeyed table When the new block is appended, the BDE also moves the
— or three records in the case of a keyed table. If the specified last record from the previous block, to leave an empty slot
block size is too small, the BDE uses the smallest block capa- in that block. This is done so a subsequent insert between
ble of holding that number of records, based on the table “A” and “J” does not require a new block to be allocated, or
type. It will then fit as many records as it can within each a split to take place. In the previous example, record “J” is
block. moved to the new block, and the new record “K” is added
after this one, as shown in Figure 9.
(The BDE uses a minimum of three records for a keyed
table because modern disk controllers read a whole block Note that this is different from the fill factor parameter refer-
from the hard disk, anyway. Research has determined that, enced in the BDE configuration program for the Paradox file
when your program is searching for a specific record, it’s format. The fill factor applies to index files only; we will dis-
invariably quicker to read a complete block into memory, cuss it in the third article of this series.
then locate the record sequentially within the block.)
Suppose you next insert two records in this keyed table
The BDE sometimes rounds the result of the record size cal- between records “E” and “F”. Assume these are called “E1”
culation. For example, with a block size of 4KB (4096 bytes), and “E2”. Since there is only one free slot in this block, “E1”

8 April 1997 Delphi Informant


On the Cover
Data Block Size Maximum Table Size If you simply restructure the table using the Database
Desktop, the BDE does not remove empty record slots. It
1KB 64MB also does not physically rearrange the blocks. However, if you
2KB 128MB restructure with the Pack Table option selected, the BDE
rebuilds the table and “squeezes out” any unused space. Each
4KB 256MB block is completely filled, and the free-block chain is emp-
tied. The BDE also re-sequences the blocks to have the same
8KB 512MB
order as the forward chain.
16KB 1024MB
The fill factor applies only to keyed tables. With unkeyed
32KB 2048MB
tables, there is less chance that a record will need to be insert-
Figure 4: Maximum table size for different block sizes. ed into the table in a specific sequence, so the BDE does not
attempt to leave slots available.
is first placed in the existing block immediately following
“E”, causing the remaining records to be pushed back one Managing Block Use
slot each, and the last slot to be filled by record “I”. When Some tables grow continuously and almost never have records
“E2” is inserted, the block must be split again. But because removed. If records are always added at the end of the table
you are not inserting at the end of the block, the split hap- — for example, in an Orders table where the primary key is
pens at the insertion point, with all records after this one an incrementing value — the table will grow continuously
placed in a new block, as shown in Figure 10. and all data blocks will be largely filled.

In the previous example, the new block is added at the end of On the other hand, if new records are scattered throughout the
the table’s file. The BDE then renumbers the forward and table — for example, in a Customer table where the primary
backward chains for each of the three blocks. The forward key starts with the customer’s name — the table will grow
chain points to the blocks in this order: 1-3-2. The backward more quickly because many blocks will have been split in order
chain points to the blocks in 2-3-1 order. to keep records in primary-key sequence. Therefore, many
blocks will not yet have been filled.

But consider also a table with a con-


tinuous stream of both added and
Figure 5: Inserting four records into a table. deleted records, such as the Orders
table previously described, where paid
orders are moved to an archive table.
It will tend to grow to an equilibrium
size and stay there. As records are
Figure 6: Deleting the third record. deleted near the beginning of the
table, blocks will be emptied, and will
move to the free-block pool, to be
used when new blocks are required at
the table’s end.
Figure 7: Inserting a new record into the table.

If you frequently insert or delete large


numbers of records, and if the table is
often queried, it’s a good idea to
restructure and pack the table after
Figure 8: Inserting another six records into the table.
such operations. Compressing and
reordering the data blocks in the .DB
file shrinks the table and places its
blocks in natural order. It also com-
presses the primary index file (.PX),
Figure 9: The BDE adds a new block and applies the fill factor. which indexes only the first record in
each block (as we’ll see in a subse-
quent article).

If your table has many partially filled


blocks, compressing it can reduce the
size of the primary index substantially.
Figure 10: The BDE adds another block. In addition, because the complete pri-
mary index is contained within every
9 April 1997 Delphi Informant
On the Cover
maintained secondary index, compressing the table will For example, if you don’t specify descending secondary
shrink secondary index files, and improve their performance indices, the BDE will not use a Level 7 table. If you
as well. restructure a table to add a feature that requires a higher
level, the BDE changes the level accordingly.
Table Levels
The BDE defines a level for each Paradox table, based on The default table level for new tables is defined in the BDE
the features it uses. Over the years, Borland has added new Administrator program. You can use this parameter to force
field and index types to the Paradox file format, and each Level 7 for all tables, although this isn’t necessary. Aside
change necessitated a bump in the level number to ensure from field changes described in next month’s article, and
that the BDE would correctly handle the newer features. index changes described in a subsequent article, Level 5
added support for the 8KB, 16KB, and 32KB block sizes.
Note: When the BDE opens a table, it first checks the
level, and if it finds a higher number than it knows how to Conclusion
handle, raises an exception. The next article in this series will explore the different
types of fields in the Paradox file format. It will explain
Level 4 corresponds to Paradox 4.0 for DOS and the first the characteristics of each field, and show you how much
version of the BDE. With this level, Borland substantially space each uses in the .DB file.
modified Paradox’s network locking model to improve
multi-user performance. They also added new field types, With this information in hand, you will learn how to cal-
including memo fields, and new index types, including culate record size and the minimum possible table size.
multi-field and case-insensitive indices. Tables that were The next article also includes a simple Delphi application
originally created under earlier versions of Paradox for to calculate this information for any specified table. ∆
DOS are treated by the BDE as Level 4 tables.

Tables at Levels 4, 5, and 7 are fully compatible with each Dan Ehrmann is the founder and President of Kallista, Inc., a database and
other, and can be used concurrently; you don’t need to use Internet consulting firm based in Chicago. He is the author of two books on
Level 7 to achieve full 32-bit compatibility. (There is no Level Paradox, and a member of Team Borland and Corel’s CTech. Dan was the
Chairman of the Advisory Board for Borland’s first Paradox conference, which
6.) When you define a new table, the BDE picks the lowest evolved into the current BDC. He has worked with the Paradox file format for more
level compatible with the features you specified, but never than 10 years. He can be reached via e-mail at dan@kallista.com.
lower than the level specified in the BDE configuration file.

10 April 1997 Delphi Informant


Delphi at Work
Delphi 2

By Ian Davies

Rich Text Control


Building a Poor Man’s Word Processor
with the RichEdit Component

S ometimes the Memo component just doesn’t do the trick. Sure, it allows
you to enter multiple lines of text (255KB in Delphi 1, unlimited in Delphi
2), but there’s not much you can do with the text to add interest or emphasis.
With the RichEdit component, however, you can — and a lot more.

The RichEdit component has many features Basic Principles


not supported by the Memo component. The fundamental properties for manipulating
With it, you can: the text style in the RichEdit control are
change the typeface, style, size, and color DefAttributes, SelAttributes, and Paragraph (see
of individual characters in the text; Figure 1). DefAttributes (which is of type
alter individual paragraph alignment; TTextAttributes) sets or returns the default
facilitate the use of paragraph indents, styles to be applied to text being entered into
numbering, and tabs; the component (i.e. the style that will be
open, save, print, and search the contents applied to text if no other style is applied).
of the control; and SelAttributes (also of type TTextAttributes) sets
drag and drop selected text within the or returns the formatting to be applied to a
component, or to other components or particular selection in the component. As its
word processors (such as Microsoft name implies, the Paragraph property (of type
Word) while retaining the text’s font TParaAttributes) controls the paragraph for-
attributes. matting applied to the current or selected
paragraph(s). The TTextAttributes object
This article will describe how to use the implements the functionality surfaced through
RichEdit component to create a basic word the DefAttributes or SelAttributes properties.
processor. (The RichEdit component is based
on a Windows 95 common control, it is not The following code sets the Style property of
available in Delphi 1.) SelAttributes to fsBold, which means the font
style of the currently selected area in the
Property Description RichEdit component will appear in bold:
DefAttributes Default attributes — Sets or returns RichEdit1.SelAttributes.Style := [fsBold];
the default styles to be applied to text
being entered in the component. Other properties of TTextAttributes enable
SelAttributes Selected attributes — Sets or returns you to set the font, its size, and its color.
formatting to apply to the selected
contents of the component. RichEdit also has two methods for handling
Paragraph Paragraph attributes — Controls the printing and searching: Print and FindText.
paragraph formatting applied to the These methods, combined with standard
current or selected paragraphs. dialog boxes, enable us to quickly build a
Figure 1: The fundamental properties of the RichEdit component. functional example.

11 April 1997 Delphi Informant


Delphi at Work
This event is useful for implementing
changes in the state of, for example,
the font style speedbuttons. In our
sample program, the Style attributes are
retrieved whenever the selection
changes; the style button’s Down prop-
erty and the font name and size are set
accordingly.

Saving documents uses the standard


Windows TSaveDialog and a call to
the SaveToFile method of the Lines
property of RichEdit. Similarly, exist-
ing documents are opened using the
TOpenDialog component and a call to
the LoadFromFile method.

RichEdit saves files in Rich Text


Figure 2: This sample application shows how the RichEdit component can be used to create Format (.RTF), which is compatible
a simple word processor. with virtually all leading word proces-
sors. You also have the option to save
A Working Example the contents as unformatted text, by setting the PlainText
Our sample application uses the power of RichEdit to imple- property to True, whereby RichEdit behaves similar to a
ment much functionality in a few lines of code (see Listing One standard Memo component.
beginning on page 13). Figure 2 shows the sample
application. The Print, Print Setup, and Font dialog boxes are standard
Windows dialog boxes displayed using their Execute methods.
In the toolbar, the Left, Center, and Right paragraph align- Following the display of the Print dialog box, TRichEdit’s
ment speedbuttons set the Paragraph property of RichEdit to Print method is called to carry out the printing.
taLeftJustify, taCenter, and taRightJustify, respectively.
A parameter can be supplied that will be used as the docu-
Similarly, the Bullet speedbutton sets the Numbering ment’s title when displayed in the print queue. Similarly, pro-
property of the TParaAttributes object to nsBullet or nsNone, vided the OK button of the Font dialog box was clicked, the
depending on whether the speedbutton’s Down property is Font property of the Font dialog box component is assigned
set to True or False. Finally, the font name and size combo directly to the SelAttributes property.
boxes set the Name and Size properties of SelAttributes to
their respective values. Cut, Copy, and Paste are implemented by calling the
CutToClipboard, CopyToClipboard, and PasteFromClipboard
The style property of the TTextAttributes (which is of type methods of RichEdit. Undo, however, does not have a cor-
TFontStyles) is a set of TFontStyle. This
allows the structure to contain more
than one value, so, for example, a font
can be bold and italic. You can imple-
ment this by adding or subtracting the
requested font style to the current style:

with RichEdit1.SelAttributes do
Style := Style + [fsBold];

This will make the current selection


bold, without affecting the other attrib-
utes that may have been set. For exam-
ple, if the font were italic, it will now
be bold and italic.

RichEdit contains an event,


OnSelectionChange, which is called
whenever the user changes the selected
text with the keyboard or the mouse. Figure 3: This sample application demonstrates how RichEdit can be made data aware.

12 April 1997 Delphi Informant


Delphi at Work
responding method, and, therefore, cannot be called in this Conclusion
way. Instead, we must send the Windows message Back in the days of MS-DOS, plain text was acceptable, even
WM_UNDO to the control using the SendMessage function. expected. Word processors offered the ability to apply differ-
ent fonts and font styles to the printed output, but only the
The Find facility, which locates specific items of text, is most expensive provided the ability to see the effects on the
achieved differently. The dialog box is displayed using the screen. With Windows, rich text is the norm, and anything
TFindDialog Execute method, but the code that handles less just isn’t good enough.
the searching of items in the RichEdit control is placed in
the Find dialog box’s OnFind method. This method is In this article, I have demonstrated how the powerful
called whenever the user clicks the Find Next button in the RichEdit component can be used as the basis of a simple
Find dialog box. word processor, and, further, how the contents of a RichEdit
component can be easily stored in a database. ∆
The code in the sample application then attempts to
locate the text (that the user entered) using the POS func-
tion. If the text is found, it’s highlighted in the RichEdit The files referenced in this article are available on the Delphi
control; otherwise, an appropriate message is displayed to Informant Works CD located in INFORM\97\APR\DI9704ID.
the user.

This is a basic attempt at creating a useful application using


the RichEdit control. Possible enhancements include adding Ian Davies is a developer of 16- and 32-bit applications for the Inland Revenue
in the UK. He began Windows programming using Visual Basic about four years
a search and replace facility, updating the Find code to find ago, but has seen the light and is now a devout Delphi addict. Current interests
the next occurrence of a particular string, and implementing include Internet and intranet development, inter-application communication, and
a recently used file list. sometimes a combination of the two. Ian can be contacted via e-mail at
106003.3317@compuserve.com.
If you have the confidence to implement the RichEdit
component in your application or expand upon the simple
application provided here, then I have achieved my prima-
ry goal. Begin Listing One — A RichEdit Word Processor
unit CEditForm;
Rich Text in Databases interface
Since version 1, Paradox has provided a way of storing rich
text data in a database, and using it in its forms. This uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
facility is notably lacking from versions 1 and 2 of Delphi; Forms, Dialogs, Buttons, ExtCtrls, StdCtrls,
despite a Formatted Memo field being available through ComCtrls, Menus;
the Database Desktop, the Database Desktop doesn’t pro- type
vide a means of entering or viewing data contained in it. TEditForm = class(TForm)
(Borland has filled the hole, providing a TDBRichEdit RichEdit1: TRichEdit;
Panel1: TPanel;
control in the forthcoming Delphi 3. This is used in the BoldButton: TSpeedButton;
same way as RichEdit, but now has DataSource and ItalicButton: TSpeedButton;
UnderlineButton: TSpeedButton;
DataField properties.) BulletsButton: TSpeedButton;
LeftAlign: TSpeedButton;
RightAlign: TSpeedButton;
Creating a data-aware RichEdit in Delphi 2 is possible by
CenterAlign: TSpeedButton;
reading and writing the contents of a RichEdit in a FontName: TComboBox;
Formatted Memo field using a TBlobField object: FontSize: TComboBox;
StatusBar1: TStatusBar;
MainMenu1: TMainMenu;
Table1.Edit;
File1: TMenuItem;
BlobStream:= Exit1: TMenuItem;
TBlobStream.Create(Table1FormattedMemo,bmWrite); N1: TMenuItem;
RichEdit1.Lines.SaveToStream(BlobStream); PrintSetup1: TMenuItem;
BlobStream.Free; Print1: TMenuItem;
Table1.Post; N2: TMenuItem;
SaveAs1: TMenuItem;
Save1: TMenuItem;
When the BlobField is created (here it’s named N3: TMenuItem;
BlobStream ), the field object that will store the rich text Open1: TMenuItem;
New1: TMenuItem;
is passed as a parameter to the Create method. The PrinterSetupDialog1: TPrinterSetupDialog;
BlobStream object is then passed as a parameter to the PrintDialog1: TPrintDialog;
OpenDialog1: TOpenDialog;
RichEdit LoadToStream and SaveToStream methods, to SaveDialog1: TSaveDialog;
load and save the rich text in the appropriate field of the Edit1: TMenuItem;
current record (see Figure 3). The entire program is shown Find1: TMenuItem;

in Listing Two on page 16.

13 April 1997 Delphi Informant


Delphi at Work

N5: TMenuItem; Application.ExeName);


Paste1: TMenuItem; FName := '';
Copy1: TMenuItem; end;
Cut1: TMenuItem;
N6: TMenuItem; procedure TEditForm.FormActivate(Sender: TObject);
Undo1: TMenuItem; begin
FindDialog1: TFindDialog; //Update the state of the indicators on the toolbar.
Help1: TMenuItem; RichEdit1SelectionChange(Sender);
About1: TMenuItem; end;
HowtoUseHelp1: TMenuItem;
SearchforHelpOn1: TMenuItem; procedure TEditForm.New1Click(Sender: TObject);
Contents1: TMenuItem; begin
ColorDialog1: TColorDialog; //Check if changes have been made and,
Font1: TMenuItem; //if OK to continue, start from scratch.
N4: TMenuItem; if ContinueEvenIfDirty = mrNo then
FontDialog1: TFontDialog; begin
StrikeoutButton: TSpeedButton; RichEdit1.Lines.Clear;
function ContinueEvenIfDirty: Word; FName := '';
procedure FormCreate(Sender: TObject); EditForm.Caption := 'TRichEdit Example';
procedure FormActivate(Sender: TObject); RichEdit1.Modified := False;
procedure New1Click(Sender: TObject); end;
procedure Open1Click(Sender: TObject); end;
procedure Save1Click(Sender: TObject);
procedure SaveAs1Click(Sender: TObject); procedure TEditForm.Open1Click(Sender: TObject);
procedure Print1Click(Sender: TObject); begin
procedure PrintSetup1Click(Sender: TObject); //Check if changes have been made and,
procedure Exit1Click(Sender: TObject); //if OK to continue, load the RTF file.
procedure FormClose(Sender: TObject; if ContinueEvenIfDirty = mrNo then
var Action: TCloseAction); begin
procedure Undo1Click(Sender: TObject); if OpenDialog1.Execute then
procedure Cut1Click(Sender: TObject); begin
procedure Copy1Click(Sender: TObject); try
procedure Paste1Click(Sender: TObject); Screen.Cursor := crHourGlass;
procedure Font1Click(Sender: TObject); Application.ProcessMessages;
procedure Find1Click(Sender: TObject); RichEdit1.Lines.LoadFromFile(OpenDialog1.FileName);
procedure FindDialog1Find(Sender: TObject); FName := ExtractFileName(OpenDialog1.FileName);
procedure RichEdit1SelectionChange(Sender: TObject); EditForm.Caption := 'TRichEdit Example - ' + FName;
procedure BoldButtonClick(Sender: TObject); RichEdit1.Modified := False;
procedure ItalicButtonClick(Sender: TObject); finally
procedure UnderlineButtonClick(Sender: TObject); Screen.Cursor := crDefault;
procedure StrikeoutButtonClick(Sender: TObject); end;
procedure BulletsButtonClick(Sender: TObject); end;
procedure LeftAlignClick(Sender: TObject); end;
procedure FontNameChange(Sender: TObject); end;
procedure FontSizeClick(Sender: TObject);
procedure FontSizeKeyPress(Sender: TObject; procedure TEditForm.Save1Click(Sender: TObject);
var Key: Char); begin
procedure About1Click(Sender: TObject); if FName = '' then
private begin
{ Private declarations } //Saving for the first time.
FName: ShortString; SaveAs1Click(Sender);
public end
{ Public declarations } else
end; begin
//Saved before.
var RichEdit1.Lines.SaveToFile(FName);
EditForm: TEditForm; RichEdit1.Modified := False;
end;
implementation end;

{$R *.DFM} procedure TEditForm.SaveAs1Click(Sender: TObject);


begin
function TEditForm.ContinueEvenIfDirty: Word; //Check if changes have been made and,
begin //if OK to continue, save the file.
Result := mrNo; if SaveDialog1.Execute then
if RichEdit1.Modified then begin
Result := MessageDlg( RichEdit1.Lines.SaveToFile(SaveDialog1.FileName);
'Changes were made. Do you wish to save them?', EditForm.Caption := 'TRichEdit Example - ' +
mtConfirmation, mbYesNoCancel, 0); ExtractFileName(SaveDialog1.FileName);
if Result = mrYes then Save1Click(nil); RichEdit1.Modified := False;
end; end;
end;
procedure TEditForm.FormCreate(Sender: TObject);
begin procedure TEditForm.Print1Click(Sender: TObject);
//Initialise the variables. var
FontName.Clear; loop: Integer;
FontName.Sorted := True; begin
FontName.Items := Screen.Fonts; //Display the print dialog box.
OpenDialog1.InitialDir := ExtractFilePath( if PrintDialog1.Execute then
Application.ExeName); begin
SaveDialog1.InitialDir := ExtractFilePath( //Print the required number of copies.

14 April 1997 Delphi Informant


Delphi at Work

for loop := 1 to PrintDialog1.Copies do end


RichEdit1.Print('TRichEdit Example : ' + FName); else
end; MessageDlg('Text not found', mtInformation, [mbOK], 0);
end; end;

procedure TEditForm.PrintSetup1Click(Sender: TObject); procedure TEditForm.RichEdit1SelectionChange(


begin Sender: TObject);
//Display the print setup dialog box. begin
PrinterSetupDialog1.Execute; //Set the state of the items in the toolbar.
end; BoldButton.Down := fsBold in
RichEdit1.SelAttributes.Style;
procedure TEditForm.Exit1Click(Sender: TObject); ItalicButton.Down := fsItalic in
begin RichEdit1.SelAttributes.Style;
Close; UnderlineButton.Down := fsUnderline in
end; RichEdit1.SelAttributes.Style;
StrikeoutButton.Down := fsStrikeout in
procedure TEditForm.FormClose(Sender: TObject; RichEdit1.SelAttributes.Style;
var Action: TCloseAction);
begin FontSize.Text := IntToStr(RichEdit1.SelAttributes.Size);
//Check if changes have been made and, FontName.ItemIndex:= FontName.Items.IndexOf(
//if OK to continue, free the form. RichEdit1.SelAttributes.Name);
if ContinueEvenIfDirty = mrNo then
Action := caFree with RichEdit1.Paragraph do
else begin
Action := caNone; if Numbering = nsBullet then
end; BulletsButton.Down := True
else
procedure TEditForm.Undo1Click(Sender: TObject); BulletsButton.Down := False;
begin
if ActiveControl is TRichEdit then case Alignment of
begin taLeftJustify: LeftAlign.Down := True;
//Send the WM_UNDO message to the TRichEdit. taRightJustify: RightAlign.Down := True;
SendMessage(ActiveControl.Handle, WM_UNDO, 0, 0); taCenter: CenterAlign.Down := True;
end; end;
end; end;
end;
procedure TEditForm.Cut1Click(Sender: TObject);
begin procedure TEditForm.BoldButtonClick(Sender: TObject);
RichEdit1.CutToClipBoard; begin
end; with RichEdit1.SelAttributes do
if BoldButton.Down then
procedure TEditForm.Copy1Click(Sender: TObject); Style := Style + [fsBold]
begin else
RichEdit1.CopyToClipBoard; Style := Style - [fsBold];
end; end;

procedure TEditForm.Paste1Click(Sender: TObject); procedure TEditForm.ItalicButtonClick(Sender: TObject);


begin begin
RichEdit1.PasteFromClipBoard; with RichEdit1.SelAttributes do
end; if ItalicButton.Down then
Style := Style + [fsItalic]
procedure TEditForm.Font1Click(Sender: TObject); else
begin Style := Style - [fsItalic];
//Set the initial values in the Font end;
//dialog box to the current style.
FontDialog1.Font.Assign(RichEdit1.SelAttributes); procedure TEditForm.UnderlineButtonClick(Sender: TObject);
if FontDialog1.Execute then begin
//Set the current style to the values with RichEdit1.SelAttributes do
//in the Font dialog box.
RichEdit1.SelAttributes.Assign(FontDialog1.Font); if UnderlineButton.Down then
RichEdit1.SetFocus; Style := Style + [fsUnderline]
end; else
Style := Style - [fsUnderline];
procedure TEditForm.Find1Click(Sender: TObject); end;
begin
FindDialog1.Execute; procedure TEditForm.StrikeoutButtonClick(Sender: TObject);
end; begin
with RichEdit1.SelAttributes do
procedure TEditForm.FindDialog1Find(Sender: TObject); if StrikeoutButton.Down then
var Style := Style + [fsStrikeout]
TextPos: integer; else
begin Style := Style - [fsStrikeout];
//Find the first occurrence of the selected text. end;
TextPos := Pos(FindDialog1.FindText, RichEdit1.Text);
//If the text is found .. procedure TEditForm.BulletsButtonClick(Sender: TObject);
if TextPos > 0 then begin
begin if BulletsButton.Down then
// .. highlight it in the RichEdit control. RichEdit1.Paragraph.Numbering := nsBullet
EditForm.BringToFront; else
RichEdit1.SelStart := TextPos - 1; RichEdit1.Paragraph.Numbering := nsNone;
RichEdit1.SelLength := Length(FindDialog1.FindText); end;

15 April 1997 Delphi Informant


Delphi at Work

procedure TEditForm.LeftAlignClick(Sender: TObject); { Private declarations }


begin public
with RichEdit1.Paragraph do { Public declarations }
begin end;
if Sender = LeftAlign then Alignment := taLeftJustify; var
if Sender = CenterAlign then Alignment := taCenter; MainForm: TMainForm;
if Sender = RightAlign then
Alignment := taRightJustify; implementation
end;
end; {$R *.DFM}

procedure TEditForm.FontNameChange(Sender: TObject); procedure TMainForm.FormCreate(Sender: TObject);


begin begin
//Set the font to the name contained Table1.DatabaseName :=
//in the FontName combo box. ExtractFilePath(Application.ExeName);
RichEdit1.SelAttributes.Name := Table1.Active := True;
FontName.Items[FontName.ItemIndex]; end;
end;
procedure TMainForm.FontButtonClick(Sender: TObject);
procedure TEditForm.FontSizeClick(Sender: TObject); begin
begin FontDialog1.Font.Assign(RichEdit1.SelAttributes);
//Set the font size to that contained if FontDialog1.Execute then
//in the FontSize combo box. RichEdit1.SelAttributes.Assign(FontDialog1.Font);
RichEdit1.SelAttributes.Size := StrToInt(FontSize.Text); RichEdit1.SetFocus;
end; end;

procedure TEditForm.FontSizeKeyPress(Sender: TObject; procedure TMainForm.SaveButtonClick(Sender: TObject);


var Key: Char); var
begin BlobStream: TBlobStream;
{ If the user presses the CR key when the cursor is in begin
the font size combobox call the routine to set the Table1.Edit;
font size of the current selection in the RichEdit. } BlobStream:=
if Key = #13 then TBlobStream.Create(Table1FormattedMemo,bmWrite);
begin RichEdit1.Lines.SaveToStream(BlobStream);
FontSizeClick(Sender); BlobStream.Free;
Key := #0; Table1.Post;
end; end;
end;
procedure TMainForm.LoadButtonClick(Sender: TObject);
procedure TEditForm.About1Click(Sender: TObject); var
begin BlobStream: TBlobStream;
MessageDlg('This is where the about box goes', begin
mtInformation, [mbOK], 0); BlobStream :=
end; TBlobStream.Create(Table1FormattedMemo,bmRead);
RichEdit1.Lines.LoadFromStream(BlobStream);
end. BlobStream.Free;
end;

End Listing One end.

Begin Listing Two — Data Aware RichEdit End Listing Two


unit CMainForm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, Mask, DBCtrls, StdCtrls, ComCtrls,
ExtCtrls, DB, DBTables, Buttons;

type
TMainForm = class(TForm)
SaveButton: TButton;
LoadButton: TButton;
DataSource1: TDataSource;
Table1: TTable;
DBNavigator1: TDBNavigator;
RichEdit1: TRichEdit;
DBEdit1: TDBEdit;
FontButton: TButton;
FontDialog1: TFontDialog;
Label1: TLabel;
Table1Header: TStringField;
Table1FormattedMemo: TBlobField;
procedure FormCreate(Sender: TObject);
procedure FontButtonClick(Sender: TObject);
procedure SaveButtonClick(Sender: TObject);
procedure LoadButtonClick(Sender: TObject);
private

16 April 1997 Delphi Informant


OP Tech
Delphi / Object Pascal

By Bill Todd

Array of Tasks
Using the Components Array for a Variety of Jobs

I n the October 1996 Delphi Informant, Robert Vivrette introduced the


Controls and Components arrays (see Vivrette’s article “Parentage and
Ownership”). This article expands on that discussion, and offers three
practical examples of using it to develop generic solutions to common
problems.

Managing Security One way to control a user’s access to pro-


A common necessity in Delphi programs is gram features is to disable all the menu
a system that manages security by requir- choices and buttons a user isn’t allowed to
ing users to log into the program with a use. This isn’t difficult, but does require a
valid user name and password. The user substantial amount of code in each form to
name and password are typically stored in check the user’s security level, then explic-
a security table — a table that also con- itly disable each menu choice and button
tains a security level to control which fea- the user isn’t allowed to use.
tures of the program the user can access.
This process can be simplified by making
the user’s security level an integer. In this
procedure DisableControls(Form: TForm; TagValue: LongInt);
system, larger numbers mean greater
{ Disables any TMenuItem, TButton, or TSpeedButton on the access, and lower numbers represent less
form whose Tag property is greater than the TagValue access. The next step is to assign a security
parameter. The uses clause of the unit that contains this
routine must include StdCtrls and Buttons.
level to each menu choice and button that
must be disabled. Fortunately, the Delphi
Parameters: development team gave every component
Form: The form whose controls are checked.
TagValue: The maximum value of the Tag property that
an unused long integer property, named
will allow the control to remain enabled. } Tag. The Tag property is not used by
var Delphi. It exists only for our use. This
I : Integer;
begin
means that by setting the component’s Tag
with Form do begin property, you can assign the security level
for I := 0 to ComponentCount -1 do to each component that must be disabled.
if (Components[I].Tag > TagValue) then
begin
Assign the levels so a user can’t use any
if Components[I] is TMenuItem then menu item or button whose security level
TMenuItem(Components[I]).Enabled := False is greater than the user’s security level.
else if Components[I] is TButton then
TButton(Components[I]).Enabled := False
Note that because the default value of the
else if Components[I] is TSpeedButton then Tag property is 0 (zero), you don’t need to
TSpeedButton(Components[I]).Enabled := False; change the value of Tag for the compo-
end; { if }
end; { with }
nents available to all users.
end;
The last step in implementing this system
Figure 1: A procedure to disable controls. is to write a routine that disables all menu

17 April 1997 Delphi Informant


OP Tech

procedure RefreshAllTables(Form: TForm);


function AllTablesPosted(Form: TForm): Boolean;
{ Refreshes all active tables on a form. }
{ Checks that all tables on form are in dsBrowse mode. }
var
var
I : Integer;
I : Integer;
begin
begin
with Form do begin
Result := True;
for I := 0 to ComponentCount -1 do
with Form do begin
if Components[I] is TTable then
for I := 0 to ComponentCount -1 do
if (Components[I] as TTable).Active then
if Components[I] is TTable then
if (Components[I] as TTable).State = dsBrowse
if (Components[I] as TTable).Active then
then
if (Components[I] as TTable).State <> dsBrowse then
(Components[I] as TTable).Refresh;
begin
end; { with }
Result := False;
end;
Break;
end; { if }
{ Display error message. } Figure 3: Refreshing all tables on a form.
if Result = False then
MessageDlg('There is an unposted'+ records in TQuery and TStoredProc components as well as
(Components[I] as TTable).Name +'record in the'+
Caption +
TTable.
'form. Please Post or Cancel your changes.',
mtError, [mbOK], 0); You can call this function in a form’s OnCloseQuery event
end; { with }
end;
and prevent the user from closing the form until the
record is posted. You can also call this method in a form’s
Figure 2: A function to warn users of unposted records. OnDeactivate event handler to warn users of unposted
records when they move focus to another form, or in the
items and buttons whose Tag value is greater than the OnChange event handler for a tabbed notebook to warn
user’s security level. The Components array provides the users before they move to another page.
perfect way to do this, as shown in the code in Figure 1.
Refreshing Data
You can call this procedure from the OnCreate event han- The last example of using the Components array ensures that
dler of any form by passing it two parameters. The first is users in a multi-user environment are viewing up-to-date infor-
the form whose controls should be checked. The second is mation. If you or your users are accustomed to using a desktop
the maximum Tag value for controls to remain enabled. database — such as Paradox — that periodically (and automat-
All TMenuItem, TButton, and TSpeedButton objects whose ically) refreshes the user’s view of a table, you may want to
Tag property is greater than the Tag parameter will be dis- implement that same functionality in your Delphi applications.
abled. Note that this also takes care of TBitButton objects, The procedure shown in Figure 3 takes a form as its only para-
because TBitButton is a descendant of TButton. meter, and if the table is active and in browse mode, calls
Refresh to re-read any data changed by another user.
This procedure uses a for loop to iterate through the
Components array, checking each component to determine
if its Tag property is greater than the TagValue parameter. procedure RefreshLinkedTables(Form: TForm);
{ Refreshes all active tables on a form if all
If it is, the component is checked to determine if it’s a tables are in dsBrowse state. }
TMenuItem, TButton, or TSpeedButton. If the component var
is one of these types, or a descendant of one of these types, I : Integer;
OkToRefresh : Boolean;
its Enabled property is set to False. begin
OkToRefresh := True;
Ensuring Records Are Posted with Form do begin
{ Make sure all tables are in dsBrowse state. This
Another problem the Components array can easily resolve prevents refreshing a master when one of its
is how to handle users who forget to post a new or details has an unposted record, since refreshing
changed record before they close a form or move to anoth- the master will post the detail. }
for I := 0 to ComponentCount -1 do
er form. To solve this problem you need a generic routine if Components[I] is TTable then
that will check each Table control and warn the user if its if (Components[I] as TTable).Active then
State property is anything other than dsBrowse. Figure 2 if (Components[I] as TTable).State<>dsBrowse then
OkToRefresh := False;
shows such a function. { Refresh the tables. }
if OkToRefresh then
Again, the code uses a for loop to traverse the for I := 0 to ComponentCount -1 do
if Components[I] is TTable then
Components array, checking each component to determine if (Components[I] as TTable).Active then
if it’s a TTable or one of its descendants. If it is, and the (Components[I] as TTable).Refresh;
table is open and the State property is not set to dsBrowse, end; { with }
end;
the function displays a message to the user and returns
False. You might want to modify this code to test for the
TDataSet type instead of TTable. This will detect unposted Figure 4: Refreshing linked tables.

18 April 1997 Delphi Informant


OP Tech
However, there is a problem with this. If you have a form
that uses linked tables to show a one-to-many relationship,
refreshing the master table will cause any unposted records
in a detail table to post.

To avoid this problem, change the code as shown in Figure 4.


The procedure checks that all tables on the form are in
browse mode before refreshing any table. This prevents unex-
pected posts from occurring.

Conclusion
The Components and Controls arrays are extremely useful
for writing generic routines that must determine the type
and number of controls in a form or any of the container
objects in a form. Using these arrays you can write code
that will work with any form in any of your programs to
manipulate the form’s components in any way you need.

Employing the Components array, we have explored three


practical solutions to common problems. But it’s just the
beginning ... ∆

Bill Todd is President of The Database Group, Inc., a database consulting and
development firm based near Phoenix. He is co-author of Delphi: A Developer’s
Guide [M & T Books, 1995], Delphi 2: A Developer’s Guide [M & T Books,
1996], and Creating Paradox for Windows Applications [New Riders Publishing,
1994], and is a member of Team Borland providing technical support on
CompuServe. He has also been a speaker at every Borland Developers Conference.
He can be reached at (602) 802-0178, on CompuServe at 71333,2146, or on
the Internet at 71333.2146@compuserve.com.

19 April 1997 Delphi Informant


DBNavigator
Delphi 1 / Delphi 2

By Cary Jensen, Ph.D.

Controlling Your Sessions


Using the TSession Class

O ver the past two months, this column has looked at some
components that don’t appear on the Component palette. Two of
them, the Application and Screen variables, are defined in the Forms unit,
and are automatically available to any application that uses this unit.

A third variable, Session, is similar in many This month’s “DBNavigator” takes a look at
respects. This variable, defined in the DB the TSession class, concentrating on the
unit, is automatically created for any applica- Session instance variable. Three issues to
tion that includes this unit. Session is an consider are:
instance variable of the TSession class, and it accessing BDE information,
provides access to properties, methods, and controlling Paradox table-related
events that generally relate to the Borland settings, and
Database Engine (BDE). Consequently, this managing aliases in Delphi 2.
is a particularly useful variable as far as data-
base developers are concerned. While Session is similar to Application and
Screen, it is also different in two important
Property ways:
Active * Non-database applications generally don’t
include the DB unit, and therefore these
ConfigMode * types of applications don’t have access to
DatabaseCount the Session variable. Because Application
and Screen are defined in the Forms unit,
Databases they are available for any application that
Handle includes at least one form, and this
accounts for nearly all Delphi applica-
KeepConnections tions.
Locale Delphi 2 includes a TSession component
on the Data Access page of the
PrivateDir Component palette. Consequently,
Name unlike Application and Screen, which
don’t appear on the Component palette,
NetFileDir at least as far as 32-bit versions of Delphi
Owner are concerned, you can have design-time
access to a TSession component.
Tag
TraceFlags * As mentioned earlier, the TSession class is
useful in database applications, providing
Figure 1: TSession properties general support for aliases and Paradox
(*Delphi 2 only). table-related issues. Figure 1 contains a list

20 April 1997 Delphi Informant


DBNavigator
Method of the TSession properties.
Figures 2 and 3 contain the
AddAlias * TSession methods and event
properties, respectively.
AddPassword
AddStandardAlias Getting Basic BDE
Information
Close
The Session variable provides
CloseDatabase easy access to some of the
more basic types of informa-
DeleteAlias * tion available from the BDE.
DropConnections This includes the available
aliases, tables, stored proce-
FindDatabase dures, and drivers, as well as
Figure 4: The BASIC project displays information
alias and driver parameters. gathered using TSession methods.
GetAliasDriverName *
For example, using the
GetAliasNames method GetAliasNames, you Only slightly more complex is the GetAliasParams method.
can easily populate a This method, which has the following syntax, empties a spec-
GetAliasParams TStrings object with the list ified TStrings object and populates it with the parameters of a
GetConfigParams * of available aliases. Likewise, specified alias:
using GetTableNames, you
GetDatabaseNames can obtain a list of the tables procedure GetAliasParams(const AliasName: string;
List: TStrings);
stored in a given alias.
GetDriverNames
The GetTableNames method has the following syntax:
GetDriverParams The project BASIC.DPR,
procedure GetTableNames(const DatabaseName, Pattern: string;
shown in Figure 4, demon- Extensions, SystemTables: Boolean; List: TStrings);
GetPassword strates some of the methods
GetStoredProcNames that provide your application The first parameter, DatabaseName, is the name of the alias
with basic BDE information. that points to the table location, while the second parameter
GetTableNames This project uses threeTSession allows you to specify a pattern for the inclusion of table
methods: GetAliasNames, names. If you want to include all table names, use the pat-
IsAlias *
GetTableNames, and tern *.*. The third and fourth parameters are Boolean val-
ModifyAlias * GetAliasParams. ues that enable you to choose whether to include extensions
in the listed table names, and whether to include the system
Open * The simplest of these meth- tables (SQL databases only), respectively. The final parame-
OpenDatabase ods is GetAliasNames, which ter is the name of the TStrings object that will be emptied,
has the following syntax: then populated with the table names as specified with the
RemoveAllPasswords first four parameters.
procedure
RemovePassword GetAliasNames(List:
TStrings);
The code in Figure 5 is associated with the OnCreate event
SaveConfigFile * handler for the main form of the BASIC project, as well as
Figure 2: TSession methods When you call this method, the OnChange event handler for the list box that displays
(*Delphi 2 only). the TStrings object is first the aliases (ListBox1). Note that for this code to work prop-
emptied, then populated erly, it was necessary to add the DB unit to this project’s
Events with a list of aliases. In uses clause; this needed to be done manually, because the
Delphi 1, this list includes project didn’t include data-aware components. If at least
OnPassword one data-access component (such as a DataSource or a
only those aliases defined in
OnStartup * the BDE configuration file, Database) appeared on the form, the DB unit would have
IDAPI.CFG. If you want to automatically been added to the unit’s interface uses clause.
Figure 3: TSession event
properties (*Delphi 2 only). list local aliases, those
defined by Database compo- Other TSession methods that provide basic BDE information
nents, you must use the include GetAliasDriverName (Delphi 2 only),
GetDatabaseNames method. In Delphi 2, the aliases included GetConfigParams (Delphi 2 only), GetDatabaseNames,
in the list are defined by the TSession property ConfigMode. GetDriverNames, GetDriverParams, and GetStoredProcNames.
This property has three possible values: cmPersistent (only
those defined by IDAPI32.CFG), cmSession (only those Controlling Paradox Table-Related Settings
defined by Database components using the TSession compo- For most local applications (those in which the data are
nent), and cmAll (both persistent and local aliases). stored in tables on a local hard disk or a local area net-

21 April 1997 Delphi Informant


DBNavigator

procedure TForm1.FormCreate(Sender: TObject);


begin
// Get the alias list.
Session.GetAliasNames(ListBox1.Items);
// Select the first alias in the list.
ListBox1.ItemIndex := 0;
// Call the OnChange event handler for ListBox1.
ListBox1Click(Sender);
end;

procedure TForm1.ListBox1Click(Sender: TObject);


begin
// Display the tables in the selected alias.
Session.GetTableNames(ListBox1.Items[ListBox1.ItemIndex],
*.*',True,True,ListBox2.Items);
Label2.Caption := 'Tables in alias ' +
ListBox1.Items[ListBox1.ItemIndex];
// Display the parameters of the selected alias. Figure 6: The BDE Configuration Utility permits you to set the NET
Session.GetAliasParams(ListBox1.Items[ListBox1.ItemIndex], DIR parameter for the Paradox driver.
ListBox3.Items);
end; user must have read and write access to this directory.
The NetFileDir property specifically points to a directory
Figure 5: TSession methods. in which the BDE writes a file called the network control
file. This file, named PDOXUSER.NET, identifies each
work), the Paradox table format is the preferred format for session that can access shared Paradox tables. (Every BDE-
several reasons. enabled application that has at least one session and some
applications, such as those written in Delphi 2 that use
First, the Paradox table format has the richest set of field additional TSession components from the Data Access page
types on the desktop. The available field types range from of the Component palette, may have more than one.)
DateTime to Currency, from BLOb (Binary Large Object) to
Autoincrement, and from Graphic to BCD (binary coded If any user attempts to access a Paradox table that is currently
decimal). being accessed by one or more users, and all users don’t use
the same network control file, bad things happen. The most
Second, Paradox tables support high-performance indexes common is that one or more of the sessions crash. For this
that provide quick filtering (using ranges) and sorting, as reason, the NetFileDir is an important property.
well as table and record-level locking.
In most applications, and in particular, Delphi 1 applica-
Finally, the Paradox table format is the default format for a tions, the NetFileDir property isn’t set by the application.
number of BDE operations, including temporary files for Specifically, it’s a parameter of the Paradox driver, and it’s
local SQL queries (SQL queries not run on a remote data- set when the BDE is installed. Figure 6 displays the
base server) and error tables for failed BatchMove opera- Drivers page of the BDE Configuration Utility with the
tions. Paradox driver selected. Notice the NET DIR parameter
on the right-hand pane of this window. This is where this
A number of TSession properties, methods, and events are property is typically set. If you must set this property at
directly related to the use of Paradox tables — specifically, run time, it must be set prior to activating any Databases
the PrivateDir, and NetFileDir properties, the or DataSets. In other words, no form that is auto-created
AddPassword, GetPassword, RemovePassword, and can contain active Tables, Queries, StoredProcs, or
RemoveAllPasswords methods, and the OnPassword event. Databases. Furthermore, before making any one of these
(The next section will begin by considering the NetFileDir components active, the NET DIR property of the Session
and PrivateDir properties. The password-related issues are component must be set to a common network directory.
covered in a later section.)
The second critical property when Paradox tables are being
The NetFileDir and PrivateDir Properties used is the PrivateDir property. In short, every session
If you use Paradox tables, or BDE operations that rely on must use a different private directory. It’s into this directo-
Paradox tables, you need to at least be aware of the ry that the BDE will create temporary tables, as needed.
NetFileDir and PrivateDir properties of the Session vari- For example, if you execute a query against local tables, or
able. For most applications, two rules apply to these prop- execute heterogeneous queries (ones that involve both local
erties. The first rule is that every user who can access and remote tables, or one that involves tables from two or
shared Paradox tables (tables that can be read or written to more remote servers), the BDE must write temporary
by more than one user at a time) must all have their tables that it will ultimately delete. To ensure that such a
Session’s NetFileDir property point to the same physical query being executed by one user is unaffected by queries
directory. In other words, this directory must be a shared being executed by another user, the BDE writes these to
directory (one that is on the network). In addition, each the private directory of the session.

22 April 1997 Delphi Informant


DBNavigator
By default, the private directory of the Session variable is set box asking the user to enter a valid password. In addition, the
to the directory in which the executable (.EXE) file resides. TSession class provides a number of methods for adding and
As long as each user of a multi-user application is running a removing passwords at run time. To add a password, your appli-
local version of the .EXE, that is, the .EXE resides on his/her cation can call AddPassword using the following syntax:
hard drive in an un-shared directory, the default private direc-
procedure AddPassword(const Password: string);
tory should work fine (assuming that every BDE-aware appli-
cation on a user’s machine is stored in a separate directory, You can also invoke the default password dialog box provided
and that only one copy of an application can run at a time). by Delphi by calling the GetPassword method. When you call
this function:
However, when the .EXE is stored on a shared drive, and
function GetPassword: Boolean;
run by each user from that drive, your code must assign a
unique private directory before any Databases or DataSets
Delphi displays the dialog box shown in Figure 7. As you can
are activated. As with the NetFileDir property, this means
see, one or more passwords can be removed using the default
that no auto-created forms can contain activated
password dialog box. Alternatively, you can remove a password
DataSources or DataSets. Furthermore, prior to activating
through code by calling RemovePassword. You can remove all
the first Database or DataSet of the application, your code
passwords from a session in a single call to RemoveAllPasswords.
should set the TSession property PrivateDir.
The following is the syntax of these methods:
The following code demonstrates one example of how this procedure RemovePassword(const Password: string);

can be accomplished: procedure RemoveAllPasswords;

procedure TForm1.FormCreate(Sender: TObject);


begin When the BDE is provided a password, either through a call
if not DirectoryExists('c:\priv') then to the appropriate TSession method or through the default
if not CreateDir('c:\priv') then
raise Exception.Create('Cannot create c:\priv. '+ password dialog box, that password remains in effect until the
'Create this directory and retry'); session is terminated, or the password is explicitly removed.
Session.PrivateDir := 'c:\priv';
end;
Also, the password is used by the BDE specifically to open a
table. Removing a password after a table has been opened has
This code, which appears in the OnCreate event handler for the no effect on the access to that table. That is, if you open a
application’s main form, assumes that no Databases or DataSets table, then remove its password, you can continue to access
are active on this form. Furthermore, it’s assumed that the main that table as long as you don’t close it. If you close an encrypt-
form is the only auto-created form in the application (every other ed table whose password has been removed, you must re-enter
form will be created as needed, and presumably released when no the password before being permitted to open it again.
longer needed, but this second part is not critical to the issue of
private directories). One final assumption is that each user is run- Use of the various password-related techniques is demonstrat-
ning on a machine with a hard drive whose drive letter is C. ed by the PASSWORD.DPR project (see Figure 8). This pro-
ject uses an encrypted Paradox table named COMPS.DB.
Using Encrypted Paradox Tables This table has a master password (required), as well as a sec-
If you are using tables from a remote database server, you must ondary password (optional). An encrypted Paradox table has
supply a valid user name and password prior to accessing those one master password, but can support any number of sec-
tables. These two pieces of information can either be assigned to ondary passwords. Normally, only the owner of the table
the Params property of a Database component or automatically knows the master password. The secondary passwords are pro-
be requested by the BDE, by way of the Database Login dialog vided to users, and they provide varying levels of access to a
box, when the application first attempts to access the server. table. The master password for COMPS.DB is the string
“MASTER”, and the secondary password is “SECONDARY”.
If your application uses an encrypted Paradox table, a valid pass- All Paradox table passwords are case-sensitive. (For more
word is also required before the table can be accessed for the first information on Paradox passwords, refer to the online Help in
time by a user in a session. Unlike remote servers, however, the Database Desktop or Paradox for Windows.)
Paradox tables don’t use a Database component to supply their
passwords. Figure 9 shows the event handlers associated with the OnClick
events for the buttons on the PASSWORD project’s main form.
Instead, passwords are supported through the TSession This code demonstrates calls to the various password-related
class. (Paradox tables can be encrypted using either Paradox methods of the TSession class. Note that if you attempt to use
for Windows or the Database Desktop, by creating or the Open Table button without first supplying a password, the
restructuring a table, then selecting Password Security from default password dialog box is automatically displayed.
the Table Properties drop-down list.)
Also related to the issue of passwords is the OnPassword event
Similar to when your application uses tables from a remote property of the TSession class. Use this property if you want
database server, the BDE can also automatically display a dialog to replace the default password dialog box with one of your

23 April 1997 Delphi Informant


DBNavigator
own. Specifically, the event handler you assign to OnPassword
will be called if you call the GetPassword method, or attempt
to activate an encrypted Paradox table for which no password
has been issued.

If you use this method, however, you should note that


Delphi 1 declares OnPassword to be a procedure pointer, not
a method pointer. Specifically, OnPassword is a procedure in
Delphi 1. Delphi 2 correctly declares OnPassword as a
method pointer, meaning that you assign a method to this
property. (For more information regarding OnPassword, use
Delphi’s online Help.)
Figure 7: The default password dialog box for encrypted Paradox
tables can be displayed automatically when an application
Sessions and Delphi 2 attempts to activate an encrypted Paradox table for which no pass-
There are two significant differences between the TSession word has yet been provided, or through a call to the TSession
class declared in Delphi 1 and that declared in Delphi 2. method GetPassword.
The first is that there are more properties, methods, and
one more event property in Delphi 2. Many of these procedure TDataModule2.Session1Startup(Sender: TObject);
enhancements provide the ability to control aliases from begin
your Delphi 2 applications using a Session, rather than if not DirectoryExists('c:\priv\thread1') then
relying on direct calls to the BDE. The second is that if not FileCtrl.ForceDirectories('c:\priv\thread1') then
raise Exception.Create('Cannot create new session');
Delphi includes a Session component on the Data Access
Session1.PrivateDir := 'c:\priv\thread1';
page of the Component palette. This component permits Session1.NetFileDir := Session.NetFileDir;
you to create and manage sessions in addition to the end;
default session created by Delphi.
This code assumes that no other thread, application, or
Using Additional Sessions user is using the directory C:\PRIV\THREAD1 as its pri-
There is only one use for a manually-placed Session com- vate directory. Notice also that this code uses the
ponent. It’s provided to support multithreaded access to ForceDirectories procedure (declared in the FileCtrl unit)
DataSets. In a multithreaded application, each thread to create the private directory. This procedure creates nest-
must access DataSets using a different session. Quite sim- ed directories, even when the nested directory’s parent
ply, doing this permits the BDE to manage each thread’s directory doesn’t exist. Obviously, a unit using this proce-
access to an application’s DataSets as if the access was by dure must include the FileCtrl unit in one of its uses
different users. In other words, multithreaded access to clauses.
data uses the same techniques for managing competition
for resources as does a multi-user application. Managing Aliases in Delphi 2
In Delphi 1, the only way to create a new alias and add it
As you learned earlier, multi-user access to Paradox tables permanently to the IDAPI.CFG configuration file is to use
involves two critical properties of the TSession class: BDE calls. Importantly, these calls must be made before
NetFileDir and PrivateDir. When using additional session initializing the default session. This means the BDE needs
components and Paradox tables, the same rules that apply to to be manually initialized, updated, then un-initialized,
these properties must be observed. Specifically, all sessions from an initialization section of a unit. Otherwise, the
must use the same network file directory and different private attempt to add the alias fails.
directories.
Creating new aliases at run time is significantly easier with
There is a special event property declared in the TSession Delphi 2 due to five new TSession methods: AddAlias,
class in Delphi 2 to manage the assignment of values to DeleteAlias, IsAlias, ModifyAlias, and SaveConfigFile. In addi-
these two properties: OnStartup. This event handler is trig- tion, the new property ConfigMode plays an important role in
gered immediately before a session becomes active. In this alias control.
event handler, you should assign the current session’s
NetFileDir property to that associated with the default ses- As you recall from earlier in this article, ConfigMode has
sion, and assign the PrivateDir property to one that is dif- three possible values: cmLocal, cmPersistent, and cmAll. If
ferent from the default session, as well as different from any you set ConfigMode to cmAll or cmPersistent, any call to
other session’s PrivateDir property. AddAlias creates an alias that can be written to the BDE
configuration file (IDAPI32.CFG). However, simply calling
The following code demonstrates the use of the OnStartup AddAlias creates the alias, but doesn’t perform the save. To
event handler: save a new alias that you create, you must call

24 April 1997 Delphi Informant


DBNavigator

Figure 10: The ALIAS project demonstrates how to use the Delphi 2
Figure 8: The PASSWORD project demonstrates the use of the TSession class to create, update, and delete aliases at run time.
various TSession methods for providing and removing passwords for
encrypted Paradox tables.
The project named ALIAS.DPR demonstrates the use of
procedure TForm1.OpenTableClick(Sender: TObject); these methods. This project, shown in Figure 10, includes
begin buttons that permit you to add a new alias to your BDE
if Table1.Active then
begin configuration file, remove it, and modify its PATH para-
Table1.Close; meter. The code associated with this project is shown in
Button1.Caption := 'Open Table';
end Listing Three.
else
begin
Table1.Open; Conclusion
Button1.Caption := 'Close Table';
end;
The TSession class and its instance variable Session provide your
end; database applications with access to some of the features of the
procedure TForm1.AddPasswordClick(Sender: TObject);
BDE, without requiring you to resort to low-level BDE calls.
begin Furthermore, while some of the properties and methods of this
Session.AddPassword(InputBox(
'Enter MASTER or SECONDARY','Password','MASTER'));
class are essential in multi-user applications, others are useful in
end; the inspection and management of aliases, tables, and drivers. ∆
procedure TForm1.RemovePasswordClick(Sender: TObject);
begin The files referenced in this article are available on the Delphi
Session.RemovePassword(InputBox(
'Enter MASTER or SECONDARY','Password','MASTER')); Informant Works CD located in INFORM\97\APR\DI9704CJ.
end;

procedure TForm1.RemoveAllPasswordsClick(Sender:
TObject); Cary Jensen is President of Jensen Data Systems, Inc., a Houston-based database
begin
Session.RemoveAllPasswords; development company. He is author of more than a dozen books, including
end; Delphi In Depth (Osborne/McGraw-Hill, 1996). Cary is also a Contributing Editor
procedure TForm1.CallGetPasswordClick(Sender: TObject);
of Delphi Informant, as well as a member of the Delphi Advisory Board for the
begin 1997 Borland Developers Conference. For information concerning Jensen Data
Session.GetPassword; Systems’ Delphi consulting and training services, visit the Jensen Data Systems
end;
Web site at http://gramercy.ios.com/~jdsi. You can also reach Jensen Data
Systems at (281) 359-3311, or via e-mail at cjensen@compuserve.com.
Figure 9: Calls to the various password-related methods of the
TSessions class.

SaveConfigFile. Following is the syntax of the AddAlias and


SaveConfigFile methods: Begin Listing Three — ALIAS.DPR
procedure AddAlias(const Name, Driver: string; List: procedure TForm1.FormCreate(Sender: TObject);
TStrings); begin
// Set the ConfigMode to display all aliases.
procedure SaveConfigFile; Session.ConfigMode := cmAll;
// Populate the Alias list and Alias parameter list boxes.
You can use IsAlias to determine if a particular alias already Session.GetAliasNames(ListBox1.Items);
exists, DeleteAlias to remove one, and ModifyAlias to change ListBox1.ItemIndex := 0;
ListBox1Click(Sender);
one or more of an existing alias’ parameters. Here is the syn-
end;
tax of these three methods:
procedure TForm1.CreateAlias(Sender: TObject);
function IsAlias(const Name: string): Boolean;
var
procedure DeleteAlias(const Name: string); AParams: TStringList;
begin
procedure ModifyAlias(Name: string; List: TStrings);

25 April 1997 Delphi Informant


DBNavigator

if not Session.IsAlias('Informant') then ShowMessage('Alias Informant does not exist');


begin Exit;
AParams := TStringList.Create; end;
try AParams := TStringList.Create;
AParams.Add('PATH=' + try
ExtractFilePath(Application.ExeName)); // Get the current PATH paramter.
Session.AddAlias('Informant','STANDARD',AParams); Session.GetAliasParams('Informant',AParams);
Session.SaveConfigFile; Dir := copy(AParams.Strings[0],6,255);
finally // Get the new PATH parameter.
AParams.Free; if InputQuery('Informant Alias Path','Path:',Dir) then
end; begin
// Update the alias parameters.
// Update the alias list. AParams.Clear;
Session.GetAliasNames(ListBox1.Items); AParams.Add('PATH=' + Dir);
end Session.ModifyAlias('Informant',AParams);
else Session.SaveConfigFile;
ShowMessage('Alias Informant already defined'); // Update the alias parameters list box.
end; ListBox1Click(Sender);
end;
procedure TForm1.DeleteAlias(Sender: TObject); finally
begin AParams.Free;
if Session.IsAlias('Informant') then end;
begin end;
Session.DeleteAlias('Informant');
Session.SaveConfigFile; procedure TForm1.ListBox1Click(Sender: TObject);
// Update the alias list. begin
Session.GetAliasNames(ListBox1.Items); // Update the alias parameters list box.
end Session.GetAliasParams(ListBox1.Items[ListBox1.ItemIndex],
else ListBox2.Items);
ShowMessage('Alias Informant not defined'); end;
end;

procedure TForm1.ChangeAlias(Sender: TObject);


var End Listing Three
AParams: TStringList;
Dir: string;
begin
if not Session.IsAlias('Informant') then
begin

26 April 1997 Delphi Informant


Sights & Sounds
Object Pascal / Delphi 2

By Peter Dove and Don Peer

Getting DIBs on Speed


Delphi Graphics Programming: Part IV

I t’s time to speed things up! This month, we expand on the texture-
mapping theories explained last month, enable our TGMP component to
perform full shading of texture-mapped objects, and make faster 3D
graphics a reality. We’ll give TGMP a boost in speed by creating a device-
independent bitmap (DIB) class capable of handling its drawing routines,
screen-clearing routines, and related procedures. And we’ll discover a little
more about pointers and bit manipulation.

On the raw, object side of the component, component that you drop on a form; rather,
we’ll enhance TGMP to process extremely it’s meant to supplement other components
accurate graphic primitives through the use of such as TGMP, and will be declared accord-
a custom file reader. To close out the article, ingly in the uses clause. However, before we
we’ll explain and implement the world coordi- delve too deeply into class development, a
nate system. An example of the graphics made small explanation of DIBs might be useful.
possible by the information presented in this
jam-packed article is shown in Figure 1. A DIB is what a .BMP file becomes on
your disk; it’s independent of any particular
The DIB Class device (such as a printer or monitor) and
To start, we’ll create a DIB class derived carries all the information that any device
from TObject. We’ll take this approach will need to display it. A DIB holds color
because the class isn’t meant to serve as a information, such as palette and bit depth,
along with file size information and any
compression algorithms used.

For our purposes, we want to create a DIB in


memory, rather than load it from disk. To
accomplish this, we’ll use a Windows API func-
tion called CreateDIBSection which takes the
following arguments:
a handle of a device context
a variable of TBitmapInfo (described later)
the constant DIB_RGB_COLORS or
DIB_PAL_COLORS indicating the type
of color data
a variable to receive a pointer to the
bitmap’s bit values
an optional handle to a file
mapping object (which we’ll set to nil)
an offset to the bitmap bit values within
the file-mappingobject (which we’ll set to 0)

To call CreateDIBSection, which will create


Figure 1: Fully-shaded texture-mapped objects. a DIB in memory, we need to understand

27 April 1997 Delphi Informant


Sights & Sounds
TDIB16bit = class(TObject) Thus, in Object Pascal:
private
{ Private declarations } FBHeader.bmiHeader.biSizeImage :=
FDIBHandle : HBitmap; ((((16*FBHeader.bmiHeader.biWidth)+31) div 32)*4)*Height;
FBheader : TBitmapInfo;
FPointerToBitmap : Pointer; FScanWidth is stored so we won’t have to recalculate it:
FScanWidth : Integer;
FDeviceContext : HDC; FScanWidth :=
protected (((FBHeader.bmiHeader.biWidth * 16)+ 31) div 32) * 4;
{ Protected declarations }
public The following are unimportant for us, and can safely be set
{ Public declarations }
to zero:
procedure FlipBackPage(DeviceContext : HDC);
procedure SetPixel(X, Y : Integer; Color : Word);
FBHeader.bmiHeader.biXPelsPerMeter := 0;
procedure ClearBackPage(Color : Word);
FBHeader.bmiHeader.biYPelsPerMeter := 0;
procedure DrawHorizontalLine(Y, X1, X2 : Integer;
FBHeader.bmiHeader.biclrUsed := 0;
Color : Word) ;
FBHeader.bmiHeader.biclrImportant := 0;
function GetHandle : HBitmap;
constructor Create(Height, Width : Integer);
destructor Destroy; override; biSize is the size of the bmiHeader structure within the
end; TBitmapInfo structure:
Figure 2: The declaration of the TDIB16bit class. FBheader.bmiHeader.biSize := 40;

the TBitmapInfo type. This is best explained by following the The bmiColors are unimportant for the moment, because
creation of our TDIB16bit class. The declaration for the class they relate only to 256-color bitmaps:
is shown in Figure 2.
FBHeader.bmiColors[0].rgbRed := 255;
FBHeader.bmiColors[0].rgbBlue := 255;
FDIBHandle is the DIB handle returned by the FBHeader.bmiColors[0].rgbGreen := 255;
FBHeader.bmiColors[0].rgbReserved := 255;
CreateDIBSection function. FBHeader is the TBitmapInfo
variable mentioned previously. We also must pass
We must also create a device context that we can supply to
FPointerToBitmap, another parameter pointing to our DIB
CreateDIBSection. A device context allows the DIB to be
pixel data, to CreateDIBSection. FScanWidth is the width of
drawn on by other GDI (graphics device interface) objects,
each line in bytes. FDeviceContext is the device context with
such as brushes and fonts. Supplying 0 as an argument to
which the bitmap is associated. We’ll follow the Create con-
CreateCompataibleDC provides a device context compatible
structor of the TDIB16bit class line-by-line. The constructor
with the current screen.
accepts the Width and Height of the required DIB:
constructor TDIB16bit.Create(Height, Width : Integer); Passing the device context to CreateDIBSection with all the
other parameters returns a handle to the bitmap, and tells us
And calls the inherited Create method: the location of the memory associated with the bitmap by
inherited Create;
assigning an address to FPointerToBitmap. You can also sup-
ply the bitmap handle to a TBitmap. This is useful if you
The biWidth and biHeight of FBHeader are obvious; they want to harness TBitmap’s ability to save to disk:
specify the Width and Height:
FDeviceContext := CreateCompatibleDC(0);
FDIBHandle := CreateDIBSection(FDeviceContext,FBHeader,
FBHeader.bmiHeader.biWidth := Width;
DIB_RGB_COLORS,PointerToBitmap,nil,0);
FBHeader.bmiHeader.biHeight := Height;

biPlanes specifies the number of planes for the target device. A Pointed Discussion
This is always set to 1: Now let’s turn to the procedures and functions for the class.
The first procedure, FlipBackPage, accepts a device context as
FBHeader.bmiHeader.biplanes := 1;
a parameter:
biBitCount specifies the color depth, and biCompression is set procedure TDIB16bit.FlipBackPage(DeviceContext : HDC);
to BI_RGB, which means “no compression”: begin
StretchDIBits(DeviceContext, 0, 0,
FBHeader.bmiHeader.biBitCount := 16; FBHeader.bmiHeader.biwidth,
FBHeader.bmiHeader.biCompression := BI_RGB; FBHeader.bmiHeader.biheight, 0, 0,
FBHeader.bmiHeader.biwidth,
The image size is dependent on the width of a line. A bitmap FBHeader.bmiHeader.biheight,
FPointerToBitmap, FBheader,
scanline must end on a double-word boundary. For instance, DIB_RGB_COLORS, SRCCOPY);
if the 16-bit bitmap were 31 pixels wide, the scanline would end;
hold enough space for 32 pixels, although the last pixel
wouldn’t be used. The formula to find the right width is: The device context could be the handle of a TCanvas
object, for instance. In fact, this is exactly what we’ll send
((bit depth * width) + 31) / 2) * 4 from our main TGMP class. The API call used in this pro-

28 April 1997 Delphi Informant


Sights & Sounds
cedure is StretchDIBits. It accepts a device context, the The GetHandle function is just one line of code that returns a
left, top, width, and height of the source bitmap, and the handle to the DIB:
same parameters for the destination area. It also takes
TBitmapInfo, which indicates the bitmap’s type; and the function TDIB16bit.GetHandle : HBitmap;
begin
copy mode, in this case SRCCOPY, indicating a straight Result := FDIBHandle;
bit-for-bit copy. end;

The ClearBackPage procedure does exactly that; it accepts a The next procedure, SetPixel, accepts an X and Y position,
color as a 16-bit word, and clears the back buffer: and color to set the pixel:

procedure TDIB16bit.ClearBackPage(Color : Word); procedure TDIB16bit.SetPixel(X, Y : Integer; Color : Word);


var var
X : Integer; BasePointer : ^Word;
BasePointer : ^Word; begin
begin BasePointer := Pointer(Integer(FPointerToBitmap) +
BasePointer := FPointerToBitmap;
(Y * FScanWidth) + (X * 2));
for X := 0 to ((FScanWidth div 2) *
BasePointer^ := Color;
(FBHeader.bmiHeader.biHeight))-1 do begin
end;
BasePointer^ := Color;
Inc(BasePointer);
end; Again, we declare BasePointer, but this time we calculate the
end; memory address of the x,y coordinates. To do this, we take
the FPointerToBitmap as the start address, then add
We declare a pointer to a Word value, then assign
(Y * FScanWidth) to get to the right line, then finally add
FPointerToBitmap to it. The X loop counter is worked out (X * 2). X is multiplied by two, because there are two bytes
by taking the number of bytes in a scanline and dividing it for every pixel, and memory addresses are measured one byte
by two, because there are two bytes in a word. The calcu- at a time. Then we assign the value of the Color variable to
lated value is then multiplied by the height of the DIB. the address in memory to which BasePointer points.

To clarify, a pointer is a variable that holds an address to a The DrawHorizontalLine procedure works in a similar fashion.
position in memory. So if we were to look at the contents It calculates the starting position of the horizontal line in mem-
of FPointerToBitmap, we would find a 32-bit number indi- ory, and loops through by a count of the parameters (X2 - X1):
cating the memory address at which the bitmap begins.
procedure TDIB16bit.DrawHorizontalLine (Y, X1, X2 : Integer;
BasePointer is assigned a color and incremented within the Color : Word);
var
loop body. Notice that the BasePointer variable is followed
X : Integer;
by a caret character (^), the Object Pascal pointer symbol. BasePointer : ^Word;
This tells the compiler to assign the value of Color to the begin
Integer(BasePointer) := Integer(FPointerToBitmap) +
address referenced by BasePointer, not to the BasePointer vari- (Y*FScanWidth) + (X1*2);
able itself. BasePointer is incremented on the next line using for X := 0 to (X2 - X1) do begin
BasePointer^ := Color;
the Inc procedure. A pointer is incremented by the size of the Inc(BasePointer);
type that it references. In this case, the pointer is incremented end;
end;
by two bytes (a word); in other words, the pointer moves
along the DIB memory by two bytes.
To close out our DIB class, the destructor deletes the DIB
If you’re still a little stumped with pointers, imagine that and its associated resources:
you are handed a cinema ticket with your seat number on destructor TDIB16bit.Destroy;
it. The ticket will point you to the place in the cinema begin
DeleteObject(FDIBHandle);
where you are meant to sit. If you think of a pointer as
DeleteDC(FDeviceContext);
your cinema ticket, it merely references where in memory inherited;
you want to go. The ticket isn’t the seat, but points to it. end;

It does this by using the Windows API call, DeleteObject,


which takes the object’s handle as a parameter. It then deletes
the device context we’ve held for the DIB using the API func-
tion, DeleteDC. Finally, the destructor calls the inherited
Destroy method, ensuring that any code in the class’ ancestor
destructor will be executed.

Figure 3: Storing a color in a Word variable (16-bit color


Color Crunching
mode). Red, green, and blue each get five bits; the highest order You’ve probably noticed that many procedures in the DIB
bit is unused. class take a Word value (i.e. a two-byte unsigned integer)

29 April 1997 Delphi Informant


Sights & Sounds
function TGMP.CalculateRGBWord(Color : TColor) : Word; procedure TGMP.CalcIntensityLUT;
var var
Calc : Single; X, Y: Integer;
UpIncrement, DownIncrement : Single;
R, G, B : Integer;
begin
begin
{ Find possible R, G, or B Values — 0 to 31. }
{ GetRValue, GetGValue, and GetBValue return a value
for X := 0 to 31 do begin
based on the color scale of 0 to 255. }
{ The up increment is from the initial
{ Gets the red value and rescale it from a 1/256 number color value to its brightest. }
to a 0/31 number. } UpIncrement := (31 - X) / 16;
Calc := GetRValue(Color) / 2.56; { The down increment is from the initial
Calc := Calc * 0.31; color value to its darkest. }
R := Round(Calc); DownIncrement := X / 15;
{ Get the green value and rescale it from a 1/256 number { Loops through from color 0 to color 15,
to a 0/31 number. } using DownIncrement. }
Calc := GetGValue(Color) / 2.56; for Y := 0 to 15 do
Calc := Calc * 0.31; IntensityLUT[X, Y] := Round(DownIncrement * Y);
G := Round(Calc); { Loops from color 16 to color 31, using UpIncrement.
{ Get the blue value and rescale it from a 1/256 number }
to a 0/31 number. } for Y := 1 to 16 do
Calc := GetBValue(Color) / 2.56; IntensityLUT[X, Y + 15] :=
Round((DownIncrement * 15) + (UpIncrement * Y));
Calc := Calc * 0.31;
end;
B := Round(Calc);
end;
{ B Value is last five bits. Leave it alone. }
{ R is shifted left 10 bits to sit at position 15-11.} Figure 6: The CalcIntensityLUT procedure works out the lookup
R := R shl 10;
table.
{ G is shifted left 5 bits to sit at position 10..6.}
G := G shl 5;
{ Add them together. }
Result := R + G + B;
Shades of Change
end; In our last article, we explained the methodology behind the
texture mapping of polygons. To accommodate texture shad-
ing and the new DIB class, we’ll need to expand the current
TPolygon = record
{ Allow only 4-point polygons. }
TPolygon and TObject3D records (see Figure 5). In TPolygon,
Point : array [0..3] of TPoint3D; we store an Intensity value, which is the light intensity against
NumberPoints : Integer; { Number of points in polygon. } that polygon. We use this with a texel (textured pixel) to
Visible : Boolean; { Visibility of polygon; determined
by RemoveBackfacesAndShade. }
determine the texel’s correct shade. An extra line has been
AverageZ : Single; { For Z Sorting. } inserted into the RemoveBackfacesAndShade procedure. It’s
PolyColor : TColor; { Color of the polygon. } shown here above an existing line:
DibColor : Word; { 16-bit value of PolyColor. }
Intensity : Byte; { Light intensity. } AnObject.PolyStore[CurrentPoly].Intensity :=
end; Round(Intensity); { Line to add }
AnObject.PolyStore[CurrentPoly].PolyColor :=
TObject3D = record RGB(R, G, B); { Existing line }
{ Stores local coordinates of the object's polygons. }
PolyStore : array [0..MAXPOLYS] of TPolygon;
{ Stores world coordinates of the object's polygons. } Next, for reasons of speed, we need to set up a two-
PolyWorld : array [0..MAXPOLYS] of TPolygon; dimensional lookup table that will return a shade for any R,
NumberPolys : Integer; { Number of polygons in object. }
Color : TColor; { Color for solid shading & wireframe. }
G, or B value. The CalcIntensityLUT procedure works out
DibColor : Word; { 16-bit value of color. } our lookup table (see Figure 6). You’ll also need to place this
World : TPoint3D; { Position of object in "the world". } statement in the Create constructor of TGMP:
end;
CalcIntensityLUT;
Figure 4 (Top): CalculateRGBWord takes a TColor as an argument
and returns the appropriate 16-bit value. Figure 5 (Bottom): The Now that we have our lookup table, we need a function
TPolygon and TObject3D records. that takes a 16-bit color and an Intensity value as argu-
ments, and returns a new 16-bit color with each correctly
as the color. In 16-bit color mode, a pixel in a DIB takes shaded RGB element. This is complicated, because the
up two bytes (see Figure 3). The RGB values are embed- method has to extract the separate RGB values from the
ded in that value; the “first” bit of the 16 — the one with Word value, then get the shades for each RGB, and recom-
the highest value — isn’t used. The next five bits are the bine them into a Word value.
red value, the following five are the green value, and the
“last” five bits are the blue value. Each color has a value of The method, named GetShadedWord (see Figure 7), is quite
0 to 31. interesting, because it covers some new programming ground
related to bit-wise manipulation. It shows how to use bit
We’ve written a support method, CalculateRGBWord, that masking, and works on the principle that and can conjoin
converts TColor into a Word value (see Figure 4), along with values to extract a new value. Figure 8 shows the logical
various other methods tied into the shaded texturing model, results of “and-ing” different binary values, then shows how
which we’ll address later. to extract the five-bit G value from a 16-bit value.

30 April 1997 Delphi Informant


Sights & Sounds

function TGMP.GetShadedWord(Texture : Word; //********** Shaded Texture ****************************


Intensity : Integer) : Word; rmShadedTexture :
var begin
intRed, intGreen, intBlue, intBitMask : Integer; RemoveBackfacesAndShade(Object3D);
begin OrderZ(Object3D);
{ Bitmask for 0000000000011111 is 31. This gives us LocalToWorld(Object3D);
the last 5 bits for Blue. } for X := 0 to Object3D.NumberPolys - 1 do
intBitMask := 31; with Object3D.PolyWorld[x] do begin
intBlue := Texture and intBitMask; if (Object3D.PolyWorld[x].Visible = False) then
{ Bitmask for 0000001111100000 is 992. This gives us Continue;
the the middle 5 bits for Green. } ClearYBuckets;
intBitMask := 992; FIntensity := Object3D.PolyWorld[x].Intensity;
intGreen := Texture and intBitMask; if (NumberPoints = 3) then
intGreen := intGreen shr 5; begin
{ Bitmask for 0111110000000000 is 31744. This gives us TextureStart.X := 63; TextureStart.Y:= 0;
the bits for the Red element — 15-11. } TextureEnd.X := 0; TextureEnd.Y := 127;
intBitMask := 31744; DrawTextureLine3D(Point[0], Point[1],
intRed := Texture and intBitMask; TextureStart, TextureEnd);
intRed := intRed shr 10; TextureStart.X := 0; TextureStart.Y:= 127;
{ Get the new shades using the lookup table TextureEnd.X := 127; TextureEnd.Y := 127;
we worked out before. } DrawTextureLine3D(Point[1], Point[2],
intRed := IntensityLUT[intRed, Intensity]; TextureStart, TextureEnd);
intGreen := IntensityLUT[intGreen, Intensity]; TextureStart.X := 127; TextureStart.Y := 127;
intBlue := IntensityLUT[intBlue, Intensity]; TextureEnd.X := 63; TextureEnd.Y := 0;
{ Shift Red and Green into their correct places, DrawTextureLine3D(Point[2], Point[0],
and add all the elements together. } TextureStart, TextureEnd);
intRed := intRed shl 10; end
intGreen := intGreen shl 5; else
begin
Result := intRed + intBlue + intGreen; TextureStart.X := 127; TextureStart.Y := 0;
end; TextureEnd.X := 127; TextureEnd.Y := 127;
DrawTextureLine3D(Point[0], Point[1],
Figure 7: The GetShadedWord method. TextureStart, TextureEnd);
TextureStart.X := 127; TextureStart.Y := 127;
TextureEnd.X := 0; TextureEnd.Y := 127;
DrawTextureLine3D(Point[1], Point[2],
TextureStart, TextureEnd);
TextureStart.X := 0; TextureStart.Y := 127;
TextureEnd.X := 0; TextureEnd.Y := 0;
DrawTextureLine3D(Point[2], Point[3],
TextureStart, TextureEnd);
TextureStart.X := 0; TextureStart.Y:= 0;
TextureEnd.X := 127; TextureEnd.Y := 0;
DrawTextureLine3D(Point[3], Point[0],
Figure 8: Bit masking: “and-ing”, then extracting the last five bits, TextureStart, TextureEnd);
for a value of 16. end;

RenderYBuckets;
After all that preparation, we can finally add the
end; { End of with statement. }
rmShadedTexture element to TRenderMode, then place a end; { End of rmSolidTexture statement. }
new section of code to implement the shaded texturing in
the RenderNow procedure. The new section of code is Figure 9: Implementing shaded texturing in the RenderNow
procedure.
shown in Figure 9. Please notice, and tolerate for the
moment, that all the points are taken from PolyWorld
rather than PolyStore. This will all be explained shortly,
GEO Files
What’s been annoying us most about the TGMP class is the
along with the LocalToWorld procedure.
fact that we must type all the vertices of an object into the
application code. To eliminate any further vertices typing,
The next step in implementing shaded texturing is adding a
we’ll write a method that will read objects that have been
new statement to the DrawLine3DTexture procedure. We
saved to disk. The first object file format that TGMP will
have given you a little more of the procedure listing than just
read will be for .GEO objects. The .GEO object file format
the statement, so you can see where the line is to be added:
provides a useful, generic way of storing data in a text file.
case RenderMode of The file format is shown below, with comments in braces:
rmSolidTexture : { Existing line }
DrawTextureLine2D(NewStartPoint, NewEndPoint,
3DG1 { This identifies this file as a .GEO file }
TextStart, TextEnd);
3 { This is the number of vertices }
rmShadedTexture : { New line }
1.000000 -1.000000 0.000000 { Vertice 0 - x,y,z }
DrawTextureLine2D(NewStartPoint, NewEndPoint,
0.923880 -1.000000 0.382683 { Vertice 1 - x,y,z }
TextStart, TextEnd);
0.707107 -1.000000 0.707107 { Vertice 2 - x,y,z }
end;
3 0 1 2 25

At long last, we show you the final change needed before we In the last line, the first number indicates the number of
can move onto our object file reader. The RenderYBuckets proce- points in the polygon. The following three numbers tell you
dure has been modified; the new version is shown in Figure 10. which three vertices — from the list — you must join. The

31 April 1997 Delphi Informant


Sights & Sounds
if (RenderMode = rmShadedTexture) then as 3D Studio, SoftImage, and ElectricImage, that can create
begin similar objects. The .GEO file format is not widely used, but
{ Loop through all buckets. }
for Y := 0 to 479 do begin is similar in many respects to the .PLG file format (.PLG files
if (YBuckets[Y].StartX = -16000) then can easily be edited into a .GEO format).
Continue;
{ Calculate all the Texture X,Y increments. }
Length := (YBuckets[Y].EndX - YBuckets[Y].StartX) + 1; (The .PLG format was invented by the writers of the real-
TextXIncr := ((TextureBuckets[Y].EndPosition.X -
TextureBuckets[Y].StartPosition.X)) / length ;
time renderer REND386, and has become quite popular.
TextYIncr := ((TextureBuckets[Y].EndPosition.Y - You can find a large number of converters on the Internet
TextureBuckets[Y].StartPosition.Y)) / length ; that will take things like 3D Studio binary files and con-
TextX := TextureBuckets[Y].StartPosition.X;
TextY := TextureBuckets[Y].StartPosition.Y; vert them to .PLG. You can find a lot of information on
{ Loop through all pixels on the Y line. } 3D modelers and converters at: http://www.hit1.washing-
for I:=YBuckets[Y].StartX to YBuckets[Y].EndX do
begin ton.edu/people/poup/internet/3D.html.)
{ Perform clipping if pixel's X value < 0. }
if (I < 0) then
begin
Figure 11 shows the CUBE.GEO file used by the sample
TextX := TextX + TextXIncr; application for this article. It’s completely commented,
TextY := TextY + TextYIncr;
Continue;
with full explanations for all lines contained in the file,
end; and includes the full implementation of the .GEO file
{ Perform clipping if pixel's X value > width. } reader function.
if (I > Width) then
begin
TextX := TextX + TextXIncr; The World Coordinate System
TextY := TextY + TextYIncr;
Continue; The last subject we’ll cover in this article is the world coor-
end; dinate system (or WCS). So far we’ve been using the local
{ Use DIBClass to set pixel. Get texel from
FCurrentBitmap, and use GetShadedWord to return
coordinate system, and have simply added a Z value to the
correctly-shaded texel to pass into SetPixel. } local coordinates to move the object back and forth. Now
FDib.SetPixel(I,Y,GetShadedWord(FCurrentBitmap[
Round(TextX),Round(TextY)],FIntensity));
we’ll introduce the ability to move the object around.
TextX := TextX + TextXIncr;
TextY := TextY + TextYIncr;
As you saw earlier, when we listed the TObject3D struc-
end;
end; ture, there was a PolyWorld array and a world TPoint3D
end; structure. The world structure holds the x, y, z position of
the object in 3D space. In addition, if you look back at
3DG1 { Standard GEO file header } the rmShadedTexture section of the RenderNow procedure,
8 { The number of vertices. } you’ll see another procedure being called, namely
-1.000000 -1.000000 -1.000000 { Vertice 0 }
LocalToWorld. This procedure copies all the polygon infor-
-1.000000 1.000000 1.000000 { Vertice 1 }
-1.000000 1.000000 -1.000000 { Vertice 2 } mation from PolyStore to PolyWorld, and adds the world x,
-1.000000 -1.000000 1.000000 { Vertice 3 } y, z coordinates to the local coordinates as it copies them.
1.000000 -1.000000 -1.000000 { Vertice 4 }
1.000000 1.000000 1.000000 { Vertice 5 }
This means that we’ve translated the object from its local
1.000000 1.000000 -1.000000 { Vertice 6 } coordinates to its world coordinates.
1.000000 -1.000000 1.000000 { Vertice 7 }

4 0 3 1 2 25 { Polygon 1 }
We won’t list the LocalToWorld procedure here, because it’s
{ The first number in Polygon 1 indicates number the of fairly simple and is available with the source code. However,
points. Join Vertice 0 to Vertice 3 to Vertice 1 to we have provided a visual comparison of local to world
Vertice 2. }
4 4 0 2 6 25 { Polygon 2 } coordinates (see Figure 12).
{ Join Vertice 4 to Vertice 0 to Vertice 2 to Vertice 6. }
4 6 2 1 5 25 { Polygon 3 }
4 5 1 3 7 25 { Polygon 4 }
A World of Changes
4 7 3 0 4 25 { Polygon 5 } Many minor changes have been made to the code in TGMP.
4 4 6 5 7 25 { Polygon 6 } In the RenderNow procedure, for instance, all references to
Figure 10 (Top): The modified RenderYBuckets procedure.
PolyStore have been changed to PolyWorld to incorporate the
Figure 11 (Bottom): The CUBE.GEO file. world coordinate system. Also, a call to the LocalToWorld
procedure has been inserted into each clause of the
final number (25 in this example) is a number you can use to TRenderMode case statement. The LightStrength property has
store color information, or anything else about the polygon been added to allow you to control the brightness of the light
(e.g. a number in an array of textures). (a value of 1 represents 100 percent).

We used the .GEO file format because it’s easy, and because Another property is an event called BeforeFlip, which is sent
we had a lot of Lightwave-generated objects that came with a to TCanvas as a parameter. This allows you to add any
converter to take the binary Lightwave file and output it to text/drawing on top of what has been rendered, before it’s
an ASCII .GEO file. There are several major modelers, such drawn onto the TGMP Canvas. The necessary code for

32 April 1997 Delphi Informant


Sights & Sounds
A look through the TGMP source
code will reveal the other modifica-
tions. (All source associated with this
article is available on disk or online;
see end of article for details.)

The Fourth Edition


In our fourth application, we’ve added
a new menu item to allow for the
selection of various textures, and
introduced some limited movement
control of the objects through the use
of the mouse and keyboard.

Figure 12: A comparison of local and world coordinate systems. Vertice A retains its original You’ll also notice that the arrays for
coordinates through the translation. the objects have been removed. These
arrays are no longer necessary, since
BeforeFlip is shown below, along with the code to declare our the objects are now read into memory via the file reader we’ve
three new mouse movement properties: the familiar created. Again, you must remember to set your display driver
OnMouseMove, OnMouseUp, and OnMouseDown: to 16-bit color, rather than 256 colors. Otherwise you’ll think
type
TBeforeFlip = procedure (Canvas : TCanvas) of object;
we’ve placed a “mud” texture onto the rendered objects!

{ Place in the private section. } Conclusion — and a Look Ahead


FBeforeFlip : TBeforeFlip;
FLightStrength : Single;
This is a long article; still, we have to leave a number of
things out until next time. In the next installment we’ll
{ Place as first line in TGMP.FlipBackPage procedure. }
if Assigned(FBeforeFlip) then
cover topics that will make TGMP a truly useful game
FBeforeFlip(FBackBuffer.Canvas); tool. First we’ll add the final coordinate system: the camera
coordinate system, that will allow you to move the camera
{ Place in published section. }
property LightStrength : Single read LightStrength through your virtual world.
write LightStrength;
property BeforeFlip : TBeforeFlip read FBeforeFlip
write FBeforeFlip;
We’ll optimize the structure of the component to easily
property OnMouseMove; allow multiple objects to be seen at once. We’ll also cover
property OnMouseDown; many optimization techniques that will let TGMP process
property OnMouseUp;
polygons much faster. These techniques include lookup
These mouse properties are defined similarly to the Align tables, pre-calculation, fast multiplication, clipping, method
property. They’ve already been declared in the class that parameter reduction, loop optimization, fixed-point math,
TGMP was inherited from, so a simple declaration will and some common sense. We’ll also add a few design-time
cause them to appear in the object inspector of TGMP. features such as positioning of objects, setting of back-
ground bitmaps, and light-source positioning. Soon we’ll be
Other changes include two new constants, MAXPOLYS and ready to make a game using TGMP. ∆
MAXPOINTS, which control how many points and poly-
gons we want to allow for TObject3D. Another minor change The files referenced in this article are available on the Delphi
is the call to ClearBackPage which now uses the DIB class to Informant Works CD located in INFORM\97\APR\DI9704DP.
do the work:

FDib.ClearBackPage(CalculateRGBWord(FColor));
Peter Dove is a partner in Graphical Magick Productions, specialists in graphics,
training, and component development. He can be reached via the Internet at
Notice how we use the CalculateRGBWord function to peterd@graphicalmagick.com.
return the 16-bit value that ClearBackPage requires. More
small changes throughout the code are similar to those Don Peer is a Technical Associate with Greenway Group Holdings Incorporated
(GGHI). He can be reached via the Internet at dpeer@mgl.ca.
already mentioned.

33 April 1997 Delphi Informant


Informant Spotlight
By James Hofmann and Cathi Pickavet

RAD Results
The 1997 Delphi Informant Readers Choice Awards

W hen the votes were tallied last year, we thought the precedents had
been set — the Delphi market’s most powerful players had emerged.
Little did we know how quickly things would change. To start, Borland
released Delphi 2, then added Internet and intranet functionality. What fol-
lowed could only be described as a plethora of new tools developed at
RADical speeds.

To reflect Delphi’s ever-changing third-party The voting was fast and fierce, so without
market, we altered this year’s ballot. You’ll still further ado ...
see some of last year’s winners in repeat perfor-
mances, dominating their respective categories; Best Internet/Communications
but don’t let your eyes stray, you might miss This year brought Internet and intranet
some of the new categories and winners — functionality into the Delphi developer’s
several we’re sure you’ll find quite surprising. hands. To satisfy your end-users’ every need,
the great majority of you agree that
ISGMapi All Others
INTERNET/ TurboPower’s Async Professional 2.0 is the
Sax Comm Objects
4% COMMUNICATIONS way to go. This communication toolkit for
3%
PowerTCP 3% Delphi 1 and 2 took 78 percent of the
5%
votes, establishing itself as the clear winner.
WebHub
7% And don’t forget TurboPower, because you’ll
78% Async Professional 2.0
see the name again. This company won two
categories in this year’s awards, the first to
ever do so.

Async Professional’s event-driven architec-


ture allows users to read, view, print, and
send faxes. It features complete serial port
control, terminal emulation, telephony
API, debugging tools, and more. According
to TurboPower, an upgraded version is
planned for release after Delphi 3 begins
The Revolutionary All Others
Guide to Delphi 2 shipping.
The Delphi 2
Programmer EXplorer 9% DELPHI BOOK
3% Best Delphi Book
4%
Delphi In Depth Delphi Programming From a field of over 25, Neil Rubenking hit
4% 25% Problem Solver
the tape first with his Delphi Programming
Mastering Delphi 2 9%
Problem Solver, from IDG Books Worldwide.
for Windows 95/NT
His “answer book” locked up 25 percent of
the votes to win the title of Best Delphi
Developing Custom
11% 19% Book. Covering Delphi 1 and 2, Delphi
Delphi 2
Delphi Components Developer’s Programming Problem Solver tackles many of
Guide
16% the vexing Delphi questions that can hinder
first time — or even seasoned — Delphi
Delphi 2
Unleashed programmers.

34 April 1997 Delphi Informant


Informant Spotlight
Xavier Pacheco and Steve Teixeira came in second with Best OCX
Delphi 2 Developer’s Guide, from SAMS Publishing. The New this year, the Best OCX category was very competitive.
sequel to last year’s Best Delphi Book, this edition accounted Apiary Inc. took the top prize with its OCX Expert, receiving
for 19 percent of the votes. 40 percent of the votes. OCX Expert enables developers to
create 32-bit ActiveX controls using Delphi 2. It also con-
All Others verts existing Delphi 2 VCL components to ActiveX controls
ABC for
Delphi with few or no changes by the developer.
VCL
Eschalon Power 7%
Controls 2 3%
4% The run for second place was close, with Integra’s Visual
Piparti 1.0
for Delphi 5% Query Builder finishing just two percent higher than Visual
Tools’ Visual Developers Tool Suite.
51% InfoPower 2.0

All Others
30%
Informix
Orpheus
4% 3% DATABASE SERVER

Oracle
17%
InterBase 4.2
48%

Best VCL
The first of the repeat winners, Woll2Woll’s InfoPower 2.0 pre-
vailed as this year’s Best VCL. Garnering more than half the
28%
votes in the category, InfoPower made an impressive showing.
Microsoft
In his January ’97 Delphi Informant review, Bill Todd touted it SQL Server

as the “must-have addition to Delphi for anyone developing


database applications.” It’s fair to say you agree.

Here are just a few of InfoPower’s features: a Table compo- Best Database Server
nent that fully supports Borland Database Engine (BDE) For the second consecutive year, Borland’s InterBase has been
filters, a QBE component that fully supports QBE queries, named Best Database Server, acquiring 48 percent of the
an enhanced data-aware grid component, high- votes. The database server field included several industry
performance search controls, a customizable pop-up favorites, such as Microsoft SQL Server (28 percent), Oracle
memo field editor, and DBComboBox, DBComboDialog, (17 percent), and Informix (four percent).
DBLookupCombo, and DBLookupComboDialog
All Others
components. And there are plans for a new and INSTALLATION SOFTWARE
PC-Install
improved version of InfoPower for Delphi 3. Stay tuned 3% 3%
Eschalon Setup 2
to DI’s “Delphi Tools” section for details. 9%

Although still second, TurboPower’s Orpheus is closing


the gap in this category. Last year InfoPower secured 47
percent and Orpheus followed with 16. This year the per- 51% InstallShield
centages have changed to 51 and 30, respectively. And WISE Installation 34%
System 4.0
next year’s competition has already begun ...

MediaDeveloper 2.0 All Others

Image BASIC 4% 5% OCX


for Delphi
6% Best Installation Software
In a repeat performance, InstallShield Corp. aced the Best
LeadTools OCX 11% Installation Software category. Great Lakes Business
40% OCX Expert
Solutions, Inc. took second with WISE Installation System
4.0. Moving from 30 and 26 percent to 51 and 34 percent
16%
respectively, it looks as if InstallShield and WISE are moving
away from the pack.
Visual Developers
Tool Suite
18%
Best Training
Visual Query There’s a new face in the winner’s circle for Best Training —
Builder ZAC Education’s Delphi Training Tour. After a whirlwind
tour of the US, ZAC garnered 43 percent of the votes.

35 April 1997 Delphi Informant


Informant Spotlight
Softbite
International
All Others when you drag a file from a directory to the project win-
TRAINING dow. The check-in and check-out process is designed to
6%
4%
The DSW eliminate redundancy and unnecessary data, improving
Group, Ltd.
7%
performance. Apparently it’s a worthy enhancement, as the
Client 43% ZAC Education’s
Delphi Training Tour
PVCS Version Manager continues to be your favorite.
Servers 9%

Microsoft’s Visual Source Safe — a write-in! — finished


Financial
Dynamics
9% second with 19 percent of the votes.
All Others
9% VERSION CONTROL
Versions
Database 5%
Programmer’s 13%
9%
Retreat
InfoCan
Management MKS Source
Integrity
12%

The wide variety of training companies competing this


55% PVCS
year are yet another reminder of Delphi’s popularity. Version Manager
InfoCan Management finished second, followed by a
19%
three-way tie between Database Programmer’s Retreat, Microsoft Visual
Source Safe
Financial Dynamics, and Client Servers. With Delphi 3
shipping soon, this category is sure to be another tight
race next year.

Best Reporting Tool Best Delphi Add-In


In a race to the finish, Nevrona Designs’ ReportPrinter Pro 2.0
In the category with the most competitors, TurboPower’s
inched to victory as Best Reporting Tool. Finishing third last
SysTools for Delphi outscored the others, grabbing 29 percent
year, ReportPrinter Pro narrowly bested the ’96 champ, Seagate
of the votes for the Best Delphi Add-In. SysTools is a collec-
Software’s Crystal Reports, by a margin of three percent.
tion of system-level classes that parallel Delphi’s menu classes.
SysTools includes units for string manipulation, date and time
In the September 1996 Delphi Informant, Bill Todd gave
handling, container classes, high-precision math, data sorts,
ReportPrinter Pro a thumbs-up, citing such enhancements as
registry and .INI file handling, and system access routines. (See
the TDBTablePrinter component’s Table Editor, the
Alan Moore’s review of SysTools in the January 1997 DI.)
TReportSystem component, and some “major” memo printing
capabilities.
Finishing with 13 percent of the vote, DFL Software took
second with Light Lib Magic Menus, a product that adds
R&R Report All Others
Writer images to Delphi menus. (See Douglas Horn’s review of
REPORTING TOOL Magic Menus in the February 1997 DI.)
5% 3%
Ace Reporter
7%
GTSizer All Others
28% ReportPrinter
Pro 2.0
DynaZIP DELPHI ADD-IN
7%
ReportSmith 16% Client Object/400 3%
& TcGuide 3%
4%
Exceed Zip SysTools
Compression Library 5% 29% for Delphi

16% Eschalon Power 6%


25% Libraries

QuickReport 7%
Crystal Reports
Multi-Edit 13%
12% Light Lib
12% Magic Menus

Best Version Control CDK


Bounds Checker 4.0
Proving again to be the Delphi developer’s versioning tool of
choice, INTERSOLV’s PVCS Version Manager dominated
the field, acquiring 55 percent of the votes to win the Best
Version Control category. Best Windows Help Authoring Tool
The closest race overall was in the Windows Help Authoring
PVCS version 5.2 features an enhanced user interface, category. The lead changed daily, if not hourly, as votes
allowing developers to drag a file from the project window poured in. In the end, ForeFront Inc.’s ForeHelp grabbed the
into a directory to check out the file. Check-in occurs victory from last year’s winner — RoboHelp, from Blue Sky

36 April 1997 Delphi Informant


Informant Spotlight
All Others
Query
Help Magician
Pro 95
WINDOWS HELP Maker All Others
DATABASE TOOL
7%
3% AUTHORING TOOL The Organized
Database Inspector 5%
3%
3%
Visual Help Pro Data Explorer 4%
13%

Titan Access 13%


40% ForeHelp for Delphi
72% Apollo 2.0

37%

RoboHelp

Software. In this donnybrook, ForeHelp finished with 40 percent of the votes. Apollo has been recognized in other
percent of the vote, and Blue Sky Software had 37 percent. award circles as well. In December, SharewareJunkies.com,
a Shareware evaluation site on the Web, had its visitors
Best Imaging Component vote on the Best Shareware of 1996. Conversion Buddy, a
Another first-timer in this year’s contest is the Best Imaging product using Apollo as its database engine, won the award
Component category. However, the winner with 37 percent for Best Windows Freeware program.
of the vote, ImageLib from Skyline Tools, is no stranger to
Delphi developers. Skyline offers a special VCL/DLL pack-
age for Delphi developers to incorporate multimedia ele- Product of the Year
ments into their applications. (See Douglas Horn’s review in The Product of the Year category was handled a bit differ-
the July 1996 DI.) ently this year. (In the inaugural Readers Choice Awards,
the product with the most votes overall won the award.)
DFL Software continued with its competitive edge as Light
Lib Images finished second with 25 percent. Because some categories have heavier voting patterns than
others, the products in those categories stood a better
IMAGING COMPONENT chance of acquiring more overall votes, thus a better
chance of winning the coveted Product of the Year award.
ImageKnife/OCX
All Others This year, we created a specific category where you either
Graphics Server
3% 5% entered the number of your product of choice from the
Fastgraph 4% ballot, or wrote in the name of your favorite product. This
4%
way, the products in the less mainstream categories had an
Chart FX 3.0 5% 37% ImageLib equal chance.

Having said all that — adding a separate category didn’t


17% change a thing! Woll2Woll Software’s InfoPower is the hands-
TeeChart 1.03 down Product of the Year for the second consecutive year.
Not only did it receive 38 percent of the votes, besting
25%

Light Lib Images


TurboPower
Eagle Software Async Professional PRODUCT OF
CDK
Best Database Tool 5%
THE YEAR
Is this the year for first timers, or what? Not only did we add 8%
the Best Internet/Communications and Best Imaging TurboPower
SysTools
Component categories to the ballot, we also tossed in Best 9%

Database Tool; Delphi is used so heavily in database develop-


ment, we thought a category recognizing some of the 38% Woll2Woll
SuccessWare InfoPower 2.0
database-centric products was in order. Apollo 2.0
19%

SuccessWare International, last year’s winner in the Best 21%


Add-In category, has established its product, Apollo 2.0, as
the clear leader in this category. Apollo blasted off to a
TurboPower
first-place finish in the first year of competition, earning 72 Orpheus

37 April 1997 Delphi Informant


Informant Spotlight
TurboPower’s Orpheus (21 percent), it also received the most
votes overall from the ballot.
Contacting the Winners
Thank You
Best Internet/Communications Best Version Control
It takes dedicated, talented people to get such quality
Async Professional 2.0 PVCS Version Manager
Delphi products to the marketplace. We’d like to thank the
TurboPower Software INTERSOLV
vendors who dare to dream, plan, create, test, and distribute Phone: (800) 333-4160 or Phone: (800) 547-7827 or
unique Delphi products. Some began developing third- (719) 260-9136 (503) 645-1150
party products at night and on weekends. We salute your Web Site: http://www.tpower.com Web Site: http://www.-
perseverance; without your wares, the Delphi developer’s intersolv.com
job would be very different. Best Delphi Book
Delphi Programming Best Delphi Add-In
This year looks to be even more exciting than the last two. Problem Solver SysTools for Delphi
The Delphi community is well established and continues By Neil Rubenking TurboPower Software
to grow, Delphi 3 is set to ship, and developers have a slew IDG Books Worldwide Phone: (800) 333-4160 or
Phone: (800) 434-3422 or (719) 260-9136
of quality products to pick from. As the Delphi projects
(415) 655-3000 Web Site: http://www.-
abound, we hope to see you push the boundaries and set Web Site: http://www.idg-
new standards — and bring Delphi development into even tpower.com
books.com
greater prominence. ∆
Best Windows Help
Best VCL
Authoring Tool
InfoPower 2.0
ForeHelp
Woll2Woll Software
ForeFront Inc.
Phone: (800) wol2wol or
Phone: (800) 867-1101 or
(510) 371-1663
Delphi Informant’s Products Editor James Hofmann is available at (713) 961-1101
Web Site: http://www.-
jhofmann@informant.com. To contact Cathi Pickavet, Editorial Assistant for DI, Web Site: http://www.ffg.com
e-mail cpickavet@informant.com. woll2woll.com

Best Imaging Component


Best OCX
ImageLib
OCX Expert
Skyline Tools
Apiary Inc.
Phone: (501) 376-3600 Phone: (800) 404-3832 or
Web Site: http://www.- (818) 766-3900
apiary.com Web Site: http://www.-
imagelib.com
Best Database Server
InterBase 4.2 Best Database Tool
Borland International, Inc. Apollo 2.0
Phone: (408) 431-1000 SuccessWare International
Web Site: http://www.- Phone: (800) 683-1657 or
borland.com (909) 699-9657
Web Site: http://www.-
Best Installation Software gosware.com
InstallShield
InstallShield Corp. Product of the Year
Phone: (800) 374-4353 or InfoPower 2.0
(847) 240-9111 Woll2Woll Software
Web Site: http://www.- Phone: (800) wol2wol or
installshield.com (510) 371-1663
Web Site: http://www.-
Best Training woll2woll.com
Delphi Training Tour
ZAC Education
Phone: (800) 463-3574
Web Site: http://www.-
zaccatalog.com

Best Reporting Tool


ReportPrinter Pro 2.0
Nevrona Designs
Phone: (888) PROGSOL or
(602) 899-0794
Web Site: http://www.-
nevrona.com/designs

38 April 1997 Delphi Informant


Inside OP
Delphi / Object Pascal

By Gary Warren King

Timely Changes
Improve Your UI’s Responsiveness
with a Nifty Timer Technique

I t’s common for forms to have interrelated UI controls. For example, we


may want a graph to change as we manipulate the values in a form’s
Edit components. Delphi makes it easy to handle this task with the
OnChange and OnExit events associated with most data entry controls.

There is a hidden problem, however. In field are complex or time consuming (for
short, the OnChange event occurs too often, example, updating a graph), the responsive-
and the OnExit event doesn’t occur often ness of the user interface will rapidly decay
enough. — as will the user’s patience and productiv-
ity. Even rapid calculations and screen
Too Much OnChange updates may cause a disconcerting flicker.
Because the OnChange event fires every
time you make a change to a data entry To see this in action, take a look at the
field, it can occur too frequently. [Note: demonstration project DEMO01.DPR (see
The term field is used throughout this arti- end of article for download information).
cle to refer to a data entry control (e.g. an From the Method radio button group, select
Edit component), not in its strict sense as Use OnChange to set the Update method (see
an object’s data member, nor as a database Figure 1). Note that the form’s fields do not
table’s column.] For example, entering the respond to change, and that an ugly screen
number 234.45 will set off six OnChange flicker occurs.
events (more if there are typos and correc-
tions). If the calculations dependent on this Getting off on the Last OnExit
Another alternative is to ignore OnChange,
and instead tie the calculations to the field’s
OnExit event. This means that no calculations
will occur until the user leaves the field. Here
we are guaranteed that the user is (at least tem-
porarily) satisfied with the value they entered,
and that it won’t be changing anytime soon.

The problem is that the user may make


changes and not exit the field. In this case,
the OnExit event will never fire because the
field was not exited. The form can there-
fore be left in an inconsistent state where,
for example, the values in the graph don’t
match the values on the rest of the form.
You can see this effect by selecting Use
OnExit in the demonstration application.

What to Do?
There are many possible solutions to the
Figure 1: The OnChange, OnExit, and TimerDelay demonstration form. updating problem. The following solution is

39 April 1997 Delphi Informant


Inside OP

Using the Demonstration Program

The demonstration program lets you see how the user


interface behaves when using the OnChange event, the
OnExit event, or the “delayed” OnChange event discussed
in the article. You can use the Method radio button to con-
trol how the interface behaves. If you are using the
TimerDelay method, you can use the Delay scrollbar to
control the length of the delay (in milliseconds) between
changes to the Amount and Interest Edit components and
the updating of the graph.

If you look at the code used to update the graph, you’ll see
I’ve added a 500-millisecond delay. I added this to the
demonstration to make the effects of the OnChange,
OnExit, and TimerDelay methods clear. Of course, you
Figure 2: The timer’s OnTimer event becomes responsible for
wouldn’t add a delay to the recalculation routines in a real-
performing calculations.
world project.
based on the idea that, because we are creating software for I first used this delayed OnChange technique in a project
people to use, we can make better software by thinking about that used VisualTools’ FirstImpression charting compo-
how people will use it. If someone is interactively entering nent. This component provides some wonderful lighting
and modifying data to create some sort of presentation, and gradient fill effects that made our graphs more visual-
the flow of their actions will generally be to: ly appealing. Although these effects took longer to calcu-
make some changes late and paint, switching from the standard OnChange
see what happens method to the TimerDelay method immediately improved
make some more changes the interface, and made using our product more appeal-
see what happens ing.
etc.
— Gary King
It would be nice if our software could model this sort of
activity. And with a little work, it can! To the program, “see more quickly (see the sidebar “Using the Demonstration
what happens” means to run through the calculations and Program” for a discussion of this setting). Set the Timer
update the presentation of the data. The only tricky part is component’s OnTimer event to execute a call to the form’s
understanding what the user’s view of “make some changes” recalculation logic. Then attach the following code to the
is. If we were watching someone work, we would see them OnChange events of each field that’s involved in the form’s
make modifications, then step back to see how things look. recalculation logic:
In other words, we need software that can sense a batch of
procedure TMyForm.ResetTimer(Sender: TObject);
modifications has occurred and that the modifications have begin
(temporarily) stopped. What we would like is a sort of Timer1.Enabled := False;
delayed OnChange event that is armed when a change occurs, Timer1.Enabled := True;
but does not go off until the changes have (at least temporari- end;

ly) stopped.
This method turns the Timer on if it was off, and resets it to
The Delphi Way its starting time if it was already running. If a specific field
We can do this in Delphi by adding a Timer component must have more processing in its OnChange event, you can
to our form and channeling the OnChange events of all use the following code for that field:
Edit components through it. The timer’s OnTimer event procedure TMyForm.Field1Change(Sender: TObject);
then becomes responsible for performing the calculations begin
ResetTimer(Sender);
(see Figure 2).
{ Other processing for field 1... }
end;
An example will make this clear. Find a form on which you
want to implement the Timer Delayed OnChange event. Change Happens
Add a timer (named Timer1 by default) to the form. Set the To return to our initial example, if a user enters 234.45 into
Timer component’s Enabled property to False and its an Edit component, the OnChange for that component will
Interval property to the number of milliseconds you want to still be triggered six times. Each time it is triggered, Timer1
elapse between a change to one of the fields and a recalcula- will be reset to the beginning of its cycle. Only when the user
tion. Smaller values will cause the recalculations to occur pauses will Timer1 have a chance to reach the end of its cycle

40 April 1997 Delphi Informant


Inside OP

How Fast Do You Type?


procedure Cleanup_DSWinSys;
begin
WinSystem.Free;
Measuring typing speed sounds relatively straightforward end;
— take the number of keystrokes and divide by the
amount of time it took to execute them. However, this initialization

approach excludes a few important details: begin


A person may type some, stop, then type some more. AddExitProc(Cleanup_DSWinSys);
We don’t really want to add the “stopped” time into WinSystem := TWinSystem.Create;
end;
the total time for our calculation.
A person may hold a key down and engage the Note: In Delphi 2 and later use the finalization section
keyboard’s automatic repeat action. We don’t really instead of AddExitProc. This would look like:
want to let someone “cheat” this way.
initialization
The TypSpeed sample application solves these problems WinSystem := TWinSystem.Create;

by only adding keystrokes into the average time when finalization


they occur less often than the keyboard repeat rate, and WinSystem.Free;
more often than a calculated maximum delay.
Catching the Keystrokes. After we have a method of
Determining the keyboard’s repeat speed. Windows determining the keyboard repeat speed, the rest of our
provides numerous functions that can be used to task is easy. Set the form’s KeyPreview property to True,
query (and sometimes set) the properties of the then attach the following to the OnKeyDown event:
Windows environment. One familiar API call is the
procedure TForm1.FormKeyDown(Sender: TObject; var Key:
GetSystemMetrics function. Among other things, it Word;
can tell you how large a standard scrollbar is, or the Shift: TShiftState);
width of a fixed window frame. A less familiar func- begin
FThisKey := GetTickCount;
tion is SystemParametersInfo: FElapsed := FThisKey-FLastKey;
if (FElapsed >= FMinDelay) and
function SystemParametersInfo(A, B: Word; C: Pointer; (FElapsed <= FMaxDelay) then
D: Word): Bool; begin
Inc(FTotalStrokes);
FTotalTime := FTotalTime + FElapsed;
This API call provides access to a hodgepodge of unrelat- lblAverageTypingSpeed.Caption :=
ed system information. As you can tell from the generic Format('%10.2f', [FTotalTime/FTotalStrokes]);
types and names of its parameters, the function is not end;
FLastKey := FThisKey;
intuitive to use. The use and meaning of each parameter end;
changes, depending on exactly which system property is
being queried or set. This method records the total number of keystrokes
(FTotalStrokes), the total time it took to make them
We can make the function easier to use, more under- (FTotalTime), and computes the average. We only count
standable, and less error-prone by creating a wrapper keystrokes whose delay (the difference between Now and
class that “surrounds” the API call with standard Delphi FLastKey) is between FMinDelay and FMaxDelay. These
and Windows types. Because all we need for our current are set up in the form’s FormCreate method:
project is the keyboard repeat speed, we’ll start with the
procedure TForm1.FormCreate(Sender: TObject);
following simple class (in the DSWinSys unit): begin
FTotalTime := 0;
TWinSystem = class(TObject) FLastKey := GetTickCount;
private FTotalStrokes := 0;
function GetKeyboardRepeatSpeed: Word; lblKeyboardRepeatRate.Caption :=
public IntToStr(WinSystem.KeyboardRepeatSpeed);
property KeyboardRepeatSpeed: Word lblAverageTypingSpeed.Caption := '';
Memo1.Lines.Clear;
read GetKeyboardRepeatSpeed;
FMinDelay := WinSystem.KeyboardRepeatSpeed * 2;
end;
FMaxDelay := WinSystem.KeyboardRepeatSpeed * 15;
end;
We can add the remaining SystemParametersInfo values
as time permits, or as we need them for other projects. After some experimentation, we limited the range to
To make using this class easy, use the initialization between two- and 15-times the keyboard’s own repeat rate.
section to create a global variable (WinSystem). Notice This works well, and seems to provide reasonable answers.
that we use the AddExitProc procedure to ensure that
what we created is also freed: — Gary King

41 April 1997 Delphi Informant


Inside OP
and tick, causing an OnTimer event. This event will then exe- way of resolving the issues by adding properties to the form
cute all the recalculation logic for the form. that mirror the values in the Edit components. Download the
code and take a look.
You can see how this will look (and feel) to your users by
selecting the Use Timer Delay in the Method radio button You may also want to provide more feedback to the user to
group in the demonstration application (again, see Figure 1). show that the form is in an inconsistent state and that calcu-
As long as you keep typing in the Amount and Interest edit lations are occurring. The demonstration changes the form’s
boxes, the graph will not be updated. As soon as you pause, caption during recalculation. If your application has a status
however, the graph will be updated. bar, you might want to use that instead.

Details, Details Conclusion


It’s important to strike a balance between the program con- This Timer technique is a great example of how a little
stantly recalculating (too small a value) and the program extra thinking, and a small amount of code, can produce a
seeming to ignore changes (too large a value). Generally better user interface. In this case, we have an interface that
speaking, I have found that a value between 500 and 1000 will seem more responsive to users: It lets them do their
milliseconds (0.5 to 1 second) works well, and provides a nice work without getting in the way, and it keeps things
responsive feel. You can experiment on the demonstration updated without the need for explicit recalculation com-
application by altering the Delay scrollbar. mands.

Because different people type at different speeds, you may As the technical environment in which we work becomes more
want to tie the delay to each user’s typing speed (see the complicated, our users will demand software that helps them
sidebar “How Fast Do You Type?” on page 41 to learn how do their work, not software that makes them work. A proper
to determine your typing speed). This might require interface is near the heart of this goal. The following references
adding a setup screen to your application or your program’s provide a good starting point for learning more about human-
installation. computer interaction and user-interface design. ∆

Another important point in making programs that seem References


smart to your users is that no recalculation should occur Borenstein, Nathaniel. Programming as if people mattered:
unless the data has been modified. Delphi makes this easy for friendly programs, software engineering, and other noble delu-
an Edit component because the Modified property is set to sions [Princeton University Press, 1991].
True when its value changes. Of course, as King Lear said, Cooper, Alan. About Face: The Essentials of User Interface
“Nothing will come of nothing;” there are three problems Design [IDG Books, 1995].
with the Modified property: Norman, Donald. The Design of Everyday Things [Doubleday,
Modified is not reset automatically after your recalcula- 1990].
tions are complete. You must iterate through each Edit Norman, Donald. Things that Make us Smart [Addison-
component and reset its Modified property to False. Wesley, 1993].
An Edit component doesn’t “understand” your applica- Tognazzini, Bruce. Tog on Interface [Addison-Wesley, 1992].
tion. For example, you and I know that 3.0 is the same as Tognazzini, Bruce. Tog on Software Design [Addison-Wesley,
3.00. However, an Edit component will think the field 1996].
has been changed and thus set Modified to True and cause
a recalculation. The files referenced in this article are available on the Delphi
Only TEdit (and its descendants) has the Modified prop- Informant Works CD located in INFORM\97\APR\DI9704GK.
erty. If your form is using other user interface controls,
you are forced to check for modifications manually.
Gary King is the principal of DesignSystems (http://www.oz.net/dsig), makers of
Interface Gizmos and other fine products for Delphi. He can be reached at
There are ways around these problems, and they all involve gking@oz.net.
adding code to your application. The demonstration shows one

42 April 1997 Delphi Informant


New & Used
By Alan C. Moore, Ph.D.

Multi-Edit for Windows


An Editor for the Power Programmer

F or many of us, Delphi’s code editor is more than adequate. It includes


all the standard features you would expect: block selection, cut and
paste, search and replace, bookmarks, and so on. But what if these capa-
bilities are not enough? What if you need, or want, more control over the
code-editing process? Then it’s time to consider one of the specialized
code editors, of which the best known is Multi-Edit for Windows by
American Cybernetics.

This editor is easy to install, and you can Unlike Delphi, but similar to Borland
implement Delphi support before or after Pascal, the main window uses a multiple
installation. When you enable Delphi sup- document interface (see Figure 1). While
port, you inform Multi-Edit of the location Multi-Edit for Windows is a 16-bit applica-
of the Delphi BIN directory. This allows you tion, it does have a number of Windows 95-
to compile Delphi programs directly from compatible features, including support for
Multi-Edit. A new menu item, Delphi, is long filenames, “Explorer-style” file prompts,
added to Multi-Edit’s already feature-rich and background compiling.
menu system, and Multi-Edit is automatical-
ly added to Delphi 1’s Tools menu (I had to Multi-Language Support/Enhanced
manually add it to Delphi 2’s Tools menu). Capabilities
In Delphi 2, the hotkey you select automati- Multi-Edit supports many other program-
cally loads Multi-Edit, and toggles between ming languages, including Assembler, BASIC,
Multi-Edit and Delphi’s IDE. The available C, dBASE, HTML, and Java. Enabling sup-
menu system and the various hotkeys are port for these languages/-
completely configurable. compilers is less straightforward than for
Delphi. Keywords and other language-specific
details are built into an internal database you
can access and modify. Built-in language-spe-
cific templates, which expand keywords into
common structures, are also available. For
example, in Delphi, you can type begin, hit
the Expand Template speedbutton, or
A9, and a begin..end block will be creat-
ed with the editing cursor placed in the body
of the block. Other common Pascal/-Delphi
structures include case, repeat, record, for,
object, if..else, function, procedure, try..final-
ly, and try..except. If the built-in templates
are not sufficient, you can define your own.
The application includes two special add-on
packages to enable seamless integration with
either Borland’s C/C++ or Delphi’s IDE.
When you install these, an extra menu item
allows you to compile, build, or check syntax
Figure 1: Multi-Edit’s multiple document interface. for an application in that particular language.

43 April 1997 Delphi Informant


New & Used
include appending a marked block to text already in the cut-
and-paste buffer, showing the contents of that buffer, and
repeating a particular keystroke a number of times.

The Block menu is even more feature-rich. The obvious


choices of indenting, unindenting, copying, moving, and
deleting a selected block are present, along with some nice
additions, including saving to file, copying a block from
another window, and moving it to another window. There
are three block-selecting modes, providing tremendous flex-
ibility in editing source files: by lines of text, by columns of
text, and by stream (beginning and ending anywhere within
particular lines). A very helpful option is the persistent
block. With it you can mark a particular block, move the
cursor somewhere else, and move or copy the still-selected
(persistent) block to that location.

Figure 2: The Save File As dialog box. The Search menu is also powerful. Using the Search dialog
box (see Figure 3) you can search for individual words,
The main menu bar includes the familiar choices (File, Edit, phrases, etc. using regular expressions. With other choices
etc.), as well as Block, Search, Text, Macro, Tags, and Vcs in this menu you can set up to 10 bookmarks or random
(Version Control System.) Many of the familiar menu items access marks (which, unlike Delphi, remain active the next
have enhanced capabilities when compared with other editors time you open Multi-Edit), highlight global expressions
(such as Delphi’s IDE.) However, there are also some unfor- (the same string in all the files open in the current session),
tunate limitations. The first half of the manual discusses the or list files from the last file search. Another powerful fea-
menu and sub-menu items in detail. Let’s take a look at some ture is the Multiple File Search and Replace dialog box (see
of the more unusual features. Figure 4). As you can see, using the same kinds of expres-
sions found in the Search dialog box provides tremendous
Managing Files: Editing, Marking, and flexibility to search for strings or complex patterns in mul-
Searching for Text tiple files. After a search is completed, you can open any
The File menu includes all the standard choices, and a few file containing the search string by double-clicking on any
new ones. Information provides statistics on the currently of its entries in the resulting window (see Figure 5). After
active file, including its status in memory. Merge activates you close this window, you can bring it up again at any
a dialog box that allows you to integrate any file into the time by selecting List Files from Last Search , or by hitting
current file, starting at the current cursor position. Finally, CG. I found this very useful.
the Session Manager allows you to organize different pro-
jects into sessions, saving the complete editor status for The Text menu provides a host of text viewing and format-
each session. The latter includes all files loaded, history ting options. For example, you can view any source or text
lists, and global variables. The standard File dialog boxes
(Open, Save, etc.) contain numerous welcome additions as
shown in the Save File As dialog box (see Figure 2). For
example, clicking the File button opens a list of File
Manager options that allow you to delete, move, or
rename files, among other choices. The All button selects
all the files in the current folder (directory), while None
deselects them all.

I did encounter a problem with the Open File dialog box.


When I browsed different directories beyond the working
directory and double-clicked on a file, that file did not
open. Instead, a new file with the same name was opened
in the working directory. If you want to open a file in a
different directory, you must first click the Change but-
ton. I found this disconcerting.

The Edit menu also has some new and enhanced choices.
Selecting Undo lets you cancel up to 65,000 changes. You
can Redo as many changes as were undone, provided you
don’t make other changes to the file. Innovative new features Figure 3: The Search dialog box.

44 April 1997 Delphi Informant


New & Used
The Tags menu allows you to create a Multi-Tags database
of source file functions, procedures, classes, and types so
you can browse the file in hypertext-like manner. After the
file has been scanned, you can select Browse Current File to
access a dialog box listing all the items (procedures, func-
tions, etc.). This allows you to quickly jump from one to
another. When you combine this feature with the book-
marks discussed earlier, moving around — even in a large
source file — becomes a snap.

The Vcs menu provides useful editing and archiving exten-


sions to the version control system you have installed. You can
lock or unlock files, maintain a log of files stored in the VCS
archive, and so on. The Help menu contains many special fea-
tures, including a Quick Overview that provides an introduc-
Figure 4: The Multiple File Search and Replace dialog box.
tion to using the product, information about technical sup-
port, and FAQs (Frequently Asked Questions). I was particu-
larly pleased with the innovative method for reporting bugs: a
dialog box records data about your computer hardware and
software configuration, provides a straightforward manner for
you to describe the bug, then e-mails the information to
American Cybernetics.

Macros also have their own menu. You can run, load, and
list macros, globals, and keystroke macros. If the macro
you want to load or run doesn’t share the exact name as
its file, you must place the filename before it as in
filename~macro-name. The Loaded Macros dialog box
(see Figure 7) is disappointing in some respects. You can’t
double-click on a macro name and execute it, nor can you
highlight and save the name to the clipboard. Having
addressed the Macro menu, let’s discuss macros themselves.

Multi-Edit’s Macro Language


Figure 5: This window, which shows the results of a multiple file The Macro Language built into Multi-Edit is powerful and
search, allows you to open any of the files by simply double-
clicking its entry.
difficult. With a syntax similar to C, Multi-Edit uses its
Macro Language to implement many of its internal opera-
file in hexadecimal mode, set layout options such as word- tions, including language indenting. In fact, the wizard that
wrap and indenting, sort text in a file, center a line, per- sets the Delphi options during installation is a macro called
form various operations on a paragraph (reformat, justify, DELPHIWIZARD. Each macro exists in a source file with
unjustify), set page breaks, and so on. One particularly a .s extension and a compiled macro file with a .mac exten-
innovative option allows you to compare two files and cre- sion. Those macros called by other macros require a proto-
ate a new file containing their differences. You can also type, which exist in a text file with a .sh extension. You can
move through files and view the previous or next difference. edit a .s or .sh file with any text editor.

Tools, Tags, Version Control, and More The Macro Language is sufficiently complex that the entire
The Tools menu includes a number of useful items, includ- second half of the Multi-Edit manual — some 100 pages
ing a spellchecker, a template builder and editor (for creat- — is devoted to explaining it. It resembles a full-fledged
ing new templates) as shown in Figure 6, an ASCII refer- programming language, with labels, expressions, typed vari-
ence table, a notebook, and a calculator. With other ables, branching, functions with parameters, and so forth.
options you can compile code, manage background tasks, There are many pre-defined functions and variables such as
find the next compiler error, comment or uncomment a BLOCK_BEGIN, COPY_BLOCK, cursor functions, file
source file, install or manage add-on packages (authors of operation functions, and machine-level interface functions.
add-on packages can download needed documentation You can build sophisticated dialog boxes with the language,
from American Cybernetics’ Web site), and customize but there’s a catch: You need to study some of the source
many of Multi-Edit’s features. files such as DLG_Exam to learn the intricate steps

45 April 1997 Delphi Informant


New & Used
involved. There is even low-level
access to the Windows API. These
features provide tremendous power
and flexibility in creating your own
macros.

As I have already hinted, however, there


is a down side: Working with the
Macro Language is hardly straightfor-
ward or intuitive. For example, from
the Macro menu you can view all the
available macros is a listbox, or run a
specific macro. You must, however, type
in the name of the macro you want to
run. Most macros require parameters. If
you don’t enter the proper parameters,
you’ll get an error message.
Unfortunately, the only way you can
get specific information on parameters
required for many macros is to examine
the source file where that macro is Figure 6: The Edit Templates dialog box.
defined. The process is similar to learn-
ing some of the undocumented or The macro is shown in
poorly documented capabilities of Delphi (e.g. creating Figure 8. The test
experts). Unlike Delphi, however, there is little online Help for source file is shown in
pre-defined macros. Such an addition would be a major Figure 9, and the
enhancement, and make this product much more useful. resulting source file
Finally, I found the documentation lacking in several respects. (after running the
Some features, such as the use of ASCIIZ string arrays in struc- macro) is shown in
tures, is not adequately explained; many other features of the Figure 10. This is not a
Macro Language would benefit from expanded examples. particularly simple
However, we can create useful and powerful macros if we are macro, even though it
willing to put forth the effort required. doesn’t involve the cre-
ation of any dialog
My First Macro boxes. If you do want
Like many of you (I suspect), I don’t like unnecessary to use dialog boxes in
work. In Delphi, when I formulate the structure for a new your macros, you
class, I resent having to copy every function and procedure should study the files
heading, adding the class prefix, and copying everything dialog.s and
(including the begin..end block) to the implementation pascal.s. The former is
section. So for my first macro, I created a structure that the basic source for
Figure 7: The Loaded Macros dialog
would automate this process. dialog box creation; box is disappointing: You can’t high-
the latter provides light and copy macro names, nor can
Specifically, after scanning the methods (functions and proce- excellent examples of you double-click and execute one.
dures) of the class, the macro would attach the class name various dialog boxes
before each method and expand each method with with different controls.
begin..end blocks in the implementation section. The basic
algorithm is as follows: Unfortunately, there are no built-in debugging tools for
1) If not on line with class definition, exit. writing macros, so you must roll your own. Here’s what I
2) Get class name; save in global variable. did. During the debugging phase, wherever I assigned a
3) Get next procedure or function name. value to a variable, I inserted two lines — one showing the
4) Move to the implementation section after uses clause value assigned on Multi-Edit’s status line, the other a
(if present). KeyPress function so that execution would be suspended
5) Create method headers with class name prefix. until I had a chance to read the result:
6) Expand each function or procedure with begin..end
block. ClassName = GET_WORD('');
make_message('ClassName = ' + ClassName);
7) Repeat steps 2-6 until there are no more methods. Read_Key;
8) Exit. WORD_RIGHT;

46 April 1997 Delphi Informant


New & Used
unit Test1;
macro_file BldClass;
struct MethodArray {str MethodStrs[100];} interface
void BldClass( ) trans2 { TestClass = class
str ThisWord; // Current word read procedure Procedure1;
str ClassName; // To append to each function/procedure procedure Procedure2(param1, param2, param3);
str MethodHeader; // Current method with all parameters function function1: integer;
WORD_DELIMITS = (' '); procedure Procedure3(param1);
ClassName = GET_WORD(' '); WORD_RIGHT; function function2: Boolean;
if (! AT_EOL) {WORD_RIGHT;} end; { TestClass }
ThisWord = GET_WORD(' (');
if (CAPS(ThisWord) != 'CLASS') { implementation
make_message('This is not a Class Definition');
Read_Key; end.
goto Finished;
} Figure 9: Source file before running the macro.
// Find Implementation Section
Down;
Set_Mark(1); // Come here after finding implementation unit Test1a;
FIRST_WORD; ThisWord = GET_WORD(' ;');
while ( CAPS(ThisWord)!=('IMPLEMENTATION')) { interface
Down; FIRST_WORD; ThisWord = GET_WORD(' ;');} TestClass = class
ThisWord = GET_WORD(' ;'); procedure Procedure1;
if ( CAPS(ThisWord)==('USES')) { procedure Procedure2(param1, param2, param3);
REPEAT_LOOP: function Function1: integer;
FORWARD_TILL(';'); procedure Procedure3(param1);
Down; FIRST_WORD; function Function2: Boolean;
if (AT_EOL) {goto REPEAT_LOOP;} end; { TestClass }
}
Set_Mark(2); // After implementation, start writing here implementation
Get_Mark(1); // Go back to method body
// Now scan for procedure and function names procedure TestClass.Procedure1;
FIRST_WORD; ThisWord = GET_WORD(' ;'); begin
while (CAPS(ThisWord) != 'END' ) { { Statements }
if ((CAPS(ThisWord)==('PROCEDURE')) || end;
(CAPS(ThisWord)==('FUNCTION'))) {
GetAnotherWord: WORD_RIGHT; procedure TestClass.Procedure2(param1, param2, param3);
MethodHeader = GET_WORD(';'); begin
MethodHeader = ThisWord + ' ' + ClassName + { Statements }
'.' + MethodHeader + ';'; end;
Down; First_Word;
Set_Mark(1); // Next time come back here and continue function TestClass.Function1: integer;
// We have header; expand it in implementation section begin
Insert_Mode = 1; Get_Mark(2); CR; { Statements }
Text(MethodHeader); CR; Indent; end;
Text('Begin'); CR; Indent;
Text('{Statements}'); CR; Undent; procedure TestClass.Procedure3(param1);
Text('End;'); CR; Undent; CR; begin
Set_Mark(2); // Next time start writing here { Statements }
Insert_Mode = 0; end;
Get_Mark(1); // Read Next Line of method
First_Word; ThisWord = GET_WORD(' ;'); function TestClass.Function2: Boolean;
} begin
} { Statements }
Finished: end;
Insert_Mode = 1; // Restore to insert mode
RM('Text^ClearRandomMark'); // Clear random marks end.
}

Figure 10: Source file after running the macro.


Figure 8: Listing of BldClass macro.

There are several aspects of the macro source file I would like to deal of control over selecting words or tokens. Functions like
address. Most of the macro statements I used are described in FORWARD_TILL, DOWN, FIRST_WORD, SET_MARK,
the chapter on System Functions and Variables, which contains and GET_MARK allow you to move the cursor around within a
many functions for scanning and writing to source files, file source file. And the functions CR and TEXT allow you to write
operations, and low-level system functions. First, the macro new code in the source file. The line:
function itself is declared as Void, because it does not return any
value. After the declaration, it is customary to list all the vari- RM('Text^ClearRandomMark');
ables, with each preceded by its type. As in C++ or Object Pascal
2.0, use a double slash to begin comments. Functions like runs the macro, ClearRandomMark, found in the file
WORD_DELIMITS, GET_WORD, and WORD_RIGHT set Text.Mac. You can call any of the other numerous macros
and use strings of word-delimiter characters that give you a great using the same syntax.

47 April 1997 Delphi Informant


New & Used
Conclusion
Multi-Edit for Windows is clearly a powerful and beneficial tool
for Delphi programmers, especially those who spend a good deal
of time writing code. The more familiar I became with the prod-
uct, the less time I spent using Delphi’s editor. Now the only
time I return to Delphi is when I need to trace through a project
and debug it. However, particularly because of the documenta-
tion and the scarcity of good examples in the manual, I found
working with the Macro Language frustrating. On many occa-
sions, developing a macro caused Windows to freeze. Sometimes
I could resolve the problem by simply hitting Ck; other
times I was forced to reboot.

Still, I found the product has


grown on me, and as I became
familiar with its features, I for-
got about the initial difficul-
ties. I was particularly pleased
with the new macro I created Multi-Edit for Windows is a powerful pro-
grammer’s editor that includes built-in
— in the future, when I create
Delphi support and support for many
new support classes, it will other programming languages, including
save me immeasurable key- Assembler, BASIC, C, dBASE, HTML, and
Java. Its menus include many standard
strokes. I can imagine a num-
and enhanced options for editing, search-
ber of other useful macros I ing multiple files, and managing projects.
may write, including one to Macros and templates help to automate
coding and working with files. Tools such
automate making changes to
as a spellchecker, a notebook, and a
the uses clauses in units to calculator are welcome additions.
transfer them from Delphi 1
American Cybernetics
to Delphi 2. I was particularly
1830 W. University Drive, Ste. 112,
impressed with the capabilities Tempe, AZ 85281
of many of the feature-rich
Phone: (602) 968-1945
dialog boxes that provide Fax: (602) 966-1654
options I have not seen else- E-Mail: tech@amcyber.com
where. When I visited CompuServe: GO CYBERNET
Web Site: http://www.amcyber.com
American Cybernetics’ Web Price: US$199
site I found updated files and
technical papers for all their
products, including Multi-Edit for DOS and Multi-Edit for
Windows. You can also download a demonstration version of
the product. In summary, I found Multi-Edit for Windows to
be a wonderful programmer’s editor that should be a wel-
come addition to many developers’ arsenal of tools. ∆

Alan Moore is a Professor of Music at Kentucky State University, specializing in


music composition and music theory. He has been developing education-related
applications with the Borland languages for more than 10 years. He has pub-
lished a number of articles in various technical journals. Using Delphi, he special-
izes in writing custom components and implementing multimedia capabilities in
applications, particularly sound and music. You can reach Alan on the Internet at
acmdoc@aol.com.

48 April 1997 Delphi Informant


TextFile

Delphi 32-Bit Programming SECRETS


The first thing that struck me this component is on the excellent tip for debugging
about Delphi 32-Bit accompanying diskette. The OLE, as well as an Object
Programming SECRETS by installation of the components Pascal trick that could be used
Tom Swan and Jeff Cogswell is simplified, but I would add in any situation.
is the absence of figures. the usual caveat about making
Normally, a computer book a copy of CMPLIB32.DCL. My favorite chapter is a long,
weighing this much has at Swan explains how to manual- in-depth discussion of print-
least a pound of screen shots ly create the file in event of an ing. Although the topic is dry,
and diagrams — here, there emergency, but I’ve found it’s the authors make up for it
are two. Chapter 11 includes a far simpler to keep a current with plenty of good informa-
good illustration of window- copy around. tion. As with the other com-
to-viewport mapping, and tackle, he lets Delphi handle ponents and code, the print
chapter 7 includes a pointless Throughout most of the book, the gory details. This is proba- preview component Swan and
hierarchy diagram of an OLE Swan digs deep into the muck bly a wise course, as OLE is Cogswell develop in this chap-
storage unit. of Windows. Covering OLE, foreboding to most program- ter is given, without restric-
however, he treads lightly on mers. However, not wanting tion, to the readers. In addi-
The ink is not missed; this the surface. Deciding the sub- to disappoint those searching “Delphi 32-Bit Programming SECRETS”
book is loaded with informa- ject is just too complex to for secrets, Swan includes an continued on page 50
tion and secrets. Of course
you knew that — it’s in the Hardcore Delphi Database Development
title. I had doubts at first; the
secrets weren’t in the first few With its definitive title, for experienced Delphi pro-
chapters. I wondered if I’d Delphi Database fessionals who know many
graduated to Delphi guru and Development will be lines of code are often nec-
my diploma was lost in the approached by program- essary to produce commer-
mail. Fortunately, the hints mers who have “A-to-Z” cial-grade applications. The
get better as the book pro- expectations. Readers can intended audience is profes-
gresses. Many books promise be assured that the A-to-Z sional programmers with
to deliver secrets; it’s refresh- promise will be met, but in experience in Object Pascal,
ing when one actually does. a different manner than the Delphi object model,
[One that definitely delivers is other books. The dense and database-specific pro-
Ray Lischner’s Secrets of text, lack of illustrations, gram development.
Delphi 2 from Waite Group and absence of the ubiqui-
Press, reviewed in the tous “Tropical Fish” data- Delphi Database Develop-
February 1997 DI, ed.] base tell the reader this is ment begins with a 12-page The second, third, and fourth
not a tutorial guide for neo- chapter on database devel- chapters cover the Data Access,
Chapter 4 covers 32-bit pro- phyte Delphi database pro- opment. Descriptions of Visual Data, and TField con-
gramming for Windows, and grammers. the VCL components, the trols, respectively. The reader
does so with an unusual topic: Borland Database Engine must have experience with
a component that encapsulates In contrast to books that (BDE), and the single rela- these tools to successfully apply
the creation of a control panel offer instruction on using tion example program are this reference material; the pro-
applet. I found the material VCL components to build the only offerings to novice vided examples don’t place the
fresh, and while the topic may database programs without programmers. The remain- function code within a project’s
be a bit esoteric, the tech- writing code, Delphi der of the book documents context.
niques are quite interesting. As Database Development pro- Delphi’s database tools in “Hardcore Delphi Database Development”
with all the code in the book, vides in-depth information low-level detail. continued on page 50

49 April 1997 Delphi Informant


TextFile
Hardcore Delphi Database Development (cont.)
Early chapters aren’t categori- expanded. For example, an Delphi Database Development chase this book, remember to
cally or functionally indexed. API call is shown surrounded is an important addition to also buy a good highlighter
So, the programmer must by code explaining the the library of any developer pen and a package of Post-It
know which concept to function’s use. building commercial database notes for marking pages that
research before delving in. The applications in Delphi. This you’ll repeatedly reference.
book offers a consistent, Experience developing data- dense, highly technical refer-
descriptive pattern through- base programs in any language ence provides valuable infor- — Warren Rachele
out. For example, a compo- is required to make full use of mation not readily available in
nent is presented with a this reference guide. The pro- the general Delphi press. Delphi Database Development
description of its application, grammer must understand the by Ted Blue, John Kaster,
followed by tables listing its specific task to accomplish The book makes no preten- Greg Lief, and Loren Scott.
properties, methods, and before consulting this book. sions about teaching the neo- M&T Books, 115 West St.,
events. The programmer phyte Delphi developer the New York, NY 10011,
focusing on a single control The last 200 pages are tightly basics of database program- (212) 886-9222.
will find this reference faster focused appendices, providing ming — other fine books can
than navigating the myriad explicit information about fill this need. A working pro- ISBN: 1-55851-469-4
windows in the Help system. error return codes, BDE data grammer needs quickly acces- Price: US$44.95
structures, converting Xbase to sible information, and this (968 pages, CD-ROM)
An alphabetical list following Delphi code, database file for- book provides it. If you pur-
the tables then details each mats, alternatives to the BDE,
scheduled item. The book was and InterBase SQL. This
disappointing in this respect dense collection of informa-
Delphi 32-Bit Programming SECRETS (cont.)
— the descriptions offer little tion is useful, and after divin- tion to that excellent compo- would explain the meandering
more than Delphi’s online ing the methodology behind nent, Swan explains several of prose. Also, maddeningly,
documentation. For instance, the presentation, programmers Windows’ poorly written API there are no chapter sum-
the examples often contain can quickly locate the required calls. I don’t know if this maries. A brief terminating
only one line of code showing answers. Delphi Database knowledge can be classified as paragraph would prevent a
the Object Pascal syntax Development bridges both secret, but I doubt I’d have tired programmer from trying
underlying the component’s releases of Delphi with those figured them out. to peel apart pages when none
property, but not the context functions applicable only to are stuck. More than once
in which it’s used. Without the 32-bit version. Finally, there’s chapter 13 — Swan ends a chapter that
the surrounding code, the unlucky 13. While the book is abruptly.
developer is left to create An accompanying CD-ROM not intended to be a compre-
example/test code. in a computer text is almost hensive resource, Swan claims Despite its problems, I think
obligatory in the current sales this chapter will pull together Delphi 32-Bit Programming
The BDE Function Refer- market; this book follows this all information on files and SECRETS will help most
ence is the most useful sec- trend. However, the program- streams. They do a workman- Delphi programmers, and I
tion, providing complete mer must ponder the useful- like job here, but I want more. definitely recommend it.
documentation for the BDE ness of the CD-ROM that I want the secrets of the uni-
API. The authors provide accompanies Delphi Database verse. I want fat-free that really — Richard A. Porter
brief paragraphs to introduce Development. It presents few tastes good. At least I want to
the BDE and its use with complete programs, and load- stream my components to and Delphi 32-Bit Programming
Delphi. This section presents ing the CD-ROM, copying from disk. I’ve been searching SECRETS by Tom Swan with
the functions in alphabetical the code, compiling, and run- for what seems an eternity for Jeff Cogswell. IDG Books,
sequence (a categorical refer- ning it takes longer than man- a definitive guide to streams 919 E. Hillsdale Blvd., Foster
ence to these functions is ually entering the code. The and filer objects. I had high City, CA 94404,
presented in Appendix C, CD-ROM has a smattering of hopes for this chapter; it’s fine, (800) 434-2086.
which may have improved demonstration copies of com- but not comprehensive.
the workability of this refer- mercial software and publish- ISBN: 1-56884-690-8
ence chapter). The entries for ing ventures. It also includes a While my overall impression Price: US$44.99
each API function are much set of reasonably valuable pro- of this book is good, I do have (738 pages, Disk)
more useful and comprehen- grams submitted by a third- a few problems with it. Two of
sible to the developer than party developer. The BDE the chapters — a long one on
the earlier component demonstration programs pro- memory management and
entries. Additionally, the vide examples of several BDE another on the IDE — are
code snippets used to present API calls, as well as several use- difficult to follow. Perhaps
the functions are greatly ful tools when compiled. they were rushed to press; that

50 April 1997 Delphi Informant


File | New
Directions / Commentary

Support! Now More Than Ever

I f you develop software, chances are you or someone in your company is responsible for supporting
the applications you build. But how much importance do you give the support itself? Before you
take it too lightly, keep this in mind: Unless the software is operable by the targeted user, it quickly
becomes “shelfware.” I learned this lesson recently trying to install and configure a Web software
package on a Microsoft IIS Web server. While I discovered it can be done, the experience was not
without a lot of avoidable pain.

Dot your i’s, cross your t’s. Once I vendors not tied to a particular server. easy to install, configure, and use gives
downloaded the software from the ven- The downside is that while Perl is you an advantage over your competitors.
dor’s Web site and prepared for set-up, ubiquitous in the UNIX world, get- (A corollary is also true: If your soft-
I became confused: I didn’t know ting it to run with NT Web servers ware is difficult to use, a user will often
which installation instructions I should (particularly Microsoft IIS) isn’t nec- react by switching to the competition,
follow. The step-by-step instructions essarily straightforward. For example, even if it also proves difficult to use.)
on the Web site were noticeably differ- the documentation for the NT ver- Perhaps this is obvious for “end-user”
ent than the INSTALL.TXT file sion of Perl I downloaded differed software, but it is also true for
included with the download. After I from the IIS docs. They did have one developer-oriented tools. I think
determined the correct set, I still had thing in common, however: They many software developers and compa-
to wade through a series of errors and were both wrong! After considerable nies miss this point. Remember the
inconsistencies in the documentation. trial and error, I was finally able to principle “time is money” also holds
To top it off, after I had spent several stumble across a technical note on the true for your targeted user. Perhaps
hours working with a junior technical Microsoft Web site that discussed we can restate this as “the less time
support engineer to track down a par- how to correctly set the Registry required to learn how to operate your
ticular bug, I was disheartened to later entries. The problem: I was forced to software will give you more money.”
talk to a senior engineer who had full fix the problem myself, because the
knowledge of the problem. Such infor- technical support people I talked with While I have singled out one software
mation was neither disseminated inter- had little experience using Perl on the vendor, it is likely each of you have
nally nor to customers. Thus, gleaning NT platform. Our second principle identical stories to tell. Think about
our first applicable principle from this therefore is to avoid relying on third- these experiences as you and your
experience: Ensure documentation is party software. In situations where company support software. Great pro-
consistent and routinely updated. that is not possible, remember this: gramming techniques, sound applica-
Known bugs should be disseminated, Any problem with that software is also tion design, and unmatched perfor-
particularly if there are workarounds. your problem. mance matter little if your software is
The Internet — or if applicable, cor- poorly supported. ∆
porate intranets — can serve as an Time is money. During much of this
excellent means of making such infor- ordeal I was able to keep my patience, — Richard Wagner
mation accessible to your user base. but I eventually gave up and began
exploring alternative options. Soon
Home brew if you can. The software the thought of starting over with Richard Wagner is Chief Technology
package uses Perl scripts to provide its another solution seemed less of a task Officer of Acadia Software in the Boston,
run-time application environment. than completing the one I already MA area, and is Contributing Editor to
Because Perl works with most Web spent countless hours on. This leads Delphi Informant. He welcomes your
servers, it seems an obvious candidate for to our third principle: Software that is comments at rwagner@acadians.com.

51 April 1997 Delphi Informant

You might also like