Delphi Informant Magazine (1995-2001)
Delphi Informant Magazine (1995-2001)
Delphi Informant Magazine (1995-2001)
Internet Delphi
Creating an SMTP E-Mail Client Program
ON THE COVER
6 Internet Delphi: Part I — Gregory Lee 38 Sights & Sounds
Adding Internet e-mail capabilities — for automated registration, O p t i m i z i n g G r a p h i c s — Peter Dove and Don Peer
program support, even database reporting — could wake up your Speed is a primary concern in graphics programming, and this ongoing
next ho-hum application. This series of articles tracks the development of example project is no exception. This month, the authors weigh several
a Delphi e-mail program, beginning with Simple Mail Transfer Protocol. optimization methods to increase speed by 50 percent.
23 Greater Delphi
I n t e r B a s e I n d e x e s — Bill Todd
InterBase uses indexes more flexibly than do most databases. Learn how
careful index creation can yield the best possible performance. REVIEWS
51 AdHocery for Delphi
27 Columns & Rows Product Review by Bill Todd
T h e P a r a d o x F i l e s : P a r t I V — Dan Ehrmann
Although the Paradox file format has extensive features for validity
checks and referential integrity, Delphi doesn’t support some, but goes
others one better. Here’s how it all shakes out.
32 DBNavigator DEPARTMENTS
C a c h e d U p d a t e s : P a r t I I I — Cary Jensen, Ph.D. 2 Delphi Tools
In previous installments, you learned the advantages of cached updates.
Now Dr Jensen explains the use of two event properties for those times 5 Newslines
when you want complete control. 53 File | New by Richard Wagner
Price: US$79
Contact: Aurorasoft, P.O. Box
DemoShield Ships ActiveX 104, Danville, CA 94526-0104
DemoShield Corp. has released
Demo-X, an ActiveX version of its Phone: (800) 987-2426 or
DemoShield product. (510) 939-3788
Demo-X contains tools for creating
drag-and-drop interactive buttons, Fax: (510) 939-3779
screen shots, hot spots, and animated E-Mail: [email protected]
transitions — without scripting.
Demo-X is available free from Web Site: http://www.aurora-
http://www.demoshield.com. soft.com
Client/Server
Professional
Delphi Comparing Delphi 3 Versions
T O O L S Features
Visual drag-and-drop RAD X X X
New Products 32-bit, optimizing, native-code compiler X X X
and Solutions Royalty-free, stand-alone EXEs and reusable DLLs X X X
Packages compiler technology for EXEs X X X
Interfaces for native COM and ActiveX support X X X
Access to Win32 API, ActiveX, multi-threading, OLE, COM, DCOM, ISAPI, NSAPI X X X
Creates multi-threaded Windows 95/NT applications X X X
Professional IDE with Editor and Debugger X X X
Object-oriented, extensible component and application architecture X X X
Object repository for storing and reusing forms, data modules, and experts X X X
Visual form inheritance and form linking X X X
Suite of Windows 95 common controls X X X
Visual component Library with over 100 drag-and-drop reusable components X X X
Create and use OLE automation controllers and servers X X X
Visual components creation for making component templates X X X
CodeTemplates Wizard X X X
CodeCompletion Wizard X X X
CodeParameter Wizard X X X
ToolTip Expression Evaluation X X X
DLL debugging X X X
Multiple database engine support X X X
Native drivers for MS Access, FoxPro, Paradox, and dBASE X X X
Data-aware components X X X
Separate business rules from application code with Data Module Objects X X X
Hidden Paths of Delphi 3
Ray Lischner Database Explorer for managing tables, aliases, and indices X X X
Informant Press Integrated reporting X X X
Delphi 1 for 16-bit Windows 3.1 applications X X X
One-step ActiveX creation for maximum reusability (100 percent compiled
high-performance ActiveX controls with no run-time redistributables) X X
One-step ActiveForm creation to Web-enable applications X X
Live graphs and charting X X
Additional 30 VCL components X X
VCL source code and printed manual X X
ODBC connectivity X X
Maintain data integrity with scalable Data Dictionary X X
Develop and test SQL applications with Local InterBase X X
Cached updates X X
Internet Solutions Pack X X
ISBN: 0-9657366-0-1 InstallShield Express X X
Price: US$39.99
(300 pages, CD-ROM)
Open Tools API X X
Phone: (800) 884-6367 or Printed documentation X X
(916) 686-6610 SQL Links native drivers, with unlimited deployment license for Oracle,
Sybase, Informix, MS SQL Server, InterBase, and DB2 X
SQL Database Explorer X
SQL Monitor X
Visual Query Builder X
Develop and test multi-user SQL applications with InterBase (4-user license) X
Decision Cube Crosstabs for multi-dimensional data analysis X
Remote DataBroker X
ConstrainBroker X
Business ObjectBroker X
WebServer X
Support for Netscape NSAPI and Microsoft ISAPI with WebBridge X
WebModules for information publishing X
WebDispatch for responding to Web client requests X
WebDeploy X
Integrated Intersolv PVCS Version Manager X
CASE Tool Expert X
Data Pump Expert X
By Gregory Lee
Over the course of the next few months, we’ll understand what TCP/IP is, or how it works,
follow the development of a fully functional to use Winsock. What you do need is the
Internet e-mail program written entirely with- WINSOCK.DLL and a basic knowledge of
in Delphi. Although we’ll focus on traditional the functions available. In the Delphi Finger
e-mail functions, you can adapt the underly- article, I touched on some of the more com-
ing code to just about any application. monly used functions. If you want to under-
stand the low-level stuff, the Finger article is
Getting Started a good place to start. To keep things simple
If you haven’t used Delphi to write an here, however, we won’t discuss the Winsock
Internet application before, you may want to interface much more.
do a little homework. A few good books are
available that focus on programming for the Pick a Protocol
Internet, but ones that help you do it in Internet e-mail is governed by two basic
Delphi are few and far between. The August protocols:
1996 issue of Delphi Informant contains an 1) The Simple Mail Transfer Protocol
article I wrote that describes a Finger pro- (SMTP) lays out the rules for sending
gram written in Delphi. Truth be told, there’s messages, from the e-mail clients’ point of
not a lot to implementing the Finger proto- view.
col itself, and the bulk of that article is really 2) The Post Office Protocol (POP) defines
about Winsock. the process for retrieving messages.
Winsock is the Windows version of the origi- In this installment, we’ll focus exclusively on
nal Berkeley sockets interface. The sockets the implementation of SMTP. POP is a little
interface was developed to provide a simple more involved, but we’ll get around to it.
API for network applications based on the You’ll be able to apply a lot of what you learn
TCP/IP network protocol. You don’t need to here when we get to POP.
The host name gets us most of the way there, but we still
need to know where on the host system we can find the
SMTP mail server. RFC 821 tells us that the mail server will
be listening for calls at port number 25. You can think of
Internet port numbers as the extension numbers in a tele-
phone system. Most established protocols have a fixed port
number where clients and servers can count on hooking up.
These fixed port assignments are often referred to as “well-
Figure 1: The ftp site ds.internic.net/rfc contains an index to all
RFC documents, as well as the documents themselves.
known ports,” and the “well-known port” for SMTP happens
to be port 25.
With the host name and port number now in place, we’re
ready to establish the connection. Unfortunately, the connec-
tion itself doesn’t happen instantaneously. Depending on the
route the connection takes and the amount of network traffic,
it may take a few seconds. Sooner or later though, we should
get a notification message indicating a successful connection.
The code in the FromSent case will look for the appropriate The Message-ID header uniquely identifies a specific piece
reply code, which, in this case is another 250. If you haven’t of e-mail. There’s no way to be positive a message identifier
bought into the importance of this state-machine scheme by we generate will be unique. However, we can greatly
now, the fact that we’re getting a second 250 reply message increase the odds by using something like our e-mail address
should close the deal. Up until this point, we could have got- and the current date and time. After all, what are the odds
ten away with using the reply number to indicate our next that somebody else is going to use our e-mail address to
move; but if we’re getting the same reply to MAIL FROM identify a piece of their e-mail? If you combine that with
and HELO, that approach would clearly be inadequate. the current time, even we would have to try pretty hard to
create another message with exactly the same identifier.
Once the 250 reply is detected, we can send the destina-
tion address. The format of this line is: After we place all the headers into the pipeline, a blank line is
sent after them to identify the end of the header section.
RCPT TO:<user@host> Now we’re ready to send the text of the message.
To finish, we use the message QUIT, and advance the State vari- for a host name that you know is a valid registered domain.
able again to QuitSent. The mail server reacts by sending us a 221
reply code, then closes the connection. All we have to do now is
close our end of the connection, and let the user know the e-mail Conclusion
has been sent. To turn this into a full-featured mail program, we’ll probably
want to log the message at this point, or at least record some-
Your Mileage May Vary where the fact that a message has been sent. And then there’s
The most common error message you’re likely to encounter with receiving messages — but we’ll get to that next time. D
the sample program is:
Valid name, no data record of requested type The files referenced in this article are available on the Delphi
Informant Works CD located in INFORM\97\JUL\DI9707GL.
This message is a direct pass-through from Winsock. This usually
indicates that one of two name lookups has failed.
You may also see this message on a LAN where the ‘HOST-
NAMES’ file has not been set up properly, or does not contain
an entry for the host name you’ve given.
After the host name has been resolved, a second name lookup
may be used to translate the service name ‘smtp’ into its associat-
ed “well-known” port number. The translation between common
Internet service names and their “well-known” port numbers is
handled by way of another (much smaller) lookup table. If this
translation fails, the Winsock error code again indicates:
By Ray Lischner
NewModuleSource Function
Begin Listing One — The Expert Unit
function NewModuleSource(UnitIdent, FormIdent,
unit Expert;
AncestorIdent: string): string;
{ Expert-creation wizard. }
Delphi calls the NewModuleSource function to obtain the
source code for the new module. The UnitIdent argument is interface
the name of the new unit, FormIdent is the name of the form,
uses Windows, Classes, SysUtils, Forms,
and AncestorIdent is the name of the ancestor form. Your Dialogs, ExptIntf, ToolIntf;
module creator must return the contents of the unit’s .PAS
file as a string. type
TExpertCreator = class(TIExpert)
public
If you’re defining a form or data module, make sure the procedure Execute; override;
unit’s source code contains a proper declaration of the form’s function GetAuthor: string; override;
function GetComment: string; override;
class, and a reference to the form description (.DFM) file. If
function GetGlyph: HICON; override;
you want to create a plain unit without a form, feel free to function GetIDString: string; override;
ignore the FormIdent and AncestorIdent arguments. function GetMenuText: string; override;
function GetName: string; override;
function GetPage: string; override;
Remember to preface the form and ancestor names with function GetState: TExpertState; override;
“T” to turn the names into type names. If your form does- function GetStyle: TExpertStyle; override;
end;
n’t use form inheritance, the ancestor name is Form. This
makes your job easier when creating the source code for the implementation
form; you can use the same code to generate the source
uses ExptDlg;
string for all situations.
resourcestring
Conclusion sAuthor = 'Tempest Software';
sComment = 'Create an expert';
Project and form experts are ideal solutions for defining sName = 'Expert Wizard';
standardized projects, units, and forms. Templates in the sPage = 'Projects';
Object Repository are static, and offer little flexibility.
procedure TExpertCreator.Execute;
Experts, on the other hand, have the full power of Delphi begin
to ask questions of the user and create customized projects, RunExpert;
units, and forms. Delphi 3 makes your job easier with pro- end;
interface
end.
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, End Listing Two
Forms, Dialogs, StdCtrls, ExtCtrls, ExptGen;
Begin Listing Three — The ExptGen Unit
type
TMainDlg = class(TForm) unit ExptGen;
ExpertStyle: TRadioGroup; { Expert-creation wizard. }
Label1: TLabel;
ExpertName: TEdit; interface
OkButton: TButton;
Button1: TButton; uses ToolIntf, ExptIntf;
procedure ExpertStyleClick(Sender: TObject);
procedure ExpertNameChange(Sender: TObject); type
private TExpertInfo = class
{ Private declarations } private
procedure EnableOkButton; fStyle: TExpertStyle;
function GetExpertInfo: TExpertInfo; fName: string;
public function GetClassName: string;
{ Public declarations } public
end; constructor Create(Style: TExpertStyle; Name: string);
property Style: TExpertStyle read fStyle;
procedure RunExpert; property Name: string read fName;
property ClsName: string read GetClassName;
implementation end;
for I := 1 to Length(Name) do
uses SysUtils, Classes, EditIntf, TypInfo; if Name[I] in ['a'..'z','A'..'Z','_','0'..'9'] then
Result := Result + Name[I];
resourcestring end;
sInvalidStyle = 'Invalid expert style, %d';
sMainLogic = ' { Fill in the main logic here. }'; { TProjectCreator class. Remember the expert info object. }
sMainIcon1 = '{ In a package, you must explicitly add the'; constructor TProjectCreator.Create(Info: TExpertInfo);
sMainIcon2 = ' MAINICON resource to the package. In a DLL,'; begin
sMainIcon3 = ' set the icon in the project options. }'; inherited Create;
sAuthor = 'Author'; fInfo := Info;
sOrganization = 'Organization'; end;
sFormPage = 'Forms';
sProjectPage = 'Projects'; const
CRLF=#13#10;
type
{ Create and return contents of project's source file. }
TProjectCreator = class(TIProjectCreator)
function TProjectCreator.NewProjectSource(
private
const ProjectName: string): string;
fInfo: TExpertInfo;
begin
public
Result := Format(
constructor Create(Info: TExpertInfo);
'library %s;' + CRLF + CRLF +
function Existing: Boolean; override;
'uses ShareMem, Forms, ExptIntf, ToolIntf;' +CRLF+CRLF+
function GetFileName: string; override;
'{$R *.RES}' + CRLF + CRLF+
function GetFileSystem: string; override;
'function InitExpert(ToolSvc: TIToolServices;' + CRLF +
function NewProjectSource(
' RegProc: TExpertRegisterProc;' + CRLF +
const ProjectName: string): string; override;
' var Terminate: TExpertTerminateProc):' + CRLF +
procedure NewDefaultModule; override; ' Boolean; stdcall;' + CRLF +
procedure NewProjectResource( 'begin' + CRLF +
Module: TIModuleInterface); override; ' Result := True; { return False for error }' + CRLF +
property Info: TExpertInfo read fInfo; ' ToolServices := ToolSvc;'+CRLF+
end; ' Application.Handle := ToolSvc.GetParentHandle;'+CRLF+
TModuleCreator = class(TIModuleCreator) ' RegProc(%s.Create)' + CRLF + 'end;' + CRLF + CRLF +
private 'exports InitExpert name ExpertEntryPoint resident;' +
fInfo: TExpertInfo; CRLF + CRLF + 'begin' + CRLF + 'end.' + CRLF
public , [ProjectName, Info.ClsName]);
constructor Create(Info: TExpertInfo); end;
function Existing: Boolean; override;
function GetAncestorName: string; override; function TProjectCreator.Existing: Boolean;
function GetFileName: string; override; begin
function GetFileSystem: string; override; Result := False { New files. }
function GetFormName: string; override; end;
function NewModuleSource(UnitIdent, FormIdent,
AncestorIdent: string): string; override; function TProjectCreator.GetFileName: string;
procedure FormCreated(Form: TIFormInterface); override; begin
property Info: TExpertInfo read fInfo; Result := '' { Default filename. }
end; end;
Free;
end;
end;
procedure TModuleCreator.FormCreated(
Form: TIFormInterface);
begin
{ This unit has no form, so Delphi should never call
this method. If you define a module creator that
defines a form, remember to free the form interface,
as shown below. }
Form.Free;
end;
end.
By Ian Davies
Automated Access
Creating Automation Clients: Part III
I n this third and final installment of the Automation series, we’ll cover the use of
Microsoft Access as an Automation Server. (In the May issue of Delphi Informant I
discussed using Word as an Automation server; last month I covered using Excel.)
Access version 2 for Windows 3.1 uses Access where Acc is declared as a Variant elsewhere
Basic as its underlying programming language, in the program. The instance data of the
but versions 7 and 8 for Windows 95 (sup- Automation object is stored in Acc, and it is
plied with the Professional Editions of Office through this that you gain access to its
95 and Office 97, respectively) use Visual underlying functionality. For example, to
Basic for Applications (VBA). Because Access open an existing database, you would call
version 2 cannot act as an Automation server, I the OpenCurrentDatabase method of the
will concentrate on the use of VBA for the Application object, as follows:
remainder of this article.
Acc.OpenCurrentDatabase(
Acc.DoCmd.OpenQuery('Sales by Category');
Acc.DoCmd.RunMacro('Macro1');
All the methods used here, together with their respective para-
Rather than a specific pre-defined object in a database,
meters, are fully documented in Access’ online Help, and need
recordsets can take as a parameter a SQL query statement.
only slight modifications to get them to work via Automation
For example:
from Delphi.
rst := dbs.OpenRecordset('Select * From Employees',
When closing the application, the instance of Access also needs dbOpenSnapshot);
to be closed. This is performed by calling the Quit method of
the Application object. As previously stated, DAO is useful for manipulating data,
rather than displaying it. The previous examples demon-
Using Access, macros that perform some local function can be strated how queries can be executed, but there was no way
stored within the database and executed remotely using for the results to be viewed. The functionality discussed
Licensing
It’s appropriate to mention the licensing implications of
using third-party products as Automation servers. In gener-
al, you have no rights to distribute any part of the
Automation server with your application unless permission
Figure 2: A demonstration form that uses DAO to access data. is obtained from the author of the server software (which
will typically involve a licensing fee). However, if you used
procedure TQueryForm.FillStringGrid(Data: Variant); Visual Basic or Visual C++ (or another similar Microsoft
var development tool) as the development language, you have a
NumRows, NumColumns, l, M: Integer;
begin
royalty-free license to distribute the Microsoft JET database
{ Move to the last record in the recordset to ensure the engine and its associated DAO with your application.
RecordCount method references all the records. }
Data.MoveLast;
Data.MoveFirst; Unfortunately, this doesn’t extend to developers using non-
Microsoft tools, such as Delphi. If you know your clients
{ Retrieve the number of fields and records, then have the rights to use a product that can be used as an
set the size of the string grid. }
NumRows := Data.RecordCount; Automation server, you are permitted to make full use of its
NumColumns := Data.Fields.Count; capabilities, provided the number of system users doesn’t
exceed the number of licenses of the Automation server.
StringGrid1.RowCount := NumRows + 1;
StringGrid1.ColCount := NumColumns; Different license agreements have different restrictions, so
it’s always wise to check with the author of the Automation
{ Add Captions to first row of string grid. }
for l := 0 to NumColumns - 1 do
server before implementing any system based on it.
StringGrid1.Cells[l, 0] := Data.Fields[l].Name;
Conclusion
{ Cycle through each cell in the recordset placing its
value in the appropriate place in the StringGrid. }
Access (and its associated file format) is an extremely popu-
for l := 0 to NumRows - 1 do begin lar database management system, largely because it’s part of
for M := 0 to NumColumns - 1 do the Microsoft Office Professional suite. Using Automation is
if Data.Fields[M].Value <> Null then
StringGrid1.Cells[M, l + 1] := Data.Fields[M].Value
only one way to get at data in its databases; various third-
else party add-ons are available for Delphi that offer native
StringGrid1.Cells[M, l + 1] := ''; access to the data without the overhead of using an
{ Move to the next record in the recordset. }
Data.MoveNext; Automation server. However, Automation is the ideal solu-
end; { for } tion if you’re interested in manipulating systems that exist in
end; Access that include more than a database.
Figure 3: A function to display the contents of a dataset in a
string grid. Referring generally to office suites, you can choose the com-
ponent of the suite that best suits a particular sub-task and
here has been implemented in the example shown in Figure 2. call (what are likely to be) pre-defined functions to produce
It includes the two queries already mentioned, plus a func- an extremely rapid solution. For example, Excel provides
tion that returns the results and displays them in a string many complex functions that would be pointless and diffi-
grid (see Figure 3), a facility that performs an indexed cult to recreate in Delphi. Using Automation, they are only
search on a particular table, and an example of inserting a a function call away, yet, at all times, control is maintained
new record with some sample data into a database table. A by your application. An ideal application of Automation is if
more complex example using DAO would involve having your client wants to maintain control over the content of,
multiple databases and multiple recordsets within those for example, printed reports, to make changes at will, yet
databases open at once, possibly stored in a Variant array. still be able to access them via a program pre-written in
Delphi (this was described in detail in Part I of this series).
If you’ve used Delphi’s standard facilities for accessing This removes the need to rebuild reports using a specialized
tables and queries, using DAO will be reasonably familiar reporting tool for what could only be a minor amendment.
to you. To add a record to a database, for example, you Furthermore, if implementing a new system, the creation of
would call the Add method of the recordset, set the values the reports (or conversion of existing ones) could be carried
Ian Davies is a developer of 16- and 32-bit applications for the Inland Revenue
in the UK. He began Windows programming using Visual Basic about four years
ago, but has seen the light and is now a devout Delphi addict. Current interests
include Internet and intranet development, inter-application communication, and
sometimes a combination of the two. Ian can be contacted via e-mail at
[email protected].
By Bill Todd
InterBase Indexes
Inside InterBase: Part II
I n Part I of this series (presented in last month’s issue), we explored creating and
using InterBase triggers and generators. In this installment, we continue looking
inside InterBase, focusing on the use of its indexes.
Additionally, you’ll get better performance using single-col- DROP INDEX ITEMX
umn indexes if a query’s WHERE clause includes the OR
operator to connect selection criteria on two columns. This You can also temporarily deactivate an index using the
is because InterBase indexes are used for moving through ALTER INDEX command. For example, these commands
records in order, and as bitmaps. In an OR operation, deactivate the ITEMX index, then reactivate it:
InterBase will use single-column indexes to evaluate the
selection conditions, then combine them to retrieve the ALTER INDEX ITEMX INACTIVE
ALTER INDEX ITEMX ACTIVE
required rows. (Yes, bitmapped indexes existed long before
FoxPro started using them.)
Setting the index to ACTIVE rebuilds the index; on a large
table, this may take some time. One case where you’ll want
The database page size also plays a role in index perfor-
to deactivate indexes is when importing a large number of
mance. A larger page size means each index page
holds more entries. That, in turn, means fewer Item Definition
pages must be read from disk to search the index.
Depth Number of levels in the index tree.
The page size also affects the depth of the index
leaf buckets Number of leaf pages in the index.
tree. If an index is more than four levels deep,
nodes Total number of pages in the index.
consider increasing the page size. If the index
Average data length Average length of each index entry in bytes.
depth on data that changes frequently is less than
total dup Total number of rows in the index with
three levels, consider decreasing the page size. duplicate values.
However, don’t decrease the page size to the point max dup Number of rows for the value that has the
that one record will not fit on a page. most duplicate entries in the index.
Fill distribution Histogram showing the number of pages in
To determine the number of levels in the index, each of the percent fill ranges.
use the InterBase Server Manager. Select Tasks | Figure 2: Explaining the numbers in the Database Statistics window shown
Database Statistics from the menu to open the in Figure 1.
Selectivity
For each index, InterBase also generates a statistic referred to as
selectivity. Selectivity indicates the number of unique values in
the index, in relation to the number of rows in the table. The Figure 3: The Basic ISQL Set Options dialog box.
query optimizer uses this number to determine if an index
should be used to process a particular query, or if scanning the
table will be faster. Adding and/or deleting large numbers of
records could change the index’s selectivity.
find a case where you can create a better plan than the optimiz-
er. Second, one of the great benefits of dynamic query optimiza-
tion is that the query plan may change over time, as the charac-
teristics of the tables change. For example, if an index’s selectivi-
ty drops dramatically, the optimizer will recognize this, and stop
using the index. If you add an index to a table, the optimizer
will detect the new index automatically, and use it when appro-
priate. On the other hand, if you specify a plan in your SQL
statement, the plan will never change — no matter what hap-
pens to the database — unless you change it.
Conclusion
You will get the best possible performance from your
InterBase application by carefully creating indexes on fields,
or combinations of fields, that you use to select and/or order
rows. InterBase is more flexible in its use of indexes than
most databases, in that it can effectively use multiple single-
column indexes to select rows, based on values in multiple
columns. Therefore, creating single-column indexes is gener-
ally better than creating multi-column indexes, unless you’re
sure you’ll always select and/or order by the same columns
in the same order.
Bill Todd is President of The Database Group, Inc., a database consulting and development
firm based near Phoenix, AZ. He is a Contributing Editor of Delphi Informant; co-author of
Delphi: A Developer’s Guide [M&T Books, 1995], Delphi 2: A Developer’s Guide [M&T
Books, 1996], and Creating Paradox for Windows Applications [New Riders Publishing,
1994]; and a member of Team Borland providing technical support on CompuServe. He is
also a nationally known-trainer, and has been a speaker at every Borland Developers
Conference and the Borland Conference in London. He can be reached on CompuServe at
71333,2146, on the Internet at [email protected], or at (602) 802-0178.
By Dan Ehrmann
D evelopers use the Paradox file format every day, yet the Delphi documenta-
tion offers little information about it. To help fill that gap, the first two articles
in this series explored the internals of Paradox .DB files: table structure, BDE
record and block management, field types, and record size calculation. The third
article examined primary and secondary indexes. In this, the fourth article of the
series, we’ll examine validity checks and referential integrity.
When you use Delphi’s field editor to add static field EDatabaseError
Field <name> must have a value.
objects to your form, Delphi doesn’t pick the underlying
ValChecks for those field types that have MinValue and
Delphi’s static field objects include a Required property. When
MaxValue properties. This is an unfortunate omission on
you instantiate one of these objects, and if the DataSet con-
Borland’s part.
nection is active, Delphi will set the Required property to
True, if there is a Required ValCheck. You can also set this
When you specify these ValChecks with an Alpha field, the
BDE gets the sort order from the table language. (If you don’t property manually without the underlying ValCheck being
specify a custom table language, each table is defined with a set, but you must then write code in the OnValidate event, to
default language driver that you specify in the BDE enforce the property.
Configuration program.) When you define a Minimum
ValCheck for an Autoincrement field, the BDE uses this The Picture ValCheck
value to seed the counter. New records increment from the Pictures are format strings that control which values can be
specified value. entered in a field, and how those values should be displayed.
Figure 3 shows the characters in Paradox’s picture “language,”
Delphi and the BDE do not test these ValChecks until you while Figure 4 shows some sample pictures.
post the new or updated record. If one of them is violated,
Character Meaning
you’ll receive the following error:
@ Any character
EDBEngineError # Any number (0-9)
Minimum (or Maximum) validity check failed.
? Any letter (uppercase or lowercase)
Field: <name>.
& Any letter (converted to uppercase)
~ Any letter (converted to lowercase)
The minimum or maximum value isn’t shown, and there’s no
! Any character (letters converted to uppercase)
easy way to obtain it. If you use the MinValue and MaxValue
[ ] Bracket-optional entries
properties instead, you’ll receive the following error when the
{ } Group-required entries
value limits aren’t met:
*<num> Specify a number of repetitions of a group
or character
EDatabaseError
X is not a valid value for field <name>. * Specify any number of repetitions
The allowed range is <min> to <max>. ; Literal escape character
Other chars Treated as literals
As you can see, the second error is more informative for the user. Figure 3: The Paradox picture language.
EDBEngineError EDBEngineError
Master has detail records. Cannot delete or modify. Field value out of lookup table range.
Again, Delphi doesn’t tell you the name of the table where Delphi doesn’t tell you which field has the bad value. Also,
the detail records reside. (There could be more than one.) Delphi doesn’t provide native support for the Lookup access
30 July 1997 Delphi Informant
Columns & Rows
and Lookup type options described previously. There is no sources. This catalog will support complex constraints pro-
automated way to display the lookup table, nor to fill in grammed in SQL and host languages. These constraints
additional values based on matching field names. will serve as business rules that function across different
data sources, and for any application using that catalog.
In general, don’t bother setting up table lookups unless With this feature, Borland implements the theory behind
you plan to manipulate data with the Database Desktop or Paradox’s ValChecks in a far more powerful and flexible
Paradox itself. The RI features described previously pro- way that’s independent of file formats.
vide the same validation as an entered value in a lookup
table. To display an actual lookup list, use a DBLookupComboBox Next Time
component instead of a TDBEdit component. The ListSource The next article in this series will examine the Paradox file
and ListField (Delphi 3) or LookupSource and LookupField format’s encryption and security mechanisms, and how the
(Delphi 1 and 2) properties allow you to bind this compo- BDE manages passwords for Paradox tables. It will also dis-
nent to a lookup table. You even have explicit control over cuss table-language options that allow you to define tables
the fields displayed in the drop-down list, as well as the with different character sets and sort orders. D
size of the list.
Should You Use ValChecks and RI with Delphi? Dan Ehrmann is the founder and President of Kallista, Inc., a database and
Although the Picture ValCheck exists in the Paradox file Internet consulting firm based in Chicago. He is the author of two books on
format, Delphi ignores it, and newer, better-integrated fea- Paradox, and is a member of Team Borland and Corel’s CTech. Dan was the
Chairman of the Advisory Board for Borland’s first Paradox conference, which
tures have replaced its functionality. The situation for evolved into the current BDC. He has worked with the Paradox file format for
Table Lookup is similar; Delphi and the BDE support it, more than 10 years. Dan can be reached via e-mail at [email protected].
but RI and built-in components have replaced it.
In the past two issues, this column has explained the use of cached updates in
Delphi 2 and 3. This third and final installment of the series will look at event
handlers related to cached updates: OnUpdateRecord and OnUpdateError.
A Quick Recap You might remember that there are four pri-
Update caching is a mechanism by which all mary advantages to using cached updates:
changes made to a DataSet are stored locally, ■ a decrease in network traffic,
then applied simultaneously using the ■ an increase in performance,
ApplyUpdates method of a DataSet or ■ more user interface options, and
Database component. Cached updates are ■ greater programmatic control over the
enabled by setting a DataSet’s CachedUpdates posting of individual records.
property to True. The cached edits are applied
only specifically to the corresponding This month’s DBNavigator will concentrate
DataSet; closing a table, or setting on the final advantage, programmatic control
CachedUpdates to False without actually over applying updates.
applying the updates, cancels all cached edits.
Using Cache-Related Event Handlers
When updates are being cached, you can For those instances where you want complete
offer editing options that would otherwise be control over the application of cached
unavailable. For example, you can permit the updates, the DataSet class provides two event
user to view all edited records in the cache, properties: OnUpdateRecord and
including those that have been deleted. Also, OnUpdateError. From OnUpdateRecord, you
you can display both the original and new replace Delphi’s attempt to update the records
field values for records that have been modi- with your own; in other words, you supply
fied. Finally, you can permit the user to revert completely customized update behavior.
any cached edit, restoring the record to its OnUpdateError is triggered if an exception is
original state. raised during the attempt to update a record,
whether the exception originated from
Last month, we looked at the UpdateSQL Delphi’s own update process, or from
component. This component can be used OnUpdateRecord.
with a DataSet to allow a user to edit read-
only DataSets, such as SELECT queries that Using OnUpdateRecord. As mentioned, if
make use of the DISTINCT keyword. The you need to define custom update behavior,
UpdateSQL component permits you to define you assign an event handler to the
up to three SQL queries — one each for OnUpdateRecord event property. However,
delete, insert, and modify updates. These SQL although this series’ preceding two articles
statements can be quickly and easily con- demonstrated how to turn on cached updates
structed using the UpdateSQL Editor, a com- with an open table, the technique can’t be
ponent editor for UpdateSQL components. used with OnUpdateRecord. Specifically, if
In the following example, a Query component will be cached, Using OnUpdateError. If you create an OnUpdateError event
rather than a Table component. With a Query component, handler, it will be executed if the attempt to update a particular
OnUpdateRecord is triggered whether it is set before or after record fails. This is true whether the update is being performed
the activation of the Query. Consequently, the following by the default DataSet behavior, an UpdateSQL object, or code
example can be similar to the preceding examples. attached to OnUpdateRecord. Unlike OnUpdateRecord, which
doesn’t work properly with Table components under all condi-
Here’s the declaration for the OnUpdateRecord event handler: tions, OnUpdateError works properly for any DataSet.
procedure(DataSet: TDataSet; UpdateKind: TUpdateKind; The following is the declaration for the OnUpdateError
var UpdateAction: TUpdateAction)
event handler:
The first parameter, DataSet, identifies the DataSet com- procedure(DataSet: TDataSet; E: EDatabaseError;
ponent to which updates are being applied. More specifi- UpdateKind: TUpdateKind; var UpdateAction: TUpdateAction);
Optimizing Graphics
Delphi Graphics Programming: Part V
{ Optimized statement. }
BasePointer := Pointer(FScanWidthArray[Y] +
(X shl 1));
Two Timing
One of the DIB class’ main tasks is to clear
the backpage. Unfortunately, the backpage is
cleared only one pixel at a time. Because a
pixel is 16 bits of data, and the largest inte-
ger size accessible in Delphi 2 is 32 bits, this
begs the question, “How can we clear the
Figure 1: The sample application — times six. backpage in 32-bit hits rather than in 16-bit
For example, let’s say you select 16 bits for the precision; you Last, we obtain the conventional integer value:
have just placed a decimal point in the middle of the 32-bit
integer. Using eight bits of a 32-bit integer to represent the 140800 shr 8 = 550
fraction provides a precision of 1/256.
which we can check using good ol’ multiplication:
Converting a normal integer value into a fixed-point value
is the easiest way to perform this optimization. The code in 10 * 55 = 550
Figure 3 is from the RenderYBuckets procedure. The Length
variable is a normal integer. To convert it (or any normal Now let’s look at division. This formula, (2 / 10) * 20 = 4, for
integer) into a fixed-point integer, shift the bits left by the example, just can’t be done with integer math. First, let’s eval-
number of bits of precision (e.g. eight) you have decided to uate the expression in the parentheses. Sixteen bits of preci-
use. You can see this effect with these two statements: sion is needed to ensure this example is correct. Convert 2 to
a fixed-point value:
TextXIncr := ((TextureBuckets[Y].EndPosition.X -
TextureBuckets[Y].StartPosition.X) shl 8) div Length ;
TextYIncr := ((TextureBuckets[Y].EndPosition.Y -
2 shl 16 = 131070
TextureBuckets[Y].StartPosition.Y) shl 8) div Length ;
The divisor, however, doesn’t need to be converted:
The start position is subtracted from the end position, and
the result shifted eight bits to the left. The divisor, Length, 131070 div 10 = 13107
remains untouched, i.e. it was not shifted. If you converted
length into a fixed point, the equation would suffer from Now we convert 20 into a fixed-point value:
incorrect scaling — the value would be too low. The oppo-
site occurs when using fixed-point multiplication — you 20 shl 16 = 1310700
must downscale the answer by eight bits.
The final equation is:
The results, TextXIncr and TextYIncr, are correctly formatted,
fixed-point numbers. If length were converted into a fixed- 13107 * 1310700 = 17179344900
Conclusion
Aside from adding the camera coordinate system, next
month we’ll add more optimizations (so you can compare
those you devised with the ones we implemented), allow
for embedding sprites into the 3D engine, and change the
system to cope with displaying more than one object at a
time. Finally, we’ll provide the ability to display and build
the scene at design time. See you then. D
Don Peer is a Technical Associate with Greenway Group Holdings Inc. (GGHI). He
can be reached via the Internet at [email protected].
By John Penman
NetCheck: Part II
Completing the 32-Bit Network Debugging Tool
I n the whirls of the Internet and intranets, developers must ensure their net-
work programs remain robust. A network debugging tool, therefore, is
essential for any developer building Internet and/or intranet applications.
In Part I of this series (presented in the May and preclude user interaction. Because Trace
Delphi Informant), we began examining descends from Sonar, it inherits this not-
NetCheck, a simple network debugging too-serious, but inconvenient, problem.
application that uses three, non-visual
Delphi components, each encapsulating a To overcome the effects of blocking, we’ll
well-known debugging service: use the TThread class to add multi-tasking
1) Sonar, a wrapper for the ping application; capability to Trace and Sonar. However,
2) EchoC, an echo client wrapper for the we’ll take a different approach when han-
Echo service, similar to ping; and dling the Echo service. To work asynchro-
3) Trace, which encapsulates the TraceRoute nously, EchoC uses the Winsock function,
service. Trace maps the route of the pack- WSAAsyncSelect. This permits you to work
ets between the sending and receiving with NetCheck while processing the back-
machines. This mapping can help find any ground, without using threads.
bottlenecks or “breaks” in the network.
Full of Echoes
In May, we implemented Sonar in NetCheck. The Echo service is used to verify the con-
This month, we’re extending the utility by nection between the target host and client
adding the EchoC and Trace components machine, and that the server is operating at
(see Figure 1). the application level. In contrast, Sonar and
other ping applications test the connection
Kicking Out the Jams only at the target machine’s network inter-
Recall that Sonar operates in blocking
face; this doesn’t indicate the target machine’s
mode, causing the user interface to “freeze”
operating system is functioning. The down-
side of using the Echo service is that an echo
server must be running on the target host.
Ping applications, including Sonar, do not
require such a server.
Next, note the first machine’s address and send another packet
Figure 5: The Echo Server program responding to echo requests
with a TTL of two. The first machine decrements the TTL by
from NetCheck. one, then forwards the packet to the next machine when it
sees the TTL is non-zero. When the second machine receives
each transmission — with FNoEchoes. If FWriteCount the packet, it decrements the packet’s TTL by one. After see-
exceeds FNoEchoes, TimerEvent calls the following code in ing this packet’s TTL is zero, the second machine returns an
TimerEvent to halt transmission: error message. Again, note the address of the second machine
and increase the TTL by one to three. Continue this cycle of
if FWriteCount > FNoEchoes then
begin
sending the packet with increasing TTL until it reaches the
KillTimer(FTWnd,1); destination host, or dies because of a network problem.
DeallocateHWND(FTWnd);
FDone := True;
OnDoneEvent;
Using the TTL mechanism isn’t foolproof, because the
Exit; routes can vary between each packet sent. In spite of this,
end; TTL is a useful tool.
OnDoneEvent then posts a message to the Stop method to The Trace Component
halt the echo process. The Trace component was created by deriving the TTrace class
from the TSonar class. Therefore, Trace inherits TSonar’s Create
To use the EchoC component, select the Echo page in constructor. This method checks the status of the ICMP and
NetCheck and enter the target host’s name or IP address in Winsock DLLs. Create sets the TTL to a default value of 128,
Host name or IP address. Then, alter the default settings for a reasonable value to cover most routes (see Figure 6).
the port number, interval, and number of echoes. Finally,
enter the test message in the Edit control, edEchoTestMsg. TTrace uses TSonar’s GetHost method to obtain the address
of the target machine to trace. Like the Sonar component,
To start the echo process, click the Echo button. The Trace must initialize the TIPOptions and TICMPEchoReply
exchange of data immediately appears in the memEchoMsg records before starting a trace. (For more information on
Memo control (see Figure 4). Figure 5 shows the Echo this, refer to Part I.)
Server program responding to echo requests from NetCheck.
You can halt transmission at any time by clicking the Stop The heart of the Trace component is the DoTrace method.
button, which activates the Stop method. The asynchronous First, DoTrace obtains the addresses of the IcmpCreateFile,
nature of EchoC provides the freedom to cease the process. IcmpCloseHandle, and IcmpSendEcho functions, exported
By Robert Vivrette
T here are times when you may need to display a long pathname in a short
space; for example, the Panel caption in Figure 1. This panel has been
placed on the form with its Align property set to alClient. As a result, the space
available for the caption changes when the form is resized. When the width of
the form is reduced, the caption is truncated on the left and right.
Suppose, however, that you want to retain the DrawTextEx API call. We won’t be using the
right and left portions of the path, and elimi- function to actually draw text, but to modify
nate characters from the middle. The Win32 the string we send.
API provides this capability with the
The first few parameters of DrawTextEx are
the Canvas’ Handle (a Windows Device
Context), the string to display, the length of
that string (-1 calculates it for us), and the rec-
tangle defining the text area. Then comes a
combination of several flags, only three of
which interest us here. The first is
Figure 1: What will happen to the pathname when the form DT_PATH_ELLIPSIS, which eliminates some
is resized? characters in the string and replaces them with
procedure TForm1.FormResize(Sender: TObject); three dots (an ellipsis). The second is
var DT_MODIFYSTRING, which modifies the
B : array[0..255] of Char;
passed string so that we get an altered string
R : TRect;
begin back after the function returns. The last is
StrCopy(B,'C:\Program Files\Borland\Delphi 3.0\' + DT_CALCRECT, which is used to calculate
'Images\Buttons\ZoomIn.bmp');
the rectangle occupied by the text. We’re not
R := ClientRect;
InflateRect(R,-10,-10); interested in this value, but DT_CALCRECT
DrawTextEx(Canvas.Handle,B,-1,R, has the side effect of telling DrawTextEx not to
DT_PATH_ELLIPSIS or DT_MODIFYSTRING or DT_CALCRECT,nil);
Panel1.Caption := B;
draw the text. If we left out this flag, the path
end; would be displayed on the Panel Canvas, sepa-
Figure 2: The pathname-shortening technique. rate from the Caption. The result? The func-
tion modifies the passed string, making it a
shortened form of the original.
ListOfBooks.Clear; Robert Vivrette is a contract programmer for Pacific Gas & Electric, and Technical
ListOfBooks.Capacity := 50000;
repeat
Editor for Delphi Informant. He has worked as a game designer and computer
blah ... blah ... consultant, and has experience in a number of programming languages. He can
ListOfBooks.Items.Add(TheNewBook); be reached via e-mail at [email protected].
blah ... blah ...
until DoneAddingBooks;
ListOfBooks.Capacity := ListOfBooks.Count;
By Bill Todd
N eed to provide ad-hoc query capabilities for your users? AdHocery from
Nevrona Designs not only makes providing such capability easy, it also
provides control of the user interface. With AdHocery, your users can see only
the data they want, either on screen or in reports.
Bill Todd is President of The Database Group, Inc., a database consulting and
If you compile and run this program, you can enter any com- development firm based near Phoenix, AZ. He is a Contributing Editor of Delphi
bination of values in the DBEdit components, then click the Informant; co-author of Delphi 2: A Developer’s Guide [M&T Books, 1996],
Apply button, and see the result of your query in the DBGrid. Delphi: A Developer’s Guide [M&T Books, 1995], Creating Paradox for Windows
To see the SQL generated by AdHocery, click the Show SQL Applications [New Riders Publishing, 1994], and Paradox for Windows Power
button, and you’ll see a display similar to Figure 3. Programming [QUE, 1995]; and a member of Team Borland providing technical
support on CompuServe. He has also been a speaker at every Borland Developers
Conference. He can be reached on CompuServe at 71333,2146, on the Internet
While this example provides basic query capability, it doesn’t at [email protected], or at (602) 802-0178.
support logical AND and OR operations, or compound con-
ditions other than “and-ing” between fields. To make more
complex queries easy, AdHocery provides two other compo-
nents: AdHocTreeView and AdHocGrid. The AdHocTreeView
S oftware development need not be an isolated process. Macho programmers may continue to insist on
coding every line themselves, but the rest of us can take advantage of the Web as a resource for com-
ponents, tips, techniques, and other technical information. The Web offers a plethora of Delphi sites. This
month, I’ve given “gold stars” to the top five Delphi-related sites on the Web.
Delphi Super Page The Delphi Deli The Delphi Information Connection
Maintained by Robert Czerwinski, this If the two previous sites provide the “meat This site, maintained by
is my favorite site to visit when looking and potatoes” for Delphi developers (com- David and Susan Bernard, deserves special
for components and other Delphi ponents), The Delphi Deli, maintained by mention. It’s an “all-around site,” contain-
resources. It has the largest library of Sylvia Lutnes, strives to provide a full ing not only a wealth of components, but
VCL components I’ve seen, and can be menu. Yes, it offers a component library, also links and other resources. Its design is
quickly searched using a variety of cri- but it also has information on Delphi mail- notable, featuring a graphic Table of
teria (e.g. 16-bit and/or 32-bit, free- ing lists, book reviews, FAQs, a chat room, Contents in the form of Delphi’s Object
ware and/or shareware, etc.). Com- and more. This is a well-rounded site, par- Inspector. Unfortunately, it looks as
ponent descriptions, while not as ticularly for those folks who want to do though the site hasn’t been updated since
extensive as Torry’s Delphi Pages or The more than just download the latest VCL. late 1996; hopefully it will be revived
Delphi Deli, are more than adequate. soon, before it becomes outdated.
Finally, Robert does a terrific job of The Delphi EXchange
updating the site regularly. Maintained by Brad Choate, this site offers While each of these “gold star” sites
a large collection of VCLs, as well as addi- deserve special merit, there are a vast
Torry’s Delphi Pages tional resources such as Delphi news and array of other Delphi-related Web sites
Running neck and neck with the Delphi announcements, volunteer Delphi experts that also prove helpful to developers (see
Super Page, this site is administered by (DEXperts), and programming tips. The table below). You owe it to yourself to
Maxim Peresada and Victor Gvozdev. Delphi EXchange also features perhaps the check ’em out. D
Not only is its library extensive, it also most exhaustive list of Delphi-related Web
provides helpful comments — by a dog links I’ve seen. When searching for compo- — Richard Wagner
named Torry — about many of the nents, you can take advantage of its power-
components; awards the best compo- ful search engine, or drill down by category,
nents with a special “Torry’s Top” using an outline view of its file library. My Richard Wagner is Contributing Editor to
award; and notes the most popular one “wish” for this site is better file descrip- Delphi Informant and Chief Technology
downloads. Another nice feature is a tions, e.g. is a file freeware? Does it include Officer of Acadia Software in the Boston,
page that lets you know what’s new on source code? This shortcoming aside, The MA area. He welcomes your comments at
other Delphi sites. Delphi EXchange is a source I visit often. [email protected].
(all URLs begin with http://) Number of Searching File Additional Links to Other
Components Descriptions Resources Delphi Sites
Delphi Super Page Excellent Excellent Good Fair Very good
sunsite.icm.edu.pl/delphi/
Torry’s Delphi Pages Very good Listing only Excellent Very good Very good
carbohyd.siobc.ras.ru/torry/
The Delphi Deli Good Good Excellent Excellent Good
www.intermid.com/delphi/
The Delphi EXchange Very good Excellent Fair Good Excellent
www.delphiexchange.com
The Delphi Information Connection Good Very good Very good Very good Good
www.delphi32.com
The Delphi Temple Fair Listing only Good Average Good
simtel.coast.net/~jkeller/
The Delphi Companion N/A N/A N/A Very good Very good
www.xs4all.nl/~dgb/delphi.html
Delphi Source Good N/A Fair Good Very good
www.doit.com/delphi/
Delphi Station Limited Listing only Fair Good Very good
www.technosoftinc.com/delphi.shtml