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

Delphi Informant Magazine (1995-2001)

This document is the March 1998 issue of the Delphi Informant newsletter. It provides summaries of 3 articles: 1) The cover article discusses a technique for passing entire Delphi objects to a DCOM server using Delphi's component streaming mechanism without needing to manually marshal the objects. 2) An article provides code editor tricks for the Delphi Code Editor, including additional functionality beyond what was discussed in a previous article. 3) An article discusses developing object databases in Delphi and introduces the POET object database for use with Delphi 3. The document also includes brief descriptions of new product releases and versions from various software companies.

Uploaded by

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

Delphi Informant Magazine (1995-2001)

This document is the March 1998 issue of the Delphi Informant newsletter. It provides summaries of 3 articles: 1) The cover article discusses a technique for passing entire Delphi objects to a DCOM server using Delphi's component streaming mechanism without needing to manually marshal the objects. 2) An article provides code editor tricks for the Delphi Code Editor, including additional functionality beyond what was discussed in a previous article. 3) An article discusses developing object databases in Delphi and introduces the POET object database for use with Delphi 3. The document also includes brief descriptions of new product releases and versions from various software companies.

Uploaded by

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

March 1998, Volume 4, Number 3

Cover Art by: Tom McKeith

DCOM Streaming
Passing Delphi Objects to a DCOM Server

ON THE COVER
6 DCOM Streaming — David Body 29 Sights & Sounds
Automatic marshaling is terrific, but what if you need to pass data that Multimedia Buttons — Christopher Coppola
isn’t automation-compatible? Mr Body shares his slick technique for Is the standard Button component ill suited to your way-cool inter-
using Delphi’s component streaming mechanism to pass entire objects face? Mr Coppola shares his techniques for creating buttons that
along the DCOM trail — and still not have to play marshal yourself. blend with the environment — and make a little noise.

36 The API Calls


FEATURES Restoring Animation — John Ayres
Ever notice that Delphi-created applications don’t exhibit that cute
11 Informant Spotlight
animated effect when they’re minimized or restored? Well so has
Deploying ActiveX Controls — Dan Miser
Mr Ayres, and he knows what to do about it.
Swell! You’ve created an ActiveX control. Getting it “out there” to users,
however, is another story. Mr Miser lays out the issues and their solu-
tions when it comes to Web deployment.
REVIEWS
15 In Development
Sounds Gud to Me — Rod Stephens 38 Crystal Reports Professional 6.0
Do your users need to search for names without knowing the exact Product Review by Warren Rachele
spelling? Check out Mr Stephens’ Delphi implementation of proven 42 Rubicon for Delphi
Soundex algorithms. You’ll like what you hear. Product Review by Peter Hyde
19 DBNavigator 45 Delphi Developer’s Handbook
More Code Editor Tricks — Cary Jensen, Ph.D. Book Review by Alan Moore, Ph.D.
You read Dr Jensen’s article regarding Code Insight last month, so you’re
hip to all of the Code Editor’s tricks. Think again! There’s still plenty you
don’t know about the tool you use every day. DEPARTMENTS
23 Columns & Rows 2 Delphi Tools
Developing Object Databases — Chu Moy 5 Newsline
Things can get confusing when OOP meets a relational database. Mr 46 File | New by Alan Moore, Ph.D.
Moy says the object database, POET, is the answer, and puts one to
work with Delphi 3. Is there an ODBMS in your future?
1 March 1998 Delphi Informant
Delphi LEAD Technologies Announces LEADTOOLS 9.0
LEAD Technologies
T O O L S announced LEAD-
TOOLS 9.0, the
New Products company’s imaging
and Solutions toolkit. This version
offers more than 500
functions, properties,
and methods to inte-
grate black and white,
grayscale, and color
imaging into applica-
tions. Among the new
features are common
dialog boxes to speed capabilities, including hand drawing, and angle and
development, support for increased DICOM multi- line size support.
over 50 image file formats, page support with 9- to 16- Included with the toolkit
and optional FlashPix, video, bit grayscale and window lev- are source code examples for
and OCR modules. eling, and 16-bit-per-pixel Delphi, Borland C/C++,
The common dialog boxes grayscale TIF support. Microsoft Visual C/C++,
extend Windows common Version 9 also offers ISIS Visual Basic, Visual FoxPro,
functions to provide image- high-speed scanning, annota- Access, and Java Script.
specific capabilities, such as tion support to include
image processing and conver- read/write support for the LEAD Technologies
sion. The dialog boxes also Wang annotation file format, Price: From US$295 for the VBX Pro
offer thumbnail previewing multi-level password access package, to US$1,995 for the Pro
of image changes. to annotation, hyperlinks for Express package.
LEADTOOLS 9.0 offers all annotation objects, Phone: (704) 332-5532
additional import/export increased control over free- Web Site: http://www.leadtools.com
Snowbound Announces RasterMaster 7.0
Snowbound Software Corp. grayscale support, and tiled Snowbound Software Corp.
introduced RasterMaster 7.0, image support. Price: US$1,350 for Silver toolkit;
a set of imaging tools for RasterMaster 7.0 is avail- US$1,995 for Platinum toolkit.
Windows 95/NT, Windows able as a Windows 95/NT Phone: (617) 630-9495
3.x, and Macintosh 68K and DLL, Windows 95/NT Web Site: http://www.snowbnd.com
PPC. This version includes ActiveX/OCX, or Windows
enhanced support for the pre- 3.x DLL.
press, medical imaging, bank-
ing, and defense industries. IDEAL Introduces Virtual Print Engine 2.2
Raster image support IDEAL Software announced with 0.1 mm precision on any
includes TIFF/CMYK, Virtual Print Engine 2.2, number of pages. The vector
JPEG/CMYK, JEDMICS, which offers developers graphics offer a free, scalable
Flashpix, Winfax, DICOM, enhanced control over printed WYSIWYG preview and
Alpha Channel, Group 4, output from their Windows printer-independent output.
Group 3, CALS, PNG, applications. Developers using The package includes the
PhotoCD, BMP, PCX, PICT, Delphi, Visual Basic, C/C++, OCX, VCL, DLL, and over
Targa, and others. Functions FoxPro, and other languages 600KB of sample-sources
include CMYK 4 plane sup- can dynamically create, pre- for all common program-
port, RGB to CMYK and view, and print reports, rich ming languages, and is
CMYK to RGB conversion documents, drawings, etc. by available in 16- and 32-bit
features, anti-aliasing (in the calling functions. versions. There are no run-
Silver toolkit), fit to Window, Objects such as text, lines, time fees or royalties.
fit to width/height, image polygons, bitmaps (JPEG,
encryption, reading TIFF tags, PNG, TIFF, GIF, BMP, PCX, IDEAL Software
animated GIF, Winfax, display WMF, EMF, DXF), and 21 Price: US$548
callbacks, erase rectangle/black different barcodes can be Phone: 49 2131 9800 23
border removal, 10- to 16-bit positioned, rotated, and scaled Web Site: http://www.idealsoftware.com

2 March 1998 Delphi Informant


Delphi MathTools Launches MATCOM
MathTools Ltd. announced matic, using a smart project US$49, and US$99 for Delphi
T O O L S MATCOM, a MATLAB manager. Integration add-in for the student, aca-
compiling and integration demic, and commercial licenses,
New Products solution for Delphi, MathTools Ltd. respectively; site licenses and other
and Solutions Microsoft Excel, and Visual Price: US$99 for single student add-ins are also available.
Basic. license, US$249 for single academic Phone: (888) 628-4866 or
MATCOM allows MAT- license, and US$499 for single com- (212) 208-4476
LAB users to incorporate mercial license; additional US$24, Web Site: http://www.mathtools.com
MATLAB’s numerical and
computational strength into
GUI and spreadsheet tools.
Scientists and engineers can
develop algorithms in MAT-
LAB and automatically
incorporate them in applica-
tions created with these
tools, saving a manual trans-
lation phase. The integration
is achieved by compiling the
algorithms to independent,
royalty-free DLLs using
MATCOM and incorporat-
ing the DLLs into an appli-
cation. Compilation is auto-

Ashley Godfrey Releases Delphi Voyager 2


Ashley Godfrey has released are displayed and, along with by a Delphi programmer. In
Delphi Voyager 2, the updat- ActiveX interfaces, can be addition, Delphi Voyager
ed version of LinkWizard for linked to their containing contains a Delphi Super
Delphi. Delphi Voyager, a type library. Page offline viewer, which
resource explorer and Delphi Voyager explores allows a programmer to
resource management facility, BDE installations, enumer- search for and obtain a com-
offers a rebuilt user interface. ates databases and their alias- ponent by clicking on a
Combining Delphi, ActiveX, es, lists the tables of each download button.
the BDE, and the Internet, alias, and displays their asso-
Delphi Voyager enables ciated parameters. Also Ashley Godfrey
Delphi programmers to included are links to Price: US$60 for e-mailed copy;
explore and manage their Borland’s BDE tech site. US$65 for diskette.
other resources. Delphi Voyager also con- E-Mail: godfa@hotmail.com
Delphi Voyager enables the tains a view of the Internet Web Site: http://members.-
viewing of the structure and sites most likely to be used tripod.com/~nfi/index.html
contents of any regis-
tered ActiveX type
library. Delphi
Voyager lists all regis-
tered type libraries
without the need to
specify an OCX or
DLL for every
ActiveX library to be
viewed. With Delphi
3 installed, Delphi
Voyager can decom-
pile that library.
Delphi Voyager also
browses ActiveX
interfaces. CLSIDs

3 March 1998 Delphi Informant


Delphi Imagination Introduces IMAGinE ActiveX Control
Imagination Software, Inc.
T O O L S introduced the IMAGinE
ActiveX control, a thin-
New Products client, intranet imaging solu-
and Solutions tion designed for use by the
government, Fortune 500
companies, integrators, inde-
pendent software vendors,
and value-added resellers.
The ActiveX control allows
paper management capabili-
ties to be added to an
intranet. Its features include
displaying, printing, annotat-
ing, fiximage, scanning, Delphi, Visual Basic, Power Imagination Software, Inc.
OCR, ICR, OMR, reading Builder, Visual C++, or any Price: From US$799 for IMAGinE
barcode, and processing developer language that sup- Desktop, to US$4,999 for the IMAGinE
forms. IMAGinE also comes ports ActiveX. Suite Plus.
with pre-built imaging appli- IMAGinE ActiveX control Phone: (800) IMAGE10
cation prototypes and their runs under Windows 95/NT, Web Site: http://www.imagination-
source codes. This ActiveX and is compatible with all software.com
control can be used with OLE automation applications.

Indigo Rose Announces AutoPlay Menu Studio 1.1


Indigo Rose Corp. filenames, integrated palette Indigo Rose Corp.
announced the release of management, alignment tools, Price: US$195
AutoPlay Menu Studio 1.1 undo/redo, background gradi- Phone: (800) 665-9668 or
for Windows 95 and ents, bitmap tiling, and a (204) 946-0263
Windows NT 4.0. This tool graphics and sound library. Web Site: http://www.indigorose.com
is designed for multimedia
software developers, CD- Seagate Launches Crystal Info 6
ROM content providers, net- Seagate Software announced Designer 6 allows users to cre-
work administrators, or any- Seagate Crystal Info 6, a suite ate presentation-quality
one using CD-ROM as a of products that simultaneous- reports, and distribute them
distribution medium. ly incorporate Online throughout the enterprise.
This WYSIWYG develop- Analytical Processing (OLAP) Page-On-Demand technology
ment tool is designed for creat- and push technology. downloads only the required
ing CD-ROM menu systems Crystal Info 6 integrates pages of a report.
that automatically load and OLAP functionality and Crystal Info 6 offers report-
execute upon insertion of the Crystal Reports into Crystal viewing and distribution and
CD-ROM. The drag-and-drop Info’s enterprise-wide reporting OLAP analysis via the Web,
environment allows developers infrastructure. The Info Cube using HTML, ActiveX, or
to create a customized menu Designer extracts information Java. It also integrates push
system. Multimedia Action from relational databases and technology as a way to access
Buttons can be used to accom- consolidates the data into information via the Web,
plish a variety of tasks without multi-dimensional OLAP allowing users to flag reports
programming, including exe- cubes. The Info Worksheet and OLAP cubes and objects.
cuting programs, jumping to then allows users to view the
Internet/intranet sites, opening contents from various perspec- Seagate Software
documents, browsing local, tives. The interface allows users Price: US$749 per user for client license
CD-ROM, and network dri- to pivot rows and columns, and Report/Query and OLAP add-in mod-
ves, sending e-mail, starting an drill down, drill across, and ules; license and modules are also sold
installation program, printing insert graphs and calculations. separately.
files, displaying Help files, and The underlying data-structures Phone: (800) 877-2340 or
downloading via FTP. of Crystal Info 6 are compati- (604) 681-3435
AutoPlay Menu Studio ble with Seagate Holos. Web Site: http://www.-
includes full support for UNC Integrating Crystal Reports seagatesoftware.com

4 March 1998 Delphi Informant


News Borland Expands in Eastern Europe
Frankfurt, Germany —
Borland announced it has
tools in Hungary and has
established a strong market
Ministry of Education.
For more information on
L I N E expanded its operations in share as a result of its focus Borland Magyarorszag, visit
Eastern Europe by establish- on the market. Borland is its Web site at
March 1998 ing Borland Magyarorszag. also a supplier of develop- http://www.borland.hu/ or
Borland has transformed ment tools to the Hungarian call (49) 6103 979-281.
Delphi Szoft (Delphi
Software), the company’s Apogee Awarded Premier Partner Status by Borland
master distributor in Marlboro, MA — Apogee enterprise-wide solutions.
Hungary since January Information Systems, Inc. Over half of their revenues
1995, into Borland announced that Borland has come from development tools
Magyarorszag. This venture given the firm Premier Partner specifically designed for those
reinforces Borland’s presence status, providing increased markets. Apogee’s success
in Hungary, providing direct access to strategic business and delivering services has led to
contact and focus for the technical information. rapid, sustained growth and
company’s Hungarian cus- Borland established the the upgraded partnership with
tomer base. Premier Partner program in Borland.
Borland is a major 1996 to shift the focus of the Apogee Information Systems
provider of development company to larger clients and is a custom application con-
sulting and development firm
Keshet Broadcasting to Use Delphi in specializing in multi-tier
Information Network solu-
Borland Buys Visigenic Windows-based Traffic System tions. The company assists
Borland has signed an agree-
ment to acquire Visigenic
Jerusalem, Israel — El-On direct SQL access to the data- clients worldwide in integrat-
Software, a developer of object Software Systems Ltd. has base. The system will manage ing data and business processes
request broker (ORB) software. signed an agreement with all aspects of controlling the across legacy, client/server,
Visigenic shareholders will
receive .81988 shares of Borland
IBM Israel to build a station, including advertising Internet, and intranet environ-
common stock for each outstand- Windows-based broadcasting spot management, tape ments. For more information,
ing share of Visigenic stock. traffic system for Keshet Ltd., library management, and pro- call (508) 481-1400 or e-mail
Approximately 12.5 million shares
of Borland stock will be issued at
one of Israel’s three commer- gram scheduling. cpatton@apogeeis.com.
the close of the transaction, slated cial television concessionaires.
for the first quarter of 1998. The 32-bit application is Borland Ships JBuilder Client/Server Suite
The companies also plan to
build application server software
being developed using Delphi Las Vegas, NV — Borland tion (with source code).
that will combine Visigenic’s 3. Approximately 50 client announced JBuilder JBuilder’s scalable, compo-
VisiBroker ORB with Borland’s stations will be linked to an Client/Server Suite, an addi- nent-based environment is
JBuilder Java development tool.
Borland’s chief technology officer,
IBM RS/6000 server running tion to their line of visual Java designed for all levels of infor-
Rick LeFaivre, will head research Oracle version 7.3. development tools. JBuilder mation network development
and development for the com- The application will be Client/Server Suite supports projects, ranging from applets
bined companies.
Visigenic will gain a broader dis-
object-oriented and will development of large-scale, and applications that require
tribution channel and the tools to include a visual component to multi-platform Java applica- networked database connec-
make its ORB more competitive. handle program scheduling. tions by integrating Visigenic tivity, to client/server and
The acquisition will not affect the
company’s existing licensing deals
The component supports Software’s VisiBroker ORB enterprise-wide, distributed
with Oracle, Netscape Windows 95 functions. The for seamless CORBA integra- multi-tier systems. It supports
Communications, or other soft- application will enable users tion; Borland DataGateway 100% Pure Java, JDK 1.1,
ware vendors.
to display various objects Enterprise for high-perfor- JavaBeans, JFC, CORBA,
along a time-ruler, as well as mance database connectivity; RMI, JDBC, and all major
place an object within a con- InterSolv PVCS software for corporate database servers.
tainer object, while modifying team management and ver- JBuilder Client/Server Suite
the container’s properties. It sion control; Borland’s SQL is available for US$2,495.
will also include a Win32 IPC tools for robust client/server Owners of other Borland
component that will synchro- development; and tools can purchase JBuilder
nize data across the network. BeansExpress for JavaBeans Client/Server Suite for
All the components will sup- creation. JBuilder also US$2,000. For more infor-
port Hebrew and English. includes the DataExpress mation, call (800) 233-2444,
The application will use the database architecture and a or visit the JBuilder Web site
Borland Database Engine and fully functional CORBA e- at http://www.borland.-
Oracle’s SQL/NET client for commerce reference applica- com/jbuilder.

5 March 1998 Delphi Informant


On the Cover
Delphi 3 / DCOM

By David W. Body

DCOM Streaming
Using Delphi’s Component-Streaming Mechanism
to Pass Objects through DCOM

D evelopers increasingly find it useful to create distributed applications, so


called because their functionality is distributed across multiple processes
running on the same or different machines on a network. These processes com-
municate and cooperate with each other to perform the functions required of
the application as a whole.

Delphi 3 provides excellent support for a article, “Delphi 3 DCOM,” in the


variety of distributed-application techniques. September 1997 Delphi Informant.
For example, developers can use the Remote
Data Broker technology in the Delphi 3 The nice thing about COM/DCOM automa-
Client/Server Suite to create and deploy tion is that it automatically takes care of all
multi-tiered database applications. In these, the details of passing parameters and return
the client process typically communicates values across process and machine boundaries
with a middle-tier process that implements (a process known as marshaling). Part of the
and enforces business rules. In turn, the price you pay for this automatic marshaling is
middle-tier process typically communicates that you can use only automation-compatible
with a database-server process. These three data types for your parameters and function-
processes can be deployed in a variety of return values. Figure 1 contains a list of
configurations, but often each is run on a automation-compatible data types.
separate computer on the network.
At first this seems a rather serious limitation.
COM Automation What if you want to pass a Pascal object as a
Delphi 3 makes it easy to create other procedure parameter, or return a Pascal
kinds of distributed applications without object from a function call? If you’re limited
using the Remote Data Broker technology. to the data types listed in Figure 1, you have
The easiest way to do this is with no choice but to break your object into its
COM/DCOM “automation.” Automation separate fields, and pass these one-by-one.
clients can call procedures and functions in This requires writing a separate procedure or
automation servers running in other function to pass each field, resulting in less-
processes. Through DCOM, automation readable and less-maintainable code. Worse,
clients can call procedures and functions in because of the overhead associated with mar-
automation servers running on other shaling variables through COM/DCOM,
machines on the network. For a good intro- your application’s performance will suffer
duction to creating automation servers and dramatically if you must pass many fields in
clients with Delphi 3, see Jeremy Rule’s this manner.
6 March 1998 Delphi Informant
On the Cover
Pascal Type OLE Variant Type Description A property needs to be published and streamable.
Some properties use the stored directive to deter-
Smallint V T_I2 2-byte signed integer
mine if the property writes itself out to a stream.
Integer V T_I4 4-byte signed integer
If stored is False, the property will not be sent
Single V T_R4 4-byte real
over — even if it’s published.)
Double V T_R8 8-byte real
Currency V T_CY currency
Through COM/DCOM automation we can use
TDateTime V T_DATE date
Delphi’s built-in object-streaming capabilities to
WideString V T_BSTR binary string
pass entire components as procedure parameters
IDispatch V T_DISPATCH pointer to IDispatch interface
or function-return values. For example, we could
SCODE V T_ERROR OLE error code
simply convert a component to text, pass the text
WordBool V T_BOOL True = -1, False = 0
from one process to another as a WideString,
OleVariant V T_VARIANT OLE Variant
then convert the text back into a component in
IUnknown V T_UNKNOWN pointer to IUnknown interface
the second process. Of course, it’s slightly more
Byte V T_UI1 1-byte signed integer
efficient to convert a component to and from a
Figure 1: A list of automation-compatible data types.
binary stream of bytes (the way Delphi stores
.DFM files) rather than using the text representa-
Streaming Objects tion. Using this approach, we can pass components across
If only there were an easy way to convert complex Pascal process and machine boundaries with a construct known as a
objects to and from one or more of the types listed in variant array of bytes.
Figure 1, i.e. those automatically marshaled by COM. Well
there is, and it’s an integral part of Delphi. Most Delphi Listing One (beginning on page 9) is a Pascal unit that con-
developers are aware that you can right-click a Delphi form tains two pairs of functions used to convert any object derived
at design time, and choose View as Text. Doing so will gen- from TComponent to and from automation-compatible data
erate text listing the form’s attributes — including all of the
form’s components and their attributes (see Figure 2). TSystemStatus = class(TComponent)
When you right-click the text listing of your form’s attrib- private
FComputerName: string;
utes and choose View as Form, Delphi converts the text FUserName: string;
back into a graphical representation of your form. FMajorVersion: Integer;
FMinorVersion: Integer;
FBuildNumber: Integer;
Delphi uses a similar streaming technique to save your form FPlatformID: Integer;
as a .DFM file. In this case, Delphi uses a more efficient FTotalPhys: Integer;
binary representation of your form and its components. This FAvailPhys: Integer;
FTotalPageFile: Integer;
takes less space, and makes saving and retrieving forms a little FAvailPageFile: Integer;
faster than using a text representation. It turns out that FTotalVirtual: Integer;
Delphi can automatically convert any component derived FAvailVirtual: Integer;
public
from TComponent to and from the text and binary represen- procedure GetSystemStatus;
tations used with forms and their components. All of the procedure DisplaySystemStatus(Strings: TStrings);
component’s published (and streamable) read/write properties published
property ComputerName: string
are automatically included in this conversion process. (Note: read FComputerName write FComputerName;
property UserName: string
object Form1: TForm1 read FUserName write FUserName;
Left = 200 property MajorVersion: Integer
Top = 108 read FMajorVersion write FMajorVersion;
Width = 696 property MinorVersion: Integer
Height = 480 read FMinorVersion write FMinorVersion;
Caption = 'Form1' property BuildNumber: Integer
Font.Charset = DEFAULT_CHARSET read FBuildNumber write FBuildNumber;
Font.Color = clWindowText property PlatformID: Integer
Font.Height = -11 read FPlatformID write FPlatformID;
Font.Name = 'MS Sans Serif' property TotalPhys: Integer
Font.Style = [] read FTotalPhys write FTotalPhys;
PixelsPerInch = 96 property AvailPhys: Integer
TextHeight = 13 read FAvailPhys write FAvailPhys;
object Button1: TButton property TotalPageFile: Integer
Left = 306 read FTotalPageFile write FTotalPageFile;
Top = 214 property AvailPageFile: Integer
Width = 75 read FAvailPageFile write FAvailPageFile;
Height = 25 property TotalVirtual: Integer
Caption = 'Button1' read FTotalVirtual write FTotalVirtual;
TabOrder = 0 property AvailVirtual: Integer
end read FAvailVirtual write FAvailVirtual;
end end;

Figure 2: A text listing of a form’s attributes. Figure 3: The interface of a TSystemStatus component.

7 March 1998 Delphi Informant


On the Cover
procedure TForm1.Button1Click(Sender: TObject);
var
SystemStatus: TSystemStatus;
begin
SystemStatus := TSystemStatus.Create(Self);
try
SystemStatus.GetSystemStatus;
SystemStatus.DisplaySystemStatus(ListBox1.Items);
finally
SystemStatus.Free;
end;
end;

Figure 4: Putting the TSystemStatus component to work.

types. The ComponentToString and StringToComponent


functions will convert components to and from text repre-
sentations, respectively. The ComponentToVariant and
VariantToComponent functions will convert components to
and from binary variant-array representations.
Figure 5: The ActiveX page of the New Items dialog box.
An Example
As an example of how to use these functions, let’s suppose we
want to monitor the system status of another machine on the
network. Figure 3 depicts the interface of a TSystemStatus
component that captures in its properties certain system
attributes of interest. (Code for the implementation section is
included in the source code for this article; download instruc-
tions appear at the end of this article.) This component is for
illustration purposes only; you can modify it to suit your par-
ticular needs. The TSystemStatus component also implements
a procedure called DisplaySystemStatus, which will display
these attributes using a supplied TStrings parameter.

The TSystemStatus component is easy to use. Simply


create an instance of the component by calling
TSystemStatus.Create. A call to TSystemStatus.GetSystemStatus
will record a snapshot of the current system status in the Figure 6: The Type Library Editor.
component’s properties. To display the system status, simply
call TSystemStatus.DisplaySystemStatus, passing it a TStrings
function TSystemStatusAuto.Get_SystemStatus: OleVariant;
component into which the TSystemStatus component can write var
its output. Figure 4 illustrates these steps in the context of an SS: TSystemStatus;
ordinary Delphi application. begin
SS := TSystemStatus.Create(nil);
try
Server Application SS.GetSystemStatus;
It’s almost as easy to use TSystemStatus through COM/DCOM Result := ComponentToVariant(SS);
finally
automation. First, we need to create a server application to run SS.Free;
on the computer whose system attributes we want to monitor. By end;
adding an Automation Object we can give our server application end;

the ability to report the system status of its host. From the File
Figure 7: The Get_SystemStatus function.
menu, choose New; then select the Automation Object icon from
the ActiveX tab, as shown in Figure 5. Enter a class name of
SystemStatus. Delphi will automatically create a type library for for a function named Get_SystemStatus, which you should
you, and display the Type Library Editor (see Figure 6). Note complete as shown in Figure 7.
that Delphi has created a COM interface named ISystemStatus.
This function simply creates an instance of TSystemStatus, calls
Click the Property icon to create a new property for the the GetSystemStatus method, then converts the TSystemStatus
ISystemStatus interface. Name this property SystemStatus, component into an OleVariant that can be returned as the func-
change its data type to OleVariant, and change the Property tion’s result. You will need to add the CompStream unit (again,
Type to Read Only. (Our TSystemStatus component is for see Listing One) to your uses clause before compiling the server
monitoring system status, not for modifying it.) Finally, click application. After the server application is complete, be sure to
the Refresh icon. Delphi will automatically create a skeleton run it once with the /REGSERVER parameter to register it.
8 March 1998 Delphi Informant
On the Cover
Practical Application
I have successfully used this technique to allow multiple users to
monitor and control complex calculation processes running on a
server. For example, the Iowa Legislative Fiscal Bureau uses a
Delphi application to project aggregate payroll costs for the
State of Iowa, using assumptions supplied by users. Multiple
users can simultaneously perform multiple projections. Each
projection uses actual payroll data from a database containing
approximately one million records. Depending on the size of the
specified population and the number of years being projected, a
single projection can take up to approximately 15 minutes to
complete. (Note: The server is a dual-processor 166MHz
Pentium Pro machine with 128MB of RAM running Windows
NT 4.0 and Microsoft SQL Server 6.5.)
Figure 8: The client application at run time.
The calculations required for each projection are performed
by a separate process running on the server, and are moni-
procedure TForm1.Button1Click(Sender: TObject); tored and controlled by a “calculator manager” application
var also running on the server. The calculator manager communi-
SS: ISystemStatus;
SystemStatus: TSystemStatus; cates with client applications running on user workstations
begin employing the technique described in this article. Performing
SS := CoSystemStatusAuto.CreateRemote(Edit1.Text); the calculations on the server in this manner improves perfor-
SystemStatus := _
VariantToComponent(SS.SystemStatus) as TSystemStatus; mance, decreases network traffic, and prevents sensitive pay-
try roll data from leaving the server during the calculations.
SystemStatus.DisplaySystemStatus(ListBox1.Items);
finally
SystemStatus.Free; Conclusion
end; Using the technique discussed in this article, you can easily
end; pass instances of any object derived from TComponent across
process or machine boundaries. The technique uses Delphi’s
Figure 9: The client application uses this code to create, use,
and release the DCOM server. built-in component-streaming support to convert a compo-
nent instance in one process into a data type that can be
automatically marshaled by COM/DCOM automation. We
Client Application pass the streamed component to the other process and con-
Now all we need is a client application that can retrieve and vert it back into a component instance, again using Delphi’s
display the system status from our server application. Our built-in streaming support. ∆
client application will have a screen layout like that shown
in Figure 8. Clicking the button will execute the code in The files referenced in this article are available on the Delphi
Figure 9. You’ll need to import the type library from our Informant Works CD located in INFORM\98\MAR\DI9803DB.
server application, and add the SystemStatus and
CompStream units to your uses clause before compiling.

As shown in Figure 9, the client application first acquires a


pointer to an ISystemStatus interface by calling David W. Body is the owner of Big Creek Software, a custom software develop-
CoSystemStatusAuto.CreateRemote, passing the machine name ment and consulting firm specializing in business, financial, and legal applica-
tions (http://www.bigcreek.com). David is Borland Delphi 3 Client/Server certi-
entered in the edit box. Delphi and COM/DCOM take care
fied, and president of the Central Iowa Delphi Users Group.
of establishing a connection to the server application on the
specified machine. Our client then passes the OleVariant —
returned by the SystemStatus property of ISystemStatus — to
the VariantToComponent function, and casts the result to a
TSystemStatus component using the as operator. This gives
our client a local copy of the TSystemStatus component creat-
Begin Listing One — The CompStream Unit
unit CompStream;
ed on the server, complete with all the properties set by the
server’s call to GetSystemStatus (refer to Figure 7). interface

uses Classes;
Finally, the client displays the system status of the remote
machine by calling DisplaySystemStatus, and frees the function ComponentToString(Component: TComponent): string;
TSystemStatus component. That’s all there is to using the function StringToComponent(Value: string): TComponent;
function ComponentToVariant(Component: TComponent):
TSystemStatus component to monitor the system status on a
Variant;
remote machine.
9 March 1998 Delphi Informant
On the Cover

function VariantToComponent(Value: Variant): TComponent; BinStream.WriteBuffer(Data^,


VarArrayHighBound(Value,1)+1);
implementation finally
VarArrayUnlock(Value);
function ComponentToString(Component: TComponent): string; end;
var BinStream.Seek(0, soFromBeginning);
BinStream: TMemoryStream; Result := BinStream.ReadComponent(nil);
StrStream: TStringStream; finally
s: string; BinStream.Free;
begin end;
BinStream := TMemoryStream.Create; end;
try
StrStream := TStringStream.Create(s); end.
try
BinStream.WriteComponent(Component);
BinStream.Seek(0, soFromBeginning); End Listing One
ObjectBinaryToText(BinStream, StrStream);
StrStream.Seek(0, soFromBeginning);
Result := StrStream.DataString;
finally
StrStream.Free;
end;
finally
BinStream.Free;
end;
end;

function StringToComponent(Value: string): TComponent;


var
StrStream: TStringStream;
BinStream: TMemoryStream;
begin
StrStream := TStringStream.Create(Value);
try
BinStream := TMemoryStream.Create;
try
ObjectTextToBinary(StrStream, BinStream);
BinStream.Seek(0, soFromBeginning);
Result := BinStream.ReadComponent(nil);
finally
BinStream.Free;
end;
finally
StrStream.Free;
end;
end;

function ComponentToVariant(Component: TComponent):


Variant;
var
BinStream: TMemoryStream;
Data: Pointer;
begin
BinStream := TMemoryStream.Create;
try
BinStream.WriteComponent(Component);
Result := VarArrayCreate([0,BinStream.Size-1],varByte);
Data := VarArrayLock(Result);
try
Move(BinStream.Memory^, Data^, BinStream.Size);
finally
VarArrayUnlock(Result);
end;
finally
BinStream.Free;
end;
end;

function VariantToComponent(Value: Variant): TComponent;


var
BinStream: TMemoryStream;
Data: Pointer;
begin
BinStream := TMemoryStream.Create;
try
Data := VarArrayLock(Value);
try

10 March 1998 Delphi Informant


Informant Spotlight
Delphi 3.01 / ActiveX

By Dan Miser

Deploying ActiveX Controls


Techniques for Testing, Deployment, Security, and More

L ast month, we offered a handy introduction of different ways to create and use
ActiveX controls within Delphi. This month, we’ll demonstrate how to deploy
those controls to a Web site, as well as how to debug your ActiveX controls.

Deploying ActiveX Controls


Deploying an ActiveX control to a Web server Note: If you fully qualify your URL with the
is straightforward, but one wrong move and domain name, you must specify the protocol
people won’t be able to use the ActiveX con- (i.e. http://) in the Target URL edit box, or the
trol from their browsers. Fortunately, every- control will fail to download properly.
thing you need to make your ActiveX deploy- Alternatively, you can designate that the con-
ment successful is wrapped up in one dialog trol is in the same directory by placing “./” in
box. If you have an ActiveX project open — Target URL.
for example, the CalendarX.DPR project we
created last month — you can select the Packages. The footprint required to create
menu item Project | Web Deployment Options even a minimal ActiveX control is a couple of
to display the dialog box shown in Figure 1. hundred kilobytes. We can reduce the size,
The options set here reflect a typical installa- however, by using run-time packages. This
tion of Microsoft’s Personal Web Server, way, the user must only download the run-
which runs under Windows 95. time packages once, and can gain substantial
download savings each time they download
any Delphi control compiled with run-time
packages.

Using run-time packages for an ActiveX con-


trol is as simple as using run-time packages
for an application. Select Project | Options,
then select the Build with runtime packages
check box on the Packages page of the
Project Options dialog box. Once you’ve
compiled your project with run-time pack-
ages, you can enable the deployment of those
packages by checking the Deploy required
packages check box on the Project tab of the
Web Deployment Options dialog box.

Installing multiple files with your ActiveX con-


trol will require you to use some combination
of an .INF file and a .CAB file. An .INF file is
Figure 1: The Web Deployment Options dialog box. an installation script that points to the loca-
11 March 1998 Delphi Informant
Informant Spotlight
copy of the BDE (Borland Database Engine) installed.
[Add.Code]
packages.ocx=packages.ocx Furthermore, the client machine must be able to physically
VCL30.dpl=VCL30.dpl connect to the database. For this reason, database applications
are best suited to intranet deployment. If you do choose to
[packages.ocx]
file=http://dmiser.comps.com/delphi/packages.ocx deploy an ActiveX control that needs database access, Delphi
clsid={ 03B3F168-C13B-11D0-9BB9-00A024604D21 } 3.01 includes a .CAB file and an .INF file to take care of the
RegisterServer=yes
BDE installation process. See the file BDEINST.TXT on the
FileVersion=1,0,0,0
Delphi 3.01 CD for more information on how to do this. On
[VCL30.dpl] the other hand, if you want a true thin-client solution over
file=http://www.borland.com/vcl30.cab
the Internet, you will need to use MIDAS.
FileVersion=3,0,5,53

Figure 2: Sample .INF file for an ActiveX control built with Using ActiveX in HTML
run-time packages. HTML syntax. ActiveX controls are frequently used in
HTML pages. The <OBJECT> tag is used to identify the con-
tions of all the files needed to make your ActiveX installation trol to the browser. In addition, several attributes are used for
complete. .CAB files allow multiple files to be distributed over this tag to help the browser display the proper ActiveX con-
the Internet in compressed form, much like a .ZIP file. If you trol to the client:
distribute many files, or if the file sizes are large, you may want CLASSID: CLSID registered to the ActiveX control
to distribute the files in compressed mode via the .CAB file. CODEBASE: URL used by the browser to download the
control to the client
Borland has code-signed some of their run-time packages and
placed them in .CAB files on their Web site (http://www.- For more information on this tag, see the HTML working
borland.com). Code-signing these packages proves that the draft at http://www.w3.org/pub/WWW/TR/-
run-time package files the end user receives are the same ones WD-object.html.
that Borland developed (see Figure 2).
Version control. If you deploy your ActiveX control with ver-
Code-signing. ActiveX controls pose a potential security risk sion information enabled, the browser will automatically
to clients who download them. For this reason, Microsoft has detect whether it needs to update the control. The syntax of
proposed a technology called Authenticode. Authenticode the HTML to take advantage of version control is to append
assures end users of the ActiveX control’s origin, and that it the version number to the CODEBASE attribute. For example,
hasn’t been tampered with since the control has been signed. the following HTML code will only download the control if
While this is not foolproof, it does provide some degree of the version number of the control is later than 1.1:
accountability for the majority of cases.
<OBJECT
CLASSID="clsid:7FD22F02-C0E1-11D0-9BB9-00A024604D21"
To obtain certification for your ActiveX control, you must CODEBASE=
contact an authorized Certification Authority (one such "http://dmiser.comps.com/calendarx.ocx#version=1,1,0,0"
>
company is VeriSign; http://www.verisign.com). You can
apply for a certificate; upon acceptance, the authorized
Certification Authority will return a certificate file you can Once the ActiveX control is deployed to a client, there will be
use to sign a control. two glaring features that need to be addressed:
1) How to use property names in HTML
For any serious ActiveX development, you must obtain the 2) Safety warnings when using the ActiveX control in
ActiveX SDK from Microsoft. In the ActiveX SDK you’ll find HTML
documentation, online resources, and a suite of programs to
help develop ActiveX content. For example, the program Assigning property names. Delphi-created ActiveX controls
MAKECER generates test certificates you can use for local implement the saving and restoring of property values
testing. For more information, visit http://www.microsoft.- through a standard persistence model. This provides a way for
com/activex. the ActiveX control to tell others about its properties. For
example, the HTML listing in Figure 3 is the result of adding
Deploy. When all the options have been properly set, select a Delphi-created ActiveX control to an HTML page using
Project | Web Deploy to execute the deployment. This will
Microsoft ActiveX Control Pad.
copy the ActiveX control, an .INF file and a .CAB file if <OBJECT ID="CalendarX1" WIDTH=320 HEIGHT=120
appropriate, and a skeleton HTML file to the directory speci- CLASSID="CLSID:7FD22F05-C0E1-11D0-9BB9-00A024604D21"
DATA="DATA:application/x-oleobject;BASE64,BS/Sf+HA0BGbuQCgJ
fied. If your Web site isn’t hosted locally, you can still deploy
GBNIVRQRjAJVENhbGVuZGFyAARMZWZ0AgADVG9wAgAFV2lkdGgDQAEGSGVp
all the files to a local directory and use the same method of Z2h0AngLU3RhcnRPZldlZWsCAAAASA=">
moving the files to your host directory after deployment. </OBJECT>

Figure 3: An <OBJECT> tag for an imported ActiveX control.


One more note: Writing an ActiveX control or ActiveForm (Note: There cannot be carriage returns in the actual DATA
that accesses a database requires the client machine to have a string; they were added here for formatting purposes.)

12 March 1998 Delphi Informant


Informant Spotlight
TCalendarX = class(TActiveXControl, ICalendarX, tines ReadPropFromBag and PutPropInBag in \Delphi
IPersistPropertyBag) 3\SOURCE\RTL\SYS\comobj.pas. These procedures make it
function IPersistPropertyBag.Load =
PersistPropertyBagLoad;
easy to implement an IPersistPropertyBag interface for any
function IPersistPropertyBag.Save = ActiveX control (see Figure 5).
PersistPropertyBagSave;
function PersistPropertyBagLoad(
const pPropBag: IPropertyBag;
Each item you add to the property bag in this function will
const pErrorLog: IErrorLog): HResult; stdcall; show up as a <PARAM> for the <OBJECT> tag. This provides
function PersistPropertyBagSave( another level of HTML interactivity by allowing the use of
const pPropBag: IPropertyBag; ClearDirty: BOOL;
fSaveAllProperties: BOOL): HResult; stdcall;
these properties in an HTML script (see Figure 6).
end;

Figure 4: Implementation of TCalendarX.IPersistPropertyBag. Automating persistence. We can borrow some logic to


implement this interface in a more generic manner by look-
function TCalendarX.PersistPropertyBagSave(
ing at the demonstration application Pbag.dpr found in the
const pPropBag: IPropertyBag; fClearDirty: BOOL; \Delphi 3\Demos\Activex\Propbag directory. This demon-
fSaveAllProperties: BOOL): HResult; stdcall; stration was included in the Delphi 3.01 update. In it, the
begin
PutPropInBag(pPropBag, 'Color', FDelphiControl.Color);
IPersistPropertyBag interface reads and writes the properties of
Result := S_OK; the control using RTTI (Run-Time Type Information).
end; Using this method ensures we won’t forget any properties
Figure 5: The IPersistPropertyBag.Save function. that need to be streamed.

It would be nice to use Borland’s implementation of


<OBJECT ID="CalendarX1" WIDTH=320 HEIGHT=120 IPersistPropertyBag in all our controls. However, because COM
CLASSID="CLSID:7FD22F05-C0E1-11D0-9BB9-00A024604D21">
<PARAM NAME="BorderStyle" VALUE="1">
only allows interface inheritance, we have but two options:
<PARAM NAME="CalendarDate" VALUE="5/29/97"> 1) Place the common code in a separate unit and implement
<PARAM NAME="Color" VALUE="-2147483643"> the interface for every ActiveX control that requires the
<PARAM NAME="Ctl3d" VALUE="-1">
<PARAM NAME="Cursor" VALUE="0">
IPersistPropertyBag interface.
<PARAM NAME="Day" VALUE="29"> 2) Create a new component derived from TActiveXControl
<PARAM NAME="Enabled" VALUE="-1"> that implements the IPersistPropertyBag interface. By
<PARAM NAME="FontName" VALUE="0">
<PARAM NAME="FontSize" VALUE="8">
doing this, all that must be changed is the inheritance of
<PARAM NAME="GridLineWidth" VALUE="3"> your new ActiveX control from TActiveXControl to
<PARAM NAME="Month" VALUE="5"> TActiveXPropBag. This will allow your control to auto-
<PARAM NAME="ParentColor" VALUE="0">
<PARAM NAME="ReadOnly" VALUE="0">
matically receive the IPersistPropertyBag implementation.
<PARAM NAME="StartOfWeek" VALUE="0">
<PARAM NAME="UseCurrentDate" VALUE="-1"> By descending from Delphi’s object reference, as opposed
<PARAM NAME="Visible" VALUE="-1">
<PARAM NAME="Year" VALUE="1997">
to COM’s interface reference, we can achieve implementa-
</OBJECT> tion inheritance.
Figure 6: An <OBJECT> tag with IPersistPropertyBag implemented.
Marking the ActiveX control as safe. When using an
ActiveX control in a script, Microsoft Internet Explorer (IE)
All the properties are set in the DATA element of this tag;
will check if the control can be safely scripted. If the control
however, there is no easy way to look at this data to deter-
is not deemed safe, a warning message will be displayed (see
mine what the value of a given property is. In addition, by
Figure 7). By default, any control in a script will generate
using this method, the ActiveX control is more difficult to
this warning. According to the ActiveX SDK documenta-
control in an HTML scripting environment, such as
VBScript or JavaScript.

Microsoft defined the IPersistPropertyBag interface to turn


the unreadable data into human-readable properties. To
implement this interface, we simply need to add the two key
methods, Load and Save, to our existing ActiveX implemen-
tation. However, because the IPersistPropertyBag interface
already defines these methods, we must use Delphi’s method
resolution clauses to make our implementation work (see
Figure 4). For more information on this topic, look up
“Method resolution clauses” in Delphi’s online Help.

The IPersistPropertyBag interface referenced in the Load and


Save methods is the interface responsible for reading and Figure 7: The error displayed when a control is not marked safe
writing individual properties. You can find the helper rou- for scripting.

13 March 1998 Delphi Informant


Informant Spotlight
TActiveXSafeFactory = class(TActiveXControlFactory)
procedure UpdateRegistry(Register: Boolean); override;
end;

After implementing this class, change the


TActiveXControlFactory.Create statement in the initialization
section of the project to TActiveXSafeFactory.Create. Then,
every time the control gets registered or unregistered, the
registry settings that belong to the control will be automat-
Figure 8: Regedit displaying a safe control.
ically updated as well.

Testing ActiveX Controls


DLL debugging. Previous versions of Delphi required you to buy
Borland’s add-on package, Turbo Debugger for Windows
(TDW), to debug DLLs. Delphi 3 allows you to debug the cur-
rently loaded DLL inside the Delphi IDE. To accomplish this,
you need to open the DLL project file, then specify an .EXE that
will use this DLL. This is known as the host application. Select
Run | Parameters to bring up a dialog box where you can specify a
host application and any command-line criteria for that applica-
Figure 9: Setting run parameters to debug Personal Web Server.
tion. Select Run | Run to start the host application. If you have a
breakpoint set in the DLL source files, Delphi will stop execution
tion, there are three ways to suppress this warning message:
of the host application and pass control to the Delphi IDE,
1) Turn off the security checking option for all scripts in IE.
where you can evaluate variables and step through code.
2) Register the control as “safe for scripting” in the Windows
registry.
Remember that an ActiveX control is nothing more than a
3) Implement the IObjectSafety interface.
DLL. IE is an application that uses these special DLLs.
Therefore, we can use the debugging technique previously
Let’s examine the implications of each scenario.
described to debug our ActiveX controls.
Marking all controls as “safe for scripting” in IE isn’t a very
Figure 9 shows the settings needed to debug the CalendarX
secure way to stop these messages from occurring. Some con-
project using IE as the host application. Place a breakpoint in
trols might really be unsafe for scripting, and the user should
the InitializeControl method in the unit CalImpl.pas. After
be warned about those instances. Implementing the
modifying the run parameters for the project, execute the Run
IObjectSafety interface is overkill for most ActiveX controls, as
| Run command to launch IE. When the ActiveX control is
a control is usually either safe or unsafe in its entirety.
loaded, the Delphi IDE will regain focus, and you can use the
Therefore, let’s concentrate on the second option — marking
integrated debugger the same way you always have. To stop
the control as safe in the registry.
the debugging session, exit IE.
IE will check for the existence of two specific registry
Conclusion
entries for every control that is placed in a script (see Figure
These two articles have covered a lot of ground in the ActiveX
8). If these entries exist, IE knows the author has marked
arena. However, even this lengthy treatment only scratches the
the control as safe, and will not display a warning. The two
surface of things that can be done with this impressive compo-
registry entries are CATID_SafeForScripting and
nent technology. To further understand ActiveX, read the
CATID_SafeForInitializing. The entries are special because they
ActiveX SDK documentation; it’s a price well worth paying as
are defined as Category IDs. A Category ID is created to group
the line blurs between Delphi and ActiveX components. ∆
components into a particular category. To belong to these cate-
gories, the author of the control must believe the control will
The files referenced in this article are available on the Delphi
not harm your system — no matter how the ActiveX control is
Informant Works CD located in INFORM\98\MAR\DI9803DM.
used in the script.

TActiveXControlFactory is the class factory responsible for


creating and registering ActiveX controls. In this class fac- Dan Miser is a software developer residing in Southern California with his wife
tory, there is a method called UpdateRegistry that maintains and daughter. He has been a Borland Certified Client/Server Developer since
registry settings for an ActiveX control. This seems like a 1996, and is a frequent contributor to Delphi Informant. You can contact him
logical place to add the registry entries that will mark this at http://www.iinet.com/users/dmiser.
control as safe. Creating our class factory is as easy as
descending the TActiveXControlFactory class and manipu-
lating the registry settings in the overridden UpdateRegistry
method:
14 March 1998 Delphi Informant
In Development
Delphi 1, 2, 3 / Soundex

By Rod Stephens

Sounds Gud to Me
Soundex Encoding in Delphi

W hen you give your name at a restaurant, it doesn’t matter whether the
host spells it correctly. As long as the name can be pronounced to tell you
when your table is ready, it can be spelled any number of ways. In a large data-
base of names, however, spelling is critical. If you tell customer support that your
name is MacCauslin, it matters a great deal whether they type “MacCauslun,”
“McCawslin,” or “Mack Awzlin.” If the database is large, finding the correct
record by trial-and-error may be difficult. Even seemingly ordinary names like
Smith can have many different spellings: Smith, Smithe, Smyth, Smythe, etc.

This problem is common in large name- For example, suppose you want to encode
database programs such as customer-support the name STEPHENS. In step 1 you remove
hotlines, company phone directories, and the vowels, along with H, W, and Y, to get
census-data files. Manually searching for a STPNS. In step 2 you encode the letters to
record slows the operator, and increases the get 23152. This value has no adjacent dupli-
length of the call. Eventually the operator cates, so step 3 leaves the result unchanged.
may need to ask the customer for the correct The final code is S315.
spelling, further delaying the process — and
possibly annoying the customer. Compare this to the encoding of the other
common spelling of this name: STEVENS.
This article describes soundex, a method for Step 1 changes the name to STVNS.
encoding names based on how they sound, Encoding the letters in step 2 yields 23152.
rather than how they are spelled. Adding This contains no adjacent duplicates, so step
soundex to a database application can make 3 has no effect. The final encoding is S315
record retrieval faster and less frustrating for — the same value as the encoding for
both the operator and the customer. STEPHENS. This is the important property
of soundex encodings: Names that sound
A Sound Beginning similar have similar encodings.
Soundex methods come in several variations.
The United States Census uses one of the For another example, consider the name
simplest. The census represents a name using MacCauslin. Removing the vowels in step 1
a letter followed by three numbers. For leaves MCCSLN. Encoding the remaining
example, STEPHENS is represented by letters in step 2 yields 522245. Removing
S315. The rules for creating a census-style duplicates in step 3 leaves 5245, so the final
0 A, E, I, O, U, H, W, Y soundex encoding are: soundex encoding is M245. You can verify
1 B, F, P, V 1) Remove vowels and H, W, and Y, except that this is the same as the encodings for
2 C, G, J, K, Q, S, X, Z as the first letter. MacCauslun, McCawslin, and Mack Awzlin.
3 D, T 2) Encode the character according to the
4 L key in Figure 1. Figure 2 shows a Delphi function that
5 M, N 3) Remove adjacent duplicate digits. For returns a census-style soundex encoding for
6 R example, change 552331 to 5231. a string. The Soundex program available on
Figure 1: US Census Bureau 4) The encoding is the first letter followed this month’s Companion Disk uses this
soundex character encoding by the second, third, and fourth code to create census-style soundex encod-
key. numeric codes. ings (see end of article for download
15 March 1998 Delphi Informant
In Development
// Calculate a normal soundex encoding. // Calculate a numeric soundex encoding.
function Soundex(in_str: string) : string; function NumericSoundex(in_str: string) : Smallint;
var var
no_vowels, coded, out_str: string; value: Integer;
ch: Char; begin
i : Integer; // Calculate the normal soundex encoding.
begin in_str := Soundex(in_str);
// Make upper case; remove leading and trailing spaces.
in_str := Trim(UpperCase(in_str)); // Convert this to a numeric value.
value := (Ord(in_str[1]) - Ord('A')) * 1000;
// Remove vowels, spaces, H, W, and Y, except as the if (Length(in_str) > 1) then
// first character. value := value + StrToInt(Copy(in_str, 2,
no_vowels := in_str[1]; Length(in_str) - 1));
for i := 2 to Length(in_str) do begin NumericSoundex := value;
ch := in_str[i]; end;
case ch of Figure 3: Converting a string encoding into a numeric encoding.
'A', 'E', 'I', 'O', 'U', ' ', 'H', 'W', 'Y':
; // Do nothing.
else database. If you use a relational database, make the soundex
no_vowels := no_vowels + ch;
encoding a key, because you’ll often search for the encoding.
end;
end;
To find a name, the program searches the database for the
// Encode the characters.
name’s soundex code. If it finds more than one match, the pro-
for i := 1 to Length(no_vowels) do begin
ch := no_vowels[i]; gram can present a list of names for the user to choose from.
case ch of The program could even arrange the names so the most likely
'B', 'F', 'P', 'V':
match comes first. For example, suppose the user enters
ch := '1';
'C', 'G', 'J', 'K', 'Q', 'S', 'X', 'Z': STEVENS, ROD, and the best matches are STEVENS, MIKE
ch := '2'; and STEPHENS, ROD. If the program checks the soundex
'D', 'T': encoding of the first names as well as the last names, it can
ch := '3';
'L': conclude that the second entry is probably the right one.
ch := '4';
'M', 'N': Soundex is useful for tasks other than name finding. For
ch := '5';
'R': example, it’s sometimes used in address-matching software.
ch := '6'; Given a street address that may have been misspelled, the pro-
else // Vowels, H, W, and Y as the first letter. gram can use soundex to find possible correct street names.
ch := '0';
end; // End case ch. This type of software is usually customized, so it knows about
coded := coded + ch; the most common mistakes in street addressing. For example,
end; // End for i := 1 to Length(no_vowels) MAIN AVE N and N MAIN AVE may be the same street.
// Use the first letter.
out_str := no_vowels[1]; Soundex is also used in some spelling checkers. If the user
incorrectly types CRL, the program can ask if this should be
// Find three non-repeating codes.
for i := 2 to Length(no_vowels) do begin CARL, CAROL, CURL, CORAL, or CHORAL, because
// Look for a non-repeating code. these words have soundex codes similar to the code for CRL.
if (coded[i] <> coded[i - 1]) then
begin
// This one works. Optimization
out_str := out_str + coded[i]; Census-style soundex codes are four-character strings. You can
if (Length(out_str) >= 4) then make operations faster using integer codes instead of strings. You
Break;
end; can convert a soundex encoding into an integer by using the
end; code shown in Figure 3, which converts a soundex string encod-
ing into a numeric code. The example program named Soundex
Soundex := out_str;
end; uses this function to display numeric soundex encodings.

Figure 2: Converting text into census-style soundex. Soundex Variations


An advantage of the census-style soundex is that every name
details). The program also demonstrates other techniques is represented by a four-character string that a program can
described later in this article. Enter a string, then click the easily translate into an integer. The small number of possible
Encode button to make the program display the different codes makes it likely that a word’s soundex encoding will
kinds of encodings. match the encoding of the correct spelling. In other words,
when the user guesses how to spell a name, the program will
Now What? probably find the correct record.
Once you know how to create soundex encodings, you can
use them in database applications. Start by computing On the other hand, the program will also probably find a lot of
soundex encodings for each record, and storing them in the incorrect records. A letter followed by three digits between 1
16 March 1998 Delphi Informant
In Development
and 6 can generate only 26 * 6 * 6 * 6, or 5,616 possible codes. // Calculate an extended soundex encoding.
If you have a large database, many entries must map to the function ExtendedSoundex(in_str : string) : string;

same code. For example, if your database has 600,000 records, // Replace instances of fr_str with to_str in str.
each code will correspond to more than 100 records (on the procedure ReplaceString(var str : string;
average). Because names are not evenly distributed (e.g. more fr_str, to_str : string);
var
names start with S than with Q), most codes correspond to fr_len, i : Integer;
even more names. Finding the correct name in a list of more begin
than 100 can be hard. fr_len := Length(fr_str);
i := Pos(fr_str, str);
while (i > 0) do begin
Short codes also mean the system cannot distinguish between str := Copy(str, 1, i - 1) + to_str +
long names that start the same way. For example, STEFFAN Copy(str, i + fr_len,
Length(str) - i - fr_len + 1);
and STEVENOWSKI both have codes S513 — even though i := Pos(fr_str, str);
only their beginnings sound alike. The program will group end;
these names together, while a human can easily tell they are end;

not different spellings of the same name. var


no_vowels : string;
ch, last_ch : Char;
Even for shorter names, this scheme’s simplistic method for giv- i : Integer;
ing similar letters the same code sometimes makes it group begin
names that do not sound alike. For instance, MAGEE and // Make upper case and remove
// leading and trailing spaces.
MEESEK both have code M2, though they sound very different. in_str := Trim(UpperCase(in_str));

For similar reasons, this scheme sometimes assigns very differ- // Remove internal spaces.
ReplaceString(in_str, ' ', '');
ent encodings for names that sound similar. For example, the
code for PHISHMAN is P25, while the code for FISHMAN // Convert CHR to CR.
ReplaceString(in_str, 'CHR', 'CR');
is F25. It would be difficult for a program to realize that P25
and F25 represented names with the same pronunciation. // Convert PH to F.
ReplaceString(in_str, 'PH', 'F');

Other versions of the soundex system address these issues. // Convert Z to S.


They replace common pairs of letters such as PH with ReplaceString(in_str, 'Z', 'S');
shorter equivalents such as F. Some of these variations use
// Remove vowels and repeats.
more than four characters to encode a name. They also use last_ch := in_str[1]; // The last character used.
the letters themselves, instead of numeric codes. The steps no_vowels := last_ch;
for generating one soundex variation are: for i := 2 to Length(in_str) do begin
ch := in_str[i];
1) Convert CHR to CR, PH to F, and Z to S. case ch of
2) Remove adjacent duplicates. 'A', 'E', 'I', 'O', 'U':
3) Remove vowels, except the first letter. ; // Do nothing.
else
// Skip it if it's a duplicate.
(It’s been suggested that these rules were used to name the if (ch <> last_ch) then
begin
UNIX commands. In many cases, the truth is much stranger.) no_vowels := no_vowels + ch;
last_ch := ch;
Following these rules, the code for STEFFAN is STFN, and end;
end;
the code for STEVENOWSKI is STVNWSK. These codes are end;
still somewhat similar — because the words themselves are sim-
ilar — but are different enough for a program to tell them ExtendedSoundex := no_vowels;
end;
apart. The new code for MAGEE is MG, and the new code for
MEESEK is MSK. These new values correctly distinguish Figure 4: An extended soundex algorithm.
between the two. Finally, PHISHMAN and FISHMAN now
both encode to FSHMN. The names sound alike, and now word, so PFEIFER, FIFER, and PHEIFER all have the same
have the same code. code. Others replace X with KS, so SOX and SOCKS have
the same code: SKS.
Figure 4 shows Delphi code that generates this new kind of
soundex encoding. The Soundex example program uses this The metaphone algorithm uses a series of rules to create pho-
code to display extended encodings. netic codes for English words. The rules are quite complicat-
ed. For example, the letter C is converted into:
Other Enhancements Nothing (i.e. it’s silent) if it occurs in SCI (conscience),
Other soundex algorithms use different rules to transform SCE (ascent), or SCY (scythe).
spellings into codes, based on the way they probably sound. X, to represent the “sh” sound if it occurs in CIA (associ-
Some change PF into F when it occurs at the beginning of a ate) or CH (luncheon).

17 March 1998 Delphi Informant


In Development
S if it occurs in CI (voicing), CE (forceful), or CY (cyclone).
K otherwise (preschool).

Because metaphone is optimized for English spellings, it may


not work well for all applications. In particular, it may have
trouble with foreign names. You can find a more detailed
description of metaphone, and Delphi source code for a
metaphone library, at http://www.intellex.net/~wcs/delphi/-
program.html.

Save Your Fingers


Adding soundex searches to an application can make finding
records faster and less frustrating. Once users become com-
fortable with soundex, they will discover many shortcuts.
They may start skipping vowels, typing only the beginning
of a name, and even entering the beginnings of extended
soundex codes. When a customer says “MALLACHIO,” the
user can enter “MALCH,” and pick the correct entry from a
short list. After a while, users may wonder how they avoided
wearing their fingers to the bone typing all those extra char-
acters. Of crs y shldnt cry ths id t fr. (“Of course, you
shouldn’t carry this idea too far.”) ∆

The files referenced in this article are available on the Delphi


Informant Works CD located in INFORM\98\MAR\DI9803RS.

Rod Stephens is the author of several books, including Visual Basic Algorithms
from John Wiley & Sons, Inc. He writes an algorithm column in Visual Basic
Developer; some of the material presented here has appeared there in Visual
Basic form. Reach him at RodStephens@compuserve.com or see what else he’s
doing at http://www.wiley.com/compbooks/stephens.

18 March 1998 Delphi Informant


DBNavigator
Delphi 3

By Cary Jensen, Ph.D.

More Code Editor Tricks


Getting the Most from Delphi’s Native Editor

P op quiz: Does Delphi’s Code Editor support keystroke recording and play-
back? If you answered “No,” you’re missing at least one feature that can
save you a great deal of effort. The fact is, very few Delphi developers use the
Code Editor to its full capacity.

Last month’s “DBNavigator” contained an in- Default, Classic, Brief, and Epsilon — define
depth look at Code Insight, but there’s a lot which features are available, and how you
more to coding productivity than that offered access them. The Default keystroke mapping
by Code Insight. Delphi’s editor has a wealth provides keystroke mapping that is CUA-
of features; so many, in fact, that I cannot fit compliant. Most Windows applications use
a detailed discussion of them all into this arti- these keystrokes, making this setting a good
cle. Instead, I’m going to highlight some of choice if you don’t already have a preference.
my favorite techniques, leaving you to discov-
er the remaining capabilities. The other mappings are provided to permit
you to customize the editor to emulate an
General Editor Issues editor you’re already familiar with. For exam-
Before we start to explore some of its fea- ple, the Classic keystroke mapping emulates
tures, a general comment about Delphi’s the editor-key combinations from Borland
Code Editor is in order. The editor has four Pascal. Likewise, Brief and Epsilon modes
keystroke mappings. These mappings — emulate the keystrokes of those editors.

You select which editor emulation you


want from the Keystroke mapping list box
on the Display page of the Environment
Options dialog box (see Figure 1). You
can display this dialog box by selecting
Tools | Environment Options. You can
further configure the editor using the
Editor SpeedSetting combo box on the
Editor page.

The techniques in this article refer to Default


keystroke mapping key combinations, so if you
aren’t using the Default setting, the keystrokes
may not work for you. In that case, use
Delphi’s online Help to find the appropriate
keystroke combinations for a particular feature.
In addition, a given keyboard mapping may
provide several alternative keystroke combina-
Figure 1: You control keystroke mapping using the Display page tions for accessing a particular feature. Again,
of the Environment Options dialog box. refer to the online Help for this information.
19 March 1998 Delphi Informant
DBNavigator
ments within the begin..end are traditionally
indented to the same degree, providing addi-
tional visual cues of their grouping.

Block indenting and unindenting refers to the


process of changing the indentation of two or
more lines of code simultaneously. This feature
is particularly valuable when you’re introducing a
new control structure to an existing section of
code. For example, you might have originally
Figure 2: Use this hypertext window to display help for the category of key- written your code to execute a sequence of state-
board mappings you’re interested in. ments, but now you want to make that sequence
conditional by introducing an if statement.
Getting Help Making the sequence of statements conditional generally
There are several ways to access Delphi’s Help pages regarding means you will also want to further indent these statements
keyboard mappings. The easiest is to search on “keyboard short- (to maintain a consistent indentation). While this can be
cuts” in online Help. The Help system will display the Topics accomplished by indenting one line at a time (or by recording
Found dialog box; select About keyboard shortcuts and press a key macro that indents one line and then moves to the next
R. Then, from the displayed Help page, select the name of line, and then plays this macro back repeatedly), block inden-
the keyboard emulation you’ve selected. This will result in the tation permits you to indent all of the conditional statements
display of a hypertext window like the one shown in Figure 2, in a single step.
containing links to the Help pages for each category of editor
operation. For example, if you want to view the keystrokes asso- To perform block indentation, first highlight the lines that
ciated with cutting, copying, and pasting, select Clipboard control. you want to indent. In the Default keystroke mapping, this
can be accomplished by holding down S, while using
Key Macro Recording the navigation (arrow) keys to highlight the rows to be
A key macro is a series of one or more keystrokes you can record indented. Dragging the mouse to highlight the desired rows
and play back repeatedly. The keystrokes that you record as part also accomplishes this effect. Once the lines that you want to
of your macro can contain simple characters from the keyboard, indent are highlighted, press CSI to indent. To
but can also include navigation keys, and even keystroke combi- block unindent, highlight the rows that you want to unin-
nations. As a result, it’s possible to record a sequence of key- dent, and press CSU. The number of columns that
strokes that can quickly and efficiently apply a wide range of the selected text is moved depends on the Block indent setting
changes to your code. on the Editor page of the Environment Options dialog box. I
prefer to set Block indent to 1, to get the greatest control.
To begin recording a key macro, press CSR. The word
Recording appears in the third panel of the Code Editor’s status
Using Bookmarks
bar. At that point, each key press or key combination you enter is A bookmark is a setting that permits you to return to a par-
ticular line of code in a unit (or any other file opened in
recorded. To conclude your recording, press CSR
the editor, for that matter). Bookmarks are ideal when
again.
you’re working on a large unit, and you want to quickly
move between two or more rows within that unit.
You play back the keystrokes in exactly the same order in
which they were recorded by pressing CSP. You You can set up to 10 bookmarks in each file opened in
can play back the same key macro as many times as you the Code Editor. These bookmarks are labeled 0 through
like. Obviously, since key macros contain only exact key- 9, and each bookmark can appear only once in a given
strokes and key combinations, your macros will be effective unit. To set a bookmark, hold down CS and press
only when they contain sequences that can be repeated suc- a single-digit key, i.e. 0 to 9. The bookmark is identi-
cessfully. fied in the left-hand gutter of the editor by a glyph that
displays the specified digit. For example, the code shown
Block Indent and Unindent in Figure 3 contains two bookmarks, one labeled 0 and
Indentation plays an important role in making your code easi-
the other labeled 5.
er to read and maintain. For example, most Delphi developers
indent the then clause of an if statement: To move to a particular bookmark, press C plus the digit
if BooleanCondition then
identifying the bookmark, e.g. C5. To remove a book-
DoThisandThat; mark, move to the line for that bookmark and press
CS plus the digit identifying the bookmark.
Furthermore, when two or more statements are condition- Alternatively, if you attempt to place the same bookmark in a
al, such as when a begin..end block is used to group a new location within the same unit, the existing bookmark is
series of statements to be executed conditionally, the state- removed before the new bookmark is placed.
20 March 1998 Delphi Informant
DBNavigator
“Form1.” Now press 3; the highlighting will advance
to the variable declaration “Form1,” as shown in Figure 4.
If you were to press 3 once more, highlighting will
move to “TForm1” in the type portion of the variable
declaration. Pressing 3 one last time will display a dia-
log box indicating that it finds no more instances of the
string “Form1” within that unit.

An incremental search always begins from the current


position of the cursor, and performs a forward search.

Find Matching Delimiters


Like most programming languages, Object Pascal
makes extensive use of delimiters to organize code.
For example, the parameters of a function call appear
within parentheses, the contents of a multi-line com-
Figure 3: Bookmarks appear in the left-hand gutter of the editor. In this ment appear within a pair of matching braces, and
example, the bookmarks are in close proximity. However, in most cases the members of a set are enclosed in a matching pair
bookmarks are in distant parts of a unit.
of brackets.

Bookmarks are temporary; they are lost when you close the The Find matching delimiter feature of the Code Editor per-
file in which they are set. mits you to quickly locate the parenthesis, brace, or bracket
that corresponds to one you have chosen. For example, if you
Incremental Search place your cursor to the right of an open parenthesis, the Find
Delphi provides a wide variety of search options. Of these, the matching delimiter command will move your cursor to the
least used, though arguably the most interesting, is the incre- right of the corresponding close parenthesis. This feature is
mental search. What makes the incremental search so nice is particularly valuable when working with nested matching
that it’s quite easy to use. delimiters, such as function calls that serve as parameters to
other function calls. Using Find matching delimiter can help
When you initiate an incremental search, Delphi monitors you verify that your parentheses are placed correctly.
your keystrokes, and positions your cursor at the first char-
acter sequence within the unit that matches the characters To find the matching close delimiter, place your cursor to the
you have entered. You initiate an incremental search by right of the open delimiter and press CQ]. Likewise, to
pressing CE, or by selecting Search | Incremental Search find the matching open delimiter, place your cursor to the
from the menu. You conclude an incremental search by right of the close delimiter and press CQ[.
pressing E, or by navigating from the located string.
Column Operations
While you are performing an incremental search, Delphi Typically, block operations are based on rows. For example,
displays Searching for: in the status bar. As you type the earlier in this article you learned how to indent and unin-
characters of your search string, these too are
displayed in the status bar. If Delphi finds a
match to your entered string, but not what
you’re looking for, you can press 3 to ask
Delphi to search for the next occurrence of
the entered string.

The use of an incremental search is easier to


demonstrate than to describe. Start by creating a
new project in Delphi. Display Unit1 in the edi-
tor, and press CE. Assuming that you want to
find the string “Form1,” begin typing Form1.
When you type f, Delphi highlights the “f” in
interface. Next, press o. Delphi now moves the
highlighting to the first instance of “fo,” which it
finds in the string “Forms” that appears in the
interface uses clause. Now enter the characters
rm1. At this point, the highlighting will be on
the word “TForm1” in the class declaration for the Figure 4: From Unit1 in a new project, you can quickly locate the Form1 vari-
form, since this is the first instance of the string able declaration by pressing CE, typing Form1, and then pressing 3.

21 March 1998 Delphi Informant


DBNavigator

Cary Jensen is President of Jensen Data Systems, Inc., a


Houston-based database development company. He is
author of more than a dozen books, including Delphi in
Depth [Osborne McGraw-Hill, 1996]. He is also a
Contributing Editor of Delphi Informant, and was a
member of the Delphi Advisory Board for the 1997
Borland Developers Conference. For information concern-
ing Jensen Data Systems’ Delphi consulting and training
services, visit the Jensen Data Systems Web site at
http://idt.net/~jdsi. You can also reach Jensen Data
Systems at (281) 359-3311, or via e-mail at
cjensen@compuserve.com.

Figure 5: By selecting a column block, it’s possible to delete the string “New”
from multiple lines, quickly converting these constant declarations.

dent one or more rows with a single key combination. The


highlighted rows in that example constituted a block. (It
should be noted that there are a great many block opera-
tions, including deletion, converting the case of its charac-
ters to upper case or lower case, and so forth.)

In addition to permitting you to create blocks based on


rows, Delphi’s editor also provides for column-based
blocks. These blocks, which are always rectangular in
shape, permit you to perform block operations on one or
more columns. For example, imagine that you have two or
more lines where you want to delete only the character
appearing in the third, fourth, and fifth rows. You can eas-
ily do this by creating a column block that includes only
the third through fifth columns of the two or more rows.
Once defined, these characters can be deleted by pressing
D (or by cutting using CX, or whatever delete key
combination is supported by your keyboard mapping).

You create column-based blocks by first marking the row


and column in which you want the block to begin by
pressing COC. You then hold down S while
you navigate to the row and column where you want the
block to end. The column block appears as a highlighted
rectangle. Once the block is highlighted, you can perform
any block operation, such as copying the block to the
Clipboard, or deleting it. Figure 5 depicts a code segment
where a column block is being used to highlight “New”
on multiple lines, and then delete them with a single
delete operation.

Conclusion
The Delphi Code Editor provides a wealth of features that
support your code-writing activities. This article has dis-
cussed only a few of these features. By taking a few min-
utes to read Delphi’s online Help, you are sure to learn
additional tricks that can improve your productivity. ∆

22 March 1998 Delphi Informant


Columns & Rows
Delphi 3 / ODBMS / POET

By Chu Moy

Developing Object Databases


A RADical Approach to Business Applications

R ecent trends in application development are highlighting the limitations of


relational database technology. Business applications now routinely tap into
complex data containing several levels of relationships and a multitude of data
types, such as multimedia data. At the same time, developers are modeling
applications using an object-based paradigm. Relational databases are not
optimized for either of these situations. RDBMSes suffer significant performance
degradation with complex data, and developers must spend considerable time
writing code to map object data to relational tables. Object adapters to rela-
tional databases must still contend with the underlying relational foundation.

The emerging technology of pure object the structure of object databases is orga-
databases promises dramatic improvements nized around the complexity of the data
in speed and development time. Some itself. An object schema can reflect real-
benchmarks have clocked ODBMSes per- world processes through inheritance, poly-
forming up to 2,000 times faster than their morphism, and encapsulation. Objects
relational counterparts. Once the exclusive have unique characteristics — such as iden-
province of C++ and Smalltalk programmers, tity and behavior — that have no equiva-
ODBMSes have lacked connectivity options lents in traditional relational theory.
for the RAD market. ODBMS vendors have Object-relational adapters add support for
addressed these issues by releasing GUI- multimedia data types to RDBMSes, and
based development tools and ActiveX and may also provide an object-like program-
ODBC interfaces to their products. ming interface; but the database engine still
operates with tables. Object databases
Delphi, with its fully object-oriented architec- extend the OOP paradigm from the pro-
ture and support for ActiveX type libraries (in gramming language to the database engine.
Delphi 3), is an excellent language for RAD
object database development. This article exam- In an ODBMS, data and procedures are
ines methods and tools for Delphi developers to grouped together as object properties and
design and access object databases. The code methods. It’s possible to view object data-
examples use POET ODBMS (http://www.- bases in relational terms (see Figure 1), but
poet.com). However, other ODBMSes, such as only at a superficial level. Broadly speaking,
Object Design’s ObjectStore (http://www.- the basic elements of an RDBMS have cor-
odi.com), and Computer Associates’ Jasmine responding elements in an ODBMS. Thus,
(http://www.cai.com), have similar connec- a table named Employee may contain
tivity options. records with columns such as Name,
Address, Empl_id, and Dept_id. An
The Object-Database Schema Employee object is an instance of an
Whereas relational databases store data in Employee class, and may have attributes
fixed tabular formats of rows and columns, such as Name, Address, and Empl_id.
23 March 1998 Delphi Informant
Columns & Rows
Object Term Relational Comment
Term

Database Database Object databases store objects and their relationships, with support for encapsulation,
inheritance, and polymorphism. Relational databases store record data in two-
dimensional tables. Relationships between tables are constructed with queries.
Class/Extent Table Classes are templates for objects. A class can have definitions for attributes and
methods. Tables use a row-and-column structure to hold data. An extent is the set
of all objects of a particular class.
Object Record An object is an instance of a class. It encapsulates data and behavior. A record is
a row of column data.
Object ID Row ID (RID) OIDs and RIDs are system-generated values that uniquely identify an object in an
(OID) ODBMS, or a row in an RDBMS. However, traditional RDBMS theory does not
require RIDs; some RDBMSes do not create them.
Attribute / Column Object-data attributes can map to table columns. Object relationships can be
Relationship mapped as foreign-key values to columns.
Method Stored Methods are associated with objects, and have the capabilities of the
Procedure programming language. Stored procedures are precompiled SQL code.
Event Triggers An ODBMS will usually have an object-management mechanism that can trigger
Management the execution of methods based on the occurrence of object events. RDBMSes use
triggers to execute SQL procedures on the occurrence of data events.
Index Index Object indices are conceptually similar to relational indices. The ODMG ODL
includes syntax to specify multiple keys for a class.
Figure 1: Object-relational structure mappings.

Object schemas generally support two types of relation- The first may sometimes be more flexible. Those databases in
ships: inheritance and associations. Inheritance simplifies compliance with the Object Database Management Group
schema design by reusing the design of the parent class. An (ODMG) standard may support its Object Definition
association is represented as a pointer to another object. Language (ODL) for defining schemas. Other products may
Thus, instead of a Dept_id attribute, an Employee object require the schema be defined according to programming lan-
would have a pointer to a Department object. In a relation- guage syntax such as C++.
al database, the relationship between the Employee and
Department tables would be constructed in a query that An ODL class declaration has two basic sections: the heading
uses Dept_id as a foreign key. An Employee object, however, and the body. The heading specifies the class inheritance, the
can find its associated Department object by going to the name of the extent (the set of all objects of that class), and
referenced memory address. The improvement in response keys for indexing. The body lists the class attributes and
time compared to an RDBMS is especially dramatic with methods. A script for defining an Employee class using ODL
complex data. might appear as follows:

There are two ways to create an object database: interface Employee


( extent employees
Specify the persistent classes in a schema script and com- key (name, empl_id)):persistent
pile the script into a database binary. {
Use a GUI-based schema design tool. attribute String Name;
attribute String Address;
attribute Short Empl_id;
interface SalesRep: Employee
};
( extent SalesReps)
{
void RequestSalaryIncrease(); The ODL can specify two kinds of schema relationships:
relationship SalesManager Is_managed_by
inverse SalesManager::Manages;
inheritance and associations called traversal paths. Inheritance
}; simplifies schema design. A named traversal path is a desig-
nated relationship between classes. If the SalesManager and
interface SalesManager: Employee
( extent SalesManagers)
SalesRep classes descend from the Employee class and are also
{ associated (e.g. SalesMangers supervise SalesReps), the ODL
void DenyRaiseRequest(); allows the relationship to be defined from either, or both,
void TerminateSalesRep();
relationship Set<SalesRep> Manages
points of view (Figure 2). In the database, relationships are
inverse SalesRep::Is_managed_by; physically represented as pointers to associated objects. In a
}; 1:1 relationship, the source object will have a single reference
Figure 2: Specifying inheritance and associations in an object to its associated object. In a 1:n relationship, the source object
schema with ODL. has a set of references to its associated objects.
24 March 1998 Delphi Informant
Columns & Rows
Large schema scripts in ODL or C++ are difficult
to maintain. GUI schema tools, such as PtRose
from POET Software (see Figure 3) and Blueprint
from Object Design, avoid the tedium and confu-
sion of scripting schemas by generating databases
from object models. Third-party tools, such as
ObjectCast from CustomLink Software (see
Figure 4), include code generation for RAD lan-
guages like Delphi.

Connecting Delphi Applications to


Object Databases
An ODBMS vendor may supply a Delphi “tight
binding” that integrates closely with the Delphi
IDE, but ActiveX and ODBC appear to be the
most popular interfaces for RAD development.
ActiveX interfaces tend to offer better performance
Figure 3: With the PtRose add-on, Rational Rose Modeler 4.0 can generate and greater functionality than ODBC, and appli-
POET databases from class diagrams.
cations can access and manipulate database objects
through ActiveX. ODBC is a relational technology
that’s been adapted to map object schemas to vir-
tual tables that can be queried with SQL. Usually,
the developer must write code to map ODBC
record sets to application objects.

However, the developer may choose ODBC because


the application doesn’t require high performance or
access to all ODBMS features. Usually, these situa-
tions involve connecting an RDBMS application to
ODBMS data, or the use of relational reporting
tools on an object database. There are more efficient
options for integrating ODBMS and RDBMS data,
but ODBC is convenient and familiar. A discussion
of ODBC is also a good way to introduce the topic
of query languages for object databases.
Figure 4: ObjectCast for Delphi, from CustomLink Software, is a
RAD/ODBMS workbench that generates object databases and application
frameworks. Open Database Connectivity. ODBC drivers for
object databases are just one option in a selection
of object-relational mapping technologies, but ODBC is cer-
tainly Delphi-compatible, whereas the others may not be. As
is true of ODBC drivers generally, ODBC drivers for object
databases are not all the same; they may translate ODBMS
structures differently. Some ODBC drivers use the SQL2
standard; others may have added object extensions to SQL,
like those in the forthcoming SQL3 standard or the ODMG’s
OQL (Object Query Language). Although these are different
standards, they have similarities because OQL is based on
SQL and SQL3 borrows from OQL.

ODBC views object databases as a series of tables, and returns


record sets which must be mapped to application objects. The
relationships between objects are broken. Derived tables sub-
stitute a foreign-key column (OID or object IDs) for each
relationship attribute. A SQL query can reconstruct the rela-
tionships with joins on the relationship keys.

The object schema of the simple sales database shown in


Figure 5: A sales ODBMS schema and its ODBC view. Figure 5 differs slightly from its ODBC view. Although the
25 March 1998 Delphi Informant
Columns & Rows
SELECT r
FROM r IN SalesReps, a IN r.Accounts
WHERE a.Contact = "Henry Ford"

Other object-relational mapping products create


object views on relational schemas. Not all are
Delphi-compatible. These high-performance
interfaces, such as DBConnect from Object
Design and SQL Object Factory from POET
Software, automatically map relational data to
application objects. They are designed to integrate
ODBMS and RDBMS schemas into a single
object model. Some relational adapters include an
ODBC driver as part of the package.

ActiveX interfaces. A native Delphi binding to an


object database would have the best performance
and most seamless integration between applica-
tion objects and database objects. ActiveX
Figure 6: The Account_SID column in the result set of the ODBC query. Automation interfaces are more commonly avail-
able. Since Delphi 3 supports type libraries, appli-
cations can use vtable binding to
extract maximum performance
from ActiveX interfaces. Some
vendors also offer ActiveX con-
trols for drag-and-drop visual
programming.

Unlike a native binding, objects


retrieved from an automation
database are instantiated on the
server side. The controller applica-
tion receives COM pointers to
Figure 7: Accessing objects in an ODBMS, via ActiveX Automation.
server objects, and doesn’t operate
directly on the objects. If the
object schema has only three classes (SalesMgr, SalesRep, and application is only concerned with manipulating object attrib-
Account), the ODBC view shows four tables. One table, utes, the COM barrier isn’t an issue. If the controller applica-
SalesRep_Account, represents the one-to-many (1:n) rela- tion must invoke object methods, the COM barrier is an issue
tionship between SalesReps and Accounts. The SalesRep because method code runs on the client, not on the sever. The
object has an object set attribute named Accounts, which is solution is to encapsulate the COM object inside Delphi wrap-
the set of all assigned accounts. The Account_SID column pers to simulate native Delphi objects, which are subject to
preserves information about the order of the account inheritance and polymorphism (see Figure 7). The following
objects in the set (see Figure 6). class declaration defines an Employee class that contains a
pointer to a database object:
From the ODBC table view, the following SQL2 query
retrieves the name of the sales rep who has an account con- PtEmployee = class
// For early binding, use IPOETApplicationObject.
tact named “Henry Ford”: PtObj: OleVariant;
procedure SetName(value: string);
SELECT R.* function GetName: string;
FROM SalesRep R, SalesRep_Account RA, Account A procedure SetAddress(value: string);
WHERE R.SalesRep_OID = RA.SalesRep_OID function GetAddress: string;
AND RA.Account = A.ACCOUNT_OID procedure RequestSalaryIncrease; virtual;
AND A.Contact = "Henry Ford" end;

OQL and SQL3 queries navigate object trees by specifying The Get/SetName and Get/SetAddress methods use the
the traversal path, and have more compact and efficient COM object PtObj to access an Employee object in the
query syntax. An OQL query operates on object sets. Because database, while the RequestSalaryIncrease method con-
the SalesRep class has two sets (a SalesReps extent and an tributes to the object behavior. When the application
Accounts set), an OQL query that traverses both sets must instantiates a PtEmployee object, it initializes the object by
specify them. The following OQL query is functionally iden- assigning the COM object to PtObj. PtObj already has its
tical to the previous SQL query: own accessor methods, but defining separate Get/Set meth-
26 March 1998 Delphi Informant
Columns & Rows

Figure 8: A simplified view of the POET Automation class hierarchy.

ods helps to further conceal the true nature of the object’s


COM identity.

An application could only require one instance of a


PtEmployee object, because retrieving another object from the
database is only a matter of reassigning the COM object.
Since PtEmployee is a Delphi class, other Delphi classes can
inherit from it and implement polymorphism. For example, a Figure 9: The sales-database schema for the sample application.
PtSalesRep class can inherit from PtEmployee and modify
PtEmployee’s method implementation: as Extent and ApplicationObjectSet represent groups of objects.
Object sets are often used as object attributes for creating one-to-
PtSalesRep = class(PtEmployee)
function GetCurrentSales: Integer;
many relationships between classes. The ApplicationObjectFactory
procedure MakeSale (value: Integer); class is responsible for generating new database objects.
procedure RequestSalaryIncrease; override; ODBMSes that support named objects will often have even
end;
simpler class hierarchies.

Programming with an ActiveX Interface The object schema of the sample application (see Figure 9)
The ODMG specification indicates that objects can be stored in shows how extents and object sets work together. In this
a database as named objects and/or extents. A named object con- scenario, each SalesRep is assigned one SalesMgr, and is
sists of a root object (which is assigned a name) and any associat- responsible for several accounts. Each account can be owned
ed objects. Once the root object is created, any objects referenced by several SalesReps. Each SalesMgr supervises several
by the root are automatically stored in the database. To retrieve SalesReps. The SalesRep class has a 1:1 relationship with the
an object, an application first retrieves the root, then navigates SalesMgr class, and a 1:n relationship with the Account class.
the object tree to reach the desired object. However, large num- The SalesMgr class has a 1:n relationship with the SalesRep
bers of named objects can make programming difficult, and slow class. The relationship between SalesReps and Accounts is
down the database. The alternative is to use extents. n:n, and is implemented as two 1:n relationships.

Some ODBMSes automatically create


and maintain extents each time an object
is stored or removed from the database.
For those that support named objects,
extents can be simulated by creating
named objects that are collections or sets,
each containing objects from a single
class. The extent programming model
organizes objects to simplify access.

The POET Automation class hierarchy


(see Figure 8) illustrates the basic classes
of an ODBMS that uses extents. The
ApplicationObject class endows each object
with database functionality, such as the ability Figure 10: The many-to-many relationship between the SalesRep and Account
to store and delete itself. Set constructs such classes in the sample database is implemented with object-set attributes.

27 March 1998 Delphi Informant


Columns & Rows
Because the SalesRep and Account classes are in a n:n relation-
ship, if a SalesRep is taken off an Account, the Account’s
SalesRep list must be updated as well (see Figure 10). Once
the application has a pointer to the selected SalesRep, it can
reference the selected Account, then locate the pointer to the
same SalesRep inside the Account object’s SalesRep set:

SalesRep.Account.SalesRep

The Object Pascal code to retrieve the second SalesRep point-


er would take this form:

// Get SalesRep from SalesReps extent.


SalesRep := SalesReps.Get;
// Get SalesRep's Account set.
RepAccounts := SalesRep.GetAttribute("Accounts");

...locate desired Account pointer in RepAccount set...

// Delete current RepAccount pointer from set.


RepAccounts.Delete;
// Get Account's set of assigned SalesReps.
AcctSalesReps := RepAccount.GetAttribute("SalesReps");

...locate desired AcctSalesRep pointer in


the AcctSalesReps set...

// Delete current AcctSalesRep pointer from set.


AcctSalesReps.Delete;

Of course, an OQL query might be easier. The sample appli-


cation demonstrates a simple, embedded OQL query in the
SalesMgr form. A query object (derived from the OQLQuery
class) performs the query and returns a result set of objects.
The principles of ODBMS sets are relevant to queries as well.
An OQL query must specify the domain of extents and
object sets over which the query will operate.

Conclusion
Object databases are a natural fit for Delphi’s object-based
development paradigm. This article has examined only a few
object-database features available to Delphi, but has empha-
sized RAD connectivity techniques. From the simple begin-
nings discussed here, developers can build complex ODBMS
applications that enjoy the same advantages of speed and flex-
ibility as C++ or Smalltalk-based systems, yet can be designed
and coded in a fraction of the time. ∆

The author thanks Prasad Jeevaniji, Kris Tanner, and Eric


Vigna for helping prepare this article.

The files referenced in this article are available on the Delphi


Informant Works CD located in INFORM\98\MAR\DI9803CM.

Chu Moy, formerly with POET Software, is a consultant with Thomson


Technology Consulting Services in San Francisco. He has a Bachelor’s degree in
electrical engineering from Yale University. He is also a Microsoft Certified
Solution Developer and a Master Certified Novell Engineer. He has had several
years of programming experience with RAD tools, and has been working with
Delphi for the last two years.

28 March 1998 Delphi Informant


Sights & Sounds
Delphi 1, 2, 3

By Christopher D. Coppola

Multimedia Buttons
Creating Special Buttons for Special Interfaces

O ne of the fundamental elements of GUIs is the button; the button


metaphor is widely used and well understood as a component of inter-
face design. In many applications, the interface calls for the functionality of a
button, but demands a more integrated look than the standard button compo-
nents are capable of producing (see Figure 1).

In this article, we’ll develop a TMMButton is to use the available interface components
component (see Listing Two, beginning on page — buttons, text boxes, menus, etc. — in a
33) that has all the functionality of a standard way the user can intuitively understand.
button, but integrates seamlessly with a creative,
high-resolution interface. The TMMButton One key to designing an intuitive interface is
component will also provide more feedback to to adhere to functionality standards. Interface
the user than a standard button. metaphors, such as the button, have been
employed for so long now that users intu-
Standards and Intuitiveness itively know how to operate a button.
When we design a user interface, our primary Therefore, when developing a button compo-
goals should be to make the interface func- nent, carefully consider every aspect of how a
tional, make the user comfortable, and final- button works.
ly, to make the interface aesthetically pleas-
ing. One of the fundamental challenges of The widely accepted standards for the func-
creating an interface with these characteristics tional operation of a button assert that the
OnClick event is only fired if the mouse but-
ton is released when the mouse pointer is
over the button. In other words, if I press a
button and (with the mouse button still
depressed) move the pointer away from the
component, the component should visually
return to its “normal” state, and shouldn’t fire
an event if I release the mouse button. A
good example of button functionality is the X
button that closes a window in Windows
95/NT. Figure 2 illustrates, in terms of
mouse events, how a button component
should function.

TMMButton vs. other Button Components


We’re going to develop the TMMButton
component so that it responds to mouse
interaction similarly to the Delphi Button,
Figure 1: Buttons seamlessly integrated with the background. SpeedButton, or BitBtn components.
29 March 1998 Delphi Informant
Sights & Sounds
Mouse Events Standard Graphic Response
Finally, TMMButton will support sound. The value of audio
feedback is often overlooked. As we’ll soon see, sound is simple
Left Button Down Show the button in its “pressed” state. to implement, and contributes greatly to the user experience.
Left Button Up Show the button in its “highlighted”
state or its “pressed” state based on Creating the Component
these conditions: If the button is in Beginning with the Component Expert, create the
an inactive “pressed” state, show
TMMButton class as an ancestor of TGraphicControl. The
the button in its “pressed” state; if
the button is not in an inactive
Component Expert graciously delivers a template from which
“pressed” state, show the button in we’ll build the TMMButton component:
its “highlighted” state.
type
Mouse Enter If the pointer is over the button, TMMButton = class(TGraphicControl)
show the button in its “highlighted” private
state, then fire the OnClick event. If { Private declarations }
protected
the cursor isn’t over the button, show { Protected declarations }
the button in its “normal” state. public
Mouse Leave Show the button in its “normal” { Public declarations }
published
state. If the mouse button is { Published declarations }
pressed, set a flag to indicate it’s in end;
an inactive “pressed” state.
Figure 2: Button functionality in terms of mouse events. The first thing I do when defining a new class or subclass is
think about the data the class must represent. The
TMMButton class will clearly need to store bitmaps that repre-
sent the button in each of its visible states — “normal,” “dis-
abled,” “highlighted,” and “pushed.” For these data elements
we’ll use Delphi’s TBitmap object. In addition to storing the
bitmaps, we’ll need a couple of Boolean variables. These will
assist the TMMButton component in determining which
bitmap should be displayed when the component is painted.
We’ll also need four variables to deal with audio feedback.
Two string variables will store the names of the mouse-over
and button-push sounds. The other two variables tell the but-
ton how and where the sounds are stored. For greater flexibili-
ty, TMMButton will allow the developer to use sound files,
sound resources embedded in the .EXE, or sound resources
embedded in a DLL.
Figure 3: The standard buttons just don’t look right for this interface.
We’ll add the data members we’ve just described to the
However, the TMMButton component will have three private section of the class declaration:
important differences. First, it will sport improved user feed-
private
back by displaying a “highlighted” state when the mouse is FBmpNormal, FBmpHiLight,
over the component. In many multimedia and game inter- FBmpPushed, FBmpDisabled: TBitmap;
FSndOver, FSndPush: string;
faces, this additional feedback is essential; buttons are inte-
FSoundType: TSoundType;
grated with the background so well that the highlight is nec- FDllInstance: Integer;
essary to identify them. Microsoft’s new “Explorer style” FDown, FOver: Boolean;
button has popularized a similar feedback mechanism for
business and productivity applications. After we’ve defined the class’ data members, we need to
consider the component’s functionality. I described earlier
Another significant difference is how TMMButton applies how the component should respond to mouse interaction;
graphics. All buttons in Windows use graphics, but most now it’s time to translate that description into Delphi code.
are simply rectangular regions with light and dark borders We’ll need the component to be aware of the following
that simulate a three-dimensional look. Some button mouse actions:
components feature bitmaps, but hardly integrate with button press
a custom interface (see Figure 3). The TMMButton button release
component, on the other hand, displays an entirely new pointer entering the component boundaries
bitmap for each of its four functional states. This gives pointer leaving the component boundaries
the developer more flexibility in designing a high-resolu-
tion interface where the buttons are nicely integrated with To react to the mouse button being pressed, the compo-
the background. nent implements a MouseDown method. We’ll simply
30 March 1998 Delphi Informant
Sights & Sounds
override the MouseDown method already implemented in mal” state bitmap has been assigned, then the component
TGraphicControl, implement the specific functionality paints using the “normal” state bitmap; otherwise the com-
required in the TMMButton component, then call the ponent paints a line around the component to make it visi-
inherited MouseDown method. The OnMouseDown event is ble. Notice from the following code that I paint the out-
fired only when the mouse button is pressed and the point- line using gray and the pmXor pen mode. This ensures that
er is over the component. When the component receives a whatever the background color of the button’s container,
WM_MOUSEDOWN message, the MouseDown method the button will be visible. Many components simply use
sets FDown to True and calls the Paint method. black to paint the outline. The result is a component that’s
difficult to locate on a black form at design time.
We’ll implement the MouseUp method to detect when the
with Canvas.Pen do begin
mouse button has been released. MouseUp (just as MouseDown) Style := psSolid;
keeps the value of FDown accurate. Each time MouseUp is Color := clGray;
Mode := pmXor;
called, FDown is set to False. MouseUp performs another vital
end;
function: It determines if an OnClick event is triggered. Within
MouseUp, we perform a simple check to determine if the point- Canvas.Brush.Style := bsClear;
Canvas.Rectangle(0, 0, Width, Height);
er is over the component. If it is, we paint the component and
trigger its OnClick event. If the pointer isn’t over the compo-
nent, nothing more needs to be done. At run time, the Paint method first determines which bitmap
should be displayed based on the evaluation of the enabled state
To detect when the pointer enters and leaves the compo- of the button, as well as the FDown and FOver variables. If the
nent’s boundaries, we’ll trap the CM_MOUSEENTER and button is not enabled, the “disabled” state bitmap is used.
CM_MOUSELEAVE messages. Do this by adding message- When the button is enabled, FDown and FOver are considered.
handling methods: If FOver, for example, is False, then the correct bitmap to dis-
play would be the “normal” state bitmap. After the appropriate
procedure CMMouseEnter(var Message: TMessage); bitmap has been determined, a final check is performed to
message CM_MOUSEENTER; ensure the correct bitmap has been assigned. It is, after all, con-
ceivable that this component would be used without a “pushed”
The message directive tells Delphi that the declared method or “highlighted” state bitmap. If the appropriate bitmap is not
should be called whenever the component receives the mes- assigned, then the component will simply use the “normal”
sage identified by the integer identifier following the message state bitmap to repaint itself. The code that paints the compo-
directive. In this case, we’ve told the component that nent is trivial; I’ve used the CopyRect method of the compo-
CMMouseEnter should be fired whenever the component nent’s Canvas to perform the paint.
receives a CM_MOUSEENTER message, i.e. whenever the
pointer enters the boundaries of the component. Declaring Properties
Now that we’ve seen how the TMMButton component works,
When CMMouseEnter is executed, FOver is set to True. it’s time to give the component the developer interface neces-
Conversely, when CMMouseLeave is executed, FOver is set to sary to assign bitmaps and other properties unique to each
False. In addition, CMMouseEnter and CMMouseLeave will instance of the component. The TMMButton component has
call the component’s Paint method if the component has a the published interface shown in Figure 4.
“highlighted” state bitmap assigned, or FDown is set to True.
published
{ Published declarations }
Painting the Component with the Correct Bitmap property PicNormal: TBitMap
We’ve seen several places where the component calls the Paint read FBmpNormal write setNormal;
property PicHiLight: TBitMap
method to display the bitmap that correctly depicts the cur- read FBmpHiLight write setHiLight;
rent state. Overriding the component’s OnPaint event is property PicPushed: TBitMap
advantageous because we can call the method from other read FBmpPushed write setPushed;
property PicDisabled: TBitmap
component methods to explicitly cause the component to read FBmpDisabled write setDisabled;
paint, but it has another distinct advantage. The component’s property SndOver: string read FSndOver write FSndOver;
OnPaint event is triggered whenever the component needs to property SndPush: string read FSndPush write FSndPush;
property SoundType: TSoundType
be repainted because of other screen activity. Because we’ve read FSoundType write FSoundType;
overridden the event and placed all the logic for determining property DLLInst: Integer
the correct bitmap within the Paint method, no additional read FDLLInstance write FDLLInstance;
property Height default 30;
logic or code is necessary to repaint the component when property Width default 30;
another screen event makes it necessary to repaint. property Enabled;
property Visible;
property OnMouseDown;
The component handles two conditions in the Paint property OnMouseMove;
method. Because this is a visual component, we must con- property OnMouseUp;
sider how the component will function at design time and property OnClick;

run time. At design time the logic is simple: If the “nor- Figure 4: The published interface of TMMButton.

31 March 1998 Delphi Informant


Sights & Sounds
The four bitmap properties simply return the value of the
associated private variable. When one of the properties is set,
however, it’s done by a method. Setting the PicNormal proper-
ty, for instance, would cause the component to execute the
setNormal method. The other three bitmap properties,
PicHiLight, PicPushed, and PicDisabled, use the setHiLight,
setPushed, and setDisabled methods, respectively. Each of the
four methods uses TBitmap’s Assign method to store the
bitmap. The setNormal method, because it’s associated with the
most rudimentary of the three properties, also sets the compo-
nent’s Width and Height. The component user can change this,
but it’s reasonable to assume that in most cases the component
should be the same dimensions as the “normal” state bitmap.

In addition to the bitmap properties, the TMMButton com-


ponent has standard Height, Width, Enabled, and Visible
properties, and it exposes the standard OnMouseDown, Figure 5: The interface of the sample application.
OnMouseMove, OnMouseUp, and OnClick events.
First, I’ll show you how to prepare the bitmaps to make
Audio Capabilities sure the buttons integrate seamlessly with the background.
Sound is the finishing touch to our new component. Using the
I’ll also demonstrate how to embed sounds in your .EXE so
properties declared earlier, the API function PlaySound, and a
you don’t have to distribute .WAV files with the application.
private function called doSound, this is a simple task. First, we
Then when the bitmaps are ready, we’ll create a sample pro-
must decide where sound is appropriate. Sounds are typically
ject in Delphi to demonstrate how the TMMButton compo-
used in response to the pointer moving over the button, as
nent should be implemented.
when the button is pushed. This means we need to add a call to
doSound to the OnMouseDown and CMMouseEnter events. In
I used Adobe Photoshop to prepare the bitmaps. If you’re
the OnMouseDown event, we call doSound, sending FSndPush as
using another graphics package, some of the specific imple-
the parameter. Recall that FSndPush is the private variable that
mentation examples might be different, but the concepts can
stores the name of the “push” sound.
be applied using any capable graphics software. Start with a
good background design. My example is from my company’s
The doSound procedure is the key to TMMButton’s audio fea-
most recent demonstration CD (see Figure 5).
tures. Using the FSoundType, FDLLInstance, and whichSound
parameters, doSound uses the PlaySound API function to play
Next, design the buttons. Each button should have four
a sound file, a sound from an application resource, or a sound
independent graphics that are drawn in position over the
from a DLL resource.
background. Photoshop’s layer transparency works great for
this. When we’re finished creating the bitmaps for each of
Installing the TMMButton Component the button states, we should be able to create a rectangular
As with any other component, installation is accomplished by
selection around each of the buttons. The selection should
selecting Install Component from the Component menu:
be the smallest rectangular area that contains each of the
Delphi 2: From the Install Components dialog box, click
button’s states. Jot down the x,y coordinates of the upper-
the Add button and locate MMButton.pas.
left pixel of the selection. This will save having to manually
Delphi 3: From the Install Component dialog box, use the
position the buttons in Delphi. Using the selection, copy
Browse button to select MMButton.pas as the unit. Select
and paste each of the button states into new documents
the package you would like MMButton to be a part of,
(see Figure 6).
then press OK.
Once the bitmaps are ready, start a new Delphi project and
When Delphi is finished recompiling the component library,
add one TImage component for the background and a
you’ll find the MMButton icon on the component toolbar of
TMMButton component for each button in the project. Our
the Additional page. To install the component on a different
sample project only has one button, so you’ll only need one
page, modify the component’s Register method. For example:
TMMButton for now.
procedure Register;
begin Set the TImage component’s Picture property to the back-
RegisterComponents('MyTab', [TMMButton]); ground bitmap. This will act as the application’s background.
end;
You might also want to add some code to the form’s OnCreate
event to size the application correctly:
Implementation Tips
One of the more important aspects of using TMMButton to ClientWidth := imgBack.Width;
create compelling interfaces is implementing it correctly. ClientHeight := imgBack.Height;

32 March 1998 Delphi Informant


Sights & Sounds
handles everything related to the button display and
audio. All you must do is add the desired code to the
Normal component’s OnClick event. ∆

The files referenced in this article are available on the Delphi


Informant Works CD located in INFORM\98\MAR\DI9803CC.

Highlight
Chris Coppola is one of the founding principals of Advanced Creative
Technologies III, Inc., a multimedia and Internet development company.
Chris has written about multimedia development in Delphi 2 Multimedia
Adventure Set [Coriolis Group Books, 1996], Director 5 Wizardry [Coriolis
Pushed Group Books, 1996], and Visual Basic 5 Multimedia & Web Adventure Set
[Coriolis Group Books, 1997]. You can reach him at coppola@act-3.com or
http://www.act-3.com.

Disabled
Begin Listing Two — TMMButton Component
unit MMButton;
Figure 6: The four bitmaps of the sample button.
interface

Next, set the PicNormal, PicHiLight, PicPushed, and uses


PicDisabled properties of the TMMButton component. The Windows, Messages, Classes, Graphics, Controls;
component will automatically size to the dimensions of the
type
PicNormal bitmap, but you’ll have to position the compo- TSoundType = (stAppResource, stDLLResource, stFile);
nents on the form so they line up with the original design. If TMMButton = class(TGraphicControl)
private
you wrote down the top-left coordinate from earlier, enter the { Private declarations }
values in the component’s Top and Left properties. Otherwise, FBmpNormal, FBmpHiLight,
FBmpPushed, FBmpDisabled: TBitmap;
you must visually align the component with the background.
FSndOver, FSndPush: string;
FSoundType: TSoundType;
Finishing Up FDllInstance: Integer;
FDown, FOver: Boolean;
Now let’s add some audio. It would be simple enough to set procedure setNormal(Value: TBitMap);
the SndPush and SndOver properties to the names of .WAV procedure setHiLight(Value: TBitMap);
files. In most cases, however, it’s more efficient to embed the procedure setPushed(Value: TBitMap);
procedure setDisabled(Value: TBitmap);
sounds in the application. This is simple to accomplish using procedure doSound(whichSound: string);
a resource script and the command-line resource compiler protected
{ Protected declarations }
that ships with Delphi (Brcc32.exe). Resource scripts are procedure MouseDown(Button: TMouseButton;
simple. The following sample script generates two WAVE Shift: TShiftState; X, Y: Integer); override;
resources that can be accessed by the names WAV_PUSH procedure MouseUp(Button: TMouseButton;
Shift: TShiftState; X, Y: Integer); override;
and WAV_OVER: procedure CMMouseEnter(var Message: TMessage);
message CM_MOUSEENTER;
WAV_PUSH WAVE "push.wav" procedure CMMouseLeave(var Message: TMessage);
WAV_OVER WAVE "over.wav" message CM_MOUSELEAVE;
procedure Paint; override;
procedure Click; override;
Delphi’s resource compiler generates a resource file (.RES) public
{ Public declarations }
that can be linked with a Delphi executable by adding a line,
constructor Create(AOwner: TComponent); override;
such as the following, to the application source: destructor Destroy; override;
published
{$R sounds.res} { Published declarations }
property PicNormal: TBitMap
read FBmpNormal write setNormal;
All that’s left to do is to tell the button component property PicHiLight: TBitMap
read FBmpHiLight write setHiLight;
which resources to use, and where they’re located. In property PicPushed: TBitMap
this case, the default stAppResource tells the component read FBmpPushed write setPushed;
to find the resource specified in SndPush in the applica- property PicDisabled: TBitmap
read FBmpDisabled write setDisabled;
tion’s executable. property SndOver: string read FSndOver write FSndOver;
property SndPush: string read FSndPush write FSndPush;
property SoundType: TSoundType
That’s all there is to it. Run the test application (see
read FSoundType write FSoundType;
Listing Three on page 35) and you’ll see that the component
33 March 1998 Delphi Informant
Sights & Sounds
property DLLInst: Integer PlaySound(PChar(whichSound), FDLLInstance,
read FDLLInstance write FDLLInstance; SND_RESOURCE or SND_ASYNC or SND_NODEFAULT);
property Height default 30; stFile:
property Width default 30; PlaySound(PChar(whichSound), 0,
property Enabled; SND_FILENAME or SND_ASYNC or SND_NODEFAULT);
property Visible; end;
property OnMouseDown; end;
property OnMouseMove;
property OnMouseUp; procedure TMMButton.MouseDown(Button: TMouseButton;
property OnClick; Shift: TShiftState; X, Y: Integer);
end; //TMMButton begin
FDown := True;
procedure Register; doSound(FSndPush);
if not FBmpPushed.Empty then
implementation Paint;
inherited MouseDown(Button, Shift, X, Y);
uses end;
MMSystem;
procedure TMMButton.CMMouseEnter(var Message: TMessage);
procedure Register; begin
begin FOver := True;
RegisterComponents('Additional', [TMMButton]); doSound(FSndOver);
end; if (not FBmpHiLight.Empty) or FDown then
Paint;
constructor TMMButton.Create(AOwner: TComponent); end;
begin
inherited Create(AOwner); procedure TMMButton.CMMouseLeave(var Message: TMessage);
Width := 30; begin
Height := 30; FOver := False;
FBmpNormal := TBitMap.Create; if (not FBmpHiLight.Empty) or FDown then
FBmpHiLight := TBitmap.Create; Paint;
FBmpPushed := TBitmap.Create; end;
FBmpDisabled := TBitmap.Create;
FSoundType := stAppResource; procedure TMMButton.MouseUp(Button: TMouseButton;
FDLLInstance := 0; Shift: TShiftState; X, Y: Integer);
end; var
DoClick: Boolean;
destructor TMMButton.Destroy; begin
begin FDown := False;
FBmpNormal.Free; DoClick := (X >= 0) and (X < ClientWidth) and
FBmpHiLight.Free; (Y >= 0) and (Y <= ClientHeight);
FBmpPushed.Free; if DoClick then
FBmpDisabled.Free; begin
inherited Destroy; Paint;
end; if Assigned(OnClick) then
OnClick(Self);
procedure TMMButton.setNormal(Value: TBitMap); end;
begin inherited MouseUp(Button, Shift, X, Y);
FBmpNormal.Assign(Value); end;
if not FBmpNormal.Empty then
begin procedure TMMButton.Click;
// Set the height and width of the component based begin
// on the size of the Normal bitmap. end;
Height := FBmpNormal.Height;
Width := FBmpNormal.Width; procedure TMMButton.Paint;
end; var
end; ARect: TRect;
Src: TBitMap;
procedure TMMButton.setHiLight(Value: TBitMap); OldPal: HPalette;
begin begin
FBmpHiLight.Assign(Value); OldPal := SelectPalette(Canvas.Handle,
end; FBmpNormal.Palette,False);
try
procedure TMMButton.setPushed(Value: TBitMap); RealizePalette(Canvas.Handle);
begin ARect := Rect(0,0,Width,Height);
FBmpPushed.Assign(Value); if (csDesigning in ComponentState) then
end; // Design-time paint response.
if FBmpNormal.Empty then
procedure TMMButton.setDisabled(Value: TBitMap); begin
begin // Add visibility when designing.
FBmpDisabled.Assign(Value); with Canvas.Pen do begin
end; Style := psSolid;
Color := clGray;
procedure TMMButton.doSound(whichSound: string); Mode := pmXor;
begin end;
case FSoundType of Canvas.Brush.Style := bsClear;
stAppResource: Canvas.Rectangle(0, 0, Width, Height);
PlaySound(PChar(whichSound), hInstance, end
SND_RESOURCE or SND_ASYNC or SND_NODEFAULT); else
stDLLResource: Canvas.CopyRect(ARect, FBmpNormal.Canvas, ARect)

34 March 1998 Delphi Informant


Sights & Sounds
else begin
begin // Run-time paint response. ShowMessage('Fired the cannon!');
// Check button state & assign appropriate bitmap. end;
if not Enabled then
Src := FBmpDisabled end.
else if not FOver then
Src := FBmpNormal
else if FDown then End Listing Three
Src := FBmpPushed
else
Src := FBmpHiLight;
// Catch all if the Src bitmap is not valid at this
// point, paint the normal bitmap.
if Src.Empty and (not FBmpNormal.Empty) then
Src := FBmpNormal;
// Paint the component's canvas.
if not Src.Empty then
Canvas.CopyRect(ARect, Src.Canvas, ARect);
end;
finally
if OldPal <> 0 then
SelectPalette (Canvas.Handle,OldPal,False);
end
end; // Paint

end.

End Listing Two


Begin Listing Three — Sample Application
program Test;

uses
Forms,
TestMain in 'TestMain.pas' {frmMain};

{$R *.RES}
{$R sounds.res}

begin
Application.Initialize;
Application.CreateForm(TfrmMain, frmMain);
Application.Run;
end.

unit TestMain;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics,
Controls, Forms, Dialogs, MMButton, ExtCtrls;

type
TfrmMain = class(TForm)
imgBack: TImage;
MMButton1: TMMButton;
procedure FormCreate(Sender: TObject);
procedure MMButton1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
frmMain: TfrmMain;

implementation

{$R *.DFM}

procedure TfrmMain.FormCreate(Sender: TObject);


begin
ClientWidth := imgBack.Width;
ClientHeight := imgBack.Height;
end;

procedure TfrmMain.MMButton1Click(Sender: TObject);

35 March 1998 Delphi Informant


The API Calls
Delphi 1, 2, 3

By John Ayres

Restoring Animation
Delphi Apps Can Exhibit Standard Minimize and Restore

D elphi does a great job of encapsulating the Windows API, insulating develop-
ers from some nasty and mundane requirements of Windows programming.
Due to this encapsulation, however, Delphi programs exhibit certain anomalies not
present in Windows applications written in other development environments; for
example, the lack of animation when a Delphi program is minimized or restored.

When a window is minimized, it displays a The OnMinimize and OnRestore events of the
series of animated rectangles, decreasing in size Application object seem like a good place to
and moving toward the task bar until the win- start. However, both of these events fire after
dow is fully minimized. The reverse occurs the Application object has been minimized or
when a window is restored from a minimized restored. If the Application object is mini-
state. Unfortunately, the way Delphi encapsu- mized before the main form is minimized,
lates a Windows application prevents this from the main form will simply vanish, and the
occurring with Delphi-created applications. minimizing animation will never appear.
Therefore, the OnMinimize event will not
When a Delphi application is created, there are work for our purposes. Conversely, the
two windows present: the main window, and a Application object must be restored before the
hidden application window. It is the hidden main form is restored, so the main form will
application window that contains the entry and be visible and the restoring animation will
exit points for the main function of the occur. We can then use the OnRestore event to
Windows application. When the main window run code that will restore the main form.
of an application is minimized, the “minimize” Another Application object event to consider is
message sent (i.e. SC_MINIMIZE) is inter- the OnMessage event. This event fires before
cepted by this hidden application window. the application is minimized, thus allowing us
Instead of minimizing the main window, it’s the to send the SC_MINIMIZE message to the
hidden application window that minimizes; the main form before it disappears.
main form and all other displayed forms are
hidden. When an application is restored, it’s the Therefore, we will create event handlers for the
application window that’s restored, and the Application object’s OnMessage and OnRestore
main form and all other forms are returned to a events to route the appropriate messages to the
visible state. Because the main window is never main form. We’ll also need a message handler
truly minimized, the minimizing animation for the WM_SYSCOMMAND message. Thus,
never fires. our TForm class declaration should appear as:
To correct this, we must re-route specific mes- TForm1 = class(TForm)
sages from the Application object to the main procedure FormCreate(Sender: TObject);
private
form, and provide additional processing to
{ Private declarations }
minimize and restore. The first step is to identi- public
fy where code must be placed to intercept and { Public declarations }
procedure AppRestore(Sender: TObject);
handle the appropriate messages. We will be
procedure AppOnMessage(var Msg: TMsg;
dealing with the WM_SYSCOMMAND mes- var Handled:
sage, which is sent when the user chooses Boolean);
procedure WMSysCommand(var Msg:
Minimize or Restore from the system menu, or
TWMSysCommand);
presses the appropriate button in the upper- message WM_SYSCOMMAND;
right corner of a window. end;

36 March 1998 Delphi Informant


The API Calls
Now that the appropriate event handlers have been added to application and hides our minimized main form. Our
the class declaration of the main form, hook them up to the WM_SYSCOMMAND message handler should look like:
Application object in the form’s OnCreate event handler:
procedure TForm1.WMSysCommand(var Msg: TWMSysCommand);
procedure TForm1.FormCreate(Sender: TObject); begin
begin { If we are receiving the Minimize message... }
{ The Application.OnMinimize event fires after the appli- if (Msg.CmdType and $FFF0) = SC_MINIMIZE then
cation has been minimized and the main form is hidden. begin
So we must provide a handler for the OnMessage event, { ...pass it to the default window procedure. This
which fires before the application is minimized. } causes the form to minimize with animation. }
Application.OnMessage := AppOnMessage; DefWindowProc(Handle, WM_SYSCOMMAND, SC_MINIMIZE, 0);

{ The Application.OnRestore event fires after the appli- { We must now minimize the application object. This
cation has been restored. This is fine for our purposes actually minimizes the application, hiding the
but the application doesn't send the Restore message to minimized form. }
the form. Our main form will reappear, but it will be Application.Minimize;
minimized in one of the lower corners behind the task
bar. Thus, we must provide a handler for the OnRestore { Indicate that the message was handled. }
event so the Restore message can be sent to the form. } Msg.Result := 0;
Application.OnRestore := AppRestore; end
end; else
{ For any other message, we must send it to the
inherited message handler. Because the form is
When the Application object gets WM_SYSCOMMAND (indi- already minimized, the Restore animation will work
cating a minimize or restore action), the message is handled by correctly. }
inherited;
the Application object, then destroyed (the main form never sees end;
the message). The OnMessage event of the Application object will
fire before the message has been handled and destroyed, allowing This should cause animation to appear when minimizing the
us to send the message to the main form before it’s hidden. In form. When the application is restored, the main form will reap-
the OnMessage event handler, determine if the message is pear, but it will remain minimized unless it’s specifically told to
WM_SYSCOMMAND, and if it indicates the application is restore. To do this, we post the WM_SYSCOMMAND message,
being minimized. If so, call the SendMessage API function to pass specifying the restore command (SC_RESTORE) to the main
this message to the main form: form from the application’s OnRestore event handler. No other
processing is required; Windows takes care of the rest:
procedure TForm1.AppOnMessage(var Msg: TMsg;
var Handled: Boolean);
begin procedure TForm1.AppRestore(Sender: TObject);
{ The OnMessage event fires before the OnMinimize event, begin
so we must put code here to route the Minimize message { When the minimized application is restored, the appli-
to the form. If the message is coming from the cation object simply sets the Visible property of its
system menu... } forms to True; the forms don't actually receive the
if Msg.Message = WM_SYSCOMMAND then Restore message. Therefore, when the application object
{ ...and it is specifically the Minimize command... } receives the Restore message, we must specifically send
if (Msg.wParam and $FFF0) = SC_MINIMIZE then it to the main form. }
{ ...send the Minimize message to the form. } PostMessage(Handle, WM_SYSCOMMAND, SC_RESTORE, 0);
SendMessage(Handle, WM_SYSCOMMAND, SC_MINIMIZE, 0); end;
end;
Your Delphi apps will now exhibit the minimizing and restoring
This message will be intercepted by our WM_SYSCOMMAND animation common to Windows apps. But this method isn’t per-
message handler. We must check the incoming message type fect; the animation will fall to one side of the screen instead of
to determine if it’s SC_MINIMIZE. This handler will receive animating toward the icon on the task bar. However, this tech-
all system command messages; check for and handle only the nique does provide a more traditional Windows look and feel. ∆
appropriate command. All other commands must be sent to
the inherited message handler so the standard functionality of The files referenced in this article are available on the Delphi
a window won’t be compromised. When checking the Informant Works CD located in INFORM\98\MAR\DI9803JA.
CmdType member of the TWMSysCommand structure passed
to our handler, combine it with the value $FFF0 using the
Boolean and operator. The least significant four bits of this
member are used internally by Windows, and we must mask
John Ayres is a consultant for Ensemble Systems Consulting in Dallas, using
them out to determine the appropriate message type. When
Delphi to produce high-end client/server applications for various Fortune 500
we’ve received SC_MINIMIZE, pass the message information companies. With over eight years of programming experience, he’s worked for a
to the Def WindowProc API function, which performs the variety of companies, producing a broad range of software, from third-party
default behavior of the specified message on the window add-in utilities to games. He keeps himself busy by co-authoring The Tomes of
whose handle is passed to the function in its first parameter. Delphi 3: Win32 Core API (ISBN 1-55622-556-3) [WordWare, 1998] and
This function causes the form to truly minimize, and the other Windows programming books for Delphi. For more information, visit
minimizing animation to appear. Then, call the Minimize http://www.WordWare.com.
method of the Application object, which minimizes the

37 March 1998 Delphi Informant


New & Used

By Warren Rachele

Crystal Reports Professional 6.0


Seagate Software’s Query and Report Writing Tool

T he flagship product of the software development company where I work,


The Hunter Group, was a case management tool that had a not-so-unique
set of reporting specifications. In addition to a plethora of menu-chosen reports
configured with user-modifiable parameters, the database was used to
research trending not immediately apparent through the stock reports. For this
reason, the user requirements specified the necessity of being able to create
free-form reports and queries. While Borland has improved reporting functions
with the addition of the QuickReport components, they remain internal, and
can only be modified through a series of parameters passed from the user.

To satisfy this requirement, we turned our Make Room!


attention to external report-query tools, specifi- A full installation of the 32-bit version of
cally report writers. The most powerful, non- Crystal Reports Professional, including sam-
intimidating tool available was Crystal Reports ples, documentation, the data access layers,
from Seagate Software, which we’ve included as and tools that ship with the product, takes a
a part of our product since its inception. In bit under 150MB. I recommend you install
version 6.0, Crystal Reports works with data the sample files if you’re not familiar with the
on a wide range of platforms, and comes with product. There are numerous tutorials in the
components that compile directly into all of printed documentation that make use of
the popular visual development environments these files, and each is helpful in shortening
in use today. The collection of tools included the learning curve. When no longer neces-
in the Crystal Reports package considerably sary, these files are easily deleted.
expands the capabilities of, and market oppor-
tunities for, your software. Performing a custom installation allows you
to install only the components you’re inter-
ested in utilizing in your development efforts.
The installation goes quickly and without
problems. Depending on the components
and data-access layers you select, the Borland
Delphi Engine (BDE) and ODBC (Open
Database Connectivity) pieces may be
installed. You will be reminded to configure
them at the end of the installation process.

Installing the Crystal Reports Engine VCL


interface component into Delphi 3 follows
Figure 1: Selecting a report format from the Report Gallery. the standard process for installing any third-
38 March 1998 Delphi Informant
New & Used
party component. Choosing to install the new component
adds the object to the Data Access tab of the VCL. Code
for Delphi 3 is located in a separate subdirectory of the
VCL directory, and the components and support code for
earlier versions of Delphi are also provided.

Basic Component Usage


Building a report into your Delphi program is a two-
step process. The report is designed in the Crystal
Reports Designer environment. Integrating the report
into your executable consists of adding TCrpe to your
form and pointing the properties to the report. When
the component is executed, it will call the Crystal
Reports Engine to handle the build and printing of the
report. New reports are formatted via a wizard that
walks you through the design process, and ends with a
completed report that needs very little modification. An
included sample report demonstrates how easily a report Figure 2: The Create Report Expert dialog box.
can be assembled using the Biolife.DB, a sample
Paradox table provided with Delphi and stored in the
\Delphi 3\Demos\Data directory.

The first step in the process is to select a report format


from the Report Gallery dialog box; the sample uses a
Standard format (see Figure 1). Selecting a format takes
you directly to the Create Report Expert dialog box in
which the rest of your report selections are made (see
Figure 2). Add the fields Species No, Category,
Common_Name, Species Name, Length, and Length_In
to the report (see Figure 3). Click on the Sort tab and
select BIOLIFE.Category as the sort field (see Figure 4).
Finally, click on the Style tab and select Leading Break
from the list (see Figure 5). Your report is finished with
one small annoyance to be corrected: Crystal Reports
subtotals and totals all numeric fields unless told not to.
Click on the Total tab and remove all the fields selected
to be totaled. Click the Preview Report button, and your
report is configured in a WYSIWYG window (see Figure 3: Adding fields in the Create Report Expert dialog box.
Figure 6).

This report is now usable in a Delphi program. As shown


in Figure 7, two components are needed to test the report:
TCrpe and a Button to execute the report. Add the follow-
ing statement to Button1Click:

Report1.Execute;

TCrpe requires minimal modification to work. The only


attribute that needs to be set for this example is the
report name. This property is set to the report just creat-
ed in Crystal Reports.

Run the sample program and you see the form window
with a button. Clicking the Report button starts the report-
generation process. The engine loads quickly (without a
splash screen) and is printed to the screen for preview by
default. The Crystal Reports Engine window automatically
provides the user with full preview capabilities: zoom, page Figure 4: Selecting categories in the Create Report Expert
by page, top of report, and end of report. A button sends dialog box.

39 March 1998 Delphi Informant


New & Used
the output to the printer and closes the window.
Another button is provided that connects to the distri-
bution expert for quickly sending the output to a des-
tination other than the printer.

The properties of the VCL component provide an


interface to nearly all the modifiable properties of
the report and its display. Data-selection parameters
and the selection formula itself can be passed direct-
ly from the component, allowing a broad range of
user control over the data selection and window pre-
sentation. The extensive range of output destina-
tions available through the report designer is also
available through your executable. Crystal Reports
provides a wide range of destinations and formats
for the report, other than a printer, and facilitates
this through an interface to the Destination Expert.
The output can be directed to a disk file in various
popular formats or sent directly to another person
Figure 5: Selecting styles in the Create Report Expert dialog box. in the form of an attachment to an e-mail message.
The e-mail formats that are supported
include MAPI, VIM (cc:mail), and
Microsoft Exchange. Crystal Reports
can also directly produce HTML out-
put for placing reports on a Web page
or intranet.

Seagate Toolbox
Seagate provides tools to make end-user
access to your data stores easier and
more secure. The Crystal Dictionary
system provides the database profes-
sional with the ability to make their
data more accessible to users by format-
ting the data elements into more
friendly terms while limiting access to
the level needed by the individual.
Users are often unable to access a data-
base directly because of their lack of
understanding of the relational model.
A database made up of multiple inter-
Figure 6: The report is configured in a WYSIWYG window. related tables is easily understood by
the database designer, but the relation-
ships aren’t always clear to the end user.

A Crystal Dictionary creates a view, a


way of presenting the data in a more
user-friendly way that hides the com-
plexity of the relationships. Field and
table names can be changed through an
alias making them more recognizable
to the user. New column headings and
contextual help can be attached to each
field or table, making the data columns
more meaningful. This friendlier inter-
face adds to the productivity of the
end-data users by making reporting
and querying easier. The view lowers
Figure 7: Putting Crystal Reports to work in Delphi. the number of support calls needed
40 March 1998 Delphi Informant
New & Used
when providing user access to interface and extensive selection and formatting capabilities.
the data stores, and has the This group will be thrilled with this latest release. The learn-
additional benefit of limiting ing curve for all but the fundamentals is rather steep
access to the specific data because of the quality of the documentation, but the results
fields the designer deems are more than worth the effort.
Crystal Reports Professional 6.0 works
with data on a wide range of platforms,
appropriate to the user’s secu-
and comes with components that compile rity level. Software developers have dual learning curves in their
directly into all of the popular visual devel-
opment environments in use today. The paths to success. The first task is to learn the Crystal
The Crystal Dictionary has a
collection of tools included in the Crystal Reports method of report creation. This will be easier for
Reports package considerably expands the
secondary benefit: It makes
capabilities of, and market opportunities the developer than the information worker as the steps will
for, your software. While the strength of
Paradox and .DBF data
the Report Designer is enough to recom-
be more intuitive. Creating a formula, for example, is a
immediately accessible to the
mend this product, the VCL provides a fast more comfortable process for the developer than the aver-
loading, highly modifiable interface to a
powerful report engine. Crystal Query Designer. The age information worker, just as the process of assembling a
query tool is designed to cre- syntactically correct sentence is the programmer’s domain.
Seagate Software
1095 West Pender St., 4th Floor ate simple-to-complex SQL The programmer then must learn the interface specific to
Vancouver, BC V6E 2M6
Canada
statements for data selection. his or her development tools. Seagate doesn’t make this
Phone: (800) 877-2340 or (604) 681-3435 The Crystal Query Designer easy. As mentioned earlier, the documentation is not set up
Fax: (604) 681-2934
E-Mail: sales@img.seagatesoftware.com works only with SQL data- to be read; it’s intended for ad hoc use. Developers are
Web Site: bases or those accessible expected to have an above-average knowledge of their plat-
http://www.seagatesoftware.com
Price: Professional Edition, US$395, through ODBC. This tool is forms. And programmers working with the Delphi VCL
upgrade US$199; Standard Edition,
US$149, upgrade, US$79.
a visual query builder that component will be comfortable adding the object to their
steps the user through the projects and setting their properties. The number of prop-
process of assembling a work- erties will require some study of the Help files in order to
able SQL sentence. If the user is prepared for building fully realize the power of the component, but the results
more complex queries without the use of the wizard, the are worth the effort.
tool provides for direct entry of the sentence. Crystal
Query Designer can make use of queries that the user has Conclusion
designed and used in other products by importing them Seagate Crystal Reports Professional 6.0 is an excellent
into the tool. Executing the sentences is simply a matter of product to consider on either of two levels. For the Delphi
clicking the Run button and having the selected rows developer, the VCL component provides a fast loading,
returned to the display window. highly modifiable interface to a powerful report engine.
Its formatting, selection, and output destination capabili-
Documentation ties open numerous possibilities for your software develop-
The documentation supporting Crystal Reports is seg- ment efforts.
mented by intended audience. The User’s Guide offers a
good basis for starting with the product, providing tutori- If the user specification of your project calls for an external
als and numerous examples for those who will use the query and report writing tool, it would be difficult to find
product interactively. Advanced users won’t be satisfied one this powerful, yet as easy to use. Properly trained users of
with the level of this document; it leaves numerous impor- Crystal Reports will discover ways of becoming more produc-
tant questions unanswered. Developer documentation is tive users of the data created by your application. The
split among several online files. For the Delphi user there strength of the Report Designer alone is enough to recom-
are two Windows Help files of interest: Ucrpe.hlp and mend this product, but the value exceeds that offered by the
Developr.hlp. The first documents the VCL component; component-only products. ∆
the second offers general help for the developer accessing
the Crystal Reports Engine. The quality of the online doc-
umentation is spotty, containing several errors, omissions,
and poor examples. For more technical information
regarding the report engine, the developer must switch for- Warren Rachele is Chief Architect of The Hunter Group, an Evergreen, CO software
development company specializing in database-management software. The company
mats completely. The Technical Reference is a .PDF file,
has served its customers since 1987. Warren also teaches programming, hardware
readable through Adobe Acrobat. architecture, and database management at the college level. He can be reached by
e-mail at wrachele@earthlink.net, or by telephone at (303) 674-8095.
Market Positioning
Seagate Software markets Crystal Reports as a desktop query
and report writer tool within an application’s segment they
call Business Intelligence. The targets of this market, infor-
mation workers, are substantially different from those of the
software development population. Information workers,
whose responsibilities include data mining and report pro-
duction from corporate data stores, are seeking an intuitive
41 March 1998 Delphi Informant
New & Used

By Peter Hyde

Rubicon for Delphi


Tamarack Associates’ Database Search Engine

H ave you ever noticed the way users encounter a new way of doing things,
grow to like it, and suddenly it has to be in every application you create?
Of late, full-text searches, like those popularized by sites such as AltaVista
(http://altavista.digital.com), have entered the ranks of the “must have.”

This is no mere peccadillo on the part of the There is a trade off, of course. Such an
user. For many classes of applications, there engine depends on generating, maintaining,
are clear benefits to using a full-text database and using indexed word tables for each
search, rather than (or as well as) a more database — a job that can be very resource
structured and formal SQL-style one. Users hungry in memory, disk and network space,
find keyword-based searching easy to learn CPU terms, and time. Having gone down
and master because they can dig into the con- this track more than once, I’ve often wished
tent of BLOB fields containing entire docu- for a component set that provides peak
ments and, above all, a well-implemented search performance and flexibility while
search can provide unsurpassed performance. keeping a close eye on resource usage.
Nor is there anything to prevent a savvy
developer from implementing hybrid search- Enter Rubicon from Tamarack Associates. I
es, i.e. those that use keywords to quickly first saw it in action at a pre-launch demon-
narrow the target range, then apply more tra- stration of the Delphi resource site at
ditional comparisons on fields such as dates http://developers.href.com (see Figure 1),
or prices to produce the final result set. where it was performing searches on 300MB
InterBase tables in under half a second.
(Now that the site has closer to a gigabyte of
information, you can go and check its speed
for yourself.) Tamarack claims speed
improvements of up to 5,000 times com-
pared to SQL queries — and the larger the
database, the better it gets.

After a closer inspection, I wished I’d discov-


ered it earlier, not least because of the creative
and efficient way it minimizes the size of its
index files. Consider how you’d go about pre-
serving a list of the records in a 100,000-
record database that might contain a given
word. Using a BLOB field to store a Longint
ID for each matching record seems like a rea-
sonable approach. But in no time at all you
Figure 1: Rubicon search performance at the Delphi resource Web have a table where each word’s index BLOB
search engine. might contain up to 100,000 Longints!
42 March 1998 Delphi Informant
New & Used
took any noticeable time. This was natural
because, at that point, each record must be phys-
ically copied across the network.

Flexibility
Rubicon gets full marks for speed and economy.
What about flexibility? Well, power users won’t
be disappointed. In addition to words, phrases,
and simple Boolean expressions, Rubicon also
supports expressions such as “search near
Delphi” or “like angle”, and reasonably complex
combinations such as “like deficit or like loss”.
Figure 2: Rubicon located four of my articles — in a Paradox database — Critical issues such as lookup fields have not
across a LAN in just over 1/100th of a second. been overlooked: Rubicon lets you define table
links so words coming from the lookup tables
Economy and Speed can be indexed, not just words from the main table.
Rubicon does better than this: Instead of storing record Additional features such as ranking, word hints, search
numbers, it simply maintains a bitmapped array of record narrowing, and on-the-fly results mean that applications
markers. If a word is present in a record, its bit is turned using Rubicon will have a friendly and finished feel. None
on; if not, it’s turned off. Immediately, the maximum of these features would be worth having if Rubicon were
BLOB size in each word-table record is reduced from weak in the critical area of index creation and mainte-
roughly 400KB to 12.5KB. Not only that, but smart bit- nance. No database or Web application would survive long
style operators are very fast and easy to use when perform- in production if it had to be shut down for long periods so
ing “and” and “or” operations in multi-word searches. In the index could be regenerated. Fortunately, again,
effect, each Rubicon word ends up with a sparse array of Rubicon shines.
bits, which leads to another optimization: The best thing to
do when storing sparse arrays is to compress them. Rubicon Highly Scalable
will typically compress that 12.5KB to under 1KB, option- To begin with, Rubicon provides not one, but two index-
ally in memory as well as on disk. The result is a search creation components. TMakeDictionary is used for initial
tool that’s skimpy on resources without sacrificing speed. index-table creation and also (desirably) whenever extensive
To test this, I ran its demonstration program against a changes have been made to the database or external files
2,000-article database (approximately 6MB of data). being indexed. It is typically run in batch mode, perhaps
Rubicon took a minute to create an index table of 2MB, overnight for very large jobs. For minor edits, updates, or
then performed multi-word searches that located matching deletes, the TUpdateDictionary component is much quicker
articles in under 1/50th of a second (see Figure 2). Only as it works by amending the index rather than rebuilding it.
the optional creation of a result table of matching records (A list of key components is shown in Figure 3.)

Class Description
TMakeDictionary Used to scan the records of the source table, and create or recreate a dictionary of all the
words used in the selected fields and their record locations in the table. Network, threaded, and
huge table versions come with Workgroup and Professional editions.
TUpdateDictionary Keeps a dictionary synchronized with changes in the source table. Network, threaded, and huge
table versions come with Workgroup and Professional editions.
TSearchDictionary Performs word searches using the dictionary. A huge table version comes with the
Professional Edition.
TMakeProgress A drop-in form that will automatically configure itself to display the progress of TMakeDictionary.
TUpdateStats A drop-in form that will automatically configure and display itself when TUpdateDictionary is
used. It’s primarily designed for monitoring the update process during development.
TUpdateTable A descendant of TTable that has several key TUpdateDictionary interface methods built in.
TUpdateDictionary checks to see if its DataSource.DataSet is a TUpdateTable, and, if so, will auto-
matically connect the appropriate methods.
TRubiconRichEdit A read-only control that can display text or RTF files with matching words highlighted. The behavior
of the control is similar to a TDBMemo or TDBRichEdit, but is only available on 32-bit platforms.
TSearchHints A TCustomGrid descendant that displays words that match or nearly match the words the user
has entered into an edit control. The words displayed may be updated as the user types, thus
giving the user instant feedback.
TSearchController Performs a search on multiple tables (Professional Edition only).
Figure 3: Key Rubicon components.

43 March 1998 Delphi Informant


New & Used
Any number of users can
search using the Standard Peter Hyde is the author of TCompress and TCompLHA component sets for
Edition of Rubicon, but only Delphi and C++Builder, and is Development Director of South Pacific
one process can do index Information Services Ltd., which specializes in creating dynamic Web sites. He
builds or updates. The can be reached via e-mail at peter@spis.co.nz or http://www.spis.co.nz.
Rubicon is a set of high-performance, Workgroup Edition supports
text-search components for all versions
of Delphi. Its excellent speed, flexible threading so updates can be
searching options, superb scalability,
and high level of finish make it an obvi-
carried out from multiple
ous choice for Web search engines, workstations, and also adds
help desks, or any application where
users want quick and easy access to useful features, such as
information, regardless of its structure.
Rubicon can be used with HTML, text
indexing of HTML and RTF
and RTF files, any BDE-supported data- files, and matching-word
base, and TurboPower’s FlashFiler.
highlighting.
Tamarack Associates
868 Lincoln Avenue
Palo Alto, CA 94301
Voice/Fax: (650) 322-2827
For high-end applications, the
E-Mail: info@tamaracka.com Professional Edition provides a
Web Site: http://www.tamaracka.com
Price: Standard Edition, US$99; version of the components
Workgroup Edition, US$199; that supports segmented
Professional Edition, US$299. The man-
ual is included with the Workgroup and indices for tables with more
Professional editions; the manual for than 250,000 records, multi-
the Standard Edition is US$20.
processor indexing, and multi-
table searching.

Help
Rubicon’s documentation includes an excellent introduc-
tion to the components and the way they should be used,
a “reference” section that alphabetically lists the compo-
nents, properties, and methods with clear examples, and
information on advanced topics, such as huge tables,
linked lookup fields, working with TQuery, memory
management, threading, and much more. In contrast,
the online Help is a little disappointing. It’s a straight
facsimile of the manual, meaning the layout and hyper-
links are somewhat idiosyncratic. Most frustrating of all is
that it’s not context sensitive in Delphi 3. (The other
compilers are fine.)

Apart from the demonstration program, a collection of tight-


ly focused example projects is provided with Rubicon to
highlight key features, components, and properties. Finally,
not to be overlooked are utility components and programs
that range from an indexing progress meter to index verifica-
tion, optimization, and comparison programs. Together, they
make this a refined product indeed.

Almost inevitably, a product as good as this will attract


third-party add-ons. In the case of Rubicon, it’s highly pre-
dictable that such support will be Web-oriented. In mid-
1997, HREF Tools Corp. (http://www.href.com) released
TWebRubicon, a component that integrated Rubicon opera-
tions into their WebHub application framework. If there
are not similar add-ons out there already from other ven-
dors, they are sure to follow.

Conclusion
Full-text searching is a very useful and oft-requested search
strategy, yet not at all easy to implement efficiently. Tamarack
Associates has tackled the job with intelligence, flair, and
thoroughness. Search no further. ∆
44 March 1998 Delphi Informant
TextFile

Delphi Developer’s Handbook


While the early crop of of Delphi’s component struc- throughout, including a
Delphi books (circa 1995) ture, including working with chapter on Run-Time Type
tended to be general in component ownership, using Information (RTTI), a
nature and aimed at begin- the FindComponent method, nearly 100-page chapter on
ning- and intermediate- and creating type-safe TList Wizards, and a fascinating
level developers, many of derivatives. chapter on “Other Delphi
those from late 1996 Extensions.” The latter
through 1997 have includ- Chapter 3, on streaming and includes a summary of the
ed more specialized and persistence, provides an ToolsAPI, a discussion of
advanced offerings. One of excellent overview of this how to handle Delphi noti-
the last Delphi books important topic with a thor- fications, and an overview
released in 1997, Delphi ough discussion of file and of the Version Control
Developer’s Handbook memory streams and tech- System (VCS) interface. Component Object Model
[SYBEX, 1997], covers a niques for copying data The section on hacking (COM) written by COM
wide range of topics, but is between them. New stream- Delphi is most intriguing, expert John F. Lam. There is
nevertheless advanced in its ing classes are also devel- with instructions regarding also a good discussion of
treatment of those topics. oped. To my delight, the a simple means of changing some of the Internet-related
Written by Marco Cantù authors included a discussion Delphi’s Component technologies, such as
(of SYBEX’s Mastering of the TWriter and TReader palette. Finally, many of the HTML, CGI, and ISAPI.
Delphi series) and Tim support classes, and how to tools developed throughout
Gooch (former editor of use them with streams. Handbook will make useful By no means have I men-
the Cobb Group’s Delphi additions to your program- tioned every topic included
Developer’s Journal ), this Any comprehensive, ming arsenal. I’m particu- in this fine volume. Suffice
work will be a welcome advanced Delphi work larly impressed with the it to say, if you’ve been pro-
addition to your library if should include a discussion Object Debugger, which gramming in Delphi for a
you’re looking for an of components and experts; clones the Object Inspector, while and are ready to
advanced reference aimed Delphi Developer’s Handbook makes it available at run explore its more advanced
more at application devel- certainly fulfills this require- time, and even adds editing aspects, this book is for
opers than tools (compo- ment. There are chapters on capabilities to it. An entire you. If you’re new to
nent/wizard) developers. building components chapter is devoted to build- Delphi, you should proba-
throughout, from basic prin- ing this useful tool. Several bly start with a more basic
This book hits the ground ciples to compound compo- of these topics are rarely work such as Cantù’s
running with advanced nents, from using TCollection written about. There are Mastering Delphi 3
information on bit manipu- classes as properties to data- others, including writing [SYBEX, 1997].
lation, long strings, classes, aware controls. (In addition Windows applications with-
and levels of protection. to the latter topic, two chap- out the VCL (Visual — Alan C. Moore, Ph.D.
While many of these topics ters cover advanced database Component Library), using
are not unusual, the treat- and client/server program- Windows messages and API Delphi Developer’s Handbook
ment certainly is: accessing ming.) Essential component- function calls, and extend- by Marco Cantù, Tim
the inner structure of long related topics such as compo- ing Delphi’s TApplication Gooch, with John F. Lam,
strings, manipulating classes nent editors, properties, and TForm classes. SYBEX, 1151 Marina
in various useful ways, and property editors, and pack- Village Parkway, Alameda,
circumventing Delphi’s pro- ages are also covered. Some of the newer technolo- CA 94501, (510) 523-2826.
tection scheme. Similarly, the gies are dealt with in a sub- ISBN: 0-7821-1987-5
second chapter exposes some There is also information stantial manner. There are Price: US$49.99
of the more esoteric aspects regarding Experts/Wizards two chapters on the (1,134 pages, CD-ROM)
45 March 1998 Delphi Informant
File | New
Directions / Commentary

Delphi and the APIs


Getting to the Source

I n an editorial in the December, 1997 issue of Delphi Informant, I discussed the need to make new tech-
nologies and their APIs available in Delphi and the Jedi movement that is attempting to do just that. But
what about the standard APIs? Since Delphi has implemented a large part of the Windows operating sys-
tem, do we need to know the gory details of Windows functions and messages? In everyday programming,
probably not. However, if we’re extending components or accessing functionality not supported by Delphi,
we might need detailed information. Where can we find it?

In the bad old days BD (Before new areas include threads and the reg- low-level API function calls used when
Delphi), if you were programming for istry. I found one of the last chapters working with audio files (WAV and
Windows in C or Pascal, you needed to particularly interesting: “File MIDI), AVI files, and the Media Control
work at the API level to a greater extent. Decompression and Installation.” If Interface (MCI). While not indicated in
In those 16-bit days, a very popular ref- you’re interested in building a set of the title, this volume also discusses the
erence work was Windows API Bible by installation components as part of a Telephony Application Programming
James L. Conger [Waite Group Press, setup-generating system, you’ll find Interface (TAPI).
1992]. This single volume contained much of what you need here: functions
the essential information of the time: to check version information, functions If you’re planning to do a significant
the message system, the various stan- to manipulate compressed files, and amount of low-level work at the API
dard controls, input/output, and even functions to determine what files level, either in application programming
multimedia and communications. (DLLs) are installed on a user’s system. or component creation, you should con-
Today, the existing APIs have grown sider adding one or more of these vol-
considerably, and new ones have been Windows 95 Common Controls and umes to your Delphi library. These ref-
added to the standard package. To keep Messages API Bible presents the controls erences include numerous code exam-
up, the Waite Group’s Bible, written inherited from Windows 3.x and the ples to show how to access particular
mainly by Richard J. Simon with help new ones added with Windows 95. It functions. While the code examples in
from Michael Gouker and Brian Barnes, also includes a complete reference to this series are in C, you should be able
jumped to three volumes between 1996 Windows messages. If you want to learn to learn something from them. ∆
and 1997. Let’s examine each. more about the new controls, such as
toolbars, status bars, tree views, and — Alan C. Moore, Ph.D.
Windows NT Win32 API SuperBible rich-text edit controls, and extend their
provides an excellent introduction to functionality, you’ll find this volume Alan Moore is a Professor of Music at
Windows 32-bit programming — both particularly helpful. Kentucky State University, specializing in
Windows 95 and NT 4.0 — and music composition and music theory. He has
includes an explanation of how an The final volume, Windows 95 been developing education-related applica-
application qualifies for the Windows Multimedia and ODBC API Bible, pre- tions with the Borland languages for more
95 logo. This first volume includes a sents Windows’ current support for than 10 years. He has published a number
discussion of windows and dialog boxes, Multimedia and ODBC. The latter topic of articles in various technical journals.
menus and other basic interface objects, is presented first and provides informa- Using Delphi, he specializes in writing cus-
message systems (messages themselves tion on connecting to various DBMSes, tom components and implementing multi-
are in the second volume), memory executing SQL statements, converting media capabilities in applications, particu-
management, input/output, graphics, data, and more. The multimedia section larly sound and music. You can reach Alan
and system information. Some of the provides a detailed account of high- and via e-mail at acmdoc@aol.com.
46 March 1998 Delphi Informant

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy