Thanks to visit codestin.com
Credit goes to www.scribd.com

0% found this document useful (0 votes)
50 views46 pages

Delphi Informant 95 2001

This document provides an overview of Delphi 2.0, which has been enhanced from Delphi 1.0 to support Windows 95 development. Key updates include full 32-bit support, longer string handling, additional data types like variants, and a faster compiler. Delphi 2.0 offers over 90 ready-made components and maintains Delphi's reputation for visual programming power and ease of use. Readers are encouraged to vote in the first Delphi Informant Reader's Choice Awards by the February 20 deadline.

Uploaded by

reader-647470
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
0% found this document useful (0 votes)
50 views46 pages

Delphi Informant 95 2001

This document provides an overview of Delphi 2.0, which has been enhanced from Delphi 1.0 to support Windows 95 development. Key updates include full 32-bit support, longer string handling, additional data types like variants, and a faster compiler. Delphi 2.0 offers over 90 ready-made components and maintains Delphi's reputation for visual programming power and ease of use. Readers are encouraged to vote in the first Delphi Informant Reader's Choice Awards by the February 20 deadline.

Uploaded by

reader-647470
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
You are on page 1/ 46

February 1996 - Volume 2, Number 2

Serendipity!
Delphi 2.0 Makes Its Debut

Cover Art By: Tom McKeith

ON THE COVER
7 Delphi 2.0 — Richard Wagner 34 The Way of Delphi — Gary Entsminger
It’s official — Delphi 2.0 has all the power and functionality of Last month, Mr Entsminger recounted great journeys and single steps
the 16-bit version, with astounding new features. Sporting new in object-oriented development, and discussed two technologies:
UI controls, database enhancements, visual form inheritance, a Dynamic Data Exchange (DDE) and Object Linking and Embedding
revved-up IDE, and much much more, Delphi 2.0 is ready for (OLE). This month, he continues the discussion and introduces OLE
launch. Mr Wagner introduces the new tool and even provides capabilities you can build into your Windows 95 applications.
us with a taste of its multithreading capability.
REVIEWS
FEATURES 42 HyperTerp Pro — Product review by Robert Vivrette
13 Informant Spotlight — Jeff Chant There may be times when a user needs to add or modify the function-
Delphi’s Outline component is the principal control for presenting ality of a delivered Delphi application. But how can you provide this to
hierarchically-related data in your Windows applications. If you’ve the user without making the source code available? Implement a
never used this powerful control, or are just now learning to use it, scripting language, such as HyperTerp Pro, says Mr Vivrette.
then this article is for you. Mr Chant takes us through the intricacies
of TOutline with an interactive example. 44 WISE Installation System — Product review by Micah Bleecher
Roughly broken down, a Windows application development cycle
22 OP Tech — Bill Todd involves designing, building, testing, deploying, and finally,
Error handling is serious business. So serious, in fact, that this installing. If you’re in the market for a capable installation utility,
month Mr Todd begins a two-part series on handling errors in Mr Bleecher strongly recommends the WISE Installation System.
Delphi applications. He covers the use of the Create, Free, and
Dispose methods to help with allocating resources and cleanup, DEPARTMENTS
discusses trapping for RTL exceptions, and will help you under- 2 Editorial
stand the RTL exception hierarchy. 3 Delphi Tools
5 Newsline
27 DBNavigator — Cary Jensen, Ph.D.
This month, Dr Jensen completes his two-part series on filtering
Paradox data in Delphi applications. First, he compares the use
of Table and Query components to filter data, and then shows
you that the Query component — combined with a bit of SQL
— is an effective tool to get the data you’re after.

FEBRUARY 1996 Delphi INFORMANT ▲ 1


Symposium
Straight line exists between me and the good thing.
I have found the line and its direction is known to me.
— David Byrne, “The Good Thing”
Talking Heads, More Songs about Buildings and Food

isual programming without walls. Delphi 1.0 — and only Delphi 1.0 — makes it possible for the
V Windows 3.x environment. Now Delphi 2.0 offers it for Windows 95 development.

Just as it did the first time I supported in Windows 95). Test PowerBuilder Visual Basic 3.0 Delphi 1.0 Delphi 2.0
fired it up, Delphi continues It’s also important to note that (loops/sec.) 3.0 (16-bit) (16-bit) (16-bit) (32-bit)
to dazzle me with its myriad Windows 95 does not support Sieve 0.22 11.95 52.77 179.37
qualities: speed, power, ele- VBXes, thus Delphi 2.0 does Whetstone 0.04 1.41 4.70 15.53
gance, ease-of-use, extensibili- not support them. This could File write 0.05 0.42 0.74 2.89
ty, sound design, and — be painful if you’re relying on File read 0.05 0.33 1.75 5.28
something else. Let’s call it a VBX that hasn’t made the
extravagance. Yes, Delphi 2.0’s metamorphosis into an OCX, Delphi 2.0 also sports a greatly And speaking of the
feature set is nothing short of although most have. enhanced compiler that deliv- Informant Communications
extravagant. How else can you ers executables even faster than Group Web site — at
refer to a product which — There are other changes that those of Delphi 1.0 (see table). www.informant.com, natch
just for starters — offers over are integral with a move to Moreover, the compiler now — don’t forget to cast your
90 ready-made components? Windows 95. As Mr Wagner shares the same back-end as ballot for the First Annual
pointed out in October, there Borland’s C++ product, mak- Delphi Informant Reader’s
With no real head-to-head com- is no more 64KB limit, and ing it possible, for example, to Choice Awards. You can also
petition for Delphi 1.0, and a Integer and Cardinal values are compile .OBJ files directly have your say on our
less-than-hurried acceptance of now 32-bit. Also, Borland has into your Delphi applications. CompuServe forum (GO
Windows 95 in the business been careful to comply with all In short, Delphi 2.0 is an ICGFORUM), or by just
world, Borland decided to Windows 95 UI standards. impressive upgrade to an mailing in the Official Ballot
spend a little extra time with already awesome product, and you’ll find later in this issue.
Delphi 2.0 and add even more And okay, despite all the nice a testament to the solid foun- And about that ballot. It
features. The result is impres- things I said earlier, Delphi dation upon which it is built. contains two farshtunken
sive, and is described by DI 1.0 — or more properly, errors: 1) the results will
Contributing Editor, Richard Object Pascal 1.0 — does Moving to another topic, your appear in the April issue, and
Wagner, beginning on page 7. have one annoying character- response to our new Web site 2) the voting deadline is
istic: its meshugeh, 255-char- has been overwhelmingly posi- February 20, 1996. The win-
But this isn’t Richard’s first acter string type. But that’s tive. Here’s a sample: ners will be announced, for
look at Delphi 2.0. He initially been taken care of with the first time, in late March
previewed it for us in October Delphi 2.0. You can have Dear Editor, at Software Development 96
of last year. This month’s offer- strings as long as you like. Thanks for the web site! I’m West at Moscone Center in
ing is really an update that Better yet, they behave as an avid reader of Delphi San Francisco.
describes the changes made Pascal strings and as C (null- Informant and very glad
since then. In case you missed terminated) strings, which will you’ve added a new dimen- Thanks for reading,
that issue, let me give you a sure make those API and DLL sion to your publishing. Now
quick run down. calls easier. Besides the greatly I’m able to locate files ...
enhanced string type, Object spotlighted in the monthly
All that is necessary to turn a Pascal 2.0 offers two entirely articles. You don't know how Jerry Coffey, Editor-in-Chief
Delphi 1.0 application into a new data types: the variant useful that can be.
fully-functional, 32-bit, type can store a string, integer, Internet: [email protected]
Windows 95 application, is to or floating-point value and is Keep up the good work, CompuServe: 70304,3633
recompile it under Delphi 2.0 particularly useful for OLE and the web site. Fax: 916-686-8497
(unless, of course, you make automation; and the Snail: 10519 E. Stockton
an exotic API call, i.e. one that ANSIChar type has been Your loyal reader, Blvd., Ste. 142,
has changed or is no longer added to support Unicode. Wai Chong Elk Grove, CA 95624

FEBRUARY 1996 Delphi INFORMANT ▲ 2


Delphi Now Shipping: Apollo 2.0 For Delphi
SuccessWare International, applications. Apollo 2.0 sup-
T O O L S of Temecula, CA, is shipping ports CA-Clipper (NTX),
Apollo 2.0, an alternative to FoxPro2.x (IDX/CDX), and
New Products Borland’s Database Engine HiPer-SIx (NSX) systems.
and Solutions (BDE) in Delphi. Apollo 2.0 Apollo 2.0 introduces new fea-
is designed to be smaller and tures to the Delphi marketplace,
faster than the current XBase including Freeform-Text
DBMS engine, without Searching (allows text docu-
requiring the BDE or ments to be embedded in data
ODBC. files with instantaneous
The Replacement Database retrieval); VariField support
Engine (RDE) technology (fixed-length fields that auto-
allows for record-based matically expand to meet the
(xBASE) data-table naviga- input requirements);
tion and management syntax image/BLOb support in memo
during program develop- fields (storage and retrieval with-
New Delphi Book
ment, with minimal concern out intermediate files); condi-
Teach Yourself Database for the ultimate format of tional indexes (only adds the
Programming with Delphi in 21 Days
Nathan and Ori Gurewich
the database. As a “no-code” records meeting a query/FOR
Sams Publishing replacement for the BDE, condition); index SCOPES
Apollo 2.0 can be installed (instant index filters for control Contact: SuccessWare International,
into an existing application over the database views); and 27349 Jefferson Ave., Ste. 101,
with no source code changes. record-level data encryption. Temecula, CA 92590
This allows for multi-user Phone: (800) 683-1657 or
access of xBASE data files Price: Apollo 2.0, US$179; Apollo 2.0 Pro (909) 699-9657
from legacy applications. All (includes Delphi VCL source code), US$269. Fax: (909) 695-5679
concurrent-access record/file- Both are royalty-free, and include 30 days BBS: (909) 964-6891
locking is compatible with of free voice and unlimited fax, E-Mail: CIS: 74774,2240
existing FoxPro and Clipper CompuServe, or BBS technical support. CIS Forum: GO SWARE
ISBN: 0-672-30851-7
Price: US$39.99, Canada
$53.99 (569 pages, CD-ROM) Announcing BSS Business Systems Software
Phone: (800) 428-5331
Business Software Systems, on Delphi 2.0, Oracle and Framework includes a Single
Inc., of Fairfax Station, VA, InterBase databases, and Document Interface (SDI)
will release BSS Business Crystal Reports PRO. menu panel with custom
Systems, a line of accounting, DB Manager, General DBNavigator VCL, registration,
distribution, and business Ledger, Accounts Payable, database login, table mainte-
management 32-bit GUI Accounts Receivable, and nance, transaction processing,
client/server software based Bank Book will be available and setup screens. It also fea-
through an Early Experience tures a special development pro-
Program (EEP) in early 1996. ject to ease team development.
According to the company,
medium to large companies Price: Standard Edition starts at US$995
ready to upgrade their inter- per module for a 5-user license; Enterpise
nal systems to a Windows- Edition starts at US$5,000 per module for
based client/server platform a 10-user license. BSS Application
can select the modules FrameWork with TurboPower’s Orpheus
required. controls 1.0, US$495.
Source code is available for
developers to modify any of Contact: Business Software Systems,
the modules, and an applica- Inc., P.O. Box 7416, Fairfax Station, VA
tion framework is available to 22039
assist in writing corporate-spe- Phone: (703) 503-5600
cific modules. Fax: (703) 503-7901
The BSS Application E-Mail: CIS: 76652,2065

FEBRUARY 1996 Delphi INFORMANT ▲ 3


Delphi Add Spell Checking to Delphi Applications with EDSSpell
T O O L S Eminent Domain Software
of Laredo, TX recently
New Products released version 2.0 of its
and Solutions EDSSpell Checking
Component for Delphi. With
EDSSpell, developers can
add a spell checking engine
to any Delphi application.
Much like an OpenDialog or
PrintDialog component,
developers can drop a
SpellDlg component onto
their form. They can then
spell check a document with
one line of code:
New Delphi Book SpellDlg1.CheckMemo(Memo1);
The EDSSpell component
Delphi 2 Programming EXplorer
Jeff Duntemann, Jim Mischel, publishes several properties, include support for Price: US$99; upgrade from EDSSpell
& Don Taylor giving developers control TurboPower’s Orpheus com- Version 1.0, US$20.
The Coriolis Group
over language, dialog style ponents, and word/unique
(WordPerfect, Microsoft word count. Contact: Eminent Domain Software,
Word, or WordPro96), and Version 2.0 is a native 413 Gale St., Laredo, TX 78041
more. EDSSpell ships with Delphi component and Phone: (800) 246-5757 or
several languages including requires no additional DLLs. (210) 729-0123
125,000+ word English and There are no run-time royal- Fax: (210) 729-0011
380,000+ word Spanish dic- ties for distributing applica- E-Mail: Internet: [email protected] or
tionaries. Other new features tions with EDSSpell. CIS: 70600,3451

ISBN: 1-883577-72-1
Price: US$44.99,
Stylus Announces Telephony Support For Delphi
Canada $62.99
(1000 pages, CD-ROM)
Stylus Innovation, Inc., of VBX controls and a graphical transferring and conferencing.
Phone: (800) 410-0192 Cambridge, MA has announced workbench for creating tele- Included in the Visual Voice
the release of its telephony phony and fax applications toolkit is the Voice Workbench,
toolkit, Visual Voice for Delphi, that include interactive voice a graphical tool to define the
which allows Delphi developers response (e.g. touch-tone telephony components of an
to add telephony features to banking), fax-on-demand, and application and generate the
their applications. voice mail. Typical business corresponding application logic.
Visual Voice is a suite of systems built with Visual Voice The Workbench is used to cre-
include 24-hour customer ate and maintain all telephony
order services, benefits enroll- objects, including voice
ment hotlines, and brochure prompts, menus, and files.
fax-on-demand systems. Stylus’ 32-bit OCX version of
Visual Voice also features Visual Voice is currently in beta.
modules for text-to-speech and
voice recognition support. In Price: Visual Voice for Windows 3.x
addition, it provides interfaces begins at US$495. There are no run-time
to the following functions: fees required.
answering inbound calls, plac-
ing outbound calls, prompting Contact: Stylus Innovation, Inc., One
for touch-tone input when calls Kendall Square, Building 300, Cambridge,
are established, playing and MA 02139
recording voice files, sending Phone: (617) 621-9545
and receiving faxes, and inte- Fax: (617) 621-7862
grating with a PBX to perform E-Mail: Internet: [email protected]
call control functions such as Web Site: http://www.stylus.com

FEBRUARY 1996 Delphi INFORMANT ▲ 4


Borland Ships Three Versions of Delphi 2.0
News Scotts Valley, CA — Borland
International has released
Delphi 2.0 Developer
includes all the features
Integrated Intersolv PVCS
Version Control,
L I N E three versions of its 32-bit found in the Delphi 2.0 ReportSmith SQL edition,
Delphi application develop- Desktop edition, plus a Visual Query Builder, and
February 1996 ment tool for Windows 95 scaleable data dictionary, cached updates. This
and Windows NT: Delphi Multi Object Grid, approxi- client/server edition also
2.0 Desktop, Delphi 2.0 mately 100 VCL compo- includes integration with
Developer, and Delphi 2.0 nents, advanced data aware CASE tools, and client/serv-
Client/Server. components, a 32-bit version er documentation.
Delphi 2.0 Desktop fea- of ReportSmith, Borland Pricing for these three versions
tures a 32-bit optimizing Database Engine low-level of Delphi 2.0 are: Delphi 2.0
native code compiler, over 85 API support and Help files, Desktop US$499.95; Delphi
standard components, a suite ODBC support, a single-user 2.0 Developer US$799.95; and
of Windows 95 common Local InterBase Server, and Delphi 2.0 Client/Server
controls, data-aware compo- InstallShield Express. US$1999.95. For more details,
VCL Contest for
Delphi 2.0 Developers nents to build local database Designed for developers, it see Richard Wagner’s article
Sams Publishing and Borland Press applications, and data mod- also has additional experts “Delphi 2.0” beginning on
are holding a VCL competition for
all Delphi 2.0 developers. The ules for centralized data and templates, Winsight32 page 14. Additional product
contest categories include: integrity and business rules. for monitoring Windows information is available from
Interface Components,
Internet/Communications, It also has an object-orient- messaging, expanded Open Borland by calling (800) 331-
Database, Multimedia, and more.
The top five winning entries in ed, extensible component Tools API, team development 0877 or (408) 461-9195, or by
each category will receive US$100 architecture, visual inheritance interface (requires Intersolv’s visiting their Web site at
and a copy of Sams’ Delphi 2.0
Developer’s Guide, 2nd edition. and form linking, Windows PVCS), and the Visual http://www.borland.com.
The grand prize is US$1,000 95 support for OLE automa- Component Library source
and a copy of Delphi 2.0
Developer’s Guide, 2nd edition. tion and OCXes, as well as code and printed reference.
The deadline for all entries is
February 19, 1996. For complete 32-bit development with Win Delphi 2.0 Client/Server
Borland Announces
contest rules, visit Borland Online
at http://www.borland.com.
API support, a Database includes all the features of Agreement with Rios
Explorer to create and modify the Delphi 2.0 Desktop and Corporation
Delphi Informant Reader’s
tables, aliases and indices, and Developer editions, plus 32-
Scotts Valley, CA — Borland
Choice Deadline Extended an object repository to store bit SQL Link native drivers
The deadline for submitting bal- International Inc. has signed an
lots for the 1996 Delphi Informant and reuse objects and forms. with unlimited deployment
agreement with RIOS
Reader’s Choice Awards has Version 2.0 can create license, SQL Database
been extended to February 20, Corporation for the marketing
1996. To vote, complete and sub- reusable DLLs and stand- Explorer, SQL Monitor, a 2-
mit the ballot included with this and distribution rights of
issue. Readers may also vote
alone EXEs, and can still be user InterBase NT license,
InterBase for UNIX in Japan.
online using VOTE_DI.TX, an used for 16-bit Windows 3.1 new 16-bit Sybase System 10
electronic version of the ballot RIOS, the largest system inte-
available from our Web site development. It ships with and DB2 Native Drivers, a
grator in Japan, chose InterBase
(http://www.informant.com) Object Pascal documentation. Data Pump Expert,
or CompuServe forum because of its ability to scale in
(GO ICGFORUM).
a client/server environment,
Delphi Informant on the Web and its support of UNIX and
Elk Grove, CA — sections including magazines, Windows-based applications.
Informant catalogs, CDs, files to down- According to the RIOS
Communications load, advertising, ICG news, Corporation, they have been
Group, Inc. (ICG), ICG apparel, and general selling InterBase in Japan since
publisher of Delphi information. 1992. In addition, RIOS has
Informant, Paradox ICG also supports a adopted Delphi and InterBase
Informant, and CompuServe forum (GO as their standard development
Oracle Informant ICGFORUM), and an FTP tools for custom applications.
magazines, has site (ftp.informant.com). The RIOS Corporation has
launched an Companies and user groups already begun marketing
Internet Web site at interested in linking their Web InterBase. Currently, InterBase
http://www.infor- site to the ICG Web site should 4.0 for Sun OS and HP-UX
mant.com. From contact Carol Boosembark at platforms is shipping.
the initial Web [email protected], InterBase 4.0 for Solaris and
page, users can or call (916) 686-6610 AIX is slated for release in
choose from several ext. 16. Japan in early 1996.

FEBRUARY 1996 Delphi INFORMANT ▲ 5


Borland Endorses JavaScript
News Mountain View, CA —
Borland International Inc. is
or the server. JavaScript is
designed specifically for the
Netscape and Sun plan to
propose JavaScript to the W3
L I N E one of 28 companies to Internet and creating net- Consortium (W3C) and the
endorse JavaScript, released work-centric applications. It Internet Engineering Task
February 1996 by Sun Microsystems, Inc. also integrates with Java and Force (IETF) as an open
JavaScript is an open, cross- HTML. Internet scripting language
platform object scripting lan- Java is available to developers standard. JavaScript will be an
guage for creating applica- free of charge. The Java open, freely licensed proposed
tions on enterprise networks Compiler and Java Developer’s standard available to the
and the Internet. Kit as well as the HotJava entire Internet community.
The JavaScript language browser and related documen- Existing Sun Java licensees
complements Java, Sun’s tation are available from Sun’s will receive a license to
object-oriented, cross-plat- Web site at http://java.sun.com. JavaScript.
form programming language. In addition, the Java source In addition, Sun and
In addition, America code can be licensed for a fee. Netscape intend to make a
Online, Inc., Apple Details on licensing are also source code reference imple-
Computer, Inc., Architext available via the java.sun.com mentation of JavaScript avail-
Software, Attachmate Web page. able for royalty-free licensing.
Borland Developers
Corporation, AT&T, Brio To date, Sun has licensed Additional information on
Conference Heads to Technology, Inc., Computer Java to a number of leading Sun Microsystems is available
Anaheim
Borland International Inc. has
Associates, Inc., Digital technology companies, on the Internet at http://www.-
announced that the 7th Annual Equipment Corporation, including Borland, sun.com or, for Java informa-
Borland Developers Conference will
be held July 27-31, 1996 at the
Hewlett-Packard Company, Macromedia, Mitsubishi, tion, http://java.sun.com. For
Anaheim Convention Center Iconovex Corporation, Netscape, Oracle, Silicon more information on Netscape
in Anaheim, CA. Conference
speakers will be announced in
Illustra Information Graphics, Spyglass, and products, visit http://home.-
March 1996. For more Technologies, Inc., Informix Toshiba. Sun’s Workshop for netscape.com, send an e-mail
information contact Borland
at (408) 431-1000 or
Software, Inc., Intuit, Inc., Java Toolkit is scheduled for to [email protected], or call
http://www.borland.com. Macromedia, Metrowerks, release in Spring 1996. (415) 528-2555.
Inc., Novell, Inc., Oracle
Borland Online Set to
Corp., Paper Software, Inc., Database & Client/Server World Set
Precept Software, Inc., RAD
Focus on Products
Borland International’s World Wide Technologies, Inc., The Santa for Boston in March
Web site, Borland Online, will be
redesigned for 1996. According to Cruz Operation, Inc., Silicon Andover, MA — Scheduled Aaron Zornes, Michael
Borland, they are changing their
Web site to better serve application Graphics, Inc., Spider for March 26-28, 1996, Stonebraker, and Thomas
developers, moving from a corpo- Technologies, Sybase, Inc., Database and Client/Server Lipscomb.
rate-centric to a product-centric
focus. From the home page, devel- Toshiba Corp., Verity, Inc., World will feature over 800 In addition, Database &
opers can select a product and be and Vermeer Technologies, exhibits at the Hynes Client/Server World will fea-
directed to Web pages specific to
that product. Borland Online will Inc., have endorsed Convention Center in ture nine conferences:
also continue to upgrade and
increase the links from its Java JavaScript as an open stan- Boston, MA. Client/Server Tools and
World page, and increase content dard object scripting lan- With over 25,000 MIS pro- Application Development,
associated with the Legal Toolbox.
guage, and intend to provide fessionals expected to attend, Data Warehouse and
it in future products. Database & Client/Server Repositories, Systems
JavaScript is an object World is an exposition of Management for the
scripting language designed databases, tools, warehousing, Client/Server Environment,
for creating online applica- and application development Parallel Databases and
tions that link objects and products. VLDBs, Data and Object
resources on both clients and The event features database Modeling, Object-Oriented
servers. While Java is used by and client/server experts Dr E.F. Technologies, Middleware,
programmers to create new Codd, Jeff Tash, Richard Groupware Application
objects and applets, JavaScript Finkelstein, Chris Date, Ronald Development, and Multi-Tier
is designed for use by HTML G. Ross, Shaku Atre, Herb Architectures and Application
page authors and enterprise Edelstein, Ken Orr, Paul Partitioning.
application developers to Harmon, Roger Burlton, Larry For more information visit
script the behavior of objects R. DeBoever, Max Dolgicer, DCI’s Web site at
running on either the client Richard Winter, Ken Lownie, http://www.dciexpo.com.

FEBRUARY 1996 Delphi INFORMANT ▲ 6


On the Cover
Delphi 2.0

By Richard Wagner

Delphi 2.0
The Serendipitous Tool Makes Its 32-Bit Debut

erendipity. This is the word that came to mind when I first saw the latest

S field test of Delphi 2.0. The enhancements from earlier field tests were
both wonderful and unexpected. If you recall, in the October 1995 Delphi
Informant, we previewed Delphi 2.0 during its initial beta release. At that time,
it already sported some amazing enhancements over the 16-bit version of the
product, including: a 32-bit optimizing compiler, OCX support, Windows 95 UI
components, multithreading support, a new 32-bit database engine, new data
types, closer coupling with C++, and an enhanced IDE.

The Delphi 2.0 R&D team was obviously not content with the status quo. In subsequent
builds, there has been a steady influx of major new features that, when combined, make Delphi
2.0 one of the most extensive version upgrades I’ve ever seen. This article will focus on many of
the new features that have been added to the 32-bit product since our October survey.

New UI Controls
In addition to the eight
Windows 95 UI components
we discussed in October,
there are three new ones:
TListView, THotKey, and
TStatusBar. First, the
TListView control will enable
you to use one of the funda-
mental Win95 user interface
elements in your applica-
tions. Anywhere you go in
Windows 95 — such as My Figure 1: Windows Control Panel uses a ListView to present
Computer, Control Panel, or options to the user.
Windows Explorer — you
work with a ListView control (see Figure 1). It’s an intuitive way of displaying list infor-
mation in a variety of styles (Large Icon, Small Icon, List, or Details).

Using a TListView component, you can use this method of presentation in your Delphi appli-
cations as well. For example, Figure 2 shows a sample application called Resource Explorer
that ships with the current Delphi 2.0 build. Using TTreeView and TListView components, it
effectively presents resource data in a metaphor similar to the Windows Explorer. Given the

FEBRUARY 1996 Delphi INFORMANT ▲ 7


On the Cover

Figure 3: A sample application executing queries in different threads.

Diving inside the code, we must first separate the two queries
into different routines that we’ll call Query1Thread and
Figure 2: This sample Delphi application employs TreeView and
ListView components.
Query2Thread. The first query is executed when
Query1Thread is called:
success of the Windows 95 user interface, you’ll probably
want to incorporate TreeView and ListView components into function Query1Thread(parms: pointer) : LongInt; far;
begin
your application UIs. Form1.LongQuery.Open;
end;
The TStatusBar component provides a simple way for
developers to give their applications a standard Windows Then, the second query is executed when Query2Thread is
95 status bar, while the THotKey component allows the called:
user to quickly perform a specified action using a combina-
tion of keys (such as CC). All the Windows 95 UI com- function Query2Thread(parms: pointer) : LongInt; far;
begin
ponents available with Delphi 2.0 are briefly described in Form1.ShortQuery.Open;
the sidebar on page 12. end;

Multithreading Now we can add the multithreading code. The Button2Click


New UI controls may change the way you present informa- procedure uses the createThread method to create threads for
tion to the user, but a new feature called multithreading the queries and then set a thread’s priority based on the posi-
could fundamentally change your approach to application tion of its associated TrackBar (note that the T3 and T4 vari-
development. ables are of type THandle):

Using Delphi 2.0, you can create threaded applications. It procedure TForm1.Button2Click(Sender: TObject);
var
will be important for developers to resist the urge of going ThreadID : dWord;
“thread crazy” and create multiple threads in even the tiniest begin
of applications. With this caveat in mind, when multi- T3 := createThread(nil,0,@Query1Thread,nil,0,threadID);
setThreadPriority(T3,FirstTrackBar.Position);
threading is used wisely, it can be extremely powerful, and is T4 := createThread(nil,0,@Query2Thread,nil,0,threadID);
perhaps the most convincing factor for developing Win32 setThreadPriority(T4,SecondTrackBar.Position);
applications instead of creating programs for the Windows end;
3.1 16-bit environment.
Many articles on multithreading (especially on how to best
In our October preview, we had a high-level discussion on employ it) are surely in the works. But even when running
Delphi 2.0’s multithreading support. In this installment, let’s this rudimentary demonstration, you can visibly see the
take a closer, interactive look at multithreading by building a results of what threading can do for you.
basic two-thread application. (Note that documentation for
multithreading was minimal at press time.) Database Enhancements
Delphi 2.0 now provides several new enhancements that data-
Suppose you want to execute two queries simultaneously in an base application developers will definitely appreciate. These
application, but would like to prioritize the queries. To do include cached updates, TField lookup fields, table filters, and
this, you could place the queries into separate threads. Figure 3 two enhanced data controls.
shows the UI for this example. The priority of the two threads
is dependent on the position of the TrackBars shown on the Cached Updates. An important part of any client/server
left of the form, and the results of the queries are shown in the application is how the client works with the SQL server to
two TDBGrid controls. process data. In Delphi 1.0, every client data event is individ-

FEBRUARY 1996 Delphi INFORMANT ▲ 8


On the Cover

ually sent to the database as a separate SQL transaction. This Delphi 2.0 makes filtering much easier by adding filter support
process is known as navigational updating. to the basic TTable component. By setting the TTable’s Filtered
property to True, each record is evaluated when it’s retrieved by
Delphi 2.0 continues to support navigational updates, but adds the data set. You can configure a filter to “block out” all records
a new transaction mechanism called cached updates. Rather that do not match your criteria by placing the data filter in the
than sending each data update as a separate SQL transaction, OnFilterRecord event handler for the TTable object.
cached updates gather multiple client transactions and send
them in a batch to the server within a single SQL transaction. For example, suppose you want to filter out all records in a
Customer table for people not living in Massachusetts. To do
Cached updates have several uses, but can be particularly effec- this, you would write the following filter:
tive in situations where network traffic and record contentions
are major concerns. However, keep in mind that both naviga- procedure TForm1.CustomerTblFilterRecord(
DataSet: TDataSet; var Accept: Boolean);
tional and cached update models have advantages and disad- begin
vantages. Therefore, the developer is responsible for employing Accept := DataSet['State'] <> 'MA';
the proper transaction method within a given context. Not end;

only do cached updates work on SQL databases, but they also


work on local databases such as Paradox and dBASE. Enhanced Data Controls. Data presentation just got much
easier in Delphi 2.0 with two new/enhanced data controls: a
To enable cached updates, set the CachedUpdates property of much improved TDBGrid component and a new
a TTable object to True. Then, any updates made by the user TDBCtrlGrid component. Both of these allow you to simulta-
will be displayed on screen, but will not actually be sent to neously display multiple records, but in different ways.
the database until the TTable method ApplyUpdates is execut-
ed. You can cancel the current record’s update using the Many people consider the TDBGrid component one of the
CancelCurrentUpdate method or cancel all updates within the weaker components in Delphi 1.0. You have little control
current transaction using CancelUpdates. over the columns and cannot easily create lookup field sup-
port within the grid itself. These deficiencies alone probably
Lookup Support for TField. In addition to providing simply cal- sold many third-party replacements for TDBGrid. Conversely,
culated field support, TField objects in Delphi 2.0 also furnish the new TDBGrid has many improvements, the two most
support for lookup fields. This capability will allow you to speci- important of which include a Columns Editor that allows you
fy lookup fields to receive a value from another table. Lookup to set an array of properties for a given column (see Figure 6),
fields are created similarly to calculated fields using the Fields edi- and the ability to display TField lookups (see Figure 7).
tor. A New Field dialog box is displayed in which you can specify
its field type (see Figure 4). You then set four properties —
Figure 6:
LookupTable, LookupMasterFields, LookupDetailFields, and
The DBGrid
LookupResultField — for the read-only lookup field. Figure 5 component’s
shows a lookup field in a TDBGrid. Columns Editor
allows you to
Table Filters. In developing database applications, developers customize the
look of each
periodically want to use both queries and filters to display a
column in the
limited set of data to the user. To use filters in Delphi 1.0, grid.
you must use a third-party component or create your own
because TTable does not provide built-in filtering.

Figure 4:
The New Field
dialog box.
Figure 7:
New and
improved
TDBGrid
Figure 5: supports
A lookup combination
field is boxes and
shown in other data
the controls.
enhanced
TDBGrid
component.

FEBRUARY 1996 Delphi INFORMANT ▲ 9


On the Cover

The TDBCtrlGrid is a powerful new component for display- updated. Additionally, if you change the event handler for the
ing data. Like TDBGrid, TDBCtrlGrid allows you to display Find Next button on the StdDataForm, the associated handler
multiple records of a table, but in a more flexible manner. in GridViewForm and RecGridForm will be updated as well.
Not only can you display any data aware control in it, but
you can also specify how many rows and columns of records The importance of visual form inheritance is clear for object-
to display. If you have ever used Paradox for Windows, you oriented programmers. In Delphi 2.0, you can extend the
will find that the TDBCtrlGrid is essentially the equivalent of OOP boundary to now include forms, as well as data mod-
Paradox’s multi-record object. Figure 8 shows an example of a ules (which we’ll discuss below).
TDBCtrlGrid.
Data Modules and Visual Form Linking. When developing
multi-form database applications in Delphi 1.0, I find the form-
Figure 8:
specific nature of data access components to be frustrating.
The new Because of this limitation, if you use a table across 16 different
DBCtrlGrid forms, you must place a TTable component on each of the 16
component forms if you linked that table to a UI object. Additionally, if you
gives you need to synchronize the data across multiple forms, you are
greater
flexibility in
forced to write the code to perform the synchronization.
presenting
multiple Thankfully, Delphi 2.0 added data modules to combat this
records of unnecessary chore. A data module serves as a container for non-
data. visual database components (see Figure 10). Using a data mod-
ule, you can manage your database access code from a central-
ized facility. You can also keep all your database logic in this sin-
gle location rather than spread throughout the application.

New IDE Features A data module is form-like, so you can work with data mod-
The development environment of Delphi 2.0 continues to ules quite similarly as you do with Delphi forms. However, a
mature with the addition of visual form inheritance, data data module does not have a UI and thus will never appear to
modules, visual form linking, and an Object Repository. the end user of your application.

Visual Form Inheritance. One of the biggest shortfalls of Delphi Having a centralized data house is possible through visual
1.0 was the inability to truly subclass TForm objects. Delphi 2.0 form linking. With this functionality, you can link a data
answers this deficiency with visual form inheritance. This func- component on one form to a data aware component on
tionality allows you to create descendant forms from an original another form or data module. Delphi 2.0 links forms and
base form, all without writing any code. Unlike Delphi 1.0’s ver- data modules by linking their associated unit files. Therefore,
sion of “form templates” (that were really just copies of your once you declare a form’s unit in the uses clause of another
favorite forms), Delphi 2.0 allows you to create true “abstract” form, you can access any of its data components. Figure 11
forms within and across projects. When you update an ancestor shows the dot notation reference in the Object Inspector.
form, all its descendants are updated at the same time. You can
even view this simultaneous update on screen. Object Repository. Delphi 2.0 uses the Object Repository as
an organizing tool to manage form objects, data modules,
To illustrate, we’ll look at a demonstration application called project experts, and project templates. The Object Repository
Gdsdemo. It’s an example of presenting the same data using is really an enhanced version of Delphi 1.0’s Gallery. After
both a TDBGrid and a TDBCtrlGrid. However, it also shows adding an object to this facility, you can inherit, reference, or
the power of inheritance. copy the object in future applications. You’ll notice the con-
tents of the Object Repository are displayed when you select
Gdsdemo uses a base form called GDSStdForm (see Figure 9) the File | New from Delphi’s menu (see Figure 12). In multi-
that has the company logo header used by two forms. A developer environments, you can maintain a single network-
descendant form of GDSStdForm, called StdDataForm (also based Object Repository and reference it from every develop-
shown in Figure 9), adds filtering controls onto the base form. er workstation.

Neither of these forms are ever seen by the user — they are Noteworthy Changes
simply abstract objects being used by their children: There are many miscellaneous changes that are noteworthy
GridViewForm and RecGridForm (again, see Figure 9). Both of (including, but not limited to):
these forms inherit the properties, events, and methods from • The File | Use Unit command adds the select unit to the
their two ancestor forms. Therefore, if you change the bitmap uses clause of the current unit file (or associated unit file
of GDSStdForm, all other forms in the application would be of the active form).

FEBRUARY 1996 Delphi INFORMANT ▲ 10


On the Cover


Figure 9: Delphi 2.0 features visual form inheritance. In this example, the form StdDataForm (second from the top) inherits all the proper-
ties and behavior of GDSStdForm. In turn, GridViewForm and RecGridForm (at the bottom of the figure) inherit from StdDataForm.

• A new comment field object will automatically be


symbol ( // ). In created and set to that field. This
addition to pre- enhancement will make form
vious comment creation much easier.
delimiters ( {} ) • The Client/Server edition of
and ( (* *) ), Delphi 2.0 will provide built-in
all words to the Figure 10: Delphi 2.0 supports centralized, PVCS version control support.
right of // are reusable holders for nonvisual database [For a complete description of
considered com- components called data modules. They’re the three new editions of
specialized Delphi forms that can be modi-
ments. fied just as other forms. Delphi 2.0 that are available,
• The Thread see the news item “Borland
Status window will allow you to monitor threads run- Ships Three Versions of
ning in your application. Delphi32” on page 5.]
• TSession is now a component on the Component Palette.
• Non-visual components are now easier to distinguish. You Conclusion Figure 11: The new visual
can select the Show Component Captions option to see their No matter how you use Delphi form linking feature allows
labels in design mode. 2.0, it’s proving to be a convinc- you to access data controls
• Delphi 2.0 supports unit aliasing. You can use an alias to ing upgrade. Database developers in other forms using dot
notation.
refer to another unit file. will be flooded with a host of
• The Fields editor now provides drag-and-drop support. You new database tools, components,
can drag a field from the Fields editor onto any form and a data modules, and database engine enhancements. General

FEBRUARY 1996 Delphi INFORMANT ▲ 11


On the Cover

Windows 95 Common UI Controls


Delphi 2.0 provides new Windows 95 common UI controls on its TTreeView. TreeView is a Win95 version of the Outline component in
Component Palette. Figure A illustrates the use of several of these Delphi 1.0. The TreeView component is used quite often in Windows
controls. 95. Look at the Windows Explorer or Delphi 2.0’s Database Explorer
(see Figure 7 earlier in the article) for other examples of a TreeView.
TTreeView TTrackBar TPageControl
TProgressBar TRichEdit TListView. The ListView control encapsulates the UI capabilities
exemplified by Windows 95 Control Panel.

TTrackBar. The TrackBar is a “slider like” component used to adjust


values that fall within a continuous range.

TProgressBar. The ProgressBar is a Win95 “percentage meter” that


enables you to show the percentage remaining in a lengthy process.

THeaderControl. Enhancing the capabilities of the 16-bit Header com-


ponent, the HeaderControl is used to display headings above columnar
data. You can divide the header into multiple sections when you need
to place a heading above multiple columns of text or numbers.

TStatusBar. The StatusBar allows you to add standard Windows 95


TTabControl. The TabControl allows you to create a set of tabs. If status bars to your applications.
you want the tabs associated with “pages,” then use the PageControl
component. TRichEdit. The RichEdit box goes beyond the basic editing capabili-
ties of the text box to support character properties (font, color, etc.)
TPageControl. PageControl is used to create Win95-style tabbed and paragraph properties (alignment, tabs, numbering, etc.).
dialog boxes. With this component, you can add pages more easily
than with its Delphi 1.0 predecessor (TabbedNotebook) by right- TUpDown. Used in conjunction with an edit box, the UpDown con-
clicking and choosing New Page from the menu. Another ease-of- trol is typically used to create a circular loop of input values.
use feature is the ability to activate a page by clicking its tab with
your mouse rather than having to change the ActivePage property THotKey. The HotKey component performs a specified action using a
in the Object Inspector. combination of keys (e.g. CC).

and OOP programmers will definitely welcome its closer


C++ integration, optimizing backend compiler, ability to
create OLE servers, and multithreading support. And every-
one will enjoy a more sophisticated IDE with its visual form
inheritance, Object Repository, and other new features.

As much as I love Delphi 1.0, I recognize that it has some


missing pieces. Quite serendipitously, many of the gaps in
Delphi 1.0 have been filled in its 32-bit successor. ∆

This article is based on a prerelease version of Delphi 2.0 and


may describe features that differ or are entirely absent from the
shipping version.

Richard Wagner is a Chief Technical Officer for Acadia Software (formerly IT


Figure 12: An enhanced version of the Delphi 1.0 Gallery, the New Solutions/Boston) located in the Boston, MA area. He is author of several Paradox,
Item dialog box is displayed when you select File | New from the Windows, and CompuServe/Internet books, and is also a member of Team Borland on
Delphi 2.0 menu. CompuServe. Richard can be reached on CompuServe at 71333,2031 or via the
Internet at [email protected].

FEBRUARY 1996 Delphi INFORMANT ▲ 12


Informant Spotlight
Delphi 1.0 / Object Pascal

By Jeff Chant

DBOutline
Using TOutline to Manage Hierarchical Data

elphi’s TOutline component is a versatile tool for viewing hierarchical-

D ly-organized data. One tremendous example of its power is its ability


to manipulate the hierarchy of data through drag-and-drop. TOutline
can even be used to navigate a DataSet and update physical data records.

Most developers will appreciate the value of this feature when confronting a table that refers
to itself for a parent record. Such self-referencing tables are numerous in the business world:
employee files referring to themselves for a supervisor; address masters referring to themselves
for a parent address; and business segment tables referring to themselves for a parent segment.
These recursive relationships are often many levels deep, and the deeper the nesting becomes
the more difficult it is for end-users to manage them with normal grid- or form-based inter-
faces. Solving this problem demands a tool capable of editing hierarchically-related data. The
TOutline component — a hierarchy-oriented tool — fulfills this requirement perfectly.

Figure 1 shows a sample application that accesses a


company’s employee data. Employees are nested
under their respective supervisors. Clicking on an
outline node (an employee) causes the TDBEdit
fields to display the information associated with
that node. For example, if employee 105 is
dragged and dropped on employee 121, then 105
becomes a child node of 121, and the Supervisor
field is updated to reflect the new parent record.
This application can be used to maintain the hier-
archy of any recursive table with only minor
changes to the source code.

Producing the sample application is a multi- Figure 1: This application provides a hierar-
stage process. First, make a self-referencing chic view of a self-referencing employee
table. The user can manipulate the hierarchy
table. Next, create the form and add the compo- (i.e. the contents of the Supervisor field)
nents. Third, construct an algorithm to popu- using drag-and-drop.
late the TOutline component with information
from the self-referencing table. Once the algorithm is complete, implement drag-and-
drop procedures to allow the manipulation of the outline nodes. Finally, establish a sim-
ple procedure to keep the current record of the TTable synchronized with the currently
selected TOutlineNode.

FEBRUARY 1996 Delphi INFORMANT ▲ 13


Informant Spotlight

Creating a Self-Referencing Table • Create a simple form.


Before the DBOutline program can be created, a self-refer- • Create a form using TTable objects.
encing table must be constructed. The example in Figure 1 • Table Name EMPL2.DB in DBDEMOS.
uses a modified version of the Employee (EMPLOYEE.DB) • Select all Available Fields.
table that ships with Delphi. It’s accessed using the default • Vertical field layout.
alias, DBDEMOS. Using the Database Desktop, the modi- • Top label placement.
fied table is produced as follows: • Generate a main form.
• Select File | Working Directory.
• Select the Alias DBDEMOS, then click on OK. The Database Form Expert has created a new main form for
• Select Utilities | Copy. the project, automatically adding and initializing the TTable
• In the Copy dialog box, select DBDEMOS for Drive (or Alias). and TDataSource components, as well as a TDBEdit compo-
• Select EMPLOYEE for Copy File From, and enter EMPL2 in To. nent for each field in the table. Our example application,
• Select OK to perform the copy. however, does not require the TDBNavigator component, so
delete it from the form.
This procedure creates a new Paradox table named EMPL2.DB
that is a replica of the Employee table. To make the table self- In addition, make these changes:
referencing, a new field is added to hold the employee number • Change the TScrollBox component’s Align property to
of each employee’s supervisor. To add this field and define the alRight.
self-referencing relation to the table, proceed as follows: • Drag the left border of the TScrollBox component to the
• Use File | Open | Table to open EMPL2, and select Table | right, leaving the TScrollBox just wide enough to view all
Restructure Table to modify it. the fields contained within (see Figure 1).
• Add a field named Supervisor of Type I (i.e. Long Integer). • Add a TSpeedButton component to Panel1, with the glyph
• Save the changes to the table, then select Table | DIRECTRY.BMP (located in \DELPHI\IMAGES\BUT-
Restructure Table to modify it again. TONS) assigned to its Glyph property. This speedbutton
• Select the Table Properties combo box and select Referential is used to execute the algorithm that loads the table data
Integrity. Select Define to create a new reference. Create the into the outline.
reference (see Figure 2), and name the reference EmpSuper. • Add a TOutline component to Panel2, and change its
Align property to alClient.
• Add a TTimer component to Panel1. Set its Enabled prop-
erty to False, and its Interval property to 50. The timer
facilitates drag-and-drop, as we’ll see later.
• Set the Enabled property of all TDBEdit fields to False.

When completed, the form should resemble the sample


shown in Figure 1.

Prior to Calculations
The information shown in the sample application includes
the last and first names of the employees. Rather than explic-
itly populating the outline with the string values of both the
LastName and FirstName fields, we’ll use a single calculated
field generated by the Fields editor.

Access the Fields editor by


double-clicking on the form’s
Figure 2: Using the Database Desktop to create EMPL2. TTable component. Before
defining the calculated field,
The table is now self-referencing. Note that the new Supervisor add all fields by selecting Add,
field must be saved before the referential integrity is added. This then OK. Pick Define to create
is because adding a self-referencing referential integrity name the calculated field definition.
must be performed with no other modifications to the table. Give the calculated field a
Field name of DisplayField,
Creating the Form a Field type of StringField,
The Employee form can be built using the Database Form and a Size of 45 (see Figure 3).
Expert. First, create a new project and remove the default
form using the Project Manager. Then select Help | Database Using Table1’s OnCalcFields Figure 3: Creating a calculated
Form Expert and make these selections within the Expert: event, compute the value of field.

FEBRUARY 1996 Delphi INFORMANT ▲ 14


Informant Spotlight

Populating the Outline


The example application uses a nested algorithm to popu-
late the outline. To keep the code modular and easy to
maintain, the algorithm is divided among six procedures:
the OnClick event handler of the TSpeedButton component,
and user-defined ParentLoad, ChildLoad, ChildReIterate,
ProcessChildRecord, and AddNodeText. Figure 5 illustrates
how these procedures are nested.

Figure 5:
Sequence of
procedural
calls used to
populate
the outline.

Figure 4: The completed form design, showing the event handler


for OnCalcFields. The user-defined procedures are intended to be called only
in sequence and only by the OnClick event of SpeedButton1.
the calculated field. To access the event’s handler, select the Therefore, declare them in the private section of UNIT1, as
TTable component, then select the Events page of the shown in the full source in Listing One on page 19.
Object Inspector. Double-click the OnCalcFields event to Here is the methodology behind the algorithm:
create the handler and enter the code shown in Figure 4. • Use the Add method of TOutline to add a master node
containing the text Employees. Load the EmpNo values
At this point, the shell of the sample application is complete. of records in which the Supervisor field is blank into the
Now let’s add the extras that make it easy to modify the outline using the AddChild method, as children of the
application to access other self-referencing tables. The sample master node Employees. These are the top-level parent
assigns the various field and index names it uses to constant records since they are not children of any other records.
values, then references the constants throughout the code so • Load the EmpNo values of all remaining records (i.e.
the application can be made to access a different TTable by Supervisor is not blank) into the outline, using the
performing only three steps: AddChild method, as children of the outline node that
1) Modify the OnCalcFields event of Table1 to create corresponds to Supervisor. Reiterate through the table
DisplayField from the new field(s). until these records have been added.
2) Assign the new field/index names to the constants. • Beginning at Table1.First, read each record, search the
3) Change the form’s TDBEdit components to access the new outline for the node representing the record using the
fields, adding/deleting TDBEdit components as necessary. GetTextItem method, and replace the node’s Text property
with a string consisting of EmpNo concatenated with
The constants are declared just before the implementation DisplayField. This procedure simply adds the employee’s
section as: name to the node, which formerly contained only the
string equivalent of EmpNo.
const
{ Index to sort the table by }
SortIndexName = 'ByName'; The OnClick event of SpeedButton1 first calls the
{ Field to display after SeekField }
ParentLoad procedure that loads the key fields of all top-
DisplayField = 'DisplayField';
{ The key field of the self-reference } level parent records. Then OnClick calls the ChildLoad
SeekField = 'EmpNo'; procedure, which calls ChildReIterate and
{ The field referencing the key field } ProcessChildRecord to load the key fields of all other
RecursiveField = 'Supervisor';
records. Finally, OnClick calls the AddNodeText procedure
that adds the text of calculated field DisplayField to the
Although the sample application’s source code accesses
appropriate TOutlineNode.
SeekField as a surrogate name to EmpNo, and
RecursiveField as a surrogate to Supervisor, the article will
continue to address these fields by their true names to
Understanding the Algorithm
To understand the algorithm, it’s important to know how the
ensure clarity.
TOutline component assigns and maintains the indexes of its
nodes. Each item in a TOutline is contained in a TOutlineNode
The next task is to populate the TOutline component from
object and has a unique Index number corresponding to its posi-
the data in Table1.
tion in the outline. The first TOutlineNode has an Index of 1,
and the last has an Index equal to the number of items in the

FEBRUARY 1996 Delphi INFORMANT ▲ 15


Informant Spotlight

outline. Index numbering progress-


es sequentially regardless of nested procedure TForm1.ChildLoad(Sender: TObject);
begin
level (see Figure 6). ReIterate := True;
IterateWithoutAdd := False;
Throughout the algorithm, while ReIterate do
begin
which TOutlineNode a child ReIterate := False;
should be added to is indicated ChildAdded := False;
by these indexes. To determine while not Table1.eof do
begin
the Index property of a node that ChildReIterate;
is not currently selected, the sam- Table1.Next;
ple application uses TOutline’s end;
Table1.First;
GetTextItem and GetItem meth- if ReIterate and
Figure 6: The Indexes of ods. GetTextItem searches for a not ChildAdded then
the TOutlineNodes are num- text string in the outline and IterateWithoutAdd := True;
end;
bered sequentially from top returns the Index of the first node end;
to bottom.
whose Text property is a match.
GetItem returns the Index of the node at the X, Y coordinates Figure 7: The ChildLoad procedure adds all records with a non-
of the mouse pointer. zero value in the Supervisor field. The while loop steps through the
table until there are no more records to add to the outline.
In Listing One, notice that the ParentLoad procedure first
creates a master parent node called Employees by calling eof loop returns with a ReIterate of True, the outer while
TOutline’s Add method. As the first node in the outline, ReIterate loop will reprocess the table.
the master parent node has an Index of 1. All other valid
employee records are nested underneath the Employees In the next iteration, the parent record of the employee that
node to facilitate the movement of employee nodes via could not previously be included may (depending on the level
drag-and-drop. (By nesting all employees under the of nesting) have been added, allowing the employee record to
Employee’s node, a child record can be dragged and be attached as its child. This repeated looping causes the out-
dropped on the Employee’s node to raise it to a top-level line’s population to become slower as the nesting becomes
parent position. We’ll discuss this more later.) deeper and more numerous. However, the repeated looping
does have an escape clause (which we’ll discuss later).
Once the master parent node is created, ParentLoad simply
loads all top-level parent records (those that have a zero value Child’s Play
in the Supervisor field) into the TOutline component using The ChildReIterate procedure that is called by ChildLoad has
the AddChild method. The nodes are added as children to a simple function. It examines all non-top-level records
node Index1, the master parent node. (Supervisor <> 0) and, if they do not exist in the outline,
calls procedure ProcessChildRecord. It uses the GetTextItem
The next procedure, ChildLoad, drives the addition of all records method of TOutline to determine if the record has previously
that are not top-level parents (those that have a non-zero value in been added. For example, if GetTextItem returns an Index of
the Supervisor field) into the TOutline component. ChildLoad 0, a TOutlineNode containing the text does not exist, and the
loops through the entire table repeatedly until there are no more record should be processed by ProcessChildRecord.
records that must be added to the outline (see Figure 7).
ProcessChildRecord must determine if the parent record of the
An Iteration employee exists in the outline (see Figure 8). GetTextItem
ReIterate is a Boolean variable that, if True, indicates that searches for and returns the Index of a node containing the
there are still employee records to add to the outline. The string equivalent of the record’s Supervisor field. If the Index
value is changed in the ProcessChildRecord procedure if returned is not zero, it adds the employee, using the
records are outstanding. If ReIterate is True, ChildLoad will AddChild method, to the parent record’s TOutlineNode via
continue to loop through Table1 attempting to add the out- the returned Index. If the Index returned is 0, it assigns a
standing employee records. value of True to ReIterate.

It will probably take several iterations of the table before Within ProcessChildRecord is the escape clause to the repeat-
all records are successfully added to the outline. An ing loop — if the previous loop added no new records to
employee record cannot be added to the outline if its par- the outline (i.e. Boolean variable, IterateWithoutAdd, is
ent has not yet been added. This can happen if the True), the current loop will add any remaining records as
employee’s parent record is the child of another record, children of node Index 0, indicating the parent records
and the employee’s parent record appears after the employ- could not be found. Orphaned records will never occur if
ee record within the indexed table. If the inner while not the self-referencing referential integrity name was added to

FEBRUARY 1996 Delphi INFORMANT ▲ 16


Informant Spotlight

the OnMouseMove, OnDragOver, and OnDragDrop events of


procedure TForm1.ProcessChildRecord; the TOutline component. Note that OnMouseMove triggers
var
NodeIndex: integer; the dragging state rather than the OnMouseDown event that is
begin generally used in Borland’s drag-and-drop examples. The rea-
with Table1 do son for this will become clear.
begin
NodeIndex := Outline1.GetTextItem(
FieldByName(RecursiveField).AsString); To enable drag-and-drop, enter this code into the MouseMove
if NodeIndex = 0 then event handler:
begin
ReIterate := True;
if IterateWithoutAdd = True then with Outline1 do
begin if not Dragging then
Outline1.AddChild(0, if (Shift = [ssLeft]) then
FieldByName(SeekField).AsString); BeginDrag(False);
end;
end Running the application with this code, however, highlights
else
begin a problem: a node can only be dragged and dropped onto
ChildAdded := True; another node that is currently visible in the outline. It can’t
Outline1.AddChild(NodeIndex, be dropped on a node that has scrolled off the viewing area.
FieldByName(SeekField).AsString);
end; If drag mode is not enabled, dragging from the outline to an
end; area above the control makes the outline scroll downward.
end; Likewise, dragging to an area below the control scrolls
upward. However, after drag mode is enabled, this feature of
Figure 8: The ProcessChildRecord procedure determines if the par- the TOutline control is disabled.
ent record is in the outline.

the Paradox table as instructed earlier. The code is added This problem is solved by using the OnMouseMove event to
simply as a safety precaution. For instance, if the applica- trigger BeginDrag. Code can be added to the MouseMove
tion is modified to access a table with no recursive relation event handler that will note, if the outline is currently drag-
defined (or a table unable to accept or enforce one), the ging, whether the mouse pointer has moved beyond the
handling of orphaned records will prevent the algorithm outline’s borders. If so, the outline will be scrolled in the
from entering an endless loop. appropriate direction. However, the OnMouseMove event
shouldn’t actually cause the scrolling, or scrolling will stop
One More Loop when the mouse ceases moving. Instead, the OnMouseMove
As a final step, AddNodeText loops once more through the event should enable the TTimer component, and TTimer’s
table, locating the TOutlineNode that corresponds to each OnTimer event will handle the scrolling. The completed
record, and replacing the node’s text: MouseMove handler is shown in Figure 9.

(EmpNo) with EmpNo + ' ' + DisplayField The X and Y coordinates of the mouse pointer are com-
pared to the TOutline top and left coordinates (0) and
This step can’t be performed as the nodes are added because of height to determine if the TOutline should be scrolled and,
the way the GetTextItem method operates. The text of the node if so, in which direction. The Boolean variables ScrollUp,
must exactly match the text being searched to find a match. ScrollDown, ScrollLeft, and ScrollRight have their values set
depending on the scrolling direction. These variables
If nodes were added with: instruct the TTimer component after it is enabled. Note
that TTimer is disabled if the outline should not be
EmpNo + ' ' + DisplayField scrolled. This stops TTimer from continuing to scroll after
the mouse has been moved back within the borders of the
then before seeking a node corresponding to the Supervisor TOutline.
field of the current record, the record pointer would have to
be moved to the record whose EmpNo field contained the The OnTimer event of TTimer evaluates the current dragging
value of Supervisor. Here, the DisplayField value would have state and the Boolean variables (ScrollUp, ScrollDown,
to be retrieved for use in the search. This would create at least ScrollLeft, and ScrollRight), and it sends the appropriate mes-
one, and usually several, SetBookMark/FindKey/GotoBookMark sage to the Outline control’s window. If TOutline is not in a
combinations for each child record — a much greater perfor- dragging state, TTimer is disabled. This ensures that the timer
mance hit than one loop through the table. is stopped if the left button is released while the mouse is still
outside the boundaries of the TOutline (see Figure 10).
Drag and Drop
With the TOutline populated, the task remains to enable WM_VSCROLL and WM_HSCROLL are the messages
drag-and-drop of the TOutlineNodes. To accomplish this, use sent to a window when its vertical and horizontal scroll

FEBRUARY 1996 Delphi INFORMANT ▲ 17


Informant Spotlight

with Outline1 do that the source of the OnDragOver event is the TOutline con-
begin trol and, if so, accept a drop if it occurs.
if not Dragging then
begin
if (Shift = [ssLeft]) then The OnDragDrop event must trigger the move of the dragged
BeginDrag(False); node to be a child of the dropped-on node (moving all its
end children with it). In addition, the OnDragDrop event must
else
begin update the dragged node’s corresponding record in Table1 to
if (Y < 0) or (Y > Height) or reflect the new Supervisor. This is accomplished in four steps:
(X < 0) or (X > Height) then 1) Extract the EmpNo values from the dragged outline node
begin
ScrollUp := False; and the dropped-on outline node.
ScrollDown := False; 2) Move the TTable record pointer to the dragged outline
ScrollLeft := False; node’s employee number using TTable’s FindKey
ScrollRight := False;
if Y < 0 then method.
ScrollUp := True 3) Call TTable’s Edit method, move the dropped-on outline
else if Y > Height then node’s employee number into the record’s Supervisor field,
ScrollDown := True;
if X < 0 then and call TTable’s Post method.
ScrollLeft := True 4) Call TOutline’s MoveTo method to move the dragged node
else if X > Height then and its children to the dropped-on node.
ScrollRight :=True;
Timer1.Enabled := True;
end The handler checks to ensure that a parent is not being dragged
else to one of its own children before accepting the drop. This pre-
Timer1.Enabled := False;
end;
vents the creation of a cyclical reference (i.e. employee 102 is
end; the supervisor of employee 54, and employee 54 is the supervi-
sor of employee 102). If this check were not put in place, and a
procedure TForm1.Timer1Timer(Sender: TObject);
cyclical reference was created, the node would not actually
begin move until the outline was cleared and reloaded. During
with Outline1 do reload, the records would be added as orphaned records.
begin
if Dragging then
begin Notice that, if the dragged node is dropped on the master
if ScrollUp then parent Employees, the extracted EmpNo value is blank.
SendMessage(Handle, WM_VSCROLL, SB_LINEUP,
SB_THUMBTRACK)
Hence the purpose of the master parent — drop a node on it
else if ScrollDown then and the node becomes a top-level parent.
SendMessage(Handle, WM_VSCROLL, SB_LINEDOWN,
SB_THUMBTRACK);
if ScrollLeft then
Keeping the Table Synchronized
SendMessage(Handle, WM_HSCROLL, SB_LINEUP, To keep the contents of the TDBEdit fields synchronized with
SB_THUMBTRACK) the currently selected node on the outline, the record pointer
else if ScrollRight then
SendMessage(Handle, WM_HSCROLL, SB_LINEDOWN,
of the TTable must be moved whenever the currently selected
SB_THUMBTRACK); node changes. This is best accomplished through the OnClick
end event of the outline. The key field value is extracted from the
else
Timer1.Enabled := False;
outline node, and the FindKey method of Table1 is called to
end; reposition the record pointer.
end;
To complement record synchronization, there are lines
Figure 9 (Top): The completed MouseMove handler.
throughout the code that hide/show the ScrollBox (thereby hid-
Figure 10 (Bottom): Setting boundaries with the Timer procedure.
ing/showing the TDBEdit fields grouped within), depending
bars (respectively) are clicked. SB_LINEUP and on whether the EmpNo extracted from the currently selected
SB_LINEDOWN indicate the direction of scroll (with UP TOutlineNode is found in the table. The scrollbox is hidden if
as left and DOWN as right on the horizontal bar). the record cannot be found. This should only occur when the
SB_THUMBTRACK indicates that the scroll box should master node Employees is selected or, in a multi-user environ-
move to the corresponding position. When many records ment, if the record was deleted and the DataSet refreshed.
with extensive scrolling are involved, it might be worth-
while to expand the evaluation of mouse movements to Conclusion
include SB_PAGEUP and SB_PAGEDOWN messages if TOutline organizes layers of hierarchical data with ease — that’s
the mouse moves above 0 - 30, or below Height + 30. its power. With a simple algorithm, the outline can be loaded
from a self-referencing database — opening the door to drag-
Once dragging and scrolling are functional, the DragOver and-drop manipulation of the data's hierarchy. Self-referencing
handler must be created. All this handler must do is check tables are by no means the only relation type where TOutline can

FEBRUARY 1996 Delphi INFORMANT ▲ 18


Informant Spotlight

be used; one-to-many and one-to-many-to-many relations may


{ Private declarations }
also lend themselves to hierarchical manipulation. Of course, a procedure ParentLoad;
completely new algorithm would need to be designed. ∆ procedure ChildLoad;
procedure ChildReiterate;
procedure ProcessChildRecord;
The demonstration projects referenced in this article are available procedure AddNodeText;
on the Delphi Informant Delphi Informant Works CD located public
in INFORM\96\FEB\DI9602JC. { Public declarations }
end;

var
Jeff Chant is the owner of Maelstrom Software, an Ontario-based company specializing Form1: TForm1;
in the design and construction of Delphi tools and applications. He is also a SYNON ReIterate, IterateWithoutAdd,
architect, working with large warehouse management systems on the AS/400. He can ChildAdded, NodeDrag, ScrollUp,
ScrollDown, ScrollLeft,
be reached on CompuServe at 71431,62. ScrollRight: Boolean;
const
Begin Listing One — Unit1
{ Index to sort the table by }
unit Unit1;
SortIndexName = 'ByName';
{ Field to display after SeekField }
interface
DisplayField = 'DisplayField';
{ The key field of the self-reference }
uses
SeekField = 'EmpNo';
SysUtils, WinTypes, WinProcs, Messages, Classes,
{ The field referencing the key field }
Graphics, Controls, StdCtrls, Forms, DBCtrls, DB,
RecursiveField = 'Supervisor';
DBTables, Grids, Outline, Buttons, Mask, ExtCtrls,
Dialogs;
implementation
type
{$R *.DFM}
TForm1 = class(TForm)
ScrollBox: TScrollBox;
procedure TForm1.FormCreate(Sender: TObject);
Label1: TLabel;
begin
EditEmpNo: TDBEdit;
Table1.Open;
Label2: TLabel;
end;
EditLastName: TDBEdit;
Label3: TLabel;
procedure TForm1.Table1CalcFields(DataSet: TDataset);
EditFirstName: TDBEdit;
begin
Label4: TLabel;
Table1DisplayField.AsString :=
EditPhoneExt: TDBEdit;
Table1LastName.AsString + ', ' +
Label5: TLabel;
Table1FirstName.AsString;
EditHireDate: TDBEdit;
end;
Label6: TLabel;
EditSalary: TDBEdit;
procedure TForm1.SpeedButton1Click(Sender: TObject);
Label7: TLabel;
begin
EditSupervisor: TDBEdit;
{ Hide the scrollbox and its TDBEdit fields }
Panel1: TPanel;
ScrollBox.Visible := False;
DataSource1: TDataSource;
with Table1 do
Panel2: TPanel;
begin
Table1: TTable;
Screen.Cursor := crHourglass;
SpeedButton1: TSpeedButton;
try
Outline1: TOutline;
Form1.ParentLoad;
Table1EmpNo: TIntegerField;
Form1.ChildLoad;
Table1LastName: TStringField;
Form1.AddNodeText;
Table1FirstName: TStringField;
finally
Table1PhoneExt: TStringField;
Screen.Cursor := crDefault;
Table1HireDate: TDateTimeField;
end;
Table1Salary: TFloatField;
end;
Table1Supervisor: TIntegerField;
end;
Table1DisplayField: TStringField;
Timer1: TTimer;
procedure TForm1.ParentLoad;
procedure FormCreate(Sender: TObject);
begin
procedure Table1CalcFields(DataSet: TDataset);
with Table1 do
procedure SpeedButton1Click(Sender: TObject);
begin
procedure Outline1MouseMove(Sender: TObject;
Outline1.Clear;
Shift: TShiftState;
{ Add a Master Parent for all valid employees }
X, Y: Integer);
Outline1.Add(0, 'Employees');
procedure Outline1DragOver(Sender, Source: TObject;
{ Index the table for outline display order }
X, Y: Integer;
IndexName := SortIndexName;
State: TDragState;
First;
var Accept: Boolean);
while not eof do
procedure Outline1DragDrop(Sender, Source: TObject;
begin
X, Y: Integer);
procedure Timer1Timer(Sender: TObject);
{ If the record is top-level (has no parent) }
procedure Outline1Click(Sender: TObject);
if FieldByName(RecursiveField).AsInteger = 0 then
private

FEBRUARY 1996 Delphi INFORMANT ▲ 19


Informant Spotlight

{ Add the record as a child of the Master { Indicate that a reiteration will be
Parent created above (index=1) } necessary }
Outline1.AddChild(1, ReIterate := True;
FieldByName(SeekField).AsString); { If this is the second iteration through the
Next; file with no new added child nodes, add the
end; record as an orphan }

{ Set the index back to the primary key } if IterateWithoutAdd = True then
IndexFieldNames := SeekField; begin
First; Outline1.AddChild(0,FieldByName
end; (SeekField).AsString);
end; end;
end
procedure TForm1.ChildLoad;
begin else
ReIterate := True; { If node representing parent record does exist,
IterateWithoutAdd := False; add the record as a child }
begin
{ While there are still child records that have not ChildAdded := True;
been added to a parent } Outline1.AddChild(NodeIndex,FieldByName
while ReIterate do (SeekField).AsString);
begin end;
ReIterate := False; end;
ChildAdded := False; end;
while not Table1.eof do
begin; procedure TForm1.AddNodeText;
Form1.ChildReIterate; var
Table1.Next; NodeIndex: integer;
end; begin
Table1.First; with Table1 do
begin
{ If an iteration through the table was performed, First;
and there were record(s) not yet added to the while not eof do
outline, but no records were added to the outline begin
throughout the iteration } NodeIndex := Outline1.GetTextItem(FieldByName
if ReIterate and (SeekField).AsString);
not ChildAdded then if NodeIndex <> 0 then
IterateWithoutAdd := True; Outline1[NodeIndex].Text :=
end; FieldByName(SeekField).AsString + ' ' +
end; FieldByName(DisplayField).AsString;
Next;
procedure TForm1.ChildReIterate; end;
var end;
NodeIndex: integer; end;
begin
with Table1 do procedure TForm1.Outline1MouseMove(Sender: TObject;
begin Shift: TShiftState;
{ If record has parent (i.e. Supervisor not 0) } X, Y: Integer);
if FieldByName(RecursiveField).AsInteger <> 0 then begin
begin with Outline1 do
NodeIndex := Outline1.GetTextItem(FieldByName begin
(SeekField).AsString); if not Dragging then
begin
{ If record was not added to the outline in a if (Shift = [ssLeft]) then
previous iteration (can't be found in outline) } BeginDrag(False);
end
if NodeIndex = 0 then else
Form1.ProcessChildRecord; begin
end; { If the mouse has been dragged outside the
end; TOutline component }
end; if (Y < 0) or (Y > Height) or
(X < 0) or (X > Height) then
procedure TForm1.ProcessChildRecord; begin
var ScrollUp := False;
NodeIndex: integer; ScrollDown := False;
begin ScrollLeft := False;
with Table1 do ScrollRight := False;
begin if Y < 0 then
NodeIndex := Outline1.GetTextItem ScrollUp := True
(FieldByName(RecursiveField).AsString); else if Y > Height then
ScrollDown := True;
{ If node representing parent record does if X < 0 then
not exist in the outline } ScrollLeft := True
if NodeIndex = 0 then else if X > Height then
begin ScrollRight :=True;

FEBRUARY 1996 Delphi INFORMANT ▲ 20


Informant Spotlight

{ Move the record pointer to the currently


Timer1.Enabled := True;
selected node to update the TDBEdit fields }
end
if Outline1.ItemCount >= 1 then
begin
else
NodeText :=
Timer1.Enabled := False;
Outline1[Outline1.SelectedItem].Text;
end;
NodeText :=
end;
Copy(NodeText, 1, Pos(' ', NodeText)-1);
end;
if Table1.FindKey([NodeText]) then
ScrollBox.Visible := True
procedure TForm1.Outline1DragOver(Sender, Source: TObject;
else
X, Y: Integer;
ScrollBox.Visible := False;
State: TDragState;
end;
var Accept: Boolean);
end;
begin
end;
if Source is TOutline then
end;
Accept := True;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
procedure TForm1.Outline1DragDrop(Sender, Source: TObject;
with Outline1 do
X, Y: Integer);
begin
var
if Dragging then
FieldFrom, FieldTo,
begin
NodeFrom, NodeTo,
NodeText: string;
if ScrollUp then
IndexFrom, IndexTo,
SendMessage(Handle, WM_VSCROLL, SB_LINEUP,
ParentIndex: integer;
SB_THUMBTRACK)
begin
else if ScrollDown then
if (Source is TOutline) and
SendMessage(Handle, WM_VSCROLL, SB_LINEDOWN,
(Outline1.GetItem(X, Y) <> Outline1.SelectedItem) then
SB_THUMBTRACK);
begin
if ScrollLeft then
IndexFrom := Outline1[Outline1.SelectedItem].Index;
SendMessage(Handle, WM_HSCROLL, SB_LINEUP,
NodeFrom := Outline1[Outline1.SelectedItem].Text;
SB_THUMBTRACK)
{ Extract the key field from the fromnode }
else if ScrollRight then
FieldFrom := Copy(NodeFrom, 1, Pos(' ', NodeFrom)-1);
SendMessage(Handle, WM_HSCROLL, SB_LINEDOWN,
IndexTo := Outline1[Outline1.GetItem(X,Y)].Index;
SB_THUMBTRACK);
NodeTo := Outline1[Outline1.GetItem(X,Y)].Text;
end
else
{ Extract the key field from the tonode }
FieldTo := Copy(NodeTo, 1, Pos(' ', NodeTo)-1);
Timer1.Enabled := False;
end;
{ Verify that FromNode is not an ancestor of
end;
ToNode before moving }
ParentIndex := Outline1[IndexTo].Parent.Index;
procedure TForm1.Outline1Click(Sender: TObject);
while (ParentIndex <> 0) and
var
(ParentIndex <> IndexFrom) do
NodeText: string;
ParentIndex := Outline1[ParentIndex].Parent.Index;
begin
{ Only perform if a node has not just been dragged
{ If FromNode is a direct ancestor of ToNode,
to a new location }
send error message }
if not NodeDrag then
if ParentIndex = IndexFrom then
begin
MessageDlg('Sorry, you have attempted to create' +
if Outline1.ItemCount >= 1 then
'a cyclical relation.',mterror,[mbOK],0)
begin
else
NodeText := Outline1[Outline1.SelectedItem].Text;
begin
NodeText := Copy(NodeText,1,Pos(' ',NodeText)-1);
if MessageDlg('Make [' + NodeFrom + '] a child of [' +
if Table1.FindKey([NodeText]) then
NodeTo + ']?',mtConfirmation,
ScrollBox.Visible := True
mbOkCancel,0) = mrOk then
else
begin
ScrollBox.Visible := False;
{ Indicate that a NodeDrag move will be
end;
performed--this will prevent OnClick from
end
triggering an Index Out of Bounds exception }
else
NodeDrag := True;
NodeDrag := False;
with Table1 do
end;
begin
if FindKey([Fieldfrom]) then
end.
begin
Edit;
FieldByName(RecursiveField).AsString
End Listing One
:= Fieldto;
Post;
Outline1[Outline1.SelectedItem].MoveTo
(Outline1.GetItem(X,Y),oaAddChild);
end;
end;
end;

FEBRUARY 1996 Delphi INFORMANT ▲ 21


OP Tech
Delphi 1.0 / Object Pascal

By Bill Todd

Error Handling: Part I


A Primer for Handling Object Pascal Exceptions

andling run-time errors is a serious concern regardless of the language

H or environment in which you develop applications. Delphi is no differ-


ent. It handles errors through an object called an exception. When a
run-time error occurs, Delphi raises (creates) an exception. If your code does
not handle it, an OnException event is raised.

In this two-part series, you’ll learn how to handle exceptions, ensure that cleanup code is
always executed, and create custom exceptions and exception handlers.

Handling Cleanup and Resource Allocations


Cleanup code is easy to implement in Delphi. Cleanup code is code that must execute to
restore the system to a safe, stable state even if a run-time error occurs. For example, disabling
an application’s data-aware controls allows you to open and close a Table or Query compo-
nent, or change a Table component’s active index without annoying screen flicker.

To handle cleanup chores, Delphi provides the try..finally construct. The easiest way to understand
it is to examine the code from the sample project, CLEAN.DPR (its form is shown in Figure 1).
The form features Table, DataSource, and DBGrid components. The Table component is connect-
ed to the Customer table in the DBDEMOS database. It also contains two Buttons that let you
change the active index from CustNo (the primary index) to ByCompany (the secondary index).
This OnClick event handler switches the indexes:

procedure TForm1.Button2Click(Sender: TObject);


begin
with Table1 do
begin
DisableControls;
IndexName := 'x';
EnableControls;
end;
end;

Unfortunately, there’s a problem with this code. If the statement


that changes the IndexName property fails because the index
does not exist, an exception is automatically generated and the
procedure’s execution terminates. This means the EnableControls
call will not execute. The result is that you will be in the form
and the DBGrid will no longer be connected to the Table. Try it
by running the NOCLN.EXE program and click the By
Company button. Try to move around in the grid. Obviously,
Figure 1: A form that changes the active index. you don’t want to leave the user in this condition.

FEBRUARY 1996 Delphi INFORMANT ▲ 22


OP Tech

Fortunately, Delphi’s solution to this problem is elegant


procedure TSearchForm.FindBtnClick(Sender: TObject);
because it allows you to guarantee that the cleanup code will var
execute in spite of any run-time errors that occur. For exam- CustTbl: TTable;
ple, look at the OnClick handler of the By Company button in begin
{ Create a TTable object. }
the CLEAN.DPR project: CustTbl := TTable.Create(SearchForm);
{ Make sure the TTable's destructor gets called
procedure TForm1.Button2Click(Sender: TObject); no matter what happens in the following code. }
begin try
with Table1 do with CustTbl do
begin begin
DisableControls; { Assign the DatabaseName and TableName. }
try DatabaseName := 'DBDEMOS';
IndexName := 'ByCompany'; TableName := 'customer.db';
finally { Open the table. }
EnableControls; Open;
end; { Search for the requested name. }
end; while EOF = False and
end; ToFind.Text<>FieldByName('Company').AsString do
Next;
{ Display a message. }
The code following the DisableControls method call is if ToFind.Text = FieldByName('Company').AsString then
enclosed in a try..finally block. Code in the finally block exe- MessageDlg('Found',mtInformation,[mbOK],0)
cutes if an exception occurs in any part between try and final- else
MessageDlg('Not Found',mtInformation,[mbOK],0);
ly. Therefore, the EnableControls call will execute even if the end;
change to the IndexName property fails. You can see this by finally
changing IndexName to X and running this code. After click- { Call the TTable's destructor to free its memory. }
CustTbl.Free;
ing the By Company button and clearing the error dialog box, end;
you can still move around in the grid. end;

In addition, it’s critical that code executes when resources Figure 3: The code from the Find button’s OnClick handler.
are allocated and you must ensure they are released.
Memory, files, and Windows resources must be recovered Here’s the important issue: Whenever you create a new instance
if an error occurs. In an object-oriented environment, this of an object by calling its constructor, you are allocating memo-
is best illustrated by creating an instance of an object in ry. Therefore, you are responsible for deallocating that memory
your code. by calling the object’s destructor method. Otherwise, memory
will not be available until you restart Windows.
Figure 2 shows the form from the sample project
ALLOC.DPR. After a company name is entered, the form To ensure that CustTbl’s Free method is called, all the code
searches the customer table and reports if a match was found. in this procedure (following the call to TTable’s Create
The Find button’s OnClick event handler declares a variable method) is enclosed in a try..finally block. The call to the
named CustTbl of type TTable that searches the customer destructor, Free, follows the finally keyword so that it will
table (see Figure 3). However, CustTbl must first be created execute even if an exception is raised by any of the code
by calling its constructor method, Create. between try and finally. (Note that try.. finally blocks can
be nested to any depth.)

The code from the FASTE.DPR


project is an example of protecting
both file and memory resources.
Figure 4 shows the form for this
project and Figure 5 is the Create
button’s OnClick event handler.

This procedure opens a new file


by calling the run-time library
(RTL) procedure, Rewrite. Then Figure 4: The FASTE pro-
ject’s main form.
after the file has been opened,
the code ensures that the file is closed by encasing the sub-
sequent statements in a try..finally block. If an error occurs,
the call is made to this statement in the finally block:

Figure 2: Dynamic object creation. System.Close(addrFile)

FEBRUARY 1996 Delphi INFORMANT ▲ 23


OP Tech

procedure TForm1.CreateBtnClick(Sender: TObject); Dispose(buff)


const
MaxRecs = 100;
var Within this outer try..finally block, the call to Reset opens a file:
buff: array[1..MaxRecs] of TAddress;
addrFile: File; Reset(addrFile, SizeOf(TAddress));
i, count: Word;
begin
AssignFile(addrFile,'addr.dat'); Next, the try keyword initiates a try..finally block that pro-
Rewrite(addrFile,SizeOf(TAddress)); tects the file resource by ensuring the following command is
try
{ Put 100 records into the buffer. } called in the finally section:
for i := 1 to MaxRecs do
with buff[i] do System.Close(addrFile);
begin
PasToArray('John Doe',name);
PasToArray('123 East Main Street',addr); If a run-time error occurs in this code, neither global heap
PasToArray('New York',city); memory nor file handles will be lost.
PasToArray('NY',state);
PasToArray('55555-5555',zip);
end; Trapping RTL Exceptions
{ Write 100 buffers (10,000 records). } Calling RTL procedures and functions can generate excep-
for i := 1 to 100 do
BlockWrite(addrFile,buff,MaxRecs,count); tions. RTL exceptions fall into one of these categories:
finally • Conversion
System.Close(addrFile);
end;
• Floating point math
end; • Hardware
• Heap (memory allocation)
procedure TForm1.ReadBtnClick(Sender: TObject); • Input/Output
const • Integer math
MaxRecs = 500;
type • Typecast
Tbuff = array[1..MaxRecs] of TAddress;
var
buff: ^Tbuff;
Before delving into the details of RTL exception handling, look
addrFile: File; at the sample INTERR.DPR project for an overview of the
total, process. Its form has a Label, Panel, and three Buttons (see
count: Word;
begin Figure 7). Here is the MathError button’s OnClick event handler:
New(buff);
try procedure TForm1.MathErrorClick(Sender: TObject);
AssignFile(addrFile,'addr.dat'); var
Reset(addrFile,SizeOf(TAddress)); i,j,k,l,m: Integer;
try begin
{ Read the file, MaxRecs records at a time. }
total := 0; i := 23;
repeat j := 0;
count := 0; l := 2;
BlockRead(addrFile,buff^,MaxRecs,count); m := 4;
total := total + count;
until count = 0;
{ Make a calculation. }
finally
k := i div j * (l div m);
System.Close(addrFile);
{ Display the result. }
end;
ReadCount.Caption := IntToStr(total); Result.Caption := IntToStr(k);
finally
Dispose(buff); end;
end;
end;
As you can see, the code declares five integer variables and assigns
Figure 5 (Top): The code attached to the Create button. values to all of them except k. When this expression is evaluated:
Figure 6 (Bottom): Code from the Read button.
k := i div j * (l div m);
The Read button’s OnClick event handler (see Figure 6) has
two resource allocations that must be protected and uses an RTL exception is raised because the value of j is zero
nested try..finally blocks. and division by zero is not allowed. In this case, Delphi’s
default exception handler will manage the error by display-
Memory is the first resource that is allocated when New is called: ing an error message in a dialog box and ending execution
of this procedure.
New(buff);

The MathErrorHandled button’s OnClick handler (see Figure 8)


This assigns the buffer array into which the records are read.
will not display the normal error dialog box that appears
Then, the try keyword begins a block that ends with the call
when an exception is raised. In addition, your code does not
to Dispose after the finally keyword:

FEBRUARY 1996 Delphi INFORMANT ▲ 24


OP Tech

Exception Description
EDivByZero An attempt to divide by zero.
ERangeError The number or expression result is beyond
Figure 7: the range of the integer type.
The INTERR EIntOverflow A mathematical operation caused an integer
project’s overflow.
main form.
Exception Description
EInvalidOp The processor encountered an invalid instruc-
tion. This usually means the processor is trying
to execute data due to a pointer error.
EZeroDivide Attempt to divide by zero.
EOverflow A floating point operation overflowed.
procedure TForm1.MathErrorClick(Sender: TObject);
var EUnderflow A floating point operation underflowed.
i,j,k,l,m: Integer;
begin Exception Description
i := 23; EFault The base exception object for all faults.
j := 0;
l := 2; EGPFault General protection fault. The most common
m := 4; cause of GPFs is an uninitialized pointer.
EStackFault Illegal access to the stack segment.
try
k := i div j * (l div m); EPageFault The Windows memory manager could not
except access the swap file.
on EIntError do k := 0; EInvalidOpCode The processor encountered an undefined
end; instruction. This is usually caused by trying
to execute data.
{ Display the result. }
Result.Caption := IntToStr(k); EBreakpoint The program generated a breakpoint interrupt.
end; ESingleStep The program generated a single step interrupt.

Figure 8: The OnClick handler for the MathErrorHandled button. Figure 9 (Top): Integer math exceptions. Figure 10 (Middle): Float-
ing point math exceptions. Figure 11 (Bottom): Hardware exceptions.

handle it because the calculation is enclosed in a try..except • EIntError is a generic integer math exception. You can
block. The except code sets the result to zero and that’s the test for it, or for the specific integer math errors shown
end of the exception. Clearly, this is much easier than han- in Figure 9.
dling the possibility of division by zero without try..except. • For floating point math operations, the generic exception is
To detect if j or m is zero would require: EMathError. The individual floating point errors are listed
in Figure 10.
if (j = 0) or (m = 0) then
• The generic hardware exception is EProcessorException and
k := 0
else its specific descendants are listed in Figure 11.
k := i div j * (l div m);
With the exception of EGPFault, you should never encounter
If the computation were more complex, testing for all possible or need to worry about any of the hardware exceptions. They
integer math errors requires a lot of code. Note that when you only occur if a serious hardware or operating system failure
run a program in the IDE, it runs under the control of the happens or if you are running under a debugger. The
Interactive Debugger. You will always see its exception dialog box remaining four categories of exception do not have a
whether your code handles the exception or not. To view what generic exception. You must test for each specific exception
the user will see, run the application from Program Manager. that you want to handle:
• For input/output errors, there is a single exception,
When an exception is raised in a try..except block, Delphi EInOutError. It has a field named ErrorCode containing the
checks if it’s listed in the except section. If so, then the code operating system error code for the error that occurred.
for that exception is executed. In this example, if any integer • EInvalidCast is a single typecast exception that occurs any-
math exception occurs then the value of k is set to zero. time you attempt a typecast using the AS operator and the
typecast fails.
Understanding the RTL Exception Hierarchy • All conversion exceptions raise the EConvertError excep-
Before continuing, you must understand how the seven tion. For example, if you call StrToInt and the string can-
classes of RTL exceptions are organized. Three of the cate- not be converted, then EConvertError is raised.
gories — integer math, floating point math, and hardware • Two heap exceptions can occur when using dynamic
exceptions — have a hierarchy: memory. They are listed in Figure 12.

FEBRUARY 1996 Delphi INFORMANT ▲ 25


OP Tech

Exception Description If you look at the except clause, you’ll notice that it contains
checks for two exceptions, EDivByZero and EIntError. In this
EOutOfMemory An attempt to allocate memory on the heap case, a specific message is displayed for EDivByZero and
failed.
another message for all other integer math exceptions. The
EInvalidPointer An attempt was made to dispose of a pointer
that points to an address outside of the heap. order of the tests in this code is critical. If you first test for
EIntError, you will never see the EDivByZero message. This is
procedure TForm1.MathErrorWithMessageClick(Sender: because EIntError includes all integer math exceptions includ-
TObject); ing EDivByZero. This construct lets you handle as many
var exceptions as needed in a single try..except block.
i,j,k,l,m: Integer;
begin
i := 23; Conclusion
j := 0; This article has presented the basics of Delphi's exception
l := 2;
m := 4; handling mechanism. You have seen how to handle resource
try allocations to ensure that the resources are freed if a run-time
k := i div j * (l div m); error occurs. You have also seen how to handle exceptions
except
on EDivByZero do begin raised by run-time library procedures. The second part of this
k := 0; series explores using the exception object, silent exceptions,
MessageDlg('Divide by zero error',mtError,[mbOK],0); writing your own custom exception handler, and more. ∆
end;
on EIntError do begin
k := 0; This article was adapted from material from Delphi: A Developer’s
MessageDlg('Integer math error.',mtError,[mbOK],0); Guide by Bill Todd and Vince Kellen [M&T Books, 1995 —
end;
end; 800-488-5233].
{ Display the result. }
Result.Caption := IntToStr(k); The demonstration projects referenced in this article are avail-
end;
able on the Delphi Informant Works CD located in
Figure 12 (Top): Heap exceptions. Figure 13 (Bottom): The OnClick INFORM\96\FEB\DI9602BT.
event handler for the MathErrorWithMessage button.

Handling Multiple Exceptions Bill Todd is President of The Database Group, Inc., a Phoenix area consulting and devel-
The third button on the INTERR.DPR form, opment company. He is co-author of Delphi: A Developer’s Guide [M&T Books, 1995],
MathErrorWithMessage, shows how to handle multiple Creating Paradox for Windows Applications [New Riders Publishing, 1994], and
exceptions in a single try..except block (see Figure 13). Paradox for Windows Power Programming; Technical Editor of Paradox Informant; a
member of Team Borland; and a speaker at every Borland database conference. He can
be reached at (602) 802-0178, or on CompuServe at 71333,2146.

FEBRUARY 1996 Delphi INFORMANT ▲ 26


DBNavigator
Delphi 1.0 / Object Pascal / SQL

By Cary Jensen, Ph.D.

Filtering Tables: Part II


An Introduction to the Query Component

n last month’s “DBNavigator” we discussed how to limit access to specific

I records in a table that match a range. We considered several techniques


that involve using Table components. In this installment of “DBNavigator,”
we’ll use a Query component to achieve a similar effect. Specifically, we’ll
discuss how to create a query that returns a subset of records, as well as how
to control which records are selected at run time.

First, we’ll compare Table and Query components for selecting subsets of records from a table.
Then, we’ll continue with three basic techniques for selecting records using queries. The rea-
sons for displaying a subset of records from a table were discussed in last month’s article, so
they won’t be repeated here.

The techniques in this article use Query components, and therefore involve SQL (Structured
Query Language). While SQL itself is not necessarily difficult to use, there are many complex
issues involved that are far beyond the scope of this article. Therefore, we’ll only cover the
issues that are relevant to filtering tables.

Query versus Table Components


Although there are some similarities between displaying subsets of a table’s records using Table
and Query components, there is one overriding difference. Due to the nature of queries, an
index is not required. This is not true for Table components. Using a Table component, you
can only define ranges for fields involved in an index. Likewise, creating linked tables requires
an appropriate index. Furthermore, those indexes must have a specific structure. For example,
you can set a range based on the values in a field named City, but only if there is at least one
index where City is the first field.

A second major difference is that Query components do not have range-related methods
(such as SetRange, ApplyRange, and so on). Instead, they have a SQL property that you use
to define SQL statements. For example, to filter a table, a SELECT statement is used. This
is SQL DML (Data Manipulation Language) statement, and is used to define which data
will be returned as an answer set.

Selecting Records with Queries


There are two critical properties for using Query components. The first is the SQL property,
which is a StringList property. You can, and usually do, enter this property at design time
using the String List editor (the third technique demonstrated in this article shows you how
to define this property at run time). The text that you enter into the SQL property defines
which process the query will perform.

FEBRUARY 1996 Delphi INFORMANT ▲ 27


DBNavigator

The second is the DatabaseName property that you must set ed. For example, the following statement will select all fields
to either a BDE alias or a subdirectory path. DatabaseName from each record where the CustNo field is equal to 1221:
defines the location of the tables being queried. There is,
however, one situation where DatabaseName is not required. SELECT * FROM CUSTOMER
WHERE CustNo = 1221
If you include the alias name in the Query’s SQL statement,
DatabaseName can remain blank.
Non-numeric comparison values must be enclosed in quotes.
For example:
When selecting subsets of records, you’ll use the SQL SELECT
statement. It has two required parts or clauses: SELECT and SELECT StateName FROM STATES
FROM. In SELECT, you specify the fields to include in the WHERE StateCode = 'AZ'
answer set that is returned, and you use the FROM statement
to specify the tables from which the fields are selected. Selecting Also, any comparison operators, including >, <, >=, and so forth,
subsets of records requires one additional clause, WHERE, for can be used as a comparison operator in a WHERE clause.
identifying the appropriate records. There are additional clauses
that you can use with the SELECT statement, but they are A WHERE clause can also include multiple conditions, using
outside the scope of this article. (For information on these the AND and OR operators. For instance, assuming the
other clauses, select Help | Topic Search from Delphi’s menu DatabaseName property is set to DBDEMOS, the following query
and enter SQL Statements in the Search All dialog box.) will select all fields from the ORDERS table where CustNo is
1221 and the SaleDate is greater than 1/1/94:
All the queries in this article will be single-table queries, since
Delphi 1.0 only supports editing of single-table queries. Let’s SELECT * FROM ORDERS
WHERE CustNo = 1221 AND
consider the SELECT statement. SELECT is followed by a SaleDate > '1/1/94'
comma-separated list of field names that you want to include
in the answer set. The FROM clause includes a comma-sepa- Using these basic rules, it’s now possible to demonstrate three
rated list of the tables where these fields are found. For exam- ways of selecting subsets of records using Query components.
ple, the following SQL statement will select the CustNo and
Company fields from the CUSTOMER.DB table: Linked Queries
SELECT CustNo, Company FROM CUSTOMER The easiest, although least flexible, way to select a subset of
records with a query is to use a linked query. In a linked
The above example assumes that the DatabaseName property query, the subset of records is defined by values in another
has been set to DBDEMOS, an alias that points to the location DataSource, such as a Table. The WHERE condition(s) use
of the CUSTOMER.DB table. (Delphi creates this alias dur- one or more fields in the DataSource’s DataSet to select spe-
ing installation. If you do not have this alias already estab- cific records to display.
lished, you must create it before trying these examples.)
To do this, you set the Query component’s DataSource prop-
Alternatively, you can include the alias in the table name. For erty to the name of the DataSource that contains the field(s)
example, the following SQL statement does not require the used in the Query’s WHERE clause. Then, within the
DatabaseName property to be assigned a value: WHERE clause, you include the field name(s) from the
DataSource’s DataSet in comparisons. The only trick to this is
SELECT CustNo, Company FROM ':DBDEMOS:CUSTOMER.DB' that the field names must be preceded by colons (:) so the
Query can distinguish them from static conditions.
If the DatabaseName property has been set to DBDEMOS, the fol-
lowing SQL statement has the same effect as the preceding one: This technique is difficult to describe, but easy to demon-
strate with an example. Follow these instructions to create a
SELECT CustNo, Company FROM CUSTOMER linked query:
1) Create a new project. On the new form, add two Label
To select all fields from the specified table, you can replace components, two DBEdit components, a DBGrid, two
the individual field names with an asterisk. For example, DataSources, a Button, a DBNavigator, a Table, and a
the following statement selects all fields from the CUS- Query component. Your form should resemble Figure 1.
TOMER.DB table (again, assuming the DatabaseName 2) Set Form1’s Caption property to Linked Form Example,
property has been set to DBDEMOS): and its Position property to poScreenCenter.
SELECT * FROM CUSTOMER 3) Set the Caption property for Label1 to Company: and the
Caption property for Label2 to Customer Number:. Next, set
While the SELECT clause specifies which fields (or columns in the DataSet property of DataSource1 to Table1, and the
SQL vernacular) will be included in the answer set, the DataSet property of DataSource2 to Query1. Set the
WHERE clause specifies which records (or rows) will be includ- DatabaseName property of Table1 to DBDEMOS, and the

FEBRUARY 1996 Delphi INFORMANT ▲ 28


DBNavigator

Figure 2: A
Figure 1: SQL SELECT
A new statement
form for for a para-
the meterized
LINKQRY query.
project.

TableName property to CUSTOMER.DB. Finally, set the Active


property of Table1 to True.
Figure 3:
4) Set the DataSource properties of both DBEdit1 and The
DBEdit2 to DataSource1. Next, set the DataField property completed
of DBEdit1 to Company, and the DataField property of query
DBEdit2 to CustNo. example.
5) Set the DataSource property of DBGrid1 to Query1. Set
the DataSource property of DBNavigator1 to Table1.
6) Set the Caption property of Button1 to &Close. Next,
double-click this button to create an OnClick event han-
dler, and enter the procedure Close. This event handler
should resemble:
In a parameterized query, the colon designates CustNumber as
procedure TForm1.Button1Click(Sender: TObject); a parameter. A parameterized query can have as many parame-
begin ters as necessary. The only restriction is that you must define
Close;
the value for a parameter before making the query active. This
end;
can be done at design time using the property editor for the
Params property of the Query component. However, the only
7) Now it’s time to modify the Query component. Select
reason for defining a parameter at design time is to activate the
Query1 and set its DatabaseName property to DBDEMOS.
query at design time. In most cases, the parameters are defined
Next, open the property editor for the SQL property and
and the query is activated (by setting the Active property to
enter the SQL statement shown in Figure 2. Notice that
True or by calling the Open method) at run time.
the CustNo field name in the WHERE condition is pre-
ceded by a colon. CustNo is a field in CUSTOMER.DB
Figure 4 shows the Parameters dialog box. This is the property
that is the DataSet pointed to by DataSource1.
editor for a Query component’s Params property. In this dialog
8) Set the Active property for Query1 to True. Now run the
box you can define default values, as well as define the data type
form. Your screen should resemble Figure 3. Notice that
of one or more of the parameters you have included in a query.
each time you move to a new record in
CUSTOMER.DB, the contents of the DBGrid update
Defining a parameter at run time requires the use of either
with the results of the linked query.
the Params property or the ParamByName method. The
Parameterized Queries Params property is a zero-based array and its elements corre-
Parameterized queries share many similarities with linked spond to the Query parameters based on order of inclusion in
queries. The SQL property is typically defined at run time, the SQL statement. In the case of the preceding parameter-
and the WHERE clause includes values that change. In ized SQL statement, CustNumber is the only parameter, so it
linked queries, these values are based on values in a DataSet will correspond with the first element, 0, of the Params array.
pointed to by the Queries’ DataSource. In parameterized Here is a sample statement that assigns the value 1221 to the
CustNumber parameter using the Params property:
queries, you control these values through code.
Query1.Params[0].AsInteger := 1221;
The following is an example of a parameterized SQL state-
ment. Notice that in the WHERE clause, CustNumber is pre- If the query includes more than one parameter, the first
ceded by a colon. one that will appear in the SQL statement is associated
SELECT * FROM ORDERS
with element 0 of the Params property; the second is asso-
WHERE CustNo = :CustNumber ciated with element 1, and so on.

FEBRUARY 1996 Delphi INFORMANT ▲ 29


DBNavigator

This code is associated with the OnClick event handler for the
Show Orders button on Form1.
Figure 4: The
Query component’s First, the code closes the Query component on Form2 (in
Params property case it’s already open — you cannot change the parame-
editor.
ters of an open query). Next, the CustNumber parameter is
assigned a value using the ParamByName method. The
value assigned to this parameter is based on the current
record of Table1.
The ParamByName method allows you to set a parameter’s
value based on the parameter’s name. For example, this state- Next, the query is opened, which executes the SELECT state-
ment uses the ParamByName method, and is equivalent to the ment. The Caption property of Form2 is then assigned an
preceding one that uses the Params property: appropriate title. Finally, the ShowModal method is used to
Query1.ParamByName('CustNumber').AsInteger := 1221;
display Form2. The result is that the DBGrid on Form2 dis-
plays only those records associated with the Customer that
Figure 5 is the main form of the PARAM.DPR project that the user has selected on Form1.
demonstrates parameterized queries. This form contains a
DBGrid, two Buttons, a DataSource, and a Query. The Table This example is somewhat more complicated since the Query,
is associated with the CUSTOMER.DB table in the directory whose properties are being modified, appears on another form.
that the DBDEMOS alias points to. Remember, to refer to Form2 and its objects from Form1, the unit
associated with Form2 (in this case, it’s ParamU2) must be listed in
a uses clause in the unit associated with Form1 (ParamU in this
example). The ParamU unit is shown in Listing Two on page 32.
Listing Three on page 32 lists all the code for ParamU2.
Figure 5:
Form1 of the
project Changing the SQL Property at Run Time
PARAM.DPR. Parameterized queries are great when the fields you need to use
to select a subset of records are known. However, to create a
query where you sometimes select all records (i.e. when there is
no WHERE clause), and sometimes select a subset based on one
or more parameters, a parameterized query is not an option.

Figure 6 is the second form in this project. It contains a Likewise, if the table being queried is not known until run
DBGrid, a Button, a DataSource, and a Query. This Query’s time, a parameterized query cannot be used because a para-
SQL property is associated with the parameterized SQL state- meter cannot appear in the FROM clause. Instead, you must
ment shown earlier. modify the contents of the SQL property at run time. This
permits you to create flexible queries.
Here is the critical code in this example:
Since the SQL property is a StringList property, it can be mod-
procedure TForm1.Button2Click(Sender: TObject); ified using the methods of the TStringList class. Among the
begin most useful methods are Add and Clear. Add inserts a new line
Form2.Query1.Close;
Form2.Query1.ParamByName('CustNumber').AsInteger :=
into a StringList in the last position, whereas Clear empties a
Table1.FieldByName('CustNo').AsInteger; StringList. The following is an example of how these two state-
Form2.Query1.Open; ments can be used to define a new SQL statement at run time:
Form2.Caption := 'Orders for ' +
Table1.FieldByName('Company').AsString;
Query1.Close; { Close the query if it’s open }
Form2.ShowModal;
end; Query1.SQL.Clear; { Remove old SQL statements }
Query1.SQL.Add('SELECT * FROM ORDERS');
Query1.SQL.Add(' WHERE CustNo = 1211');
Query1.SQL.Open; { Open the new query }

Figure 6: This technique is demonstrated in the SQL.DPR project (its


Form2 of the
project
main form, Form1, is shown in Figure 7). This form contains
PARAM.DPR. three Labels, Edits, and Buttons. When the user presses the
Show SQL or Show Records button, a StringList is constructed
based on the values entered in the three Edit components (this
is done with a custom procedure named BuildQueryString). If
the Edit components are empty, the StringList will not contain

FEBRUARY 1996 Delphi INFORMANT ▲ 30


DBNavigator

The code for SQL.DPR is shown in Listings Four, Five, and


Six beginning on page 33. Note that this example was simpli-
Figure 7:
fied by defining custom procedures and functions for those
Form1 of
the project routines requiring repeated calls.
SQL.DPR.
Other Query Issues
This article’s purpose is to describe the basics of creating
queries that display subsets of a table’s data. However, there
are many important query-related issues that are not directly
a WHERE clause. Otherwise, the StringList contains selection related to this topic. If you are new to generating queries, you
conditions based on the entered data. should read the online help concerning SQL queries, as well
as the Query component. In particular, you should study the
When the user presses Show SQL, the constructed StringList RequestLive and UpdateMode properties of the Query compo-
is assigned to the Lines property of a Memo component on nent. These properties allow you to edit the query result. In
Form2 (associated with the SQLU2 unit). This form is addition, you may want to examine the Database component.
then displayed (see Figure 8) and enables the user to view It allows you to explicitly control transactions when you are
the SQL statements. When the user presses Show Records, editing the results of a live query.
StringList is assigned to the SQL property of a Query
object on Form3 (associated with SQLU3). This query is Conclusion
then opened and returns the appropriate subset of records Queries provide you with an alternative to Table components
(see Figure 9). when you must display a subset of records from a table. Using
linked queries, you can have the Query component automati-
cally update itself based on values in the linked DataSource.
Figure 8: When you want to control the displayed records using code,
The SQL
parameterized queries offer a simple alternative. When you
statements
generated in need the greatest flexibility in your SQL statements, editing
this example the SQL property at run time is the best solution. ∆
are based on
user-entered The demonstration forms referenced in this article are available
criteria.
on the Delphi Informant Delphi Informant Works CD located
in INFORM\96\FEB\DI9602CJ.

Cary Jensen is President of Jensen Data Systems, Inc., a Houston-based database devel-
Figure 9: opment company. He is author of more than a dozen books, and is Contributing Editor of
An answer Paradox Informant and Delphi Informant. Cary is this year’s Chairperson of the Paradox
set based Advisory Board for the upcoming Borland Developers Conference. He has a Ph.D. in
on the cri- Human Factors Psychology, specializing in human-computer interaction. You can reach
teria shown Jensen Data Systems at (713) 359-3311, or through CompuServe at 76307,1533.
in Figure 8.

FEBRUARY 1996 Delphi INFORMANT ▲ 31


DBNavigator

Begin Listing Two — The Paramu Unit public


unit Paramu; { Public declarations }
end;
interface
var
uses Form2: TForm2;
SysUtils, WinTypes, WinProcs, Messages, Classes,
Graphics, Controls, Forms, Dialogs, StdCtrls, Grids, implementation
DBGrids, DBTables, DB, Mask, DBCtrls;
{$R *.DFM}
type
TForm1 = class(TForm) end.
Table1: TTable; End Listing Three
DataSource1: TDataSource;
DBGrid1: TDBGrid; Begin Listing Four — The Sqlu1 Unit
Button1: TButton; unit Sqlu1;
Button2: TButton;
procedure Button1Click(Sender: TObject); interface
procedure Button2Click(Sender: TObject);
private uses
{ Private declarations } SysUtils, WinTypes, WinProcs, Messages, Classes,
public Graphics, Controls, Forms, Dialogs, StdCtrls;
{ Public declarations }
end; type
TForm1 = class(TForm)
var CustomerNumber: TEdit;
Form1: TForm1; BeginDate: TEdit;
EndDate: TEdit;
implementation ShowRecords: TButton;
CloseButton: TButton;
{$R *.DFM} Label1: TLabel;
Label2: TLabel;
uses Label3: TLabel;
paramu2; ShowSQL: TButton;
procedure ShowRecordsClick(Sender: TObject);
procedure TForm1.Button1Click(Sender: TObject); procedure ShowSQLClick(Sender: TObject);
begin procedure BuildSQLString(var TheQuery: TStringList);
Close; procedure CloseButtonClick(Sender: TObject);
end; private
{ Private declarations }
procedure TForm1.Button2Click(Sender: TObject); public
begin { Public declarations }
Form2.Query1.Close; end;
Form2.Query1.ParamByName('CustNumber').AsInteger :=
Table1.FieldByName('CustNo').AsInteger; function IsDate(Source: TEdit) : Boolean;
Form2.Query1.Open; procedure AddCondition(var TheQuery: TStringList;
Form2.Caption := 'Orders for '+ const field, comparison, value: string);
Table1.FieldByName('Company').AsString;
Form2.ShowModal; var
end; Form1: TForm1;

end. implementation
End Listing Two
{$R *.DFM}

Begin Listing Three — The Paramu2 Unit uses sqlu2,sqlu3;


unit Paramu2;
procedure AddCondition(var TheQuery: TStringList;
interface const field, comparison, value: string);
begin
uses if TheQuery.Count > 2 then
SysUtils, WinTypes, WinProcs, Messages, Classes, { When Count > 2, there is already at
Graphics, Controls, Forms, Dialogs, DB, DBTables, least one condition }
StdCtrls, Grids, DBGrids; TheQuery.Add('and '+field+comparison+value)
else
type TheQuery.Add(field+comparison+value);
TForm2 = class(TForm) end;
DBGrid1: TDBGrid;
Button1: TButton; function IsDate(Source: TEdit) :Boolean;
DataSource1: TDataSource; { This function returns True if a TEdit contains a date }
Query1: TQuery; begin
private try
{ Private declarations } StrToDate(TEdit(Source).Text);
result := True

FEBRUARY 1996 Delphi INFORMANT ▲ 32


DBNavigator

TheQuery.Free;
except
end;
result := False;
end;
end;
end; procedure TForm1.CloseButtonClick(Sender: TObject);
begin
procedure TForm1.BuildSQLString(var TheQuery: TStringList); Close;
begin end;
if BeginDate.Text <> '' then
if not IsDate(BeginDate) then end.
begin End Listing Four
BeginDate.SetFocus;
raise Exception.Create('Date value expected'); Begin Listing Five — The Sqlu2 Unit
end; unit Sqlu2;

if EndDate.Text <> '' then interface


if not IsDate(EndDate) then
begin uses
EndDate.SetFocus; SysUtils, WinTypes, WinProcs, Messages, Classes,
raise Exception.Create('Date value expected'); Graphics, Controls, Forms, Dialogs, StdCtrls;
end;
type
TheQuery.Add('SELECT * FROM ORDERS') ; TForm2 = class(TForm)
if not ((CustomerNumber.Text = '') and Button1: TButton;
(BeginDate.Text = '') and SQLStatements: TMemo;
(EndDate.Text = '')) then private
begin { Private declarations }
TheQuery.Add('WHERE'); public
if CustomerNumber.Text <> '' then { Public declarations }
AddCondition(TheQuery,'CustNo','=', end;
CustomerNumber.Text);
if BeginDate.Text <> '' then var
AddCondition(TheQuery,'SaleDate','>=', Form2: TForm2;
#39+BeginDate.Text+#39) implementation
if EndDate.Text <> '' then
AddCondition(TheQuery,'SaleDate','<=', {$R *.DFM}
#39+EndDate.Text+#39);
end; end.
end; End Listing Five

procedure TForm1.ShowRecordsClick(Sender: TObject);


Begin Listing Six — The Sqlu3 Unit
unit Sqlu3;
var
TheQuery: TStringList;
interface
begin
TheQuery := TStringList.Create;
uses
SysUtils, WinTypes, WinProcs, Messages, Classes,
try
Graphics, Controls, Forms, Dialogs, StdCtrls,
BuildSQLString(TheQuery);
Grids, DBGrids, DB, DBTables;
{ Query is done. Process it }
Form3.Query1.SQL := TheQuery;
type
Form3.Query1.Open;
TForm3 = class(TForm)
Form3.ShowModal;
DataSource1: TDataSource;
finally
Query1: TQuery;
TheQuery.Free;
DBGrid1: TDBGrid;
end;
Button1: TButton;
private
end;
{ Private declarations }
public
procedure TForm1.ShowSQLClick(Sender: TObject); { Public declarations }
var end;
TheQuery: TStringList;
begin var
TheQuery := TStringList.Create; Form3: TForm3;

try implementation
BuildSQLString(TheQuery);
{ Query is done. Process it } {$R *.DFM}
Form2.SQLStatements.Lines := TheQuery;
Form2.ShowModal; end.
finally End Listing Six

FEBRUARY 1996 Delphi INFORMANT ▲ 33


The Way of Delphi
Delphi 1.0 / Object Pascal / OLE

By Gary Entsminger

Creating an
OLE Object Manager
State of the Object Art: Part II

Don’t you hate it when someone breaks into an article before it even gets rolling? I do. But I can’t help myself. I
must warn you that the project we develop in this article behaves a bit differently on systems running
Windows 3.1 than those running Windows 95. And worse yet. I’m not sure why. So… if you’re a
Windows 95 user, you’ll experience all the thrills and chills of our project. If you’re using Windows 3.1,
compile the project and let me know what happens.

Delphi application can be the “glue” that allows users to manipulate a

A group of objects within a single application or document. These objects


can be ones you create in Delphi, or those controlled by another
Windows application. For example, you can control or manipulate a Paradox
table with Paradox for Windows, or the Database Desktop that ships with
Delphi. Typically, to edit or modify a table, you open one of these applications
to handle the work. But this is not the only way to do it.

Let’s say that you want to create an application manager program that runs a set of applications
to display a corresponding set of data files on a single screen or page of text. Imagine a scenario
where you could:
• obtain the values of your favorite stocks from an Internet provider
• view the data in a table
• manipulate the data and display results with a Delphi model application
• store the results in a second Paradox table

In addition, all these applications could be open on the screen


simultaneously.

Alternatively, you could display several kinds of data in a single doc-


ument using OLE (Object Linking and Embedding). For example,
in Microsoft Word you can use the Insert | Object command, or its
OLE drag-and-drop capability to insert objects into a Word docu-
ment (see Figure 1).

This article begins an exploration of Delphi’s OLE capability. You’ll


create an OLE object manager application called Objman that uses
two forms to maintain a group of MDI child windows and the
object that each child window contains. In the Objman project, its
Figure 1: You can use Microsoft Word’s Insert | Object menu item to
main form, OLEFrameForm, is an MDI form that manages the
insert objects into a Word document. group of MDI child windows based on the form class described in

FEBRUARY 1996 Delphi INFORMANT ▲ 34


The Way of Delphi

OLEOBJ.PAS. This is the unit for the form, OLEObjectForm,


that manages the objects.

Each time a user selects File | New Object Window from the applica-
tion’s menu, a child window is created to contain a new object.
Alternatively, users can drop an object onto OLEFrameForm and
automatically create a new child window containing the dropped
object. Additionally, OLEFrameForm has event procedures for cas-
cading and tiling child windows and arranging child window icons. Figure 2: Our main form, OLEFrameForm, at
design time.
Note that any child window will use OLEFrameForm for basic
window management. Therefore, a child window must reference
OLEFrameForm in its unit’s implementation section to avoid a dragged object. Alternatively, in two steps, the user can create
circular reference. This is necessary because OLEFrameForm uses a child window and paste an object from the Clipboard into a
the child’s unit, OLEOBJ.PAS, in the interface section of its unit. child window.

You can use the Objman application to launch sets of applica- First, we’ll create the user interfaces of both forms, and then
tions and documents, as a model for a more complex File we’ll write the “glue” code. To begin, create a new project by
Explorer-type application, or as a file or application organizer. selecting File | New Project from Delphi’s menu. Delphi will
But before we get into the project details, let’s briefly review a automatically create a form and its corresponding unit. Then
few central aspects of OLE. select File | Save Project As and save the form’s unit as OLE-
FRAME.PAS. This will be the main form. Save the project as
OLE Servers, Containers, and Objects OBJMAN.DPR.
Basically, OLE is a way for applications to share data. It differs
from DDE (Dynamic Data Exchange) by allowing the “glue” In the Object Inspector, change the form’s FormStyle property to
application to connect to the OLE server application that creat- fsMDIForm. This establishes the form as an MDI “parent.” Now
ed the data. [For more information on DDE, see Gary change the form’s Caption property to OLE Object Manager,
Entsminger’s article “Great Journeys, Single Steps” in the and change its Name property to OLEFrameForm. Figure 2 shows
January 1996 Delphi Informant.] our form in design view.

In DDE, you simply receive a copy of data, but not the applica- From the Standard page of the Component Palette, select the
tion that created it. Additionally, you cannot edit the original MainMenu component and add it to the form. Double-click on
data. In OLE, you get both — the data and the application that the MainMenu component to display the Menu Designer. Add a
created it. Therefore, you can use the application to manipulate menu item (&File), and a sub-menu item (&New Object Window)
the data. as shown in Figure 3.

The application that creates an OLE object is called the OLE Figure 3:
Use the
server and you use it to edit the object. For example, you can use Menu
Microsoft Excel as an OLE server to edit spreadsheets. Likewise, Designer to
the Borland Database Desktop can be used as an OLE server to add a menu
edit tables. item
(&File) and
a sub-menu
In short, in an OLE interaction, one application acts as the serv- item (&New
er and another as an OLE container that contains the OLE Object
objects. The OLE object is the data — or a representation of the Window).
data — that the OLE server creates and maintains (e.g. a spread-
sheet, table, .TXT file, .DOC file, etc.).
Merging MDI Menus
In the Objman project, you’ll use Delphi to create an OLE contain- MDI applications always merge the menus of child windows
er application that can interact with many OLE server applications. with the main menu of the parent MDI window (i.e. form).
However, you can control how this merging occurs.
Creating the Project
As mentioned, Objman uses two forms (an MDI container All menu items have a GroupIndex property that is used to speci-
and an OLE object container) to allow users to add new MDI fy whether menu items are inserted or replaced in a menu. By
children that contain OLE objects. Each window can contain default, all items in a menu have the same GroupIndex value, but
one OLE object. In one step, the user can drag an object onto you can explicitly change these values according to a group of
the main form to create an MDI child and a link to the menu merging rules.

FEBRUARY 1996 Delphi INFORMANT ▲ 35


The Way of Delphi

For instance, any menu item with a GroupIndex property of 1, open, edit, or convert the object within the OLE container.
3, or 5 will be replaced by the menu items with correspond- Although you always add this menu item when using OLE
ing index values in the MDI child (the OLE server). Merging containers, you never write event code for it. OLE handles it.
the menus of the OLE server and its container is called in-
place activation. To enable each child to merge its File1 menu items with
OLEFrameForm’s File1 menu, set the GroupIndex property of the
Note that in non-MDI applications, File1 menu item to 1 (to match the OLEFrameForm’s File1 menu
the main menu’s AutoMerge property item). If you’re in an experimental mood, try leaving either the
determines if the menus (TMainMenu) main menu’s File1 GroupIndex property or the child’s set to 0.
of forms other than the main form Either way the second File1 menu item is added to the first, not
merge with the principal form’s main replaced.
menu in non-MDI applications at run
time. Figure 5 shows OLEObjectForm at design time.

To complete the visual aspect of Adding Object Pascal


OLEFrameForm, set the GroupIndex Let’s begin with our main form, OLEFrameForm. First, you
property of the File1 menu item to 1 must write a routine to create and show a child form. The
Figure 4: Set the
(see Figure 4). We’ll add code after we GroupIndex property CreateMDIChild function creates a new child form of type
design the OLE container child window. of the File1 menu item TOLEObjectForm:
to 1.
Creating the Child function TOLEFrameForm.CreateMDIChild: TOLEObjectForm;
begin
OLEObjectForm is the child form that will contain OLE objects. { Create a new MDI child }
Each child window features one OLEContainer component (the Result := TOLEObjectForm.Create(Self);
OLE container) to hold OLE objects. When a new MDI child { Show the child }
Result.Show;
window is created, it’s responsible for managing the object con- end;
tained in its OLE container.
This is the form type declared and implemented in
The OLE container can receive an object from the Clipboard, OLEOBJ.PAS. However, note that other children could be
or as the result of a drag-and-drop event. Once an object is created just as easily with this pattern.
contained by the OLE container, we can then use the contain-
er’s built-in capability to edit the object or the object package. Dragging and Dropping Objects
In addition, we can copy objects to the Clipboard as well as Before attempting to paste (e.g. drag-and-drop) objects into an
paste them from the Clipboard into the OLE container. OLEContainer, you must register Clipboard formats for the
type of object you want to drag-and-drop (e.g. linked or embed-
To create our second form, select File | New Form from Delphi’s ded OLE objects). For example, Delphi uses these formats to
menu, and save the unit as OLEOBJ.PAS. This will be the blue- initialize the array passed in the Fmts parameter of the
print for a child form. Change the form’s FormStyle property to PasteSpecialDlg function:
fsMDIChild to establish the form as a child window. Change the
form’s Name property to OLEObjectForm. From the System page if PasteSpecialEnabled(Self,OLEFrameForm.Fmts) then
of the Component Palette, select the OLEContainer component
and add it to the form. and in the Fmts parameter of the RegisterFormAsOLEDropTarget
procedure:
From the Standard page of the Component Palette, select a
MainMenu component and place it on the form. Use the RegisterFormAsOleDropTarget(Self,Fmts)

Menu Designer to add the menu items, &File, &Edit, and


&Window, and implement their sub-menu items as follows: Now, we’ll register the Clipboard formats. First, declare a
• Under &File, add &New Object Window and E&xit. FLinkClipFmt variable of type Word in the private section of the
• Under &Edit, add &Copy Object to Clipboard, &Paste OLEFrameForm class:
Object from Clipboard, and &Object.
private
• Under &Window, add &Cascade, &Tile, and Arrange { Private registration declarations }
&Icons. FLinkClipFmt: Word;

In the Object Selector, select the &Object menu item and set Now, declare an array of type BOLEFormat with one element
its Enabled property to False. The Edit | Object command for each object format to be processed. For example, to allow
remains dormant until the OLE container holds an object. dragging and dropping of linked objects only, declare a one-
When the container has an object, Edit | Object becomes active element array. Likewise, to allow dragging and dropping of
and allows the user to access the object. The user can then linked and embedded objects declare a two-element array.

FEBRUARY 1996 Delphi INFORMANT ▲ 36


The Way of Delphi

procedure TOLEFrameForm.FormCreate(Sender: TObject);


begin
{ Register this form to allow drag-and-drop }
FLinkClipFmt := RegisterClipboardFormat('Link Source');

{ Initialize the registration array }


Fmts[0].fmtId := FLinkClipFmt;
Fmts[0].fmtMedium := BOLEMediumCalc(FLinkClipFmt);
Fmts[0].fmtIsLinkable := True;
StrPCopy(Fmts[0].fmtName, '%s');
StrPCopy(Fmts[0].fmtResultName, '%s');
Figure 5: OLEObjectForm at design time.
{ Register the form }
RegisterFormAsOleDropTarget(Self, Fmts)
end;
In this project, let’s simplify the process by registering only
linked objects. Thus, we need a single-element array. Declare this
array in the public section of the OLEFrameForm class: procedure TOLEFrameForm.FormDragDrop(
Sender,Source: TObject; X, Y: Integer);
public
var
{ Public registration info for drag-and-drop --
NewMDIChild: TOLEObjectForm;
a single-element array to allow linking only }
begin
Fmts: array[0..0] of BOLEFormat;
{ Is the dropped object usable? }
if Source is TOLEDropNotify then
To continue, initialize the fmtId, fmtMedium, fmtIsLinkable, begin
{ Then create a new child }
fmtName, and fmtResultName fields of each BOLEFormat ele- NewMDIChild := CreateMDIChild;
ment of the array. Use the Object Pascal BOLEMediumCalc { Give the OLE container info about the object }
function to calculate the value of the fmtMedium field that with Source as TOLEDropNotify do
NewMDIChild.OLEContainer.PInitInfo := PInitInfo
corresponds to the value of the fmtId Clipboard format. end;
end;
BOLEMediumCalc returns the BOLEMedium value to use with
the Clipboard format ID passed in the fmtId parameter. Figure 6 (Top): Registering the form.
BOLEMedium is the type of the fmtMedium field of the Figure 7 (Bottom): The FormDragDrop procedure.
BOLEFormat record. In this project, we handle the registration
process when we create the OLEFrameForm (see Figure 6). First, the TOLEObjectForm.NewObjectWindow1Click event pro-
cedure tells OLEFrameForm to create a new child:
OLEFrameForm’s FormDragDrop event procedure is triggered
each time an object is dropped onto the form (see Figure 7). procedure TOLEObjectForm.NewObjectWindow1Click(
Sender: TObject);
After each drop, the TOLEDropNotify object is used to evalu- begin
ate the Source object that’s been dropped onto the form. If { Ask the OLEFrame to create a new child }
Source is of type TOLEDropNotify, then a new child window OLEFrameForm.NewObject1Click(Sender)
end;
will be created to contain the dropped object. The new child
window is created by the NewObject1Click procedure:
Next, the TOLEObjectForm.Cascade1Click event procedure sends
procedure TOLEFrameForm.NewObject1Click(Sender: TObject); a message to OLEFrameForm to cascade the child windows:
var
NewMDIChild: TOLEObjectForm; { Ask the OLEFrame to handle child window behavior }
begin procedure TOLEObjectForm.Cascade1Click(Sender: TObject);
{ Create a new MDI child } begin
NewMDIChild := CreateMDIChild; OLEFrameForm.Cascade
end; end;

Control then passes to the new child window when it’s displayed The TOLEObjectForm.Tile1Click event procedure instructs
on screen (see the CreateMDIChild procedure). The complete OLEFrameForm to tile the child windows:
code for OLEFRAME.PAS is shown in Listing Seven beginning
on page 40. procedure TOLEObjectForm.Tile1Click(Sender: TObject);
begin
OLEFrameForm.Tile
OLEOBJ.PAS end;
Next, let’s proceed to the code for the child form,
OLEObjectForm. Since OLEFrameForm is the MDI manager, At this point, the TOLEObjectForm.ArrangeIcons1Click event
menu items on child windows must ask OLEFrameForm to procedure informs OLEFrameForm to arrange any minimized
handle window management when the user requests it. child window icons:

FEBRUARY 1996 Delphi INFORMANT ▲ 37


The Way of Delphi

procedure TOLEObjectForm.ArrangeIcons1Click(Sender: TObject);


procedure TOLEObjectForm.PasteObject1Click(Sender: TObject); begin
var OLEFrameForm.ArrangeIcons
ClipFmt: Word; end;
DataHand: THandle;
{ Pointer to the object’s info }
Info: Pointer;
The TOLEObjectForm.Edit1Click event procedure keeps track
begin of the Clipboard’s contents. Each time the user selects Edit
if PasteSpecialEnabled(Self, OLEFrameForm.Fmts) then from the menu, the Edit item’s sub-menus appear after a check
{ Show the user a PasteSpecial dialog to permit the
user to decide the specifics of the Object paste }
for a pasteable object is made. If a pasteable object is detected
if PasteSpecialDlg(Self, OLEFrameForm.Fmts, 0, in the Clipboard, PasteObject1 is enabled:
ClipFmt, DataHand, Info) then
begin { If there's no pasteable object in the Clipboard,
OLEContainer.PInitInfo := Info; {the object’s info } disable the PasteObject menu item. }
ReleaseOLEInitInfo(Info); procedure TOLEObjectForm.Edit1Click(Sender: TObject);
end; begin
end; PasteObject1.Enabled :=
PasteSpecialEnabled(Self, OLEFrameForm.Fmts)
end;

Copy and Paste Procedures


The TOLEObjectForm.CopyObject1Click event procedure
copies the OLEContainer’s object in the active child window
to the Clipboard:

procedure TOLEObjectForm.CopyObject1Click(Sender: TObject);


begin
OLEContainer.CopyToClipboard(False);
end;

Finally, the TOLEObjectForm.PasteObject1Click event proce-


dure (see Figure 8) pastes the OLEContainer’s object from the
Clipboard into the OLE container in the active child window.
Figures 9 and 10 show Objman at run time. Feel free to
explore and modify the application. (The complete code for
OLEOBJ.PAS is shown in Listing Eight on page 40.)

Conclusion
In the next installment, we’ll continue our OLE discussion as we
implement additional functionality in our OLE Object Manager.
For example, to allow a user to selectively close child windows, a
parent window needs to keep track of its children (in an array, for
example), then match a user’s selection from a menu (or equivalent)
to the corresponding child window. Deleting a child is equivalent to
Figure 8 (Top): The TOLEObjectForm.PasteObject1Click event proce- deleting an object. In Objman2, we’ll get to that. See you then. ∆
dure. Figure 9 (Middle): Here, Objman is shown with Microsoft
Word in a child window. Figure 10 (Bottom): In this running exam- The demonstration project referenced in this article is available on
ple of Objman, the user is entering information into a table with the
Database Desktop.
the Delphi Informant Delphi Informant Works CD located in
INFORM\96\FEB\DI9602GE.

Gary Entsminger is the author of The Way of Delphi [Prentice-Hall, 1996], The Tao of
Objects [M&T Books, 1995], Secrets of the Visual Basic Masters [Sams, 1994], and
Developing Paradox Databases [M&T Books, 1993].

FEBRUARY 1996 Delphi INFORMANT ▲ 38


The Way of Delphi

Begin Listing Seven — OLEFRAME.PAS FLinkClipFmt: Word;

unit OLEFrame;
public

{ Description: An OLE main MDIform to contain MDI child { Public registration info to drag-and-drop

windows. Use this form to launch and manage MDI child a single-element array to allow linking only }

windows. Fmts: array[0..0] of BOleFormat;

In the project, OBJMAN.DPR, in this article, OLEFRAME.PAS { Public created MDI child that can be

launches MDI child windows described in OLEOBJ.PAS. accessed from child forms }
function CreateMDIChild: TOLEObjectForm;

This OLEOBJ.PAS unit is responsible for managing end;

the objects.
var

The OLEFrame is the main form for the OLE Object Manager. OLEFrameForm: TOLEFrameForm;

Each time a user selects New Object Window from its main
menu, it creates a child window to contain a new object. implementation

Alternatively, users can drop an object onto this form to


create a new child window containing the dropped object. {$R *.DFM}

This form is an MDI window. It contains event procedures procedure TOLEFrameForm.FormCreate(Sender: TObject);

to cascade and tile child windows and arrange child begin

window icons. { Register this form to allow drag-and-drop }


FLinkClipFmt := RegisterClipboardFormat('Link Source');

Note that any child window using this MDI container,


OLEFrame, must reference this form in the implementation { Initialize the registration array }

section of its unit to avoid a circular reference. Fmts[0].fmtId := FLinkClipFmt;


Fmts[0].fmtMedium := BOLEMediumCalc(FLinkClipFmt);

This is necessary because this unit uses its child unit,


OLEOBJ.PAS, in the interface part of this unit. Fmts[0].fmtIsLinkable := True;
StrPCopy(Fmts[0].fmtName, '%s');

Code Patterns: StrPCopy(Fmts[0].fmtResultName, '%s');

Derive a new form from TForm (inheritance). { Register the form }

Merge Parent and Child MDI menus. RegisterFormAsOleDropTarget(Self, Fmts)

Register a form to allow drag-and-drop. } end;

interface function TOLEFrameForm.CreateMDIChild: TOLEObjectForm;


{ Create a child form of TOLEObjectForm type }

uses { Note that other children could also be created

SysUtils, WinTypes, WinProcs, Messages, Classes, using this pattern }

Graphics, Controls, Forms, Dialogs, Menus, ExtCtrls,


BOLEDefs, TOCtrl, begin

{ This is the child unit for this project }; { Create a new MDI child }

OLEObj Result := TOLEObjectForm.Create(Self);


{ Show the child }

type Result.Show;

TOLEFrameForm = class(TForm) end;

MainMenu1: TMainMenu;
File1: TMenuItem; procedure TOLEFrameForm.Exit1Click(Sender: TObject);

NewObject1: TMenuItem; begin


Close

{ Menu item event procedures } end;

procedure NewObject1Click(Sender: TObject);


procedure Exit1Click(Sender: TObject); { Create a new child for a dropped object.
The new child will contain the dropped object.}

{ Form event procedures } procedure TOLEFrameForm.FormDragDrop(

procedure FormCreate(Sender: TObject); Sender, Source: TObject; X, Y: Integer);

procedure FormDragDrop( var

Sender, Source: TObject; X, Y: Integer); NewMDIChild: TOLEObjectForm;


begin

private { Is the dropped object usable? }

{ Private registration declarations } if Source is TOLEDropNotify then

FEBRUARY 1996 Delphi INFORMANT ▲ 39


The Way of Delphi

begin SysUtils, WinTypes, WinProcs, Messages, Classes,


{ Then create a new child } Graphics, Controls, Forms, Dialogs, ToCtrl, Menus;
NewMDIChild := CreateMDIChild;
{ Give the OLE container info about the object } type
with Source as TOLEDropNotify do TOLEObjectForm = class(TForm)
NewMDIChild.OLEContainer.PInitInfo := PInitInfo OleContainer: TOleContainer;
end
end; { Menu system }
MainMenu1: TMainMenu;
procedure TOLEFrameForm.NewObject1Click(Sender: TObject); File1: TMenuItem;
var NewObjectWindow1: TMenuItem;
NewMDIChild: TOLEObjectForm; Exit1: TMenuItem;
begin N1: TMenuItem; { A line for looks }
{ Create a new MDI child } Edit1: TMenuItem;
NewMDIChild := CreateMDIChild; CopyObject1: TMenuItem;
end; PasteObject1: TMenuItem;
OLEObjectMenuItem: TMenuItem;
end. Window1: TMenuItem;
End Listing Seven Cascade1: TMenuItem;
Tile1: TMenuItem;
ArrangeIcons1: TMenuItem;
Begin Listing Eight — OLEOBJ.PAS
unit OLEobj; { Menu item click event procedures }
procedure NewObjectWindow1Click(Sender: TObject);
{ Description: A child form to contain OLE objects. procedure Exit1Click(Sender: TObject);
In the project, OBJMAN.DPR, OLEFRAME.PAS launches procedure Edit1Click(Sender: TObject);
MDI child windows described in this unit, OLEOBJ.PAS. procedure CopyObject1Click(Sender: TObject);
procedure PasteObject1Click(Sender: TObject);
procedure Cascade1Click(Sender: TObject);
This OLEOBJ.PAS unit is responsible for managing the procedure Tile1Click(Sender: TObject);
objects. This form manipulates an object contained by a procedure ArrangeIcons1Click(Sender: TObject);
TOLEContainer component. private
{ Private declarations }
The OLE container can get an object from the Clipboard or
by a drag-and-drop operation. This allows good object public
linking capability and keeps the application simple and { Public declarations }
easy to understand, but does not allow objects to be end;
inserted directly into the OLE container without the
use of a drag/drop event or the Clipboard. var
OLEObjectForm: TOLEObjectForm;
* It allows basic editing of the object or the object
package contained by a TOLEContainer component.
* It can copy an object to the clipboard or paste an implementation
object from the clipboard.
* It allows the user to manipulate child windows { Uses the MDI container, OLEFrame, in the implementation
(Create, Tile, Cascade windows and Arrange Icons) by section to avoid a circular reference }
using the OLEFrame form class described in the uses OLEFrame;
OLEFRAME.PAS unit.
* The OLEFrame is the main form for the OLE Oject {$R *.DFM}
Manager.
* It creates a child window for each object and manages procedure TOLEObjectForm.NewObjectWindow1Click(
the child windows. Sender: TObject);
begin
Code Patterns: { Ask the OLEFrame to create a new child }
Derive a new form from TForm (inheritance). OLEFrameForm.NewObject1Click(Sender)
Use the OLEContainer component. end;
Use the Clipboard to copy and paste objects. }
procedure TOLEObjectForm.Exit1Click(Sender: TObject);
interface begin
OLEFrameForm.Exit1Click(Sender)
uses end;

FEBRUARY 1996 Delphi INFORMANT ▲ 40


The Way of Delphi

{ Ask the OLEFrame to handle child window behavior }


procedure TOLEObjectForm.Cascade1Click(Sender: TObject);
begin
OLEFrameForm.Cascade
end;

procedure TOLEObjectForm.Tile1Click(Sender: TObject);


begin
OLEFrameForm.Tile
end;

procedure TOLEObjectForm.ArrangeIcons1Click(
Sender: TObject);
begin
OLEFrameForm.ArrangeIcons
end;

{ If there's no pasteable object in the Clipboard,


disable the PasteObject menu item. }
procedure TOLEObjectForm.Edit1Click(Sender: TObject);
begin
PasteObject1.Enabled :=
PasteSpecialEnabled(Self, OLEFrameForm.Fmts)
end;

{ Clipboard Copy and Paste object procedures }


procedure TOLEObjectForm.CopyObject1Click(Sender: TObject);
begin
OLEContainer.CopyToClipboard(False);
end;

procedure TOLEObjectForm.PasteObject1Click(
Sender: TObject);
var
ClipFmt: Word;
DataHand: THandle;
Info: Pointer;
begin
if PasteSpecialEnabled(Self, OLEFrameForm.Fmts) then

{ Show the user a PasteSpecial dialog to permit the


user to decide the specifics of the Object paste }
if PasteSpecialDlg(Self, OLEFrameForm.Fmts, 0,
ClipFmt, DataHand, Info) then
begin
OLEContainer.PInitInfo := Info;
ReleaseOLEInitInfo(Info);
end;
end;

end.
End Listing Eight

FEBRUARY 1996 Delphi INFORMANT ▲ 41


New & Used
by Robert Vivret te

HyperTerp Pro
HyperAct’s Scripting Language for Delphi

yperTerp Pro is, simply put, a scripting lan- Many of us are familiar with DOS batch files. These are actu-

H guage. It enables developers to implement


user-definable programming scripts into
their applications. To many of us, the real need for
ally a rudimentary form of a scripting language. UNIX pro-
vides a much more sophisticated scripting language. A script-
ing language such as HyperTerp is similar to these, but more
tailored to a Windows environment. Whereas the first two
such a capability may not be clear, so hopefully in examples run within an operating system, DOS and UNIX
this review I can explain enough about HyperTerp to respectively, HyperTerp runs within your application.
clarify this. But first, let me provide a brief definition
of a scripting language. There are two basic limitations to the use of a scripting language.
The first is that the scripting language itself will almost always be
What Is a Scripting Language? a subset of a more powerful language. Simply put, there may be
A Delphi programmer writes a program using Object Pascal, some commands in a language that, for various reasons, simply
which is then converted into machine code by the Delphi do not transfer into a scripting language. The second limitation
compiler. However, suppose that you want your program’s is speed — scripting languages are almost always interpreted
users to also have some control over the code in the applica- rather than compiled; there is a scripting interpreter that takes
tion. You can’t ship a Delphi compiler to each user, can you? the user’s commands and executes them one at a time.
Besides, I don’t think you want them fiddling with your code.
HyperTerp for Delphi
Instead, you can provide a scripting ability within your pro- HyperTerp is a scripting language for Delphi, and there are
gram. The user could write scripts, or macros (whatever you also versions that support C++ and Borland Pascal. Some of
want to call them), to perform some user-defined — rather HyperTerp’s roots extend back to a similar product called
than application-defined — task. These scripts could do pret- PasterP that some readers will no doubt be familiar with.
ty much anything that you might think of. For example:
• A charting program that graphs mathematical formulas. The language used in HyperTerp has a “Pascal-ish” structure
The user could define the formulas, as well as define how with a bit of BASIC thrown in for good measure. Figure 1 shows
the graph will appear. I am not just talking about “pick a the demonstration program that can load and execute all the
graph type from one of the X number available.” Rather, sample scripts included with HyperTerp. This script is a short
the user could write an actual script or sub-program that program that draws various graphic shapes on a canvas and
would take the formula results and then draw, plot, or should give you a good idea of how the language is structured.
print the data in a specified format.
• An automation system. The user could define various Incorporating the HyperTerp scripting language into your appli-
chores that would be automated through the use of cation is fairly straightforward. The installation steps add a com-
scripts. This may include a complex file or database ponent to Delphi’s Component Palette. Placing one of these
maintenance, or even an “agent” system that manages components on a form (typically the main form) attaches the
various programs on your computer (e.g. retrieving e- scripting interpreter to your program. The interpreter “object” is
mail, automated report printing, examining network responsible for performing all the HyperTerp script processing
devices, etc.). and will add about 150K of overhead to the application.

FEBRUARY 1996 Delphi INFORMANT ▲ 42


New & Used

Documentation
My one key complaint with
HyperTerp regards its docu-
mentation. Its accompanying
manual is a flimsy, 20-page
affair that covers installation of HyperTerp Pro by HyperAct, Inc. is a
scripting language that programmers can
the software, a simple tutorial, include in their Delphi applications. With
a four-page reference on the HyperTerp, users can implement custom
scripts (or macros) to add user-defined
three variations of the script functionality to the program. This can be
interpreter, and a few more done by adding a HyperTerp component
to the Component Palette, which in turn
pages of some extensions to automatically attaches a scripting inter-
the example code. As someone preter to the program. Although the doc-
umentation is lacking, HyperTerp
Figure 1: The demonstration program included with HyperTerp show- new to scripting languages, the includes a solid online language refer-
ing some of its graphics capabilities. documentation creates more ence to assist you with the scripting syn-
tax. If your system requires scripting,
questions than it answers. HyperTerp Pro is the tool for you.
After dropping the interpreter component onto your project,
HyperAct, Inc.
you will need to write a couple of event handlers to provide Fortunately, the package 3437 335 Street
links between your program and the scripting engine. The includes two help files. One West Des Moines, IA 50266
Phone: (515) 987-2910
demonstration program includes about four or five event han- is (for the most part) an elec- Fax: (515) 987-2909
dlers of about six to eight lines of code each. If you know tronic form of the printed E-Mail: CIS:76350,333 or Internet:
[email protected]
what you want HyperTerp to do, it won’t take long to make material, while the other is a Web Site: http://www.hyperact.com/
these connections. fairly complete language ref- Price: Standard version, including
the .DCU files, US$149; Professional
erence file. I would have liked version that includes all source code
The HyperTerp script files are ASCII text files, and it’s a sim- to see some more detailed files, US$395. There are no royalties
for distributing the scripting engine.
ple matter to create a quick program that loads/saves and exe- discussion of some of the
cutes these scripts. (Actually, the sample program described in commands, but it’s sufficient
the documentation does just this.) as a reference on the scripting language. You’ll find that you’ll
be working quite close to this language reference as you get
Inside HyperTerp up to speed with HyperTerp’s scripting syntax. The inclusion
As mentioned, HyperTerp is a subset of Pascal. The Standard of this reference went a long way toward taking the bad taste
function library includes 19 math, 10 string, six file, and two out of my mouth caused by the printed “manual.”
memory functions and procedures. The Extended library adds
six more file functions and six system functions (primarily HyperTerp comes in two flavors. The standard version
date/time stuff ). The Windows library adds 27 of the more includes the .DCU files for the component objects and sells
common Windows API routines, allowing scripts to create for US$149. The professional version adds all the source code
windows, dialog boxes, add and manipulate controls, access files as well and sells for US$395. There are no royalties for
profile strings from .INI files, and so on. distributing the scripting engine. The enclosed literature indi-
cates that the scripting engine will work within a DLL if
There are also some procedures and functions unique to its desired. Both versions are completely native Delphi objects —
language. For example, the ForEachFile procedure allows the no DLLs or VBXes. HyperAct has provided a demonstration
user to automatically traverse all the files that match a valid version of HyperTerp in library 22 (3rd Party Products) of the
DOS wildcard mask in the current directory (and optionally Borland Delphi forum on CompuServe (GO DELPHI). The
in all the subdirectories) and perform a common operation file name is PTRPDEMO.ZIP. They also have a Web site at
on each of these files. http://www.hyperact.com/.

Because HyperTerp is a subset of a more powerful lan- Conclusion


guage, users may occasionally need a function or procedure If you need the ability to provide a scripting system within
that is not currently supported. In the past, HyperAct has your Delphi applications, you need look no further than
released updated versions that support additional program- HyperTerp Pro. Printed documentation aside, the whole
ming functions and procedures, and this policy will surely package is stable and capable. ∆
continue.

Due to its interpreted nature, HyperTerp probably won’t Robert Vivrette is a contract programmer for a major utility company and
break speed records, but it is nonetheless a capable, solid Technical Editor for Delphi Informant. He has worked as a game design-
system. What it does, it does very well. I don’t need the fea- er and computer consultant, and has experience in a number of pro-
tures it provides, but I know there are many developers gramming languages. He can be reached on CompuServe at
who would welcome HyperTerp’s power and flexibility. 76416,1373.

FEBRUARY 1996 Delphi INFORMANT ▲ 43


New & Used
by Micah j. Bleecher

WISE Installation System


Create Distribution Diskettes Quickly and Easily

f you’re in the market for a powerful, easy-to-

I use Windows application installation system,


then waste no time and order the WISE
Installation System Version 4.0 by Great Lakes
Business Systems (GLBS) today. In my search for a
solid installation utility, no other package could
compare to the combination of power, performance,
and ease-of-use provided by WISE. The other pack-
ages I examined either lacked crucial features or
required that I learn another programming lan-
guage just to create an installation program. WISE
beautifully combines the best of both worlds.

Most people believe that installation utilities are reserved for


Figure 1: The WISE screen layout showing all the script actions.
developers of commercial or vertical software. However, WISE
(the acronym for Windows Self-Installing Executable), is so easy manual refers to these as script actions, the text of the selected action
to use that it’s also an excellent choice to replace standard com- is not directly editable. Script actions must be edited by double-
pression utilities for custom software developers to perform clicking on the appropriate line that displays a property editor.
installations and upgrades.
In addition, a different property editor is available for each script
The IDE action. Examine the Set Variable property editor in Figure 2. If
The IDE (Integrated Development Environment) comes in both you’re a Borland product user, you will naturally want to right-click
16- and 32-bit versions, although they both produce a 16-bit single on a script action. It’s important to note that the Properties choice
installation file. It will create one large file for electronic deliveries on the resulting menu is the global properties for the project. Select
or divide the file so it will span multiple floppy disks if necessary. Edit to modify the properties for the selected script action.

The screen layout is intuitive, as seen in Figure 1. The left side of In addition to numerous examples, WISE has an Installation
the screen displays a list box containing 57 script actions and the Expert that guides you through the entire process, and the resulting
right side displays a list of the selected script actions. To use a script script can be edited as if you started from scratch. It’s so easy that
action, simply highlight it and drag it to the selection window. you can create a professional install script in a matter of minutes.

When you run the compiled installation program, each action is Programming and Control Structures. Although there are only
executed sequentially, much like a batch file. Although the WISE 57 script actions, these are more than enough to do anything

FEBRUARY 1996 Delphi INFORMANT ▲ 44


New & Used

Figure 2: The Set Variable property editor.


Figure 3 : A custom dialog box that resembles a Microsoft Wizard.
you want or need to model your installation logic. You can create
an unlimited number of variables or use pre-defined system vari- stances, your install program can even be controlled using
ables that can be referenced throughout your script and within DDE (Dynamic Data Exchange). Code samples of DLLs
custom dialog boxes. using Delphi and C are provided in addition to an explana-
tion of how the DDE functionality works using Visual Basic.
There are no GOTO actions, but you can accomplish your
objectives by using the built-in IF, THEN, ELSE, and Multi-Media Support. To add a little pizzazz to your installa-
WHILE actions for conditional branching and looping. For tions, you can implement custom graphics and play .WAV
special needs, a string parsing action is conveniently available files. WISE features a built-in graphics editor that is, while
if you are reading strings out of text files where only a portion elementary, adequate for most needs. You can display graphics
of the string is needed. at any time during the installation process and simple anima-
tion effects, including fade-in, are supported. Most important
System Access. WISE features numerous actions that allow you however, is the ability to directly install fonts that are
to interface with nearly every conceivable aspect of the operating required by your application.
system. These include: reading/writing files and .INI files, locat-
ing files, checking system configuration, video resolution, avail- Windows 95 Install Support. A host of new issues must be
able memory and disk space, installed hardware capabilities and considered when installing applications into Windows 95.
much more. The following script actions are included to interact with this
operating system:
These features, along with the control structures, allow you to • Create shortcuts and shell links.
create a single install project that behaves differently in vari- • Edit and retrieve registration database values.
ous operating systems. This functionality becomes extremely • Self-registration of OCX and DLL modules.
important with the advent of Windows 95 and the growing • Support for and conversion between long and
popularity of NT. short filenames.
• Get Win32 system directory.
Custom Dialog Editor. To fully customize the user interface, • Windows 95 shared DLL counter support.
you can create custom dialog boxes using a variety of built-in • Complete support for uninstall.
controls to present data and options to the user and then
return selected values to your script. These dialog boxes can Specific BDE Support. When working with Borland products
be saved to the disk so they can be shared between projects. it’s important to install and configure the BDE (Borland
There are numerous samples, an example script that uses cus- Database Engine). Unfortunately, using the separate install pack-
tom dialog boxes to create an MS Wizard-like install program age for the BDE that is provided with Delphi is simply awkward
(see Figure 3), and a host of pre-written templates for a vari- and unprofessional.
ety of functions.
Fortunately WISE manages these tasks quite easily. A sample
Extendibility and Interoperability. If you cannot find what BDE configuration script is included that you can import into
you need in the built-in script actions, you can easily call a your projects. You can configure both BDE system parameters
function from within a DLL or run an executable file. This as well as add and configure aliases. You can see support for the
can be helpful when calling an external serializing module or 32-bit BDE that will be available shortly after Borland ships
migrating data during software upgrades. For unusual circum- their 32-bit products.

FEBRUARY 1996 Delphi INFORMANT ▲ 45


New & Used

For those of you accessing licensee to phone support beyond the first 30 days and free
data via ODBC, WISE easily upgrades throughout the year.
installs and configures both
16- and 32-bit ODBC drivers Demonstration Versions. You can download a demonstration
as well. version of WISE from their CompuServe forum or from the
The WISE Installation System, by GLBS Web site at http://www.glbs.com. The demonstration
GLBS, Inc., is a solid installation utility Technical Support. The prod- restricts you to using the resulting executable on the comput-
for Windows-based applications. uct comes with 30 days of free er it was created on, but is otherwise fully functional.
WISE’s IDE is intuitive and creates
technical support. Although I
16-bit installation files. Script actions
are executed as if they are batch files needed to leave a message on Conclusion
and are edited in individual property voice mail, GLBS technical WISE has too many features to mention in the space of this article.
editors. An Installation Expert can also support staff promptly So don’t hesitate to download a demonstration and try WISE for
guide you as you create an installa- returned my call, was helpful, yourself. I am confident that you will be won over by this product
tion script. WISE also supports system as I was. My only complaint is that I would like to see more detail
and was knowledgeable of the
access, multi-media, Windows 95, the
BDE, and much more. If you’re look- product. In addition, they in the manual — not only regarding each of the features, but also
ing for the right installation tool, have a section in program installation methodology in general. There are many new
WISE is a worthwhile investment. CompuServe’s Windows issues with Windows 95 and I would like to see some detailed infor-
Third-Party H Forum. To mation on the subject, even in the form of a help or supplemental
WISE Installation System
Great Lakes Business Solutions, Inc. access this forum, type WISE- text file. Nonetheless, the money I spent on WISE was well spent. I
2200 North Canton Center Road, INSTALL at the GO prompt wish I could say that about all developer utilities I’ve purchased. ∆
Ste. 220 and GLBS will answer ques-
Canton, Michigan 48187
Phone: (800) 554-8565 tions online. They are good
E-Mail: CIS: GO WISEINSTALL about making maintenance Micah Bleecher is a partner in Datacraft Systems, Inc., a database consulting firm that
Web Site: http://www.glbs.com/ releases available from their serves the Northeast. They specialize in Delphi, Paradox, and Web database integra-
Price: US$199; Annual Technical
Support Contract, US$95. BBS using passwords for regis- tion. Mr Bleecher can be reached at (609) 227-0202, e-mail:
tered users. An annual mainte- [email protected], or visit the Datacraft Systems Web site at:
nance contract entitles the http: //www.datacraft.db.com.

FEBRUARY 1996 Delphi INFORMANT ▲ 46

You might also like