9507
9507
9507
orking
W with Data
Delphi as a Database Front-End
ON THE COVER
6
16
21
26
JULY 1995
31
34
FEATURES
REVIEWS
DEPARTMENTS
2 Delphi Tools
4 Newsline
Delphi INFORMANT
Delphi
T O O L S
New Products
and Solutions
Delphi Training
HTR, Inc. is an authorized
Delphi Training center offering
two Delphi classes: Client/Server
Application Development Using
Delphi and Advanced
Client/Server Application
Development. The classes run
through July at various
cities nationwide.
The three-day Client/Server
Application Development Using
Delphi class costs US$1050,
and Advanced Client/Server
Application Development is a
two-day event priced at US$700.
For more information, or to
enroll, call (301) 881-1010.
JULY 1995
Delphi
T O O L S
New Products
and Solutions
and OS/2 using the same syntax and one function call.
TransPortal PRO documentation includes sample code
for all supported languages,
on-line help in Windows and
DOS, enhancements to
Gupta SQL Windows, a new
installation program, and
improvements to TurboMap.
Using TurboMap, PC programmers can automate complicated mainframe terminal
operator procedures.
Price: US$2450
Contact: The Frustum Group, Inc., 525
North Broadway, White Plains, NY 10603
Phone: (914) 428-7200 or
(800) 548-5660
Fax: (914) 428-0795
JULY 1995
Delphi INFORMANT
News
L
July 1995
JULY 1995
News
L
July 1995
JULY 1995
DB/Expo: A Look at Borlands RAD Pack for Delphi and New dBASE
San Francisco, CA With
close to 100 new product
announcements and presentations, DB/Expo attracted
31,108 attendees to the 7th
annual conference and exposition held May 1-5 at the
Moscone Convention Center
in San Francisco, CA. This
years database, client/server,
and networking event featured
900 exhibits from more than
200 IT companies worldwide.
Borland International
announced the release of the
RAD Pack for Delphi, a new
companion tool set that combines many of Borlands products into one package. It
includes the Visual Component
Library source code and the
Resource Workshop for extracting and modifying standard
Windows resources, such as
icons, cursors, bitmaps, and dialog boxes.
It also features a Resource
Expert that converts standard
resource scripts into Delphi
forms, a Delphi Language
Reference Guide, and the Turbo
Windows Component
Resource Goes
On-Line
Layton, UT Imagicom is
now providing a free comprehensive component resource
service to Windows developers. The service has information on OCX, VBX, DLL,
and Delphi custom controls
organized by component type,
file format, and vendor.
Imagicoms service is located on a World Wide Web
(WWW) home page and can
be accessed by any Internet
browser. The home page is
similar to an interactive catalog, where the user can pointand-click until the correct
information is found. Once a
developer has selected the
right grouping, a specific
component can be located
from text descriptions, product images, and downloadable
demonstration copies.
Vendors can put basic
information about their components on the service for
free. Imagicom charges a fee
to maintain higher levels of
service such as downloadable
demonstration copies, independent Web pages, and
images of custom controls.
Imagicoms home page is
http://www.xmission.com/~ima
gicom/. For more information
send e-mail to
imagicom@xmission.com.
Delphi INFORMANT
On the Cover
Delphi / Object Pascal / IDAPI
By John OConnell
A TTable component provides access to a tables records via IDAPI the Borland Database
Engine (BDE). A TQuery retrieves selected records from a table based on selection criteria, and a
TStoredProc enables procedures and functions to be executed on a remote database server. These
components are known collectively as datasets.
The TDataSource component is the link between data-aware controls such as TDBEdit or
TDBGrid and a dataset. TDataSource is the middle layer in Delphis database connectivity scheme.
The TTable component is the lower layer, and data-aware controls are the upper layer.
Another component of Delphis database connectivity is the TField component. It allows programmatic access to a field in the record. The TField object also performs default formatting and validating of field values. A TField object is created when a dataset is opened at run-time, or at design
time by using the Fields editor for a TDataSet descendant component. In the latter case, properties
and event handlers can be defined for the TField at design time. The list of TFields in a dataset can
be accessed using the Fields array property at run-time. [For an in-depth introduction to TFields,
see Cary Jensens article The TField Class in the June 1995 Delphi Informant. He continues the
discussion in this months article The TField Class: Part II, beginning on page 21.]
The TDataSource can be seen as a record buffer between data-aware controls and datasets. The
TTables current record is copied to the TDataSource buffer and read by the
data-aware controls using that datasource.
Datasource and dataset components generate notification events at various
stages during manipulation of the database tables associated with them.
This article will discuss the events generated by the TTable dataset and
TDataSource using Paradox tables.
A TTable generates notification events for these reasons:
The dataset is being opened or closed.
The dataset is about to enter or exit edit mode.
The dataset is about to insert or delete a record.
The dataset is about to post or cancel changes to a record.
JULY 1995
Delphi INFORMANT
On The Cover
TDataSet Event
BeforeOpen, AfterOpen
BeforeClose, AfterClose
BeforeInsert, AfterInsert
BeforeEdit, AfterEdit
BeforePost, AfterPost
BeforeCancel, AfterCancel
BeforeDelete, AfterDelete
OnNewRecord
OnCalcFields
TDataSource Event
OnUpdateData
OnStateChange
State
When Triggered
dsInactive
dsBrowse
dsEdit
dsInsert
dsSetKey
dsCalcFields
Figure 4 shows the database events triggered just after the dialog box
is displayed, and the master and detail tables are opened by code in
the forms FormCreate event. Lets examine this list more closely:
The master table is opened which triggers BeforeOpen for
tblMaster.
JULY 1995
When Triggered
OnDataChange
The table the method buttons call can be chosen from a combo box
labeled Call Method for. It contains the names of all TDataSource
components found in the form at run-time. When DBTracker is
first started, the first TDataSources dataset table is affected by the
Methods buttons. The contents of the ListBox with the tracked
database events can be saved to an ASCII text file and the list can
also be emptied.
The master TTable in the form is called tblMaster, the detail
TTable is called tblDetail these are the datasets for the respective TDatasources dsMaster and dsDetail. The tables are linked
one-to-many by the detail tables secondary index.
When Triggered
On The Cover
the state at form startup). Because the datasource needs to
refresh its view of the new empty record, the OnNewRecord
event is triggered for tblMaster followed by the
OnDataChange event.
Entering a value in the CustNo field in the new empty record and
moving to the next field (by pressing F for example) triggers the
following events:
OnCalcFields for tblMaster
OnDataChange for dsMaster.CustNo
Using DBTracker
The buttons in the Methods group box explicitly call various
TTable methods (Insert, Delete, Edit, Post, Cancel, Open, and Close,
respectively). Pressing the equivalent buttons on the navigator
toolbar has the same effect. However, using the Methods buttons
allows you to clearly see what occurs between the moment the relevant TTable method is called, and the moment the method has
completed. To specify which datasource the buttons will act upon,
specify the desired datasource name from the combo box.
The Open and Close buttons allow you to open or close the
TTable associated with the selected datasource. If the linked
detail table dsDetail is closed, then the links between
tblDetail and tblMaster will be ignored. If dsMaster is closed
then tblDetail will effectively be an unlinked table with all
records in view instead of the restricted view thats in force
when tblMaster is open.
Lets look at the sequence of events for some common database
usage scenarios.
As a result of moving from a modified field, the value of the current record has changed in the underlying table. This causes the
dataset to signal that its calculated field needs recalculating, and
that the datasources controls need refreshing with the changed
field values of the record.
When the text in a data-aware control has changed and that control has lost focus, the text is sent to the underlying TField
object. It then converts the text to the fields data type, validates
the field value, and applies formatting.
Because the record has changed, the dataset notifies the
associated datasource (dsMaster) that its view of the record
needs to be updated, thus triggering the OnDataChange
event. This ensures the data-aware controls are seeing exactly whats in the underlying record after the TField has done
its bit. Note that the record itself isnt actually committed
or posted to the table until TTable.Post is called or the
entire record is refreshed.
Pressing the Post button triggers the following events:
OnUpdateData for dsMaster
BeforePost for tblMaster
OnStateChange to dsBrowse for dsMaster
Table state change to dsBrowse for tblMaster
OnCalcFields for tblMaster
OnDataChange for dsMaster
AfterPost for tblMaster
Delphi INFORMANT
On The Cover
OnStateChange to dsBrowse for dsMaster
Table state change to dsBrowse for tblMaster
OnCalcFields for tblMaster
OnDataChange for dsMaster
AfterCancel for tblMaster
The chain of events triggered in this case is much the same, except
the record is canceled and no changes are written to the table.
Pressing the Edit button generates the following chain of events:
BeforeEdit for tblMaster
OnCalcFields for tblMaster
OnStateChange to dsEdit for dsMaster
Table state change to dsEdit for tblMaster
OnDataChange for dsMaster
AfterEdit for tblMaster
JULY 1995
Delphi INFORMANT
On The Cover
BeforeCancel for tblDetail
OnStateChange to dsBrowse for dsDetail
Table state change to dsBrowse for tblDetail
OnDataChange for dsDetail
AfterCancel for tblDetail
BeforePost for tblMaster
OnStateChange to dsBrowse for dsMaster
Table state change to dsBrowse for tblMaster
OnCalcFields for tblMaster
OnDataChange for dsMaster
AfterPost for tblMaster
Event Handlers
Tracking TDataSource and TTable events is pretty straightforward just add the appropriate event handlers for all
TDataSource and TTable objects. This doesnt have to be done
for all DataSource and Table objects, just one of each. We can
then copy and paste each object as needed at design time
each copy will use the same event handlers.
The event handlers log event information to the list box using
the custom LogEvent method. Each event handler can identify
the dataset or datasource that triggered the event by typecasting
the Sender parameter to the appropriate type and then accessing
the Name and/or DataSet properties.
The OnStateChange event handler, TrackStateChange, logs the
new state and name of the datasource and checks to see if there
is a DataSet property defined for Sender (the only parameter for
a TNotifyEvent). If not, a warning message is logged. Otherwise
the datasets new state is logged. The datasource and dataset
states will usually be the same.
We can identify Sender as a TDataSource by typecasting it as a
TDataSource (using the as operator) and assigning it to
MySender. We can do that because TDataSource is really a pointer to a dynamically allocated DataSource component.
With Delphi there isnt a static instance of a component or
object because all components are created dynamically at runtime, and hence are referenced using pointers. So in effect, typecasting objects means treat this pointer as a pointer to the type
being cast as.
Whenever you reference an objects property or method, the
pointer is automatically de-referenced so it doesnt seem as if
youre dealing with a pointer. The following statement from
TrackStateChange is pure pointer assignment MySender
points to the same dynamic object instance that Sender points
to. Because TObject is the class all VCL objects are descended
from, we can typecast it to the TDataSource descendant class
that we know is the event sender:
MySender := Sender as TDataSource;
NameString := MySender.Name;
The ToggleButtons method called in TrackStateChange simply disables the relevant Methods buttons according to the state of the
datasource.
JULY 1995
Delphi INFORMANT
10
On The Cover
The OnDataChange event is triggered for a number of reasons
that were discussed earlier. This TDataChange event is handled
by the TrackDataChange method. A TDataChange event has an
additional parameter, called Field, that can be used to determine
why OnDataChange was triggered. If Field is a nil pointer, then
the event occurred as a result of moving from record to record in
browse mode because more than one field contributed to the
record changing. If Field is not nil and the table state is dsEdit or
dsInsert, then the event occurred because a fields value changed
and the unposted record was updated.
The TTable event handlers are very straightforward they simply log each event with the senders name. Most of the TTable
events are triggered before and after a particular TTable action.
When a record is posted, a BeforePost and AfterPost event is triggered. When a before event is logged all events subsequently
logged are indented by one level. When an after event is
logged the indenting is reduced by one level. This indentation
displays which events occur between, before, and after events.
The cboTables TComboBox object lists the datasources that can be
chosen for the Methods buttons to act on. The items in the
TStrings object property of the ComboBox contain both the names
of each datasource and a pointer to the named datasource instance.
A TStrings list is able to store both a string and pointer instead of
just a string. Adding a string to a TStrings list is done using the
Add or Insert methods. To add both the string and pointer to the
list, use either the AddObject or InsertObject method (see the
FormCreate method in Listing One). Strings and pointers to
objects in the list can be accessed using the Items and Objects array
properties respectively.
The TStrings object can be very useful for creating bag type
arrays (containers) used to store items of different types. If youre
familiar with Paradoxs DynArray type, the same storage functionality can be achieved in Delphi by using a TStrings object.
As previously mentioned, DBTracker can easily be changed at
design-time to include more TTables and TDataSource components (whose properties can be set interactively) and then data aware controls set up to work with them. All events will still be
tracked without you having to modify any source code. The only
caveat is that you must have a TDataSource called dsMaster and a
TTable called tblMaster in the form. You may need to define a
few field objects for those tables that are linked. Youll also need
to define a calculated field called MyCalcField for tblMaster.
labeled Call Method for. As mentioned, the TStrings list contains a list of names of TDataSource components in the form
with a pointer to each TDataSource instance. This drop-down
list allows the user to select the table to be affected when pressing one of the Call Method buttons.
The TDataSource and TTable variables CurrentSource and
CurrentTable are used to track the DataSource and Table pair
to be acted on by the Methods buttons. When the form is created CurrentSource is set to default to the first datasource found
in the form. CurrentSources DataSet property is used to initialize CurrentTable. If there isnt a DataSet property defined
for CurrentSource, then CurrentTable cannot be set and the
program will halt.
Assuming that CurrentTable is defined, all tables associated
with the datasources are then opened. All TTables must be not
be open (i.e. their Active properties must be set to False) at
design time. If a TDataSource has no DataSet property, a warning message is displayed stating the TDataSource has no DataSet
and no associated table can be opened.
The two TDBEdit components are then set to have their DataField
properties set to point to the first two fields in tblMaster. (Note that
tblMaster doesnt necessarily have to be the master table in a multitable form. Ive used this as a convention to identify the table used
by the two TDBEdit components for their data fields.) Finally, the
forms buttons have their Enabled properties set.
The TComboBox OnChange event is handled by the custom
cboTablesChange procedure. Here the CurrentSource and
CurrentTable are set to that of the chosen datasource, and its
DataSet property (if this is undefined then a warning error message is displayed). The Methods buttons have their Enabled
properties set by the call to the custom ToggleButtons procedure.
Notice how the object pointer assignments were typecast using
value typecasting, instead of the type-safe casting using the as
operator. The difference between these ways of typecasting is this:
if the typecast fails, then using as will raise an exception whereas
the value typecast may cause undefined behavior later on (possibly causing a crash when the pointer is dereferenced). However,
in this case we know that this particular typecast is safe.
The SaveEventList method that handles the OnClick event for
the Save List button writes each item in the TStrings list to an
ASCII file whose name is chosen using a SaveDialog component.
Extending DBTracker
Conclusion
I mentioned earlier that DBTracker was written with easy extensibility in mind. So how is this achieved? Lets start with the
FormCreate method.
Delphi INFORMANT
11
On The Cover
DBTracker was developed as a result of my need for a way of
debugging code in a Data Access components event handler. It
also serves as a tool for learning and understanding how and
when database events are triggered.
If desired, you could dig deeper into the data controls event model
by logging the TField events, but Ill leave that exercise to you.
The DBTracker utility is available on the 1995 Delphi Informant
Works CD located in INFORM\95\JUL\JO9507.
JULY 1995
Label3: TLabel;
Label4: TLabel;
procedure
procedure
procedure
procedure
procedure
procedure
procedure
procedure
procedure
procedure
procedure
procedure
procedure
procedure
procedure
procedure
procedure
procedure
procedure
procedure
procedure
procedure
procedure
procedure
procedure
procedure
procedure
procedure
procedure
procedure
procedure
btnInsertClick(Sender: TObject);
btnDeleteClick(Sender: TObject);
btnEditClick(Sender: TObject);
btnPostClick(Sender: TObject);
btnCancelClick(Sender: TObject);
btnClearClick(Sender: TObject);
TrackDataChange(Sender: TObject;
Field: TField);
TrackStateChange(Sender: TObject);
TrackUpdateData(Sender: TObject);
FormCreate(Sender: TObject);
TrackAfterCancel(DataSet: TDataset);
TrackAfterClose(DataSet: TDataset);
TrackAfterDelete(DataSet: TDataset);
TrackAfterEdit(DataSet: TDataset);
TrackAfterInsert(DataSet: TDataset);
TrackAfterOpen(DataSet: TDataset);
TrackAfterPost(DataSet: TDataset);
TrackBeforeCancel(DataSet: TDataset);
TrackBeforeClose(DataSet: TDataset);
TrackBeforeDelete(DataSet: TDataset);
TrackBeforeEdit(DataSet: TDataset);
TrackBeforeInsert(DataSet: TDataset);
TrackBeforeOpen(DataSet: TDataset);
TrackBeforePost(DataSet: TDataset);
TrackOnCalc(DataSet: TDataset);
TrackNewRec(DataSet: TDataset);
btnOpenClick(Sender: TObject);
btnCloseClick(Sender: TObject);
cboTablesChange(Sender: TObject);
FormCloseQuery(Sender: TObject;
var CanClose: Boolean);
SaveEventList(Sender: TObject);
private
ListLen: Word;
IndentLevel: Byte;
CurrentTable: TTable;
CurrentSource: TDataSource;
procedure LogEvent(EventStr: string);
procedure ToggleButtons(StateParam: TDataSetState);
public
end;
var
FrmDBTrack: TFrmDBTrack;
implementation
{$R *.DFM}
procedure TFrmDBTrack.ToggleButtons(StateParam:
TDataSetState);
var
ButtonEnabled: Boolean;
begin
{ If the table is in edit or insert mode then disable
the Insert Delete and Edit buttons else disable the
Post and Cancel buttons. If the table is closed/
inactive then disable all pushbuttons. }
ButtonEnabled :=
not(StateParam in [dsEdit, dsInsert, dsInactive]);
btnInsert.Enabled := ButtonEnabled;
btnDelete.Enabled := ButtonEnabled;
btnEdit.Enabled
:= ButtonEnabled;
if StateParam = dsInactive then
begin
btnPost.Enabled
:= False;
btnCancel.Enabled := False;
end
Delphi INFORMANT
12
On The Cover
else
begin
btnPost.Enabled
:= not ButtonEnabled;
btnCancel.Enabled := not ButtonEnabled;
end;
ButtonEnabled
:= (CurrentTable <> nil);
btnOpen.Enabled := ButtonEnabled;
btnClose.Enabled := ButtonEnabled;
end;
procedure TFrmDBTrack.LogEvent(EventStr: string);
var
IndentStr : string[32];
i
: byte;
begin
with lstEvent do
begin
IndentStr := '';
if IndentLevel > 0 then
begin
for i := 1 to IndentLevel do
IndentStr := IndentStr + ' ';
Items.Add(IndentStr + EventStr);
end
else
Items.Add(EventStr);
Inc(ListLen);
edCount.Text := IntToStr(ListLen);
ItemIndex := Items.Count - 1;
end;
btnSaveList.Enabled := (ListLen > 0);
end;
begin
with (Sender as TDataSource) do
LogEvent('OnUpdateData for ' + Name);
end;
JULY 1995
Delphi INFORMANT
13
On The Cover
CurrentSource.Name +
#10' has no DataSet property defined.'#10+
#10'This application will terminate',
mtError, [mbOk], 0);
Halt;
end;
IndentLevel := 0;
{ Open all tables in form, but check that each
TDataSource in list has a dataset defined }
for i := 0 to Items.Count - 1 do
if (TDataSource(
Items.Objects[i]).DataSet = nil) then
MessageDlg('The datasource ' +
TDataSource(Items.Objects[i]).Name +
' has no DataSet property defined',
mtWarning, [mbOK], 0)
else
TDataSource(Items.Objects[i]).DataSet.Open;
end;
DBEdit1.DataField := tblMaster.Fields[0].FieldName;
DBEdit2.DataField := tblMaster.Fields[1].FieldName;
ToggleButtons(CurrentSource.State);
btnSaveList.Enabled := (ListLen > 0);
end;
procedure TFrmDBTrack.btnClearClick(Sender: TObject);
begin
lstEvent.Clear;
ListLen := 0;
IndentLevel := 0;
edCount.Text := IntToStr(ListLen);
btnSaveList.Enabled := (ListLen > 0);
end;
procedure TFrmDBTrack.TrackAfterCancel(
DataSet: TDataset);
begin
Dec(IndentLevel);
with (DataSet as TTable) do
LogEvent('AfterCancel for ' + Name);
end;
procedure TFrmDBTrack.TrackAfterClose(
DataSet: TDataset);
begin
Dec(IndentLevel);
with (DataSet as TTable) do
LogEvent('AfterClose for ' + Name);
end;
procedure TFrmDBTrack.TrackAfterDelete(
DataSet: TDataset);
begin
Dec(IndentLevel);
with (DataSet as TTable) do
LogEvent('AfterDelete for ' + Name);
end;
procedure TFrmDBTrack.TrackAfterEdit(DataSet: TDataset);
begin
Dec(IndentLevel);
with (DataSet as TTable) do
LogEvent('AfterEdit for ' + Name);
end;
procedure TFrmDBTrack.TrackAfterInsert(
DataSet: TDataset);
begin
Dec(IndentLevel);
with (DataSet as TTable) do
LogEvent('AfterInsert for ' + Name);
end;
procedure TFrmDBTrack.TrackAfterOpen(DataSet: TDataset);
JULY 1995
begin
Dec(IndentLevel);
with (DataSet as TTable) do
LogEvent('AfterOpen for ' + Name);
end;
procedure TFrmDBTrack.TrackAfterPost(DataSet: TDataset);
begin
Dec(IndentLevel);
with (DataSet as TTable) do
LogEvent('AfterPost for ' + Name);
end;
procedure TFrmDBTrack.TrackBeforeCancel(
DataSet: TDataset);
begin
with (DataSet as TTable) do
LogEvent('BeforeCancel for ' + Name);
Inc(IndentLevel);
end;
procedure TFrmDBTrack.TrackBeforeClose(
DataSet: TDataset);
begin
with (DataSet as TTable) do
LogEvent('BeforeClose for ' + Name);
Inc(IndentLevel);
end;
procedure TFrmDBTrack.TrackBeforeDelete(
DataSet: TDataset);
begin
with (DataSet as TTable) do
LogEvent('BeforeDelete for ' + Name);
Inc(IndentLevel);
end;
procedure TFrmDBTrack.TrackBeforeEdit(DataSet: TDataset);
begin
with (DataSet as TTable) do
LogEvent('BeforeEdit for ' + Name);
Inc(IndentLevel);
end;
procedure TFrmDBTrack.TrackBeforeInsert(
DataSet: TDataset);
begin
with (DataSet as TTable) do
LogEvent('BeforeInsert for ' + Name);
Inc(IndentLevel);
end;
procedure TFrmDBTrack.TrackBeforeOpen(DataSet: TDataset);
begin
with (DataSet as TTable) do
LogEvent('BeforeOpen for ' + Name);
Inc(IndentLevel);
end;
procedure TFrmDBTrack.TrackBeforePost(DataSet: TDataset);
begin
with (DataSet as TTable) do
LogEvent('BeforePost for ' + Name);
Inc(IndentLevel);
end;
procedure TFrmDBTrack.TrackOnCalc(DataSet: TDataset);
begin
with (DataSet as TTable) do
LogEvent('OnCalcFields for ' + Name);
end;
procedure TFrmDBTrack.TrackNewRec(DataSet: TDataset);
begin
Delphi INFORMANT
14
On The Cover
JULY 1995
Delphi INFORMANT
15
On the Cover
Delphi / Object Pascal / IDAPI
By Marco Cant
As weve heard, Delphi can be used to build client/server applications. Further, developing a
new Delphi database application using a SQL server is similar to developing an application
based on local files.
Having said this, however, whats involved in upsizing an application? To make it function,
very little. And just a little more work is required to take advantage of SQL server features
such as transaction processing.
Delphi INFORMANT
16
On The Cover
of their data-aware components. When you define new fields
at design-time, they are listed in the Object Inspector.
After generating the code, you can remove the previous blank
form from the project (Form1 by default), compile the program, give appropriate names to the files, and run it. The
result (with some modifications) is shown in Figure 1.
Delphis DBNavigator component is already transaction-oriented, so you can easily use it for transaction processing. In
this example, the DBNavigator components VisibleButtons
property has been set so that nbInsert, nbDelete, nbEdit,
nbPost, nbCancel, and nbRefresh are False. In addition, the
Caption property of Form1 was changed to Expert Grid.
and country area data from the Country table to compute the
population density of each country.
To do this, well need to add another field to the table by
clicking the Add button in the Fields editor. Then click on
the Define button and enter a proper Field name,
Population Density in this example, and Field type
FloatField for the new calculated field. Note that as
you type in the field name, the component name is generated by default.
Of course, we also need to provide a way to compute the
new field. This is accomplished in the OnCalcFields event of
the Table component with the following Object Pascal code:
Table1.Fields[0].AsString
As you can see, this code accesses the other fields directly (e.g.
Table1Area.Value). This is possible because the Fields editor has added the fields to the form. Here is the Object Pascal
JULY 1995
Delphi INFORMANT
17
On The Cover
Also note that each time you add or remove fields in the
Fields editor, the forms grid is automatically updated. Of
course, you wont see the values of a calculated field because
the method used to compute it must be compiled, and will be
available only at run-time.
After defining components for the fields, we can use their
properties to customize some of the grid's visual components. For example, as shown in Figure 4, we've changed the
first column's name to Country (instead of Name). To do
this, simply select Table1Name in the Object selector and
change its DisplayLabel property to Name. Last, press J to
accept the changes.
We also set a display format, adding a comma to separate
thousands. For example, you can select Table1Area in the
Object selector and enter #,# for its DisplayFormat property
(likewise, select Table1Population and change its
DisplayFormat property).
These changes have an immediate effect on the grid at design
time. Also, the form's Caption property has been changed
from Expert Grid to Calculated Field.
Figure 4 (Top): The grid at design time, after some changes to the
properties of the field components. Figure 5 (Bottom): The output of
the example, with the calculated Population Density field.
Now that we have built the basic program, we can start the
upsizing process by copying the table to a Local InterBase
database.
JULY 1995
Delphi INFORMANT
18
On The Cover
batAppendUpdate updates
matching records of, and
appends new records to, an
existing table
batCopy creates a destination
table with structure and content of source table
batDelete deletes matching
records in existing table
Now were ready for the second step of the upsizing process.
Open the old application (or a copy), select the Table component, and set its Active property to False. Then select the
IBLOCAL database and the America table (that should
appear in the list of available tables in the Object Inspector).
Activate the table again, and the live data is displayed. The
difference is that now we are accessing a SQL server. When
you run the program, the new calculated field will appear,
exactly as it did in the previous version.
There is still a minor problem, however. The division calculation we wrote in the SQL expression results in a floatingpoint number with several decimal places. The problem is
that the default grid is not wide enough to accommodate all
Delphi INFORMANT
19
On The Cover
do not need compiled Object Pascal code to make this computation. Instead, the Local InterBase server does it while
processing a SQL statement.
This is a key point. We have moved a processing task from
the client application to the SQL server. This means that we
can move computations from the client computer to the server computer, which can usually handle the large amount of
data involved in big queries more quickly and efficiently.
Although, admittedly, we are currently running both the
client and server code on the same computer (since were
using Local InterBase), this doesnt modify the general perspective. A true client/server application distributes the processing workload of an application between a client and server
computer. The client should be primarily involved in the user
interface, and the server in data processing.
Conclusion
To obtain all the benefits of true client/server programming, we would need to go one step further and employ a
SQL server that resides on another computer. The easiest
migration would be to an InterBase database on another
platform (e.g. Microsoft Windows NT, Novell NetWare,
SCO UNIX, etc.).
Still, just by moving the data to Local InterBase, we receive
many advantages over a Paradox database. Greatly enhanced
data integrity via triggers, stored procedures, and transaction
processing is just one example.
Figure 8 (Top): The Expression dialog box of the Visual Query Builder
can be used to define calculated fields. Figure 9 (Bottom): The form
with the SQL-calculated Density field at design time.
JULY 1995
Marco Cant is a freelance writer and consultant based in Italy. Besides writing
books and articles, he enjoys training Delphi and C++ programmers and
speaking at conferences. You can reach Marco on CompuServe at 100273,2610.
Delphi INFORMANT
20
DBNavigator
Delphi / Object Pascal
Field components are a class of objects that permit you to directly manipulate the data displayed on a form. These objects are created automatically at run-time or manually at design time. By creating TFields at design
time you can exert greater control over the default characteristics of the corresponding fields. For example, you can display only some of the fields from a
table in a DBGrid component.
Last months article examined how to use the Fields editor dialog box to instantiate fields associated
with a Table object, and how to control their properties both at design time and run-time. This month
we will explore TFields further. However, instead of instantiating TFields that belong to a Table, we
will learn how to create calculated fields. [For a discussion of calculated fields in a client/server configuration, see Marco Cants article Moving to Local InterBase beginning on page 16.]
JULY 1995
Delphi INFORMANT
21
DBNavigator
OnCalcFields events occur even when no change has been made
to data. Each time its necessary for a Table object to update the
display of a record, an OnCalcFields event occurs. For example,
OnCalcFields events occur for each displayed record when a form
first opens, as well as when you advance to a new record.
If the DataSet (TTable, TQuery, or TStoredProc) is responsible
for more than one record being displayed, the OnCalcFields
event occurs once for each record. For example, if a DBGrid is
associated with a detail table in a one-to-many form, there will
be one OnCalcFields event generated for each record being displayed in the DBGrid when the form first initializes, as well as
when the user advances to a new master record.
Figure 1: As shown here, the total of price and quantity, as well as the
custom company name and employee last name fields, are displayed
using calculated fields.
22
DBNavigator
list.) You should now activate the connection to the table by setting Active to True.
Now set the DBEdit component properties by changing the
DataSource property to DataSource1 and setting its DataField
property to OrderNo.
You are now ready to instantiate TField components. From the
Forms window, double-click the Table object to display the
Fields editor dialog box. First, add a TField component for the
OrderNo field (from the ORDERS.DB table). Do this by clicking the Add button on the Fields editor dialog box, highlighting
OrderNo from the Available Fields list, and then clicking OK
to return to the Fields editor dialog box.
Next, add the calculated field. Click the Define button on the
Fields editor dialog box to display the Define Field dialog box.
In the Field Name text box enter CalcDemo. Delphi will automatically add Table1CalcDemo in the Component Name text
box. You can optionally define an alternative component name,
but this is usually not necessary.
The Fields editor dialog box should emulate the one displayed in
Figure 4. The objects and properties are now in place. Because you
instantiated the calculated field, an OnCalcFields event will be generated each time Delphi attempts to update the display of the
DBEdit component. Close the Fields editor dialog box.
After you respond to this message box, the form will appear.
Thereafter, the message box is
displayed each time you move
to a new record. It will also
display if you edit the order
number. (However, you
shouldnt do this since the
Orders table has detail records
that rely on the value of the
order number field.)
23
DBNavigator
Also, be mindful that OnCalcFields is triggered by many events
that modify a DataSet. For example, navigating to a new record
may generate one or more OnCalcFields events. You must avoid
triggering these events with the code you add to the
OnCalcFields event handler. Failure to do so may result in
unwanted and uncontrollable recursion.
Under normal conditions, an OnCalcFields event triggers automatically each time you modify a field in a record. This occurs
because the AutoCalcFields property of TTable, TQuery, and
TStoredProc components is True by default. If you change the
AutoCalcFields property to False, OnCalcFields events will only be
triggered automatically when Delphi first loads and displays a
record from a database.
Finally, remember that OnCalcFields events occur when objects
are initialized on an opening form. Consequently, the creation
order of objects can have an impact on the success of the code
you place in an OnCalcFields event. For example, if you are using
an OnCalcFields event to display lookup data, the lookup table
must be created and opened first. You can easily control the creation order of objects on a form by selecting View | Creation
Order from Delphis menu.
Modifications
Youll want to make a few modifications to the DBGrids display. First, remove the OrderNo field from display. This field is
part of the master record, and is displayed in the DBEdit
object. To do this, select the Table2OrderNo object and set its
Visible property to False.
Youll also want to modify the order of the fields in the
DBGrid. You can change a columns location by dragging the
column heading for a field in the DBGrid to a new location.
As you drag the mouse, the column will move. Drop the
columns in the order you want them to appear. Probably the
most reasonable order for these columns is ItemNo, Price,
Description, PartNo, Qty, Discount, and Amount. (You can
also change the order of the fields in a DBGrid by dragging the
field names to new locations within the Fields editor window.)
You may want to make additional changes to the DBGrid to
make it look better. For example, you can increase the size of
the form, and then increase the size of the DBGrid. You can
adjust the size of the individual fields in the DBGrid by dragDelphi INFORMANT
24
DBNavigator
Figure 9 (Top):
Adjusting the
creation order to
ensure that the
OnCalcFields event
does not try to
reference Table3
before its created.
Figure 10
(Bottom): A form
making use of
calculated fields.
Figure 8: The example form has been spruced up with a Label component. The non-visual objects were placed over the DBGrid to remain
unobtrusive during design.
Notice that this code first attempts to locate the current value
displayed in the Table2PartNo field in Table3. It does this by
using the FindKey method. (Use the on-line help for more information about FindKey.)
If FindKey returns True it means that a record in Table3 has been
located and corresponds to the part number in Table2. Under this
condition the lookup fields can be assigned. The value in the
ListPrice field of Table3 is assigned to the calculated field
Table2Price. And the value in the Description field of Table3 is
assigned to the calculated field Table2Description. This completes
the lookup operation.
The last two steps in this code are used to produce the calculated field. The values displayed in Table2Price (a lookup
field) and Table2Qty are multiplied, and then divided by the
percentage represented by Table2Discount. The results of this
calculation are assigned to the calculated field Table2Amount.
(Note: This operation was performed in two steps for the sake
of clarity. The entire calculation can be performed in a single
statement.)
JULY 1995
Conclusion
Calculated fields are an important part of database programming. Using these fields you can display related data from other
tables, and perform calculations to display data that doesnt otherwise need to be stored in the database.
The demonstration projects referenced in this article are available
on the 1995 Delphi Informant Works CD located in
INFORM\95\JUL\CJ9507.
Cary Jensen is President of Jensen Data Systems, Inc., a Houston-based
database development company. He is a developer, trainer, and author
of numerous books on database software. You can reach Jensen Data
Systems at (713) 359-3311, or through CompuServe at 76307,1533.
Delphi INFORMANT
25
Informant Spotlight
Delphi / Object Pascal
By Robert Vivrette
A Question of Size
Or, Why Are Delphi Executables So Big?
veryone loves small, efficient programs. Perhaps that explains all the
hubbub. Since the release of Delphi, there has been quite a bit of discussion about the size of executable files created with Delphi. In this article well discuss some of the issues relating to executable size, and present a
detailed picture of what exactly goes into a Delphi executable.
First, lets see if we can understand the issue a little better. I created an example do-nothing
Windows application with Delphi and with Borland Pascal 7. The application presents a normal,
resizeable main window (see Figure 1). The main window (by default) has a control box in the
upper-left corner, minimize and maximize buttons in the upper-right corner, and can be resized.
There are no buttons, fields, or controls on the form. This type of application is what you get if
you choose File | New Project in Delphi and then immediately compile the result. To see how big
these resulting executable files are, take a look at Figure 2.
As you can see, Delphi applications are larger by more than a factor of 10. By using Options
| Project and selecting Delphis Optimize for size and load time option, the final executable
is reduced by about 37KB. However, both options are nowhere near the size of a Borland
Pascal 7 executable. Everyone else seemed to notice this as well. Borlands answer to this is
accurate, if a bit vague. Here is the related excerpt from one of Borlands FAQ (frequently
asked questions) sheets:
Delphis VCL is based on RTTI and exceptions. This requires a footprint
of about 120Kb for an empty application. The 200K you get has additional
debug info or is not optimized by the compiler. Note that the size of your
.EXE doesn't go to 400K for two buttons, but rather to 201K, i.e. after the
footprint each additional control just adds the usual amount of data/code
size. Additionally, you can slim your .EXEs down by checking the
Optimize For Size And Load Time checkbox on the Linker page of the
Options | Project dialog.
Thats a satisfactory answer, but lets dig a little deeper. What is Delphi
doing in that extra 140KB that has no apparent visual or functional effect
on the program? Lets uncover the truth.
Delphi INFORMANT
26
Informant Spotlight
of that compiled code in the executable file. Using information
like this, Turbo Debugger allows you to step through your programs lines of source code while its running.
Indirectly, this same information tells which lines of code actually made it into the .EXE file after linking. Remember, the
Delphi compiler performs dead code elimination, so any code
thats not referenced in a program wont be written to the executable, and hence will not be listed in the map file.
Equipped with this information, it becomes just a matter of
reviewing this map file and finding the lines of source code in the
.EXE file. This is not a pleasant job, I assure you. However, the
resulting information is very useful, and helps us gain a much
deeper appreciation for the efforts that went into creating Delphi.
Lets examine the results. Below is a list of the units included (at
least most of them) in our sample program. Most of these units
arent represented in their entirety in the final executable, so I
have listed only those features that are. Again, this is because
Borlands smart linker removes code that isnt referenced. As a
result, the information below provides a summary of all inherited behavior a Delphi developer receives with each and every
application created all in only 154KB!
Length
Name
0001:0002
0001:0069
0001:0126
0001:0C00
0001:1164
0002:0002
0002:0157
0002:01A0
0003:0002
0004:0002
0005:0002
0006:0002
0007:0002
0007:2244
0008:0000
0067H
00BDH
0ADAH
0564H
0075H
0155H
0049H
22CAH
5E7FH
6971H
76A1H
5075H
2242H
104EH
0E34H
Project1
Unit1
Printers
TypInfo
WinProcs
Dialogs
Buttons
Menus
Graphics
Controls
Forms
Classes
SysUtils
System
DATA
Class
CODE
CODE
CODE
CODE
CODE
CODE
CODE
CODE
CODE
CODE
CODE
CODE
CODE
CODE
DATA
Figure 1 (Top): Whats going on? The executable file for a basic,
do-nothing form can consume a whopping 191KB of disk space.
Figure 2 (Middle): Here are the executable sizes for the form shown
in Figure 1, created with Delphi, Delphi (optimized), and Borland
Pascal 7. Figure 3 (Bottom): A portion of the map file created by
Delphi by selecting the Detailed option in the Map file area. Among
other things, this file is useful for discovering just what Delphi includes
in the executable files it produces.
tells Delphi to create a text file that provides additional information about the content of an .EXE file. Named Project1.Map by
default, a portion of this file is shown in Figure 3.
The information provided in this map file is similar to what
Delphi includes in an .EXE file for use by an external debugger
(if you have Include TDW debug info checked on the Linker
page). This debug information identifies each source code file
and maps the line numbers used with the corresponding address
JULY 1995
As its name implies, the Forms unit is responsible for all the
code necessary for creating and managing Forms. Since we do
have a form, it should come as no surprise that this unit gets
linked into our sample program. However, theres a lot more
here than meets the eye.
CTL3D Support The CTL3D and CTL3DV2 dynamic link
libraries (DLLs) provide a 3-D look to dialog boxes and window
controls. These DLLs essentially sub-class existing controls to provide them with an elegant 3-D look. You will really only see a difference in the form if you change its BorderStyle property to
bsDialog. However, it also serves as the default behavior through
the ParentCTL3D property of any controls placed on it.
Auto Scrolling Client Area Though not immediately apparent,
our simple main form example has the ability to automatically scroll
its contents in case the window shrinks below a certain size. To test
this, place a button near the lower-right corner of a form and run
the program. When the form (and button) appears, resize the window so it partially obscures the button. Scroll bars will appear automatically to provide a way of scrolling the button back into view.
Default Windows Message Handling This is an essential,
low-level part of any Windows program. By providing default
message handling, Delphi greatly minimizes development time.
For C or C++ Windows programs, the programmer is generally
responsible for writing and maintaining the code necessary to
handle the flurry of message traffic for the application. For
Delphi however, we only need to generate events and write handlers for those events. The maintenance of the Windows message queues is handled for us.
Delphi INFORMANT
27
Informant Spotlight
Form Recreation A very useful capability, it allows us to change
the basic class of a form (such as changing a normal resizeable window to a dialog box). Normally we would have to jump through a
number of hoops to achieve this, but Delphi manages it by automatically recreating the form for us. To see this behavior, create an
OnClick event for the form and insert the following code:
if BorderStyle = bsSizeable then
BorderStyle := bsDialog
else
BorderStyle := bsSizeable;
28
Informant Spotlight
Mouse Detection This adds basic mouse detection for controls. Virtually any control can now respond to mouse clicks and
mouse movement.
The Graphics unit automatically manages a wide range of graphics chores for us. The foremost of these is the encapsulation of
the Windows device contexts into the Canvas property. The
Graphics unit also provides the support outlined below.
Loading Graphics from a Resource File This allows the program to extract graphics data (icons, bitmaps, etc.) from an
embedded resource file and incorporate this data into object
properties (such as a Picture or Glyph).
Pens and Brushes Need a green pen three pixels wide? How
about a hatched brush with a clear border? Rather than create
these, they are encapsulated in the Pen and Brush objects.
User Events Perhaps one of the more important capabilities, this support allows us to connect custom procedures to
controls. We can attach code to a wide range of events such as
Mouse Click/Movement events, Drag/Drop events, and
Keyboard events.
Other Support Our sample application also provides support
for Short and Long Hints, Fonts, Colors, Palettes, and CTL3D.
29
Informant Spotlight
programs. I recently had to add my own metafile support to a
Borland Pascal 7 program and it wasnt a pleasant experience.
The metafile format is an integral part of the Delphi graphics
system; we can load, save, and display metafiles with no more
effort than that required for simple bitmaps.
Clipboard Support This allows Delphi to easily move
graphics data (in many supported formats) to and from the
Windows Clipboard.
Handle Caching Delphis graphics system can cache various
graphic objects such as pens, brushes, fonts, etc. By caching
these objects, the program can quickly flip between various
objects without having to recreate them each time. This is entirely invisible to the programmer.
Management of Stock Objects This adds some minor support for the various stock graphic objects that Windows has
available (such as a black pen, or a white brush).
User Event Handling These events are the hooks we can
put in our code to extend an objects basic behavior. For example, events allow us to have a graphic respond to the mouse passing over it, or to have a bitmap know when it has changed.
Conclusion
As you can see, there is quite a bit of inherited behavior in Delphis
executables. Equipped with this knowledge, 154KB of inherited
code doesnt seem like much does it? Somewhere down the line Id
like to see extensions to the compiler options that would allow programmers to tailor this inherited behavior to suit their needs
perhaps a series of check boxes disabling support for certain features, such as MetaFiles or PopupMenus, for example.
The developers of Delphi have gone to great lengths to provide
a rich and powerful programming environment for all of us. By
encapsulating complex Windows programming concepts into
easy-to-use properties and methods, the Delphi environment
saves programmers from having to deal with the drudgery of
programming. Instead, it allows us to concentrate on the creative and innovative aspects of software development.
Robert Vivrette is a contract programmer for a major utility company. He has worked
as a game designer and computer consultant, and has experience in a number of programming languages. He can be reached on CompuServe at 76416,1373.
Delphi INFORMANT
30
OP Tech
Delphi / Object Pascal / Windows API
By Bill Todd
To make editing the text in a memo control component easier, the TMemo and TDBMemo controls treat the text as an array of Pascal strings that you can access via the memos Lines property.
Each element in the string array corresponds to one line in the memo. This enables you to manipulate the text in a memo using all the Object Pascal run-time library procedures and functions for
Pascal strings.
Access to the contents of the Windows multi-line edit control as an array of strings is provided by
the TStrings class. TStrings includes the methods listed in Figure 1 that let you manipulate the text
in a memo.
The TStrings class also includes a Count property that tells you the number of lines the memo contains. Count is zero-based, so the statement:
Panel1.Caption := Memo1.Lines[0];
Delphi INFORMANT
31
OP Tech
Function
Method
Add
Clear
Delete
Exchange
IndexOf
Insert
Move
The first parameter in the call to SendMessage is the memo components window handle. This identifies the component the message will be sent to. The second is the constant that identifies the
message to send, EM_LINEFROMCHAR in this case. The third parameter is the offset from the beginning of the memo of the character whose line number you want to find. In this case you want
to find the line that contains the cursor. The memos SelStart
property is what you need, since it gives the cursor position as an
offset from the beginning of the memo.
The second Windows message you need is EM_LINEINDEX.
Sending the EM_LINEINDEX message to a memo returns the position of the first character of the line you specify as an offset from
the beginning of the memo. For example:
LineStart := SendMessage(Handle, EM_LINEINDEX, 3, 0);
returns the offset of the first character of the fourth line of the
memo. The first two parameters in the SendMessage call are the
same as before. The third parameter is the number of the line
whose offset you want to find.
JULY 1995
Delphi INFORMANT
32
OP Tech
unit Memos;
interface
uses WinProcs, SysUtils, StdCtrls, Dialogs, Messages;
{ Get the line number and column number the cursor
is positioned at in the memo. }
procedure GetMemoLineCol(Memo: TCustomMemo;
var MemoLine, MemoCol : Integer);
{ Set the cursor position in a memo to the specified
line and column. }
procedure MemoCursorTo(Memo: TCustomMemo;
MemoLine, MemoCol: Integer);
implementation
{ Get the line number and column number the cursor
is positioned at in the memo. }
procedure GetMemoLineCol(Memo: TCustomMemo;
var MemoLine, MemoCol : Integer);
begin
with Memo do
begin
{ Get the line number of the line that
contains the cursor. }
MemoLine := SendMessage(Handle, EM_LINEFROMCHAR,
SelStart, 0);
{Get the offset of the cursor in the line.}
MemoCol := SelStart - SendMessage(Handle,
EM_LINEINDEX,
MemoLine, 0) + 1;
end;
end;
{ Set the cursor position in a memo to the specified
line and column. }
procedure MemoCursorTo(Memo: TCustomMemo;
MemoLine, MemoCol : Integer);
begin
Memo.SelStart := SendMessage(Memo.Handle, EM_LINEINDEX,
MemoLine, 0) + MemoCol - 1;
end;
end.
Figure 6 shows the code for the two buttons. Note the statement:
ActiveContol := Memo1;
in the SetCursorBtnClick procedure. When you change the cursor position in a Memo component, the memo must have
focus or the cursor will seem to disappear. This statement
moves focus from the button back to the memo before calling
the MemoCursorTo procedure.
Conclusion
Delphis memo components let you access the text in the memo
as an array of strings. However, they provide no way to locate or
move the cursor using the line and column position notation.
The Windows API messages EM_LINEINDEX and EM_LINETOCHAR let you convert the cursor position between line and
column, and character offset so you can easily determine or
change the cursor position.
Bill Todd is President of The Database Group, Inc., a consulting firm based near
Phoenix. He is also a member of Team Borland (supporting Paradox on CompuServe)
and a speaker at all Borland database conferences. Bill can be reached at (602) 8020178, or on CompuServe at 71333,2146.
Figure 4 (Top): The complete code for the Memos unit. Figure 5
(Bottom): The Cursor Demo application in action.
JULY 1995
Delphi INFORMANT
33
Visual Programming
Delphi
When you create a form, Delphi maintains both a unit file (with a .PAS extension), and a graphical form file (with a .DFM extension). A simple form and the corresponding unit are shown in
Figures 1 and 2. Looking at the unit file in Figure 2, youll notice that it does not include any
specifications of the properties of the form or its components, only declarations of the objects
themselves. Delphi stores the properties in a separate file, the graphical form file, which well refer
to as the .DFM file.
Figure 1: This sample form contains only one component, a TShape named Shape1.
Delphi INFORMANT
34
Visual Programming
Figure 2: This is the unit file, Demo1.PAS, that goes with the form in
Figure 1. Notice that it doesnt specify any properties of the form or the
component.
35
Visual Programming
Finally, consider the problem of creating many similar components. Copy-and-paste might be faster in the Code Editor than
in the Form Designer, or you might prefer to use the Clipboard
to paste text from another application. You could, for example,
write a Word for Windows macro that generates the appropriate
text for 100 rectangles in a 10-by-10 grid, then paste that text
into the .DFM file. You might even create a Delphi project than
writes a text file you can paste into a .DFM file!
If you do create or delete components in the .DFM file, dont
forget to update the unit file. When you use the Code Editor to
change the .DFM file, you dont have Delphi helping you keep
everything synchronized.
Also remember that the .DFM file is strictly a design object. You
will see your changes when you go back to the Form Designer,
but the .EXE file is not changed until you recompile the project.
Conclusion
Delphi does so much of the work for us that most of the time we
can forget that the .DFM file exists. But easy access to this file is
one of the many features that makes Delphi so flexible. Hopefully
these examples will have you thinking about even more ways of
beefing up your RAD repertoire through the .DFM file.
Dr. Simons is a senior systems analyst at Jensen Data Systems, Inc., a Texas-based
provider of Database training, consulting, and application development. He writes
applications and does consulting in Delphi and Paradox. You can reach him through
CompuServe at 70771,75 or by calling Jensen Data Systems, Inc. at (713) 359-3311.
JULY 1995
Delphi INFORMANT
36
DOUG
HORN
First Impression
JULY 1995
Delphi INFORMANT
37
Formula One
Formula One is Visual Tools Excel-compatible spreadsheet.
As noted earlier, it forms the link between program data and
First Impression charts. However, Formula One is capable of
much more.
Formula One includes two VBX controls: a spreadsheet and
an edit box. These two objects link easily via the spreadsheet
controls EditBox property. Such thoughtful integration is
found throughout the suite. Formula One spreadsheets can be
modified by the end-user as any Excel-type worksheet file.
However, the program also includes a more useful interface.
Selecting the spreadsheet controls ApplicationDesigner property calls the Formula One application screen. From this
screen, designers can create any number of pre-configured
worksheets. Changes made to the worksheet here are reflected
in the Delphi control. Once the sheet is complete, the developer can turn off whichever editing rights she chooses to protect the finished sheet.
While Formula One is called Excel-compatible, it contains a
number of features such as in-cell editing that many
users may find preferable to Excels. On the other hand,
although Formula One provides a great many mathematical
and financial functions, it does not include Excels more
advanced add-in libraries or Solver capabilities. As a spreadsheet component, however, it has all the features most developers would probably use.
JULY 1995
VisualSpeller
VisualSpeller is a 100,000-word spell checker VBX that functions seamlessly with VisualWriter components. VisualSpeller
also works independently in any Delphi application.
Visual Speller is excellent. It goes far beyond the capabilities of
the spell checkers included with most Windows applications
not only does it allow multiple dictionaries and custom dictio-
38
A Delphi application
linking a Formula One
spreadsheet to a First
Impression two-dimensional, open-hi-lowclose chart, with an
image and gradient-fill
background.
naries, but also includes options available in few other applications. For example, one of the more useful options is Allow
Joined Words. As Madison Avenue and Silicon Valley continue
to force words such as OpenDoc and VisualSpeller on us, its
nice to finally have a spell checker that recognizes them.
Another nice VisualSpeller feature is the ability to tell the component how to search for suggestions. There are eight techniques
available, ranging from simple capitalization to exchanges, insertions, and deletions. In this vein, VisualSpeller also allows users
to provide suggestions even for properly spelled words. This
method, while tedious, is the best way to halt errors, such as
form for from (which normal spell checkers never catch).
Conclusion
Developers Visual Suite Deal from Visual Tools is exceptional. It
takes custom applications to a new level. Even the most mundane of the included tools is worth the price of the package.
Getting Formula One and First Impression at this price almost
makes the buyer feel guilty.
ImageStream will be sorely missed by many developers, but even
without it, the Developers Suite Deal is well worth the money. It
brings much needed functionality to Delphi applications with
minimal programming. Buy this package and use it. The competition certainly will.
Douglas Horn is a freelance writer and computer consultant in
Seattle, WA. He specializes in multilingual applications, particularly
those using Japanese and other Asian languages. He can be reached
via CompuServe at 71242,2371.
Delphi INFORMANT
39
Te xt F i l e
Instant Will Score with Windows Programmers
Despite the fact that it was
the first Delphi-specific book
on the market, Instant Delphi
Programming by Dave Jewell
(Wrox Press Inc.) does not
suffer from beta screen shots
and the editorial gaffes typical
of a quick-to-market effort.
Instead, Instant is an excellent
introduction to Delphi programming for an experienced
developer. (Conversely, Instant
would be a poor choice for a
beginning programmer; it
assumes a good deal of programming knowledge.)
Jewell is obviously very comfortable with Delphi and
Windows programming, and
liberally peppers Instant with
Windows programming savvy.
Its full of unexpected treats,
such as a section of tips for
keeping your Object Pascal
code ready for a port to the
32-bit Win95. This will be
especially appealing to developers coming from a
Windows programming background who are curious about
what Delphi is doing behind
the scenes as it interacts with
Microsoft Windows.
The coding examples are easy
to follow and Jewell breaks
them up with small excursions
into related tidbits of information. He carries this off very
well and succeeds at spicing up
the material rather than going
off on tangents. For example,
immediately after a demonstration of creating a graduated
blue backdrop la Windows
Help (itself, a nifty trick), Jewell
JULY 1995
40
TextFile
Instant
Example
41