Delphi Informant 95 2001
Delphi Informant 95 2001
Delphi Informant 95 2001
Sharing
Components
Techniques for Delphi 1.0 and 2.0
Working in Streams
(without Getting Wet)
Using Streams to Read and Write Memo Field Data
BlobStream1.Write(SourceList.GetText^,
As you can see, MemoToList takes three parameters. The first StrLen(SourceList.GetText));
is a TTable that should be attached to the table and posi-
tioned on the record you want to read. The second is a That’s it. You now have the code to read and write text to
String containing the field name from the table to read, and and from memo fields in a database. For convenience, I
the third is the TStringList that will contain the text read placed the MemoToList and ListToMemo procedures in a unit
back from the memo field. In the Create constructor, we use called MemoList. When I need their functionality in a Delphi
the TMemoField to inform the compiler that SourceField is form, I include the MemoList unit in the form’s uses clause.
actually a memo field.
This article is accompanied by the sample Memo I/O appli-
ListToMemo: Writing Memo Fields to a Table cation (see Figure 2) that uses these two routines. The form
Now that we can read data from the memo field, we must be contains two TMemo components (Memo1 and Memo2), two
able to put data into it. As in the previous example, we have TButtons (one to save and the other to load a button), and a
to create a TBlobStream that references the field we want to TTable.
manipulate. This time, we’ll use bmWrite as the mode. This
will clear the field before the data is inserted. We then save
the text from the string list to the BLOb stream. The syntax
of the Write method is:
Conclusion
We have discussed getting data into and out of tables. Dana Kaufman is a Senior Consultant with Apogee Information Systems, Inc., a
However, we haven’t addressed the types of tables that can be Massachusetts-based consulting and development firm specializing exclusively in
used. The sample Memo I/O program uses a Paradox table to Delphi and Paradox applications. He is a contributing technical editor for QUE on
store the data. The ListToMemo and MemoToList methods can their Delphi 2 products. Dana can be reached at (508) 481-1400 or via the
also be used on SQL server tables. I have successfully used Internet at [email protected].
By Craig L. Jones
PQA: Part II
Practical Quality Assurance Techniques for Delphi
s every programmer knows, much can go wrong with even the simplest
A of programs. Worse yet, the number of possible problems grows geo-
metrically with the size of an application. The good news is that learning just
a little quality assurance (QA) savvy can go a long way towards heading off
disaster. There’s nothing mysterious about QA — just think of everything
that can go wrong and test for it. The trick is to eliminate the tedious aspects
by writing test drivers that automate as much of the testing as possible.
This is the second of three articles on assuring In addition, a tool kit was established for
the quality of Delphi programming projects. performing unit testing — this was probably
This series is primarily directed towards Delphi the most important of the eight.
programmers and assumes no prior knowledge
on the subject of QA. Hopefully, programmers Unit testing is a matter of focusing on the sub-
who previously gave QA little thought will dis- processes contained within a program and test-
cover some enthusiasm for applying the tech- ing each individually. To introduce the subject,
niques presented here. These techniques are a test driver was written to exercise a simple
quick to implement, easy to maintain, and function, showing how to consider four differ-
thereafter automatically reusable. ent areas of concern: path coverage, boundary
conditions, performance, and regression.
This installment will expand the QA tool kit
(introduced last month) to cover the testing The example function tested took one string
of more complicated procedures and object argument, a book title, and returned another
methods. Facilities will be added for running string that represented the title in a sortable
multiple test drivers consecutively (unattend- form. For example:
ed) and using a comparison program to
check the results against an established base- “A 3 Tier Solution”
line. [For an introduction to the QA tool kit,
see Craig Jones’ article “PQA: Part I” in the would be converted to:
March 1996 Delphi Informant.]
“THREE TIER SOLUTION, A”
A Quick Review
Last month, some general QA theory was The test driver called the function multiple
presented, along with how it applies to the times, passing it a series of different titles and
different stages of program development. checking the results with an “assertion tool.”
Eight types of testing were outlined:
1) requirements verification Once an automated test driver is written, it
2) design validation can be executed quickly at any time to ensure
3) unit that the unit (still) works properly.
4) integration
5) user acceptance Expanding and Encapsulating the Tool Kit
6) alpha Before proceeding, it would be prudent to bet-
7) beta ter organize the tool kit. For the sake of simplic-
8) gamma ity, the tools were initially presented as a collec-
Object Element Description The TVagueDate object handles storing and processing
TQA fileQALog File handle for writing the test incomplete or non-specific dates. Such an object may be
results to disk (as an ASCII .TXT found as part of a scheduling or contact management appli-
file).
cation. TVagueDate allows dates to be specified with
sTestID 8-character test ID, used as the
name of the .TXT file. unknown portions. For example, you may know when a per-
FilePath Specifies the drive and/or sub- son celebrates a birthday (i.e. month and day), but not know
directory to contain the test result that person’s year of birth. Likewise, you may know the
log files.
month and year an event occurred, but not the specific day.
UseForm Set to True if the results will be
displayed on-screen using the
TFormQALog object. Figure 4 is a simplified form that is the front-end to a
UseFile Set to True if the results will be contact management database using TVagueDate. The
written out to disk according to birthday is stored in the database using a raw byte storage
the FilePath and sTestID fields
shown above. field that is 7 bytes long (the size of the combined data
Start Procedure called to start a new fields of the TVagueDate class). The form uses a display-
test run, passing it an 8-character only calculated field to display a text representation of the
string to identify the test.
VagueDate birthday that is stored in the byte field (via
Stop Procedure called to end a test run.
TVagueDate’s AsString property). In addition, a Specify
Log Procedure called to directly record
an entry in the log. button allows the birthday to be entered or changed, via
sAssert Procedure called to assert an the Vague Date Entry dialog box (see Figure 5).
equality between two string values.
bAssert Procedure called to assert an Another database field of type Date is automatically filled
equality between two Boolean
values. with a fully-specified approximation of the birthday using the
iAssert Procedure called to assert an AsDateTime property of TVagueDate. The Birthday field is set
equality between two integer to read-only on the form so that the user is forced to properly
values.
go through the Vague Date Entry dialog box to change it.
nAssert Procedure called to assert an
equality between two floating point
values. By entering records into the database and specifying vari-
TFormQALog memoQALog Memo component to display the ous birthdays, we can put the TVagueDate object through
results of a test run.
its paces. Such manual testing, however, is tedious, incon-
btnQAOK OK button to clear the window
(hide the form). sistent, and error prone. Thus, the form also features a
SelfTest button that calls TVagueDate’s SelfTest method. The
Figure 2: Summary of the new QA tool kit that is organized button is invisible when the application is not in test mode
around two objects: TQA (based on TObject) and TFormQALog
(based on TForm). (i.e. the compiler directive token QA_Mode is not defined,
as described in our first article).
tion of stand-alone functions (see Figure 1). A better way would
be to redefine those functions as the methods of an object. For The code in Figure 6 shows how TVagueDate’s SelfTest method
one thing, this allows for defining some data fields that the newer uses the AsString and AsDateTime properties (specifically their
methods need in common. Figure 2 shows this new organization. read methods, GetString and GetDateTime). Figure 7 shows
The associated code is shown in Listing One on page 16. the on-screen test results. So far, the only real difference
between this test driver and the one in last month’s article, is
A secondary object, a general-purpose form, has been defined that there are lines of code before each Assert call that are
for displaying the results of any test run. The form simply needed to set up for the assertion check. Primarily, though, we
consists of a Memo component that displays test results, and still are only verifying a function’s returned value.
an OK button that closes it. With this generic form, the tester
no longer needs to create a specific form for the task (as in last Testing More Complicated Functions
month’s article). Also, the various Assert methods have been Let’s move on to testing some more complicated functions where
written to report their findings through the Log method. Log, the function’s result value is not the only thing affected. For
in turn, has been modified to optionally handle writing those example, let’s say that the code changes the value of an object’s
findings to a disk file in addition to, or instead of, the screen. property, the value of a global variable, or the contents of a data-
If a status flag is returned, it can certainly be tested using As before, to test this routine we’ll call it several times, setting it
bAssert, for example, which is used to test a Boolean value. As up with a series of sample data designed to exercise all the various
with sAssert, the first argument passed to bAssert is the actual combinations. We can check the Boolean result flag for an expect-
result of the function being tested, and the second argument is ed value, but that hardly tells us anything. How do we know that
the expected result (True or False). Another routine, iAssert, the routine actually adjusted anything (or properly refrained from
can be used to test functions that return an integer status flag. modifying anything)? The answer lies in using our tool kit to
make some secondary assertions. In this case, we’ll simply check
One of TVagueDate’s methods, AdjustDay, is used to check the the value of the day field directly to see if it’s correct. A portion of
combination of values currently defined by an instance of the the SelfTest method (see Figure 8) illustrates such test code.
Begin Listing Two — CM1Vague.PAS { Copy data to an external buffer. Parameter: 7-byte
{ Project: CM1 - Contact Manager Example Application buffer in which to place the data. }
Function: Object to represent vaguely specified dates procedure TVagueDate.GetData(var Buff: TVDBuff);
(e.g. "After Apr 1996") } begin
unit Cm1vague; Move(iVagueness,Buff,SizeOf(TVDBuff));
{$I UT1Incl.PAS} end;
interface
{ Load the data from an external buffer. Parameter:
uses 7-byte buffer with the source data. }
SysUtils, WinTypes, WinProcs, Messages, Classes, procedure TVagueDate.SetData(const Buff: TVDBuff);
Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, begin
UT1App, UT1QA;
Move(Buff,iVagueness,SizeOf(TVDBuff));
const end;
MonthList = 'JanFebMarAprMayJunJulAugSepOctNovDec';
{ Build a string representation of the date }
type function TVagueDate.GetString: string;
{ Holding buffer for TVagueDate data } var
TVDBuff = array[0..6] of Byte; sVagueness,sMonth,sDay,sYear: string;
TVagueness = (vdOn, vdAbout, vdBefore, vdAfter); begin
sVagueness := ''; { Initialize }
sMonth := '';
TVagueDate = class(TObject)
private sDay := '';
iVagueness: TVagueness; sYear := '';
iMonth,iDay,iYear: Word; case iVagueness of { Vagueness prefix }
public vdAbout: sVagueness := 'About ';
procedure InitBlank; vdBefore: sVagueness := 'Before ';
private vdAfter: sVagueness := 'After ';
function GetDT: TDateTime; end;
function GetString: string; { Month, e.g. Oct }
procedure SetDT(dtFrom: TDateTime); if (iMonth >= 1) and (iMonth <= 12) then
procedure SetMonth(iValue: Word); sMonth := copy(MonthList,iMonth*3-2,3)+' ';
procedure SetDay(iValue: Word); case iDay of { Day, e.g. 31st }
procedure SetYear(iValue: Word); 0: sDay := '';
procedure SetVagueness(iValue: TVagueness); 1,21,31: sDay := inttostr(iDay)+'st ';
public 2,22: sDay := inttostr(iDay)+'nd ';
property AsString: string read GetString; 3,23: sDay := inttostr(iDay)+'rd ';
property AsDateTime: TDateTime read GetDT write SetDT; else sDay := inttostr(iDay)+'th ';
property Month: Word read iMonth write SetMonth; end;
property Day: Word read iDay write SetDay; if iYear>0 then { Year, 4 digits }
property Year: Word read iYear write SetYear; fmtstr(sYear,'%4.4d ',[iYear]);
property Vagueness: TVagueness read iVagueness write { Combine them }
SetVagueness; result := sVagueness + sMonth + sDay + sYear;
procedure UpdateDlg; while (length(result)>0) and
procedure GetData(var Buff: TVDBuff); (copy(result,length(result),1) = ' ') do
procedure SetData(const Buff: TVDBuff); delete(result,length(result),1);
function AdjustDay: Boolean; end;
{$IFDEF QA_MODE}
procedure SelfTest; { Build a specific approximation of the date }
{$ENDIF} function TVagueDate.GetDT: TDateTime;
end; var
iM, iD, iY: Word; { Working copies }
TformVagueDate = class(TForm) iThisMonth, iThisDay, iThisYear: Word; { Today }
radioVagueness: TRadioGroup; bConverted: Boolean; { Conversion success flag }
btnOK: TButton; begin { -- Initialize -- }
boxMDY: TGroupBox; iM := iMonth;
editMonth: TEdit; iD := iDay;
editDay: TEdit; iY := iYear;
editYear: TEdit; if (iM<1) then begin { Month }
btnCancel: TButton; case iVagueness of
procedure btnOKClick(Sender: TObject); vdBefore: iM := 1;
procedure btnCancelClick(Sender: TObject); vdAfter: iM := 12;
end; else iM := 1; { Use July 1st? }
end;
var end;
formVagueDate: TformVagueDate; if (iD<1) then begin { Day }
case iVagueness of
implementation vdBefore: iD := 1;
vdAfter: iD := 31;
{$R *.DFM} else iD := 1; { Use the 15th? }
{ Initialize a blank vague date } end;
procedure TVagueDate.InitBlank; end;
begin if (iY<1) then begin { Year }
iMonth := 0; DecodeDate(AppCtrl.dtToday,iThisYear,
iDay := 0; iThisMonth,iThisDay);
iYear := 0; iY := iThisYear;
iVagueness := vdOn; end;
end; { Combine them }
bConverted := FALSE;
Sharing Components
Sharing Objects Between Forms:
Techniques for Delphi 1.0 and 2.0
Figure 2 (Top):
Figure 1: A form under construction. A menu for
Form1.
to DataSource1 and its Align property to alClient. Your form
should now resemble Figure 1.
Figure 3 (Left):
A single-record
Now double-click the MainMenu component and create two form created
main menu items, &File and &View. Add an E&xit option using the
under the &File menu, and add an &Single Record item Database Form
under the &View menu. Your menu should resemble Figure 2. Expert.
Close the Menu Designer and enter the following statement
in the OnClick event handler for the MenuItem Exit1:
Close;
On the fourth page, set the field layout to Vertical and
click Next to advance.
Then enter the following statement in the OnClick event
On the fifth page, select Left to display field labels to the
handler for SingleRecord1:
left of the fields, and then click Next.
Form2.Show;
Finally, on the sixth page, remove the check from the
Generate a main form check box. Click the Create button
Now, while we’re still working with Unit1, we’ll need to add to build the form shown in Figure 3.
Unit2 to a uses statement in Unit1 (we’ll create the second
form shortly). In the implementation section of Unit1, add Since the purpose of this demonstration is to use Table1
the following uses clause: from Form1 in Form2, remove the Table component from
Form2.
uses
Unit2; The final two steps require code to be placed in Unit2 to
assign Table1 from Form1 to the DataSet property of
The completed Unit1 is shown in Listing Three on page 23. DataSource1 on Form2. (If you’re working in Delphi 2.0,
replace these final two steps with the ones under the section
Building Form2 “Sharing Objects Between Forms in Delphi 2.0.”) Begin by
Now, we’ll create Form2 that will be a single record form in selecting Form2 in the Object Inspector and displaying the
this project. A single record form — where individual fields Events page. Then, double-click the OnCreate event proper-
are displayed using DBEdit components — is easier to create ty to display Form2’s OnCreate event handler. It already
with the Database Form Expert. contains this statement:
You’re done! Run this project and select View | Single The only limitation to the Use Unit feature in Delphi 2.0
Record to display Form2. With both forms displayed, notice is that it does not allow you to select event handlers
that as you navigate one form, the other automatically defined in another unit at design time. This is, however,
remains synchronized, with both forms always showing the much less of a common need than assigning objects to
same record (see Figure 4). This is because the forms share object properties.
the same DataSet, Form1.Table. Thus, they share a cursor.
Using Data Modules
Sharing Event Handlers In addition to a form, which can hold visual as well as non-
We’ve demonstrated that it’s possible to assign a value defined visual objects, Delphi 2.0 supports a non-visual version of a
in another unit to a component’s property. As mentioned, this form, called a data module. One of the primary uses for a
same technique can be used to assign an event handler defined data module is to hold DataSource and DataSet objects that
in another unit to the event property of an object. For exam- can be used by multiple forms. The advantage of a data mod-
ple, if you create a button named Button1 on Form2, you can ule over a regular form is that it takes fewer resources because
add the following statement to the OnCreate event handler for it does not have a visual representation.
Form2 to call the OnClick event handler for Exit1 from Form1
when Button1 is clicked: The use of a data module is demonstrated in the project
Sharedm.DPR. This project is similar to the one shown in
Button1.OnClick := Form1.Exit1Click; Figure 4, with one important exception — neither Form1 nor
Form2 contains any DataSource or DataSet components.
Instead of assigning the procedure name defined in Unit1 to the Instead, a data module was created by selecting File | New
Button’s OnClick event handler, you can alternatively assign the Data Module. This data module, named DataModule1, is
OnClick property of the MenuItem, Exit1, to this property. The shown in Figure 7.
following statement is functionally identical to the preceding one:
Both Form1 and Form2 must use the unit associated with
Button1.OnClick := Form1.Exit1.OnClick; the data module to use the components placed in the data
module. This is accomplished by selecting Form1, selecting
With this button added to Form2, clicking it causes Form1 to File | Use Unit, and then selecting the data module’s unit
close. As a result, Form2 closes as well.
Using Delphi 1.0, the objects and event handlers defined end.
with one unit can be used by objects on other forms by End Listing Three
adding a small amount of code that makes the necessary
property assignments at run time. Begin Listing Four — Unit2.PAS
unit Unit2;
With Delphi 2.0, the Use Unit dialog box enables you to interface
assign object properties at design time as well, greatly simpli-
uses
fying the process of using objects across forms. Finally, the SysUtils, WinTypes, WinProcs, Messages, Classes,
ability to define data modules — non-visual, form-like Graphics, Controls, StdCtrls, Forms, DBCtrls,
DB, DBTables, Mask, ExtCtrls;
objects — permits database developers to separate data asso-
ciations from the forms used to display the data. ∆ type
TForm2 = class(TForm)
ScrollBox: TScrollBox;
The demonstration forms referenced in this article are available Label1: TLabel;
on the Delphi Informant Works CD located in EditCustNo: TDBEdit;
Label2: TLabel;
INFORM\96\APR\DI9604CJ. EditCompany: TDBEdit;
Label3: TLabel;
EditAddr: TDBEdit;
Label4: TLabel;
Cary Jensen is President of Jensen Data Systems, Inc., a Houston-based database EditAddr2: TDBEdit;
development company. He is author of more than a dozen books, including the Label5: TLabel;
EditCity: TDBEdit;
upcoming Delphi in Depth [Osborne, MacGraw-Hill, 1996]. He is also Contributing Label6: TLabel;
Editor of Paradox Informant and Delphi Informant, and this year’s Chairperson of the EditState: TDBEdit;
Paradox Advisory Board for the upcoming Borland Developers Conference. You can Label7: TLabel;
reach Jensen Data Systems at (713) 359-3311, or on CompuServe at 76307,1533. EditZip: TDBEdit;
Label8: TLabel;
EditCountry: TDBEdit;
Label9: TLabel;
Begin Listing Three — Unit1.PAS EditPhone: TDBEdit;
unit Unit1;
Label10: TLabel;
EditFAX: TDBEdit;
interface
Label11: TLabel;
EditTaxRate: TDBEdit;
uses
Label12: TLabel;
SysUtils, WinTypes, WinProcs, Messages, Classes,
EditContact: TDBEdit;
Graphics, Controls, Forms, Dialogs, Grids, DBGrids, DB;
Label13: TLabel;
EditLastInvoiceDate: TDBEdit;
type
DBNavigator: TDBNavigator;
TForm1 = class(TForm)
Panel1: TPanel;
DBGrid1: TDBGrid;
DataSource1: TDataSource;
DataSource1: TDataSource;
Panel2: TPanel;
Table1: TTable;
Table1: TTable;
MainMenu1: TMainMenu;
procedure FormCreate(Sender: TObject);
File1: TMenuItem;
private
Exit1: TMenuItem;
{ Private declarations }
View1: TMenuItem;
public
SingleRecord1: TMenuItem;
{ Public declarations }
procedure Exit1Click(Sender: TObject);
end;
procedure SingleRecord1Click(Sender: TObject);
private
var
{ Private declarations }
Form2: TForm2;
public
{ Public declarations }
implementation
end;
{$R *.DFM}
var
Form1: TForm1;
uses
Unit1;
implementation
procedure TForm2.FormCreate(Sender: TObject);
{$R *.DFM}
begin
DataSource1.DataSet := Form1.Table1;
uses
end;
Unit2;
end.
procedure TForm1.Exit1Click(Sender: TObject);
begin
End Listing Four
Close;
end;
By Bill Todd
interface
Then, when the transition to a SQL server is
const
made, you can modify the constant without hav-
{ Tables }
tnCustomer = 'customer.db'; ing to revisit every reference to the primary key.
tnOrders = 'orders.db';
tnInvoice = 'invoice.db';
Column names. Column names in Paradox
tnItems = 'items.db';
{ Indices } tables can include spaces and punctuation
inCustCustNo = ''; characters. RDBMS column names are typi-
inCustName = 'LastFirst';
cally restricted to letters, numbers, and the
inOrderOrderNo = '' ;
inOrderCustNo = 'CustNo'; underscore character.
implementation
The only way to avoid column name incom-
end. patibilities is to use column names in your
Figure 1: The Globals unit.
local tables that will be acceptable to most
Figure 2: Using the String list editor to set the path parameter Figure 3: A Windows 95 shortcut — starting PHONE.EXE and
at design time. passing the PhoneNet alias on the command line.
For example, if you have a production database and a test Figure 3 shows an example of a Windows 95 shortcut that
database, you can provide two icons on the Windows desk- starts an application named PHONE.EXE and passes an alias
top. One will include the name of the production database name, PhoneNet, on the command line.
on the command line, and the other will include the name
of the test database. Conclusion
You should write your Delphi programs to make conversion to
Another situation where this technique is useful is with a a client/server architecture as easy as possible. It takes little effort
traveler who uses a notebook computer while on the road. and can save many hours of conversion and debugging time
By passing the database alias or path on the command later on. Even if you do not convert the program to client/serv-
line, you enable the user to employ a copy of the database er, using a Globals unit (instead of sprinkling literal values
on the local hard drive while traveling, and the production throughout your code), will pay dividends in both readability
database on the network when he or she is in the office. and maintainability. In addition, using a Database component
To get a path from the command line, use the following code in enables you to change your data’s location at run time. ∆
your main form’s OnCreate handler.
with Database1 do
begin Bill Todd is President of The Database Group, Inc., a Phoenix area consulting and
Connected := False; development company. He is co-author of Delphi: A Developer’s Guide [M&T
Params.Clear; Books, 1995], Creating Paradox for Windows Applications [New Riders
Params.Add('Path=' + ParamStr(1));
Connected := True;
Publishing, 1994], and Paradox for Windows Power Programming; Technical
end;
Editor of Paradox Informant; a member of Team Borland; and a speaker at every
Borland database conference. He can be reached at (602) 802-0178, or on
CompuServe at 71333,2146.
To pass an alias name on the command line, change the code
as follows:
with Database1 do
begin
Connected := False;
AliasName := ParamStr(1);
Connected := True;
end;
This month, however, we turn the tables. It’s time to voice your opinion and this is the
result: The First Annual Delphi Informant Reader’s Choice awards.
We asked you to pick your favorites from nearly 100 products in 12 categories. And you
responded, sending ballots by fax, e-mail, the World Wide Web, and even snail mail. As
expected, some categories were highly competitive, with winners determined by few
votes. In others, precedents have been set by establishing clear leaders in the Delphi add-
on market.
The Reader’s Choice Awards proved to be highly competitive. In fact, the vote was so close
for Product of the Year that we decided to give the second place finisher — Borland’s
InterBase server — an Honorable Mention award.
VCL Oracle
VisualPROs — 4%
Borland’s InterBase 4.0 Workgroup Server has had a great
Light Lib Images VCL — 5%
Other — 4% year. In its biggest sale ever, Borland closed a deal with the US
Power Controls — 5% Army to use InterBase in its Advanced Field Artillery Tactical
InfoPower 47% Crystal
Data System (AFATDS). The Army selected InterBase because
7%
Reports VCL of its platform-independence, robustness, and other unique
12%
16% features. For more information about InterBase, see the side-
bar “InterBase Emerges from the Shadows” on page 30.
Light Lib
Business VCL
Best Database CASE Tool
Orpheus
There’s no doubt about your choice as Best Database CASE
Blow the dust off your August 1995 DI for a detailed look Tool. Half of you agreed that Asymetrix Corporation’s
at InfoPower’s unique features. In his review, Joseph Fung InfoModeler is the best way to automate database creation
called InfoPower’s components “complete and well thought and maintenance. Asymetrix shipped InfoModeler 1.5 in
out, significantly enhancing the development process.” It October of 1994, adding connectivity to several databases
appears you agree. In one of the more populated categories, including Informix, Ingres, Sybase, and Visual dBASE.
Woll2Woll carried a whopping 47 percent of the votes. Version 1.5 also incorporates model import/export, and two
new tools: Fact Assistant and Verbalizer.
In the second tier of popularity, TurboPower’s Orpheus and
DFL Software’s Light Lib Business VCL, took second and Database CASE Tool
third places respectively.
Chen ER-Modeler — 7%
Best VBX
Given the variety of VBXes, it’s no surprise the competi- 16% System
tion was stiff in this category. Yet when all was said and InfoModeler 50% Architect
done, Visual Components’ Visual Developers Tool Suite 27%
edged out its opponents with 30 percent of the total votes.
VBX S-Designor
GigaSoft ProEssentials — 4%
ImageBASIC S-Designor from SDP Technologies came in second place,
for Delphi — 5% followed by Popkin Software’s System Architect, and Chen &
Visual Associates’ Chen ER-Modeler.
30% 15% Media
Developers Developer 2.0
Tool Suite Best Installation Software
20%
26% To the finish, the Best Installation Software category was one
of the tightest races. Three products dominated the category:
ImageKnife/VBX
InstallSHIELD from InstallShield Corporation finished first,
Communications Library 3.0
InfoPower Selected as Product of the Year Still expanding, Softbite recently added accounting services.
Regarding their 1996 agenda, Softbite founder Kevin Smith
Delphi Informant readers selected Woll2Woll Software’s
InfoPower as their favorite Delphi add-in product by casting said, “We’ll increase support for Delphi and stay on the
more votes for it than any other product in any category. developer side of Paradox 7.”
Woll2Woll reviewed a beta version of Delphi in late 1994 Best Reporting Tool
with hopes of creating a Delphi tool. They wanted to help All questions have been answered about the Best
database developers familiar with products like Paradox for
Windows feel more productive with Delphi. In their market Reporting Tool — Crystal Reports from Crystal
research, Woll2Woll found database developers didn’t want to Services/Seagate Software is the clear winner. The recently
compromise the quickness or functionality of their applica- released version, Crystal Reports 4.5, includes a Delphi
tions, but wanted the speed of true .EXE files. VCL, a full-featured OLE (OCX) control, a 32-bit Report
Engine DLL, the ability to drill down on graphs, new
The InfoPower suite includes 15 visual and non-visual data-
aware Delphi components, designed for 16- and 32-bit Lotus Notes and Excel 5.0 export formats, and the ability
Windows database applications. The suite features an to save report options with the report.
enhanced data-aware grid that supports fixed columns, check-
boxes, embedded multi-field lookup combo boxes, dynamic Reporting Tools
cell coloring, multi-line titles, memo display, and editing. It
also includes customizable dialog boxes for locating field val- Quick Reports — 4%
ues in a table, query, or query-by-example (QBE), where R&R Report Writer — 5%
searches can be case-sensitive, pattern based, or incremental.
InfoPower also provides database lookup facilities and customiz- Crystal 44% 16%
able dialog boxes that can be attached to any event. Additionally, Reports
InfoPower includes a QBE component that operates just as its ReportPrinter
31%
Paradox counterpart, as well as a Table component that provides
data filtering capabilities.
ReportSmith
Woll2Woll is currently working on InfoPower 2.0 which they
expect to ship in May, 1996. New functionality will include These are some of the features that keep Crystal Reports a
field validation, visual filtering and querying, and the ability
to print the contents of a grid. The new version will also fea- leader in the reporting tool market, and the winner of the
ture enhancements to existing InfoPower components, such as Best Reporting Tool with 44 percent of the votes. Borland
multi-record selection and bitmap support in the grid compo- International’s ReportSmith finished a healthy second, and
nent, and other developer-requested features and functions. Nevrona Designs’ ReportPrinter took third place.
Delphi Add-In
Codewright — 3% InterBase Emerges from the Shadows
Other — 8% VB2D Translator — 4%
InterBase’s strong showing in the Delphi Informant Reader’s
Graphics Server — 4% Choice Awards is just another example of its recent, rapid rise
Apollo 26% Conversion into prominence. Considered the “Crown Jewel” of the Ashton-
Rock-E-T Assistant — 7%
Tate acquisition, InterBase nevertheless remained obscure and
HyperTerp/Pro isolated from the rest of the Borland product line. Its bundling
16% — 7% with the phenomenally successful Delphi changed all that.
10%
15%
ezDialogs Delphi/Link for Borland has finally begun a tighter integration of InterBase with
for Delphi Multi-Edit Lotus Notes the rest of their products, propelling InterBase forward in sales
for Windows and market penetration. Delphi 2.0 Client/Server Suite is the
latest effort in Borland’s “Trojan horse” strategy to entice Delphi
Woll2Woll Software’s ezDialogs for Delphi received 16 per- developers to embrace InterBase. The new Suite includes not
cent of the votes. And in a near-tie for second place, only the new Windows 95/NT Local InterBase server, but also
American Cybernetics’ Multi-Edit for Windows took third the multi-user InterBase NT Server for prototyping and devel-
oping multi-user applications. In addition, a new Event Alerter
with 15 percent of the votes. component has been added to the VCL, allowing developers
much tighter integration to the InterBase engine.
Of the 12 categories, the race for Best Delphi Add-In was the
most crowded with over 13 products receiving votes. Other Borland isn’t content with Delphi/InterBase synergies, how-
notables included: Borland/Brainstorm Technologies’ ever, and has rumored that future versions of their Java tools,
code named “Latte,” may include InterBase or InterBase con-
Delphi/Link for Lotus Notes, HyperAct’s HyperTerp/Pro, nectivity as well. The endorsement by Borland’s InterBase of
and EarthTrek’s Conversion Assistant. the new JDBC standard by Sun Microsystems, Inc. is further
evidence that something formidable is brewing.
Best DLL
Giving it 22 percent of the votes, you named Inner Media’s With growth of InterBase sales close to 40% in 1996, and
new versions available almost every month, it looks like
DynaZip as Best DLL. Balloting was tight however, and Borland’s evolution from desktop tools provider to
MicroHelp’s Communications Library 3.0 came in a close client/server provider is succeeding.
second with 19 percent of the votes.
By Andrew J. Wozniewicz
DLLs: Part II
Completing the Example Dynamic Link Library
ast month, we introduced dynamic link libraries (DLLs) and how to cre-
L ate them using Delphi. As part of that interactive discussion, we created
the sample unit, XSTRING.PAS, to contain five string-handling functions:
FillStr, UpCaseFirstStr, LTrimStr, RTrimStr, and StripStr.
In this installment, we’ll create the last three Here’s how LTrimStr is implemented in the
functions needed to fill XSTRING.PAS, implementation section of the XString unit:
then learn to export DLLs. [If you haven’t
built XSTRING.PAS, refer to “DLLs: Part I” function LTrimStr(const S: string):
string;
in the March 1996 Delphi Informant.] var
Index, MaxIndex: Integer;
The LTrimStr Function begin
Index := 1;
Often, you’ll need to ensure that the string MaxIndex := Length(S);
you’re working with begins with a mean- while (Index <= MaxIndex) and
ingful (i.e. non-blank) character. If there (S[Index] = ' ') do
Inc(Index);
are any leading blanks, you may need to Result := Copy(S,Index,
remove them. MaxIndex-Index+1);
end;
After the index of the first non-blank character of the argu- When the loop counter Index — initialized to mark the argu-
ment string is determined, the portion of the argument ment string’s last character — indicates a position of a non-
string S containing the leading blanks is removed, and the blank character in the argument string, the loop has counted
remainder is returned as the function’s result. all trailing blanks and terminates.
The standard string function Copy is used to select only the In case the argument string consists of blanks only, the loop
meaningful, non-blank portion of the original argument condition checks if the iteration has reached the beginning of
string S, omitting the leading blanks from the function’s the string, indicated by an Index value of less than 1. This
result, as required. condition also terminates the while loop.
Here’s how LTrimStr is called in an application: After the while loop is finished, the Index variable holds the
position of the argument string’s last non-blank character.
var The Copy function is then used to extract the non-blank lead-
S1, S2, S3 : string;
begin ing portion of the string and return it to the caller as the
... function result.
S1 := LTrimStr(' Teach Yourself Delphi');
S2 := LTrimStr(' 123');
S3 := LTrimStr(' 456 '); The RTrimStr function can be used as follows:
...
end; var
S1, S2, S3: string;
begin
After the assignment statements are executed: ...
S1 contains, “Teach Yourself Delphi” S1 := RTrimStr('Teach Yourself Delphi ');
S2 contains, “123” S2 := RTrimStr('123 ');
S3 := RTrimStr(' 456 ');
S3 contains, “456 ” ...
end;
Note that LTrimStr has no effect on trailing blanks. This is
the job for the next trimming function, RTrimStr. After the assignment statements execute:
S1 contains “Teach Yourself Delphi”
The RTrimStr Function S2 contains “123”
In concept, RTrimStr is similar to LTrimStr. Instead of S3 contains “ 456”
removing the leading blanks, however, RTrimStr removes
any trailing blanks, ensuring that the resulting string is as RTrimStr removes the trailing blanks from the argument, but
short as possible. A typical use of the RTrimStr function is the leading blanks remain.
to prepare a string for conversion to a number using the
Val procedure. The StripStr Function
The last string-handling subroutine you’ll implement inside your
Enter the following declaration inside the interface section of DLLFirst library is StripStr. It removes all blanks — whether they
the XString unit: are leading, trailing, or embedded within the argument string —
and returns a result of all non-blank characters. The declaration
function RTrimStr(const S: string): string; export; of the StripStr function is similar to that of the others:
As you can see, the declaration of RTrimStr is nearly identical function StripStr(const S: string): string; export;
to that of LTrimStr. RTrimStr is implemented as follows in
the implementation section of the XString unit: Since the number of iterations is known, StripStr uses a for
loop for top performance. The loop must run through all the
function RTrimStr(const S: string) : string; characters in the argument string, removing any blanks as it
var
Index: Integer; visits the consecutive character locations. Here’s the imple-
begin mentation of the StripStr function:
Index := Length(S);
while (Index > 0) and (S[Index] = ' ') do function StripStr(const s: string): string;
Dec(Index); var
Result := Copy(S,1,Index) Index : Integer;
end; begin
Result := '';
The trick to implementing RTrimStr that makes it even sim- for Index := 1 to Length(S) do
if S[Index] <> ' ' then
pler than LTrimStr, lies in the while loop. This code deter- Result := Result + S[Index];
mines the position of the last non-blank character of the string end;
The for loop visits every character position of the argu- library DLLFirst;
Using the XString Unit The key to accessing externally implemented subroutines
And that does it! You’ve implementing the XString unit (e.g. a DLL function) is to create import units. Any appli-
which now contains five useful string-handling functions. To cation that wants to use the DLL’s services must have the
help you verify the code you entered is complete and valid, appropriate import reference in its uses clause. The
Listing Five (beginning on page 35) shows the entire unit. WinProcs unit, for example, imports the subroutines
Comments were added to make the code more readable and defined in three Windows DLLs that comprise the
understandable. Windows API: USER, KERNEL, and GDI. To use subrou-
tines implemented in these DLLs, you must include
Each of the functions implemented in the unit’s imple- WinProcs in the uses clause of your application.
mentation section — FillStr, UpCaseFirstStr, LTrimStr,
RTrimStr, and StripStr — is listed in the interface section. In Part III of this series, we’ll create a corresponding import unit
In turn, each function is made exportable by placing the for the custom DLLFirst library. We’ll also discuss the external
export directive after the function declaration. This makes directive, interface with DLLFirst, import subroutines by ordinal
it possible to include the subroutines in an exports clause number, and build a sample application. See you then. ∆
and make them available outside the DLL.
This article was adapted from material for Teach Yourself
With the functions implemented, now it’s time to com- Delphi in 21 Days [SAMS, 1995], by Andrew Wozniewicz.
plete the DLL project by actually exporting the functions
so they are visible and accessible outside DLLFIRST.DLL.
interface
function FillStr(C : Char; N : Byte): string; export;
{ Returns a string with N characters of value C }
function UpCaseFirstStr(const s: string): string; export;
{ Capitalizes the first letter of every word }
function LTrimStr(const S: string) : string; export;
{ Trims the leading blanks }
function RTrimStr(const S: string) : string; export;
{ Trims the trailing blanks }
function StripStr(const s: string): string; export;
{ Strips all blanks }
implementation
end.
End Listing Five
By Walker Lipscomb
ave you ever spent a relaxing vacation at the beach with several
H friends, only to have the idyllic experience spoiled at the end by a heat-
ed argument over who owes whom?
This article provides a software solution to this Thursday, some of the group charters a
problem. Along the way, the sample program fishing trip and others rent a ski boat. At
illustrates several examples of Delphi compo- the end of the week, there is a minor argu-
nents and features — what they are, and how ment about whether to count the kids at
they can be used to construct a user interface “full fare.” The family with the most kids
that is both attractive and intuitive to use. As points out that the kids slept four to a
Marshal McLuhan wrote, “The Medium is the room, and did not have any of the drinks
Message,” and in this case, the medium is a (or so they think). The family without
program, which by its nature, will be used children notes that they weren’t the ones
only occasionally. However, this program will who left the refrigerator door open
be indispensable on those occasions. overnight with US$150 worth of shrimp
in it (or made that mess on the carpet). A
The Scenario compromise is reached: children are to be
Four families of varying sizes rent a large beach counted at 50 percent for rent and 75 per-
house for a week. One couple pays the US$800 cent for food. Now — who owes whom?
deposit, and another pays the remaining
US$2600 due on arrival. One family picks up Who Owes Whom (WOW) is a Delphi
US$125 worth of drinks at the discount liquor program designed to simplify the job of
store. During the week, each family buys gro- allocating shared expenses and determining
ceries several times (and keeps the receipts). who should pay whom, and how much.
Information is arranged on four notebook
As the week progresses, other friends and pages. The Summary page (see Figure 1) is
relatives show up to stay a few days. On the primary mechanism for entering fami-
lies and charges. Use of the Details by
Family page (see Figure 2), the Rent
Calculations page (see Figure 3), and the
Food Calculations page is not required,
but they will help in the inevitable and
unenviable task of explaining to people
why they owe so much. You move to each
page by pressing the appropriate notebook
tab with the mouse, or by holding down
A and pressing the underlined letter.
Operation
Follow these steps to enter a simple scenario
(Rent and Food only). Operational details
are provided in the usual Windows Help file:
If the Summary Page is not displayed,
Figure 1: The Summary page. press AS to display it.
Design Considerations
By its nature, WOW is not likely to be used on a huge
database or by multiple simultaneous users. Therefore, lit-
tle consideration was given to efficiency or multi-user
access. Due to Delphi’s inherent speed, WOW does run
quite nicely with up to a couple of hundred families
entered — surely larger than any conceivable beach party
— even if you invited the Kennedys.
Implementation
TTabbedNotebook. Delphi offers myriad visual components
“out of the box” and a virtually unlimited number of add-in
VBX controls are available. Many additional Delphi compo-
nents are already on the market, and the number is growing.
The trick is to choose the right mix of components that will
maximize usability.
Figure 2 (Top): The Details by Family page.
The TabbedNotebook component (TTabbedNotebook) is the
Figure 3 (Bottom): The third notebook page is for calculating rent.
primary “motif ” for WOW. TabbedNotebooks offer the neo-
phyte user a familiar real-world visual metaphor, and can
Enter the first family’s name in the Family Name column immediately show the user what major application areas are
at the left of the grid. available — in this case the Summary, Details by Family,
Under Rent Nights/Adult, enter the number of adults multi- Rent, and Food calculations. A TabbedNotebook effectively
plied by the number of nights they should be charged rent provides sub-forms that can be used to change the type of
(e.g. 2 adults for 7 nights equals 14 nights). information shown in an area of the main screen. In the
Similarly, enter the appropriate information into Rent design phase, the TabbedNotebook will be typically sized to
Nights/Child, Food Nights/Adult, and Food Nights/Child. occupy a large portion of the form — a quarter to a half or
Using the b to move to each subsequent row, enter the more. Then, to create the multiple sub-forms, you add two
remaining families. or more Page names to the Pages property of the component.
Press the Payments button (or
AP) to display the Payments As many other visual components (e.g. labels, grids, edit
form (see Figure 4). boxes, etc.) as desired can be placed on each page. At run
For each payment, use the time, when the user or the program changes the notebook
drop-down edit boxes to select page, the components on the selected page are displayed and
the appropriate family who the components on the other pages are hidden.
made the payment, and the
correct category (Rent or Note that while “page” is the correct programming terminol-
Food). Enter the amount and Figure 4: Calculating
ogy, the Help file uses the term “sheets.” If the user thinks in
food expenses per family.
press OK or J. terms of pages, he or she will naturally expect to move from
Press the Cancel button or E to one to the other by pressing z and x. (Wouldn’t
return to the Summary page. you?) The Windows standard use for these keys is to “scroll
Note that a status box at the bottom of the screen suggests grid” to a new set of records, and we happen to need that
re-calculation. Press the Calculate button (or AC or functionality. Therefore, WOW provides the 6 / V6
9) to calculate who owes whom. keys to rotate among Notebook “sheets,” and accelerator keys
procedure TMainForm.DepartNotebookPage;
procedure TMainForm.ArriveNotebookPage;
begin
begin
case TabbedNotebook1.PageIndex of
case TabbedNotebook1.PageIndex of
0: begin
0: begin
CurrentFamilyName:=
SyncToFamily(FamilyTable,CurrentFamilyName);
FamilyTableFamilyName.AsString;
FamilyGrid.SetFocus;
if FamilyTable.State<>dsBrowse then
end;
FamilyTable.Post;
1: begin
end;
LoadFamilyNames(FamilyTabSet.Tabs);
1: begin
FamilyTabSet.TabIndex:=
CurrentFamilyName:=
FamilyTabSet.Tabs.IndexOf(CurrentFamilyName);
FamilyTable2FamilyName.AsString;
AdultNightsRentBox.setfocus;
if FamilyTable2.State<>dsBrowse then
end;
FamilyTable2.Post;
2: begin
if PaidTable.State<>dsBrowse then
SyncToFamily(RentTable,CurrentFamilyName);
PaidTable.Post;
RentGrid.SetFocus;
end;
end;
2: begin
3: begin
CurrentFamilyName:=
SyncToFamily(FoodTable,CurrentFamilyName);
RentTableFamilyName.AsString;
FoodGrid.SetFocus;
if RentTable.State<>dsBrowse then
end
RentTable.Post;
end;
end;
end;
3: begin
CurrentFamilyName:=
FoodTableFamilyName.AsString; Figure 6: The OnClick event works with the
if FoodTable.State<>dsBrowse then ArriveNotebookPage procedure to synchronize each new page
FoodTable.Post; to the same specified family.
end;
end;
end; get no respect. And, actually they are smarter than they
seem. They provide accelerator keys to set the focus to
Figure 5: The DepartNotebookPage procedure is called by the another control. These accelerator keys are indicated by
OnChange event.
putting an ampersand (&) in the Label’s Caption. Herein lies
to select a page directly. Clicking the appropriate tab with the the problem: Sometimes you may not want an ampersand in
mouse works as well, of course. the Caption to create an accelerator — you want it to be a
plain ol’ ampersand. To display ampersands normally (and
Two TTabbedNotebook events are particularly important. The not as accelerators) in a Label component, simply set its
OnChange event occurs before leaving a page, and the OnClick ShowAccelChar property to False. Or if that is unworkable,
event occurs after arriving on the new page. In WOW, each simply enter && to let Delphi know you simply want the
page displays data from the Family table, but each references a ampersand character.
different TTable component. The OnChange event calls the
procedure DepartNotebookPage (see Figure 5) which stores the TPopup. The Details by Family page provides a method of
Family Name from the “old” page, and posts any outstanding changing payments once they have been entered. Direct
edits. The OnClick event calls the ArriveNotebookPage proce- entry works fine for the date and amount, but to change a
dure that synchronizes the table on the “new” page to the same payment category by typing it in, the user would have to
family (see Figure 6). remember the exact spelling and punctuation of the desired
category.
TTabSet. On the Details By Family page, you will see a differ-
ent kind of tab, the TabSet. Unlike the TTabbedNoteBook, the A PopupMenu component provides an easy method for
TTabSet has no “built-in” functionality other than changing entering the correct category name. Since PopupMenus are
the highlighted tab. You must write code to affect any other normally accessed by right-clicking, a keyboard alternative is
controls. The TabSet could be used to change any value, for required to meet our design goals. By providing the Change
example the color of another component, or to modify the Category button, we create the AT accelerator key and
field’s value in a record. WOW uses TabSet for perhaps its document it at the same time. Similarly the Prior and Next
most obvious use — changing position within a table. buttons are included primarily to provide a keyboard
method of accessing the details of other families.
When the table referenced contains a small number of
records, the TabSet is great because the user can immediately When the PopupMenu is initiated with a right-click, it
see the available choices. For larger data, however, TabSet appears next to the mouse pointer. When you display it
would be a poor choice. Delphi provides scroll keys if all using the Popup method, you must include the screen coor-
choices cannot be seen, but scrolling through more than two dinates where the menu should appear. On the other hand,
page-widths of tabs would be inconvenient to say the least. the menu should appear near the Payments grid on the
form, and the user may move the form around on the
TLabel. Labels? Why write about labels in Delphi Informant? screen. The ClientToScreen method (see Figure 7) provides
Labels are the Rodney Dangerfield of components — they the needed translation.
Delphi Informant April 1996 38
Visual Programming
procedure TMainForm.ChangeCategoryButtonClick(
Sender: TObject);
var
PopupPoint: TPoint;
begin
PopupPoint :=
{ Get SCREEN coordinates }
FamilyDetailPanel.ClientToScreen(
Point (PaymentsPanel.Left+trunc(
{ 2/3 accross panel }
PaymentsPanel.Width*0.66),
{ Mid-way down }
PaymentsPanel.Top+trunc(PaymentsPanel.Height/2)));
CategoryPopup.Popup(PopupPoint.X,PopupPoint.Y);
end;
Conclusion
Designing a pleasing and functional user interface is a matter
of picking the right mix of components for the job and cus-
tomizing those components as needed. Delphi’s extensive set of
user-interface components and infinite capacity for expansion
can provide any component required. Delphi’s strong object
orientation and well-organized programming environment
makes customization of these components easy, and its inher-
ent speed makes the resulting program nimble and efficient. ∆
By Karl Thompson
elphi takes Windows programming to a higher plateau, for the most part
D because it hides the complexity of Windows programming. With Delphi’s
component architecture, the Object Pascal developer can easily create an
application by dropping components on a form, setting the initial values of
some properties, and adding code to selected event handlers. Delphi also
makes pointers obsolete for the great majority of applications — no small
feat for a sophisticated optimizing compiler. Undoubtedly however, there will
be times when some coders need to dig in and get into the guts of Windows.
One of Delphi’s strengths is that it allows istered Windows classes, and how memory is
programmers easy access to Windows API being allocated on the Windows global heap.
functions and data structures. The API func- Walker will also do a memory dump of any
tion calls are encapsulated in various Delphi global memory block. Both a hex and ASCII
units. (For more details, click on Delphi’s dump can be done for any block up to 64K.
Help menu and select Windows API.) Walker will also provide additional resource
statistics including:
This article will explore some of the API available GDI and user heap,
functions and data structures of the total available memory and largest avail-
ToolHelp unit. These API functions enable able contiguous memory block,
you to query Windows about available sys- the number of free items and least-
tem resources and how those resources are recently used (LRU) items,
being used. The sample Walker project (see and the number of virtual pages and free
Figure 1) demonstrates how to use the virtual pages.
ToolHelp unit to get information about the
currently loaded modules, running tasks, reg- Note that the Walker program is definitely a
work in progress. Time permitting, we’ll add
additional functionality and robustness in
future articles. And naturally, you are free to
take on the task yourself.
These are enough terms to get started. Other terms are cov-
ered in the online help included with Walker. For now, let’s First, a pointer called pOurClassEntry is initialized with a call
move on and take a look at Walker’s organization. to InitClassEntry (located in uGlobal). The pOurClassEntry is
of type pClassEntry and is a pointer to a TClassEntry struc-
Walker’s Structure ture. pOurClassEntry must be initialized before the Class
Walker’s files are organized so that if you need to use the walk can begin.
functionality of the ToolHelp unit, you’ll be able to easily
locate the appropriate initialization code. There is one global The call to InitClassEntry not only initializes a pointer but
unit, uGlobal.PAS, that initializes all the pointers and also allocates memory on the heap to hold one TClassEntry
ToolHelp data structures used by Walker. Normally you structure. One of the requirements for using many of the
would not want to write a function just to initialize a pointer, ToolHelp data structures is that the structure’s dwSize field
particularly if it’s initialized only once. In this case, however, must be initialized to the size of the structure. The Walker
it was done to clarify how the pointers are initialized. InitXXXX functions also handle this requirement. Notice
that all we have to do is call the Object Pascal function
Even if you don’t understand pointers, but do understand SizeOf while passing it the data type of the Windows API
that the functions in uGlobal return properly initialized structure.
pointers, then you can easily use the ToolHelp unit. Other
files include: the main program module, Walker; the Task If Walker were to be a truly robust application (i.e. suitable
Walker, uTasks; the Class Walker, uClasses; the Module for commercial distribution), the code should handle the
Walker, uModule; and the Global Heap Walker, uGlobalW. run-time error that’s generated if there isn’t enough memory
The unit that handles the display of the global data dump is available to allocate the structure. For more information, see
wGlobalD. [The source code is too extensive to list in its Delphi’s help topic “RTL Exceptions.” For now however,
entirety. However, it is available on diskette and for down- we’ll overlook this shortcoming so that we can proceed with
load. See end of article for details.] learning about these Windows data structures.
When you review the code in the other units, you’ll see that the
Class, Task (see Figure 3), and Module (see Figure 4) Walkers
work the same way. The Global Walker, however, is different.
So how does the component know that Delphi is running? Why Walker, and Where to Now?
There is no IsDelphiRunning function. At least one way to Walker came about because of an interest in learning more
find out if Delphi is loaded is to use the ToolHelp function, about how a developing application was working in Windows.
ModuleFindName. Walker uses this technique in the Hopefully, if you spend some time with Walker, you’ll gain
MainForm.FormShow method (see Figure 8). This is because some insight into the way that Windows works as well.
if a form that is using the trial version of Perseus is loaded
when Delphi is not running, Perseus will display a polite There is still a lot that can be done with this program. For
message saying it cannot be used outside Delphi. It will then one thing, the User and GDI Walkers need to be written.
close the application. A hex dump routine could be written for all the local
heaps, and a tool could be developed for monitoring stack
procedure TMainForm.FormShow(Sender: TObject); space. Beyond that, Walker could be made application
begin specific. That is, Walker would monitor just the resources
GDI1.Enabled := False;
User1.Enabled := False; used by an application (that is part of the purpose of the
SpeedButton4.Enabled := False; Options page). With further development, Walker could
BitBtn10.HelpContext := 0; show the developer resources that haven’t been properly
{ If Delphi isn’t running, disable Global Button
and menu item. disposed after the application terminates. If anyone makes
Note: Global Walk depends upon Perseus, a virtual any of these enhancements, please send them my way!
list box component that must be registered
separately to run when the Delphi IDE is not running. }
pOurModuleEntry := InitModuleEntry(pOurModuleEntry); References and Acknowledgements
if (ModuleFindName(pOurModuleEntry,'DELPHI') = 0) then A great deal of the material this article is based upon came from
begin the book, Undocumented Windows by Andrew Schulman, David
SpeedButton7.Enabled := False;
Global1.Enabled := False; Maxey, and Matt Pietrek [Addison-Wesley, 1992]. I would like
end; to thank Yon Barrenechea who recommended this text to me,
Dispose(pOurModuleEntry); and Peter Below for seconding the recommendation. They were
end;
right on the money with their advice. Additional information
Figure 8: The MainForm.FormShow method detects if Delphi is
came from Charles Petzold’s Programming Windows 3.1
running. [Microsoft Press, 1992].
In the case of Walker, we did not want to prevent anyone I’ve known about these books for a long time, but I never both-
from using the program when Delphi was not loaded. In ered to read them because I’m a Pascal/Delphi programmer at
the FormShow method, the code checks if Delphi is run- heart (I’ve not written a line of C in 11 years) and I was put off
ning. If not, then the Global walk button and Global menu by their C orientation. I figured that I would just buy the
item are grayed. This prevents the user from selecting Delphi or Pascal books that interested me. This was a mistake.
Global Walk and then receiving a message saying that
Perseus is not registered, and having Walker close down. Unfortunately, the topic of Windows architecture is not cov-
Naturally, you can use the same technique for any trial ered in-depth in any Pascal-specific book that I’ve encoun-
components that you want to ship. tered. So if you have even a passing interest in how Windows
works “under the hood,” pick up a copy of one or both of
If you happen to select Options | Project from the Delphi these books. And don’t worry about the C code if you don’t
menu, you’ll notice that the only form that is automatically know C. It’s not that tough to follow. ∆
created is MainForm. All other forms are created as Walker
needs them. This saves from having to allocate memory for The demonstration Walker program and trial version of Perseus are
the forms before they are used. It also allows Walker to available on the Delphi Informant Works CD located in
load faster. To see how a form is created/allocated manually INFORM\96\APR\DI9604KT.
as needed, look at the code in each of the XXXXWalker
methods in uTlHelp.
Karl Thompson is an independent Paradox and Delphi developer serving clients
The technique is very simple. Call Application.Create, passing it from New York City to Philadelphia. He has been writing applications using some
the type of form and the variable instance of the form. Call a vintage of Borland’s Pascal since 1984. He can be reached at (800) 242-9192,
procedure that will eventually call ShowModal (or Show if the or on the Internet at [email protected].
Light Lib Images This flaw aside, the VCL’s native BLOb (Binary Large
The Images VCL consists of a single component, Object) handling is very useful for storing large numbers of
TImageWindow. It contains an area for displaying images, images. TImageWindow offers three choices for BLOb com-
and a toolbar that allows users to access and manipulate those pression: none, speed-optimized, and size-optimized.
images. As Delphi developers would expect, this toolbar can
be configured or concealed altogether. The component’s inte- Unfortunately, because of the proprietary format and the
grated scrollbars and progress gauge can also be hidden. need to use Binary fields rather than Graphic fields, the Light
Lib BLObs are not compatible with existing image BLObs in
TImageWindow allows a wide range of image operations, includ- Paradox tables. TImageWindow can write and retrieve its own
ing: scanning or opening existing images; zooming; scaling; BLObs from Paradox tables, but cannot read those that have
rotating and flipping; dithering; and printing. The component been input using TDBImage components. The reverse is also
also handles conversion between any of its supported image for- true of TDBImage components and Light Lib BLOb images.
mats. The Standard Edition supports BMP, PCX, TGA, TIF, Although DFL says this will be fixed in future releases, users
and PNG. (The public domain PNG format replaces the wide- of existing databases must re-enter all their database images if
spread but proprietary GIF format.) The Professional Edition they use the Light Lib component rather than Delphi’s.
adds GIF, JPG, and Light Lib’s own BLOb format.
Light Lib Images includes a few other features that add to its
Using TImageWindow as a standard image viewing compo- usability. The first is a stripping algorithm that divides images
nent is useful, but not particularly ground-breaking. Much into 32K memory blocks before processing. The result is faster
more interesting is the fact that TImageWindow is a data- loading and display than is available in many other image view-
aware component. Using its DataSource and DataField prop- ers. Even large JPEG images load quickly. The component also
erties, developers can attach TImageWindow to a database just takes advantage of what it calls “intelligent dithering” to
as they would Delphi’s native DBImage component. improve image quality. This allows users to save hi-color images
However, by using the TImageWindow, developers can access as 16-color images with surprisingly little image degradation.
images from any of the supported formats, or from TWAIN
(Technology Without An Interesting Name — no kidding!) Although Light Lib Images is a Delphi-specific VCL, in some
compliant scanners. This is an excellent feature for databases areas it strays from some of Delphi’s established norms, to the
that require a large number of images (see Figure 1). chagrin of Delphi users. For example, the ShowToolbar prop-
erty would best be implemented as a nested property, much
The traditional method of entering images into a TDBImage as TDBNavigator’s VisibleButtons property is. Instead, double-
component uses the Windows Clipboard to cut and paste clicking the ShowToolbar property calls a dialog box with
them into a database. The TImageWindow component elimi- numerous check boxes (see Figure 2).
nates this requirement and makes image-intensive databases
easier to use and implement in Delphi. While functional, this variance from the norm does stand
out. More troublesome is the fact that the ImageName and
It does have a shortcoming, however, in that images must be ImagePath properties used to set the image (when not in data-
saved to a Binary type field rather than a Graphic type field aware mode) do not call Open File dialog boxes. Rather than
(in Paradox for Windows tables). Also, to save an image to browsing for the desired file, the developer must manually
the database, the TImageWindow’s Save button must be add the path and file name.
pressed. The more Delphi-esque way of handling this would
be to automatically update the image when the record is post- Light Lib Business
ed. In fact, this can be added programmatically by trapping As with Light Lib Images, Light Lib Business consists of a
the Post procedure and calling TImageWindow’s Save method. single data-aware component. The component,
However, the documentation does not mention this possibili- TGraphWindow, has one claim to fame — it turns live data
ty, so developers are on their own. into live graphs (see Figure 3). For example, developers can
connect a table or query to a DataSource with a DBNavigator
component. Adding a TGraphWindow component will then
allow them to scroll through their database, viewing graphs of
Figure 1: their data in real time.
Light Lib Images’
TImageWindow
component allows Delphi
TGraphWindow has many strengths, but a few weaknesses
developers as well. The graphing is surprisingly quick — fast enough
to scan and import to render complex graphs in the time it takes to do a basic
images into a screen redraw. In addition, the graphs are fully end-user
database. configurable by default. When a user clicks on a section of
the graph, the component is intelligent enough to deter-
mine what section has been selected (vertical or horizontal
axes background, bar, etc.), and open the dialog box con-
trolling that section.
Delphi Informant April 1996 47
New & Used