Beruflich Dokumente
Kultur Dokumente
ON THE COVER
5
On the Net
39
Inside OP
FEATURES
REVIEWS
11
42
Greater Delphi
17
On Language
23
Informant Spotlight
30
OP Tech
35
In Development
45
IntraWeb
Product Review by Paul Stockton
48
51
52
52
53
DEPARTMENTS
2
Delphi Tools
54
Delphi
T O O L S
New Products
and Solutions
Book Picks
Wireless XML Developers Guide
Mikael Hillborg
McGraw-Hill/Osborne
at http://www.asp-express.com/
aspexpress.
Hansen North America
Price: Standard, US$199; Professional,
US$499; Premium, US$999.
Contact: aspx@hansenusa.com
Web Site: www.asp-express.com
ISBN: 0-07-219536-3
Cover Price: US$49.99
(525 pages)
www.osborne.com
ISBN: 0-7821-2847-5
Cover Price: US$49.99
(1,155 pages, CD-ROM)
www.sybex.com
Delphi
T O O L S
New Products
and Solutions
Book Picks
XML and Web Services
Unleashed
Ron Schmelzer, et al.
SAMS
ISBN: 0-7897-2504-5
Cover Price: US$29.99
(495 pages)
www.quepublishing.com
RAPWare announced
Easy SOAP 1.1.0, a rapid
application development
environment for creating
Web, thin-client, distributed
applications based on
the SOAP protocol. This
product is designed to
promote Object Pascal
routines to fully-functional
SOAP Web Services, without
the difficult wrapping and
mapping needed by most
other implementations of
SOAP. Easy SOAP creates
these mappings for you, and
you can even use existing
Delphi objects in your Web
Services.
The Easy SOAP Object Pascal
parser locates the routines it
needs to promote to SOAP
Web Services and builds the
Object Pascal source wrappers
automatically. It even creates
the necessary WSDL files
for you. Since you can use
your existing code, you can
start building SOAP-enabled
applications in no time. It gives
you the power to expose back-
Delphi
T O O L S
New Products
and Solutions
Book Picks
C#: Tips & Techniques
Charles Wright
McGraw-Hill/Osborne
ISBN: 0-07-219379-4
Cover Price: US$49.99
(626 pages)
www.osborne.com
Programming Web
Services with SOAP
James Snell, Doug Tidwell,
& Pavel Kulchenko
OReilly
ISBN: 0-596-00095-2
Cover Price: US$34.95
(244 pages)
www.oreilly.com
TurboPower announced
Orpheus 4, a major new version
of the companys user interface
components for programmers
using Delphi and C++Builder
compilers. Orpheus 4 has over
120 native VCL components
focused on helping programmers easily create intuitive and
attractive user interfaces for
their programs.
Key features in this release
include new Orpheus Data
Input Validators; new Orpheus
FlexEdit data entry fields with
built-in support for multiline displays, complete border
control, and connections to
Orpheus 4s new Data Input
Validation system; a new
On the Net
WebSnap / Web Development / Delphi 6
By Nick Hodges
WebSnap at Work
Descending from TAdapter
type
TnxCustomNewUserAdapter = class(TDefaultFieldsAdapter)
private
FOnValidatePassword: TValidateNewUserItemEvent;
FOnValidateUserName: TValidateNewUserItemEvent;
FOnValidateUserEmail: TValidateNewUserItemEvent;
FOnNewUserIsValid: TNewUserIsValidEvent;
protected
function ImplCanAddFieldClass(AParent: TComponent;
AClass: TClass): Boolean; override;
function ImplCanAddActionClass(AParent: TComponent;
AClass: TClass): Boolean; override;
procedure ImplGetFieldsList(AList: TStrings); override;
procedure ImplGetActionsList(AList: TStrings);
override;
function ExecuteCheckNewUser(aUserName, aUserEMail,
aPassword, aConfirmPassword: string): Boolean;
public
property OnValidatePassword: TValidateNewUserItemEvent
read FOnValidatePassword write FOnValidatePassword;
property OnValidateUserName: TValidateNewUserItemEvent
read FOnValidateUserName write FOnValidateUserName;
property OnValidateUserEmail: TValidateNewUserItemEvent
read FOnValidateUserEmail write FOnValidateUserEmail;
property OnNewUserIsValid: TNewUserIsValidEvent
read FOnNewUserIsValid write FOnNewUserIsValid;
end;
TnxNewUserAdapter = class(TnxCustomNewUserAdapter)
published
property OnValidatePassword;
property OnValidateUserName;
property OnValidateUserEmail;
property OnNewUserIsValid;
// Declared in ancestor.
property Data;
property Actions;
property OnBeforeExecuteAction;
property OnAfterExecuteAction;
property OnBeforeGetActionResponse;
property OnAfterGetActionResponse;
property OnGetActionParams;
end;
On the Net
Interface
Purpose
IAdapterEditor
Defines functions for the IDE and component editor. Provides the run-time
functionality that allows you to add the
components fields and actions to your
TAdapterPageProducer at design time.
IWebActionsList
IWebFieldsList
Description
OnValidatePassword
OnValidateUserName
OnValidateUserEmail
OnNewUserIsValid
Occurs when a new users information has been validated. The event
passes all the validated information
as parameters, and then you can process the new user as needed. Usually
you will add the new user information
to your user database in this event.
Where to Descend?
When building a custom TAdapter descendant, the first issue is finding
the right parent class from which to descend. There are numerous standard WebSnap classes in the hierarchy of the TAdapter class types. Some
deal with normal adapter functionality, and others provide specialty
functionality, such as the ValueList and EndUser components. In this
case, well build a class that will have custom fields and actions. Therefore, well want a class that handles default fields as well as all the other
components that can be part of a TAdapter.
Fortunately, such a class exists: TDefaultFieldsAdapter. Its function is
as its name suggests: Its designed to handle lists of default fields and
actions. For example, the TLoginFormAdapter component descends from
TDefaultFieldsAdapter because it has three default fields and a default
action. Thus, well declare our class as shown in Figure 1.
Note that we declare a custom class with no published properties,
and then we declare the actual class that just publishes the desired
properties. The class descends directly from TDefaultFieldsAdapter
and implements more properly, overrides the existing
implementations of the IAdapterEditor, IWebDataFields, and
IWebActionsList interfaces. These three interfaces perform the
6 June 2002 Delphi Informant Magazine
function TnxCustomNewUserAdapter.ImplCanAddActionClass(
AParent: TComponent; AClass: TClass): Boolean;
begin
Result := inherited ImplCanAddActionClass(
AParent, AClass) or AClass.InheritsFrom(
TnxCustomNewUserSubmitAdapterAction);
end;
function TnxCustomNewUserAdapter.ImplCanAddFieldClass(
AParent: TComponent; AClass: TClass): Boolean;
begin
Result := inherited ImplCanAddFieldClass(
AParent, AClass) or AClass.InheritsFrom(
TnxAdapterNewUserField);
end;
Figure 5: This code adds the name and a pointer for each of the
default fields and actions.
functions outlined in Figure 2. The class also defines the four events
described in Figure 3.
Implementation
Of course, the real meat of a component is the implementation,
so well examine that next. First, well look at the overridden
implementation of the interfaces. Then well look at how the
events are implemented and called.
The first interface, IAdapterEditor, is implemented as shown in Figure
4. These two functions are similar in that they merely combine the
inherited behavior with a check for proper behavior in the current class.
Their job is to tell the IDE if a given class of action or field can be added
to the current component in the design-time component editor of the
TAdapterPageProducer, i.e. the Web Surface Designer. In this case, the
acceptable classes are any classes the parent class accepts, or any fields
or actions that descend from the fields and actions that are part of the
TnxNewUserAdapter. In other words, these methods ensure that only the
right fields and actions are added to the TAdapter at design time.
Next, the IWebActionsList and the IWebFieldsList interfaces
are implemented as shown in Figure 5. This code is simple.
TDefaultFieldsAdapter maintains lists simple TStrings of
On the Net
the default fields for itself, and this code merely adds the name
and a pointer to the class for each of the default fields and actions
well add to the component. Well get to these classes later.
The event gives you the chance to check out the information passed by
the new user, and either validate it or invalidate it, and pass a reason back
to the TnxNewUserAdapter for display to the user.
Once the event has occurred, the code checks to see if the event has
decided the input is invalid. If it has, the code adds the reason you gave
to the errors list for the component. If there are any errors in the list
due to the user submitting the page, the page is shown again and the
errors are displayed on the page if the page has a TAdapterErrorList
on it attached to the current adapter. This is why its good to add a
TAdapterErrorList to each TAdapterForm in your page.
Then, the code basically does the same thing for the new users
e-mail address and password. The code also checks to ensure the
two passwords given in the Password and ConfirmPassword fields
are the same. If not, an error is raised.
Finally, if the user name, password, and e-mail address all have
been validated, the code calls the OnNewUserIsValid event (if a
handler is present), and passes all the new information as parameters. In this event, we can add the new user to our databases or
wherever we store that information.
The ExecuteCheckNewUser method we just looked at is nothing
special. Its just an event we added to encapsulate the functionality of
checking the new users information. Well call it manually when we
get around to implementing the fields and actions mentioned earlier.
type
TnxAdapterNewUserField = class(TAdapterNamedDisplayField)
private
function GetAdapter: TnxCustomNewUserAdapter;
protected
property Adapter: TnxCustomNewUserAdapter
read GetAdapter;
published
property FieldName;
property DisplayLabel;
property DisplayWidth;
end;
On the Net
TnxAdapterUserNameField = class(TnxAdapterNewUserField)
protected
function GetDefaultFieldName: string; override;
{ IWebGetFieldValue }
function ImplGetValue: Variant; override;
end;
TnxAdapterUserEMailField = class(TnxAdapterNewUserField)
protected
function GetDefaultFieldName: string; override;
{ IWebGetFieldValue }
function ImplGetValue: Variant; override;
end;
TnxAdapterPasswordField =
class(TnxAdapterNewUserPasswordField)
protected
function GetDefaultFieldName: string; override;
{ IWebGetFieldValue }
function ImplGetValue: Variant; override;
end;
TnxAdapterConfirmPasswordField =
class(TnxAdapterNewUserPasswordField)
protected
function GetDefaultFieldName: string; override;
{ IWebGetFieldValue }
function ImplGetValue: Variant; override;
end;
TnxNewUserAdapterSubmitAction =
class(TnxCustomNewUserSubmitAdapterAction)
protected
procedure ImplExecuteActionRequest(AActionRequest:
IActionRequest; AActionResponse: IActionResponse);
override;
function GetDefaultActionName: string; override;
published
property DisplayLabel;
property OnBeforeExecute;
property OnAfterExecute;
property OnBeforeGetResponse;
property OnAfterGetResponse;
property OnGetParams;
end;
tion submitted, and the form is redisplayed. The new user name
and e-mail address will be saved and displayed, but the password
information will not. As a result, the ImplGetValue method for
the password fields simply returns an empty string.
Next, well implement a single default action for the new TAdapter. It
will process the information as a normal HTML POST event. First,
well create a class similar to the TnxAdapterNewUserField: the
TnxCustomNewUserSubmitAdapterAction class. It has the
same function and basically the same implementation as the
TnxAdapterNewUserField class did: to find and retrieve the correct
owning adapter. Thus, its declared like this:
TnxCustomNewUserSubmitAdapterAction =
class(TImplementedAdapterAction)
private
function GetAdapter: TnxCustomNewUserAdapter;
protected
property Adapter: TnxCustomNewUserAdapter
read GetAdapter;
end;
Listing Two contains a lot of code, but its fairly straightforward. First,
it grabs the submitted value for each of the fields. It does that via the
IActionFieldValue interface, which it gets from AActionRequest parameter
via the call to Supports. The IActionFieldValue is an interface that, when
implemented, holds all the information about the fields in an HTML
POST request. Thus, it contains all the values from the controls on the
HTML form submitted by the user, so we can grab that information
with a call to the ValueOfField method for that interface.
First, the code assumes there is no value. But, if the Adapter property and the WebContext variable are both assigned to something,
the form will return the value stored in the session variable. The
fields are designed so the UserName and UserEmail values are
retained between page requests, but so that the password information is not retained. This means the new users password wont
be sent out over the Internet if there is an error in the informa-
We do that for each of the four fields by placing the values in local
variables as we go, and setting them to empty strings if nothing is
sent for a particular value. Once weve done that, we simply call
the ExecuteCheckNewUser method from the adapter, which you
saw above and which verifies the entries and lets you enter them
into your user database. Note that this is an example of using the
Adapter property for these default fields and actions. We know
On the Net
unit nxNewUserAdapterReg;
interface
procedure Register;
implementation
uses WebComp, nxNewUserAdapter, Classes;
procedure Register;
begin
RegisterWebComponents([TnxAdapterUserNameField]);
RegisterWebComponents([TnxAdapterUserEMailField]);
RegisterWebComponents([TnxAdapterPasswordField]);
RegisterWebComponents([TnxAdapterConfirmPasswordField]);
RegisterWebComponents([TnxNewUserAdapterSubmitAction]);
RegisterComponents('WebSnap', [TnxNewUserAdapter]);
end;
initialization
finalization
UnRegisterWebComponents([TnxAdapterUserNameField]);
UnRegisterWebComponents([TnxAdapterUserEMailField]);
UnRegisterWebComponents([TnxAdapterPasswordField]);
UnRegisterWebComponents(
[TnxAdapterConfirmPasswordField]);
UnRegisterWebComponents([TnxNewUserAdapterSubmitAction]);
end.
from the GetAdapter function that the Adapter property will hold
an adapter of the proper type, and, as long as it isnt nil, we can call
the ExecuteCheckNewUser method safely.
Note the UserName and UserEmail values, once retrieved, are
stored in the Session.Values property using their names as values.
We saw earlier that these values will be saved between requests,
but that the users password wont be. This is how thats done.
Conclusion
In this article, we built a customized TAdapter-based component that
provides the controls needed to register a new user on a Web site. We
saw how to descend from TDefaultFieldsAdapter to add default fields
to the component, and we saw how to implement the default fields
9 June 2002 Delphi Informant Magazine
On the Net
Begin Listing Two ImplExecuteActionRequest
procedure TnxNewUserAdapterSubmitAction.
ImplExecuteActionRequest(AActionRequest: IActionRequest;
AActionResponse: IActionResponse);
var
Value: IActionFieldValue;
ActionFieldValues: IActionFieldValues;
aUserName, aUserEmail, aPassword,
aConfirmPassword: string;
begin
inherited;
if Supports(AActionRequest, IActionFieldValues,
ActionFieldValues) then begin
Value :=
ActionFieldValues.ValueOfField(strUserNameField);
if Value <> nil then
aUserName := Value.Values[0];
else
aUserName := '';
// Only save the UserName in the session. If we
// return to the page, don't send the potential
// password the user entered back to the browser.
WebContext.Session.Values[Value.FieldName] :=
aUserName;
Value :=
ActionFieldValues.ValueOfField(strUserEmailField);
if Value <> nil then
aUserEmail := Value.Values[0];
else
aUserEmail := '';
WebContext.Session.Values[Value.FieldName] :=
aUserEmail;
Value :=
ActionFieldValues.ValueOfField(strPasswordField);
if Value <> nil then
aPassword := Value.Values[0];
else
aPassword := '';
Value := ActionFieldValues.ValueOfField(
strConfirmPasswordField);
if Value <> nil then
aConfirmPassword := Value.Values[0];
else
aConfirmPassword := '';
if Adapter <> nil then
Adapter.ExecuteCheckNewUser(aUserName, aUserEmail,
aPassword, aConfirmPassword);
end;
end;
Greater Delphi
Microsoft Word / COM / Automation / Delphi 3-6
By Jason Sweby
Word Merge
Automating Microsoft Word Mail Merge
n his series on Word automation in the September and October 2000 editions of Delphi
Informant Magazine, Ron Gray covered the basics of incorporating Microsoft Words
functionality into your projects using COM technology. COM is a language-independent,
software-component model designed to enable interaction between software components and applications. It enables developers to use other software tools in their own
programs. Although its not initially as intuitive as standard Delphi components, when
used correctly, COM can add powerful capabilities to your applications.
Early or Late?
Let me explain why I will use the late-binding
automation approach in this article. You can communicate with a COM server using one of two
techniques. The technique that has been used for the
past few years involves using Delphis OLE Automation unit to create a manual link to a COM object as
an OLE variant type, using the published program
ID (see Figure 1). Using this method, you have none
of Delphis Code Insight available to you, and its the
responsibility of the COM client application to call
the available objects and methods correctly.
Greater Delphi
uses ComObj;
procedure TForm1.Button1Click(Sender: TObject));
var
oleWord: OleVariant;
begin
try
oleWord := CreateOLEObject('word.application');
except
on E: Exception do begin
E.Message := 'Microsoft Word is unavailable'.
Raise;
end;
end;
Screen.Cursor := crHourGlass;
Application.ProcessMessages;
oleWord.Quit;
Screen.Cursor := crDefault;
{ This is the standard Delphi way of clearing up the
instance. The VBA equivalent is oleWord = Nothing; }
oleWord := UnAssigned;
end;
Even for applications in which this facility is not available, there should
be some documentation on the COM object hierarchy. For Microsoft
Word, its in the Microsoft Word Visual Basic help file (vbawrd8.hlp
for Word 97 users). For Microsoft Access, you can find the hierarchy in
the normal help file (under the DBEngine Object section).
Lets look at an example of how to use this, through a simple findand-replace operation. Open Word and any document. Click the
Tools menu and select Macros | Record New Macro. Call the macro
FindAndReplace and press OK. The macro is now in record mode,
as shown by the tape-recorder controls that appear in a toolbar on
the screen (see Figure 3). Everything you do until you click the Stop
button (on the left) will be recorded into the macro.
Select Edit | Replace. In the Find What edit box, enter My Text. In
the Replace With box, enter My New Text. Then, click the Replace All
button. Whether or not any text was replaced, click the Stop control
button in the Macro Recorder tool window (or select Tools | Macro
| Stop Recording). The macro has now been recorded and saved. To
see the code that was generated, open the VBE. Figure 4 shows the
result. (Thanks to my colleague Richard Quadling for enlightening
me on this matter.)
Greater Delphi
Sub FindAndReplace()
Selection.Find.ClearFormatting
Selection.Find.Replacement.ClearFormatting
With Selection.Find
.Text = "My Text"
.Replacement.Text = "My New Text"
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute Replace:=wdReplaceAll
End Sub
The second procedure uses late binding, with which Delphi will not
try to syntax-check the properties, methods, and parameters you use
for a COM object. Note that you cannot use the with statement
around the object; its not a Delphi object, but rather a variable of
type variant. Now, however, you can set any or all of the properties of the Find object in the standard way. And, when you call the
Find.Execute method, only the parameters you want to pass need to
be included. There are three different ways of passing an optional
number of parameters, as you can see here in the Document.SaveAs
method:
expression.SaveAs(FileName, FileFormat, LockComments,
Password, AddToRecentFiles, WritePassword,
ReadOnlyRecommended, EmbedTrueTypeFonts,
SaveNativePictureFormat, SaveFormsData, SaveAsAOCELetter)
Or you can name the parameters you are using and supply values:
oWord.ActiveDocument.SaveAs(sFilename);
oWord.ActiveDocument.SaveAs(Filename := sFilename,
AddtoRecentFiles := True);
Or you can leave certain parameters blank, but still provide them all:
oWord.ActiveDocument.SaveAs(sFilename,,,,True,,,,,,,);
Mail Merge
So far, weve established how to get the objects from the COM server
and begin setting their properties and using their methods. Weve also
established how to pass just the parameters you need. Now, we will put
this information to good use. Well build an automated mail-merging
tool from our own application by using Words COM objects.
Before starting, we need to have a Word document containing
mail-merge fields and a data source that contains the data to merge.
A range of data-source types are available to be imported into a document, including a text document, database fields, or a SQL statement
linking to a database. For ease of use, well use a comma-separatedvalues (CSV) file containing a list of fields and data. This file is
available along with a mail-merge template. (See details at the end of
the article.) The document template is in Word 95 format to provide
as much compatibility as possible.
I wont go into the specifics of setting up a mail-merge letter. Suffice
it to say you only really need to insert MergeField type fields into
your documents with the field names correlating to the field names
in the data source. Microsoft Word provides a great deal of help on
this subject.
If you take a look at the CSV file to be used as the data source
(Figure 5 shows the first few fields from the text file given as an
example), you will notice the first line contains the field names that
appear in the file, in the sequence in which they appear. In doing
Greater Delphi
this, you dont need to make any more effort
in matching up fields in the data source to the
merge fields on the document. Word will pair
them for you.
Figure 5: A snapshot of the example datasource used in the mail merge project.
Application
Dictionaries (Dictionary)
Documents (Document)
MailMerge
MailMergeDataSource
MailMergeFields (MailMergeField)
FileConverters (FileConverter)
Figure 6: The hierarchy of the MailMerge object in Word.
Sub MailMerge()
ActiveDocument.MailMerge.MainDocumentType = wdFormLetters
ActiveDocument.MailMerge.OpenDataSource Name:= _
"datasource1.txt", ConfirmConversions:=False, _
ReadOnly:= False, LinkToSource:=True, _
AddToRecentFiles:=False, PasswordDocument:="", _
PasswordTemplate:="", WritePasswordDocument:="", _
WritePasswordTemplate:="", Revert:=False, _
Format:=wdOpenFormatAuto, Connection:="", _
SQLStatement:="", SQLStatement1:=""
With ActiveDocument.MailMerge
.Destination = wdSendToNewDocument
.MailAsAttachment = False
.MailAddressFieldName = ""
.MailSubject = ""
.SuppressBlankLines = True
With .DataSource
.FirstRecord = wdDefaultFirstRecord
.LastRecord = wdDefaultLastRecord
End With
.Execute Pause:=True
End With
End Sub
is true with COM objects: The MailMerge properties are set before the
Execute method is called. Theres no overhead in setting them all. Ironically, the MailMerge Execute method takes a parameter, but it doesnt
affect what the method does. The parameter controls Words behavior
while its processing, as explained in the Word VBA help file.
Simulate the code generated by the Macro Recorder in the Delphi project by adding the objects, properties, and methods in the OnClick event
handler for Button2 as shown in Listing Two (on page 16). What youve
achieved is an exact replica of the Word mail-merge facility in Delphi.
This project is an example of a COM client you can adapt to perform
dozens of other Word operations, such as spell checking, converting
documents, or editing global documents.
The code in this article has been tested with the recently released
Microsoft Office XP, and all versions of Word between XP and Word
97. The steps outlined earlier, which build up the mail merge, have
been slightly altered in Word XP (see Figure 8). The process is now
easier because of the Mail Merge Wizard shown in Figure 9. The
Greater Delphi
Begin Listing One Early Binding vs. Late Binding
unit Word;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, ComObj, Word97, OleServer;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
oleWord: TWordApplication;
oleWordDoc: TWordDocument;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
Visual Basic code, however, is identical. With this in mind, you can
be reasonably sure any work you do with Microsoft Word will work
across most platforms.
You can find an example of the same application using Delphi 5/6s
TWordApplication Server component in the accompanying files.
The projects and supporting files referenced in this article are available on the Delphi Informant Magazine Complete Works CD located
in INFORM\2002\JUN\DI200206JS.
Jason Sweby graduated from the University of Manchester Institute of Science and
Technology in Manchester, England, with a joint-honors degree in computation and
French. He is now a Windows software developer at Carval Computing. The company,
a provider of human-resources systems in Plymouth, England, works with payroll,
personnel, and time-and-attendance projects. You can often find Jason wandering the
Delphi topic area at the Experts Exchange (http://www.experts-exchange.com). You
may contact Carval Computing via the companys Web site at http://www.carval.co.uk
or write to Jason at jason.sweby@carval.co.uk.
// Early binding.
procedure TForm1.Button1Click(Sender: TObject);
var
FindText, ReplaceText, sFilename, oleType,
PreserveFormatting: OleVariant;
bMatchCase, bMatchWholeWord, bMatchWildCards,
bMatchSoundsLike, bMatchAllWordForms, bForward, bWrap,
bFormat, bReplaceWith, bReplace, bSaveChanges:
OleVariant;
begin
sFilename := 'c:\my documents\mydoc1.doc';
{ If connection fails, Word is not available. }
try
oleWord.Connect;
except
on E: Exception do begin
E.Message := 'Word is not available.';
Raise;
end;
end;
with oleWord do begin
Documents.Open(sFilename, EmptyParam, EmptyParam,
EmptyParam, EmptyParam, EmptyParam, EmptyParam,
EmptyParam, EmptyParam, EmptyParam);
FindText := 'My Text';
ReplacementText := 'My New Text';
with Selection.Find do begin
ClearFormatting;
{ These properties do not necessarily need to be set
here. They are only included to show how the code
can be copied from the Macro code. }
Text := FindText;
Replacement.Text := ReplacementText;
Forward := True;
Wrap := wdFindContinue;
Format := False;
MatchCase := False;
MatchWholeWord := False;
MatchWildcards := False;
MatchSoundsLike := False;
MatchAllWordForms := False;
{ These are the variables that will be passed to the
Execute method. These DO need to be set. }
bReplaceWith := Replacement.Text;
bForward := Forward;
bWrap := Wrap;
Greater Delphi
bFormat := Format;
bMatchCase := MatchCase;
bMatchWholeWord := MatchWholeWord;
bMatchWildcards := MatchWildcards;
bMatchSoundsLike := MatchSoundsLike;
bMatchAllWordForms := MatchAllWordForms;
{ This is an extra parameter of the Execute method
that is not a property of the Find object. }
bReplace := wdReplaceNone;
{ Now call the Execute method, having to pass all
the parameters. }
Execute(FindText, bMatchCase, bMatchWholeWord,
bMatchWildCards, bMatchSoundsLike,
bMatchAllWordForms, bForward, bWrap, bFormat,
bReplaceWith, bReplace);
end;
end;
{ Save the document with the new text. }
with oleWordDoc do begin
ConnectTo(oleWord.ActiveDocument);
SaveAs(sFilename);
bSaveChanges := False;
Close(bSaveChanges);
Disconnect;
end;
oleWord.Disconnect;
end;
// Late binding.
procedure TForm1.Button2Click(Sender: TObject);
var
oWord: OLEVariant;
sFilename, FindText, ReplaceText: string;
begin
sFilename := 'c:\my documents\mydoc1.doc';
try
oWord := CreateOLEObject('word.application');
except
on E: Exception do begin
E.Message := 'Word is not available.';
Raise;
end;
end;
try
{ Word isn't visible by default, so you can run it
without the user knowing. }
oWord.Visible := False;
oWord.Documents.Open(sFilename);
{ This method cannot be placed in a with statement,
because it is an OLEVariant, not an object. }
oWord.Selection.Find.ClearFormatting;
oWord.Selection.Find.Text := 'My Text';
oWord.Selection.Find.Replacement.Text := 'My New Text';
oWord.Selection.Find.Forward := True;
oWord.Selection.Find.Wrap := wdFindContinue;
oWord.Selection.Find.Format := False;
oWord.Selection.Find.MatchCase := False;
oWord.Selection.Find.MatchWholeWord := False;
oWord.Selection.Find.MatchWildcards := False;
oWord.Selection.Find.MatchSoundsLike := False;
oWord.Selection.Find.MatchAllWordForms := False;
oWord.Selection.Find.Execute(Replace:=wdReplaceAll);
oWord.ActiveDocument.SaveAs(sFilename);
oWord.ActiveDocument.Close(wdDoNotSaveChanges);
finally
oWord.Quit;
oWord := UnAssigned;
end;
end;
end.
On Language
Interfaces / Delphi 6
By Leo Seaman
Everyday Interfaces
Interfaces Can Simplify Common Programming Tasks
interface
Interface Basics
You declare an interface to group a set of methods
together to perform an operation. This is similar
to a class, but with an interface, only the methods
can be listed. The interface syntax is quite simple,
as shown in the Delphi help file:
type InterfaceName =
interface (AncestorInterface)
['{GUID}']
MemberList
end;
uses IBankAccountUnit;
type
TCheckingAccount = class(TInterfacedObject, IBankAccount)
private
FBalance: Currency;
// These three functions are required by IBankAccount.
function Debit(Amt: Currency): Currency;
function Credit(Amt: Currency): Currency;
function GetBalance: Currency;
public
constructor Create(OpeningBalance: Currency = 0);
end;
On Language
After youve declared the interface, you can define classes that
support it. When a class supports an interface, its required to
implement all methods of the interface and all ancestor interfaces. If the class doesnt implement all the methods, Delphi will
generate a compile error. The syntax to support an interface is to
list the interfaces after the ancestor class, as shown in the Delphi
help file:
type ClassName = class (AncestorClass, Interface1,
..., InterfaceN)
MemberList
end;
Now that youve declared the class and interfaces, you can create the
object. The code for creating an interfaced object is similar to that for
creating a normal object. The difference is that the variable into which
the object is stored is declared as the interface, not the class:
var
FBankAccount: IBankAccount;
...
begin
FBankAccount := TCheckingAccount.Create;
FBankAccount.Debit(100);
As you can see, one interface variable is being used to hold a reference to
an object that supports the interface. At design time, its unknown which
object will be created, so all calls are done via the interface methods. This
allows us to call any method of the interface, and youre guaranteed that
the object will implement those methods.
In addition, any object that supports the interface can be created and stored into the interface variable. If additional classes
that support the interface are designed later, theyre automatically compatible with this code. The downside is that you can
call only methods of the interface. If the object has additional
methods, they wont be callable through the interface, so
design the interface with that in mind. An example of creating an object and storing it in an interface can be found in the
/SimpleInterfaceExample folder in the sample code that accompanies this article (see end of article for download details).
Factories
A common practice when using interfaces is to have a factory
class generate objects for you. At first, this seems to add unnecessary coding to your project. However, the advantage is being able
to replace or subclass the factory to change the object being created. Also, when creating an interfaced object, only the interface
and factory units are in the uses clause, not the unit that declares
the class being created. This allows you to change and move the
interfaces and interfaced objects between projects with minimal
code changes elsewhere.
Figure 2 shows a simple class factory. The class factory declares a single
class function, Create, that creates an object and returns it as an interface.
A class factory should be written for each interface. The class factory
On Language
should be declared in a different unit from the interface and the class that
supports the interface.
Now, a form can use the class factory to create objects:
procedure TfrmMain.rgAccountTypeClick(Sender: TObject);
begin
case rgAccountType.ItemIndex of
0 : FBankAccount := TCheckingAccountFactory.Create;
1 : FBankAccount := TCreditCardAccountFactory.Create;
end;
ShowBalance;
end;
Common Tasks
Now that you understand the basics of interfaces, you can apply
them to common tasks. One such task is to set the focus on a form
to a specific field when a database problem occurs in a data module.
Although there is some support for this in the TField class by setting
the Required property, or by calling the FocusControl method,
such procedures normally dont work well enough in a complex
application. FocusControl, for example, will not focus a control if
it isnt visible. To solve this problem using interfaces, declare an
interface, IFocusField, that specifies a single method to call when a
control should be focused:
IFocusField = interface
procedure FocusField(AField: TField);
end;
Each form that needs to be able to focus a control programmatically should implement the interface:
// TMyForm supports the IFocusField interface.
TMyForm = class(TForm, IFocusField)
...
private
// TMyForm is required to implement this method.
procedure FocusField(AField: TField);
...
end;
When an error occurs in a data module, the data module will need
to call the FocusField method of the IFocusField interface. The data
module must declare an instance variable to store the interface:
TMyDataModule = class(TDataModule)
...
public
// FocusFieldHandler can hold a reference to any
// object that supports the IFocusField interface.
FocusFieldHandler: IFocusField;
end;
When the form creates the data module, it sets the interface field on
the data module to the form:
procedure TMyForm.CreateForm(Sender: TObject);
begin
// Create normally.
FMyDataModule := TMyDataModule.Create(Self);
// Data module can call us via IFocusField.
FMyDataModule.FocusFieldHandler := Self;
end;
unit BankAccountFactoryUnit;
interface
uses
IBankAccountUnit, CheckingAccountUnit;
type
TCheckingAccountFactory = class
class function Create(OpeningBalance: Currency = 0):
IBankAccount;
end;
implementation
{ TCheckingAccountFactory }
class function TCheckingAccountFactory.Create(
OpeningBalance: Currency): IBankAccount;
begin
Result := TCheckingAccount.Create(OpeningBalance);
end;
This allows the data module to call the form via the IFocusField
interface without needing to know the class of the form or what
controls are on the form. This greatly simplifies the data module and
puts the code that deals with the data-aware controls back on the
form where it belongs (see Figure 3). An example is available in the
/FocusFieldExample folder in the code for this article.
On Language
ILoginInfo
function
function
function
end;
= interface
Logon: Boolean;
getName: string;
getPassword: string;
The Logon method would display the form modally and get the login
information. As Figure 4 shows, the getName and getPassword methods would retrieve the login information from the form.
Use a factory so you can change the class you use to collect the
login information without re-coding your data module:
TLoginInfoFactory = class
public
class function Create: ILoginInfo;
end;
class function TLoginInfoFactory.Create: ILoginInfo;
begin
Result := TfrmLogin.Create(Application);
end;
Finally, the data module or form that required the login information would declare a variable of ILoginInfo. When the data module
required login information, it would use a factory to create a login
form and retrieve the login information. In Figure 5, a Database
component is used, and on the OnLogon event, you would prompt
for the user name and password.
If the data module is used in a Web-server application later, the same
interface could be used and the factory would be changed to retrieve
the login information from an HTTP request instead of a login
form. An example of this can be found in the /LoginFormExample
folder in the supporting code for this article.
Reference-counting Forms
If you noticed in the factory for the last example, the login form
was created, and the owner was passed as the application object.
The form will exist until the Free method is called or the application
closes. Because you didnt declare the Free method in your interface,
you cannot call it. Therefore, the form will exist until the application
ends. This isnt memory efficient, however, and could cause problems
if the login method is called multiple times. There are several solutions for this problem. The simplest solution is to add an owner
property to the factorys Create method, so you can create the form
with an owner other than the application:
TLoginInfoFactory = class
public
class function Create(AOwner: TComponent = nil):
ILoginInfo;
end;
class function TLoginInfoFactory.Create(
AOwner: TComponent = nil): ILoginInfo;
begin
Result := TfrmLogin.Create(AOwner);
end;
On Language
TfrmLogin = class(TForm, ILoginInfo)
protected
FRefCount: Integer;
{ ILoginInfo }
function Logon: Boolean;
function getName: string;
function getPassword: string;
{ IInterface }
function _AddRef: Integer; reintroduce; stdcall;
function _Release: Integer; reintroduce; stdcall;
end;
The downside to this is that the implementations of these interfaces are not inherited. Each of the three classes above would
need to implement all the methods of the interfaces they support.
Besides being inefficient, this also introduces the possibility
of errors by implementing the code in several different places.
Because you dont have an ancestor class from which to descend
that implements the methods of the interfaces, you need to come
up with another way to encapsulate the code. To get around this
problem, you can build helper classes. The job of the helper class
is to implement the methods of an interface and then be embedded into a class that supports the interface. The helper calss can
be descended from TObject because it isnt interfaced:
type
TPlaceOrder = class
procedure PlaceOrder;
end;
All classes that implement the interface will declare the helper
class and get it to do the implementation. Delphi provides a
technique for handling this by extending the property keyword.
The property must have a read operator and must declare that
it implements the interface. A write operator is optional. When
declaring the property, include the implements keyword, and you
have an interfaced class that uses a helper:
TManager = class(TInterfacedObject, IPlaceOrder)
private
FPlaceOrder: TPlaceOrder; // Helper class.
protected
// Calls to IPlaceOrder will be handled by FPlaceOrder.
property PlaceOrder: TPlaceOrder read FPlaceOrder
write FPlaceOrder implements IPlaceOrder;
end;
On Language
// Declare interfaces in the private section of class.
private
FCancelOrder: ICancelOrder;
FPlaceOrder: IPlaceOrder;
FReturnOrder: IReturnOrder;
// Create object and assign it to interfaces it supports.
OEClerk := TOrderEntryClerk.Create;
FPlaceOrder := OEClerk;
FReturnOrder := OEClerk;
// Call the method of the interface.
if assigned(FPlaceOrder) then
FPlaceOrder.PlaceOrder;
The interfaced class should create the helper in its constructor and free its helper in the destructor. The FPlaceOrder helper
object will now automatically handle any calls to the IPlaceOrder
interface.
The advantage of using the helper class is how quickly it can
be changed. New methods can be added to the interface and
implemented in the helper class, without changing any of the
classes that support the interface. All the classes that support the
interface and use the helper class will pick up the new methods
automatically. An example of this is in the /MultipleInheritance
folder in the code that accompanies this article.
Typecasting Interfaces
Typecasting interfaces is not the same as typecasting objects.
When an interface is typecast, the QueryInterface method
is called, and if the interface is supported by the object, the
interface is returned. If the interface is not supported, an
EIntfCastError exception is raised. For QueryInterface to work,
the interface being typecast must have a GUID. A GUID is
generated by the operating system and is guaranteed statistically
to be unique. The GUID is added after the declaration of the
interface. Whenever a GUID is added to the source code, use
CSG instead of duplicating the GUID in the examples:
IReturnOrder = interface
// Add this using Ctrl+Shift+G.
['{B785D7F1-7303-4FAA-8C26-024E7598CAFE}']
procedure ReturnOrder;
end;
Typecasting interfaces is useful when the run-time type of the interface isnt known at design time. For example, in the prior example,
three interfaces were declared to hold a reference to either the
order-entry clerk, manager, or supervisor objects. This worked fine,
because the number of objects and interfaces was small. However,
as the interfaces and objects increase in number, you would need
to declare each additional interface and assign your objects into the
Conclusion
In this article, weve seen several ways to use interfaces in day-to-day
programming. Once you start using interfaces, I think youll find them
an invaluable asset in your programming toolkit. Enjoy.
The nine example projects associated with this article are available
on the Delphi Informant Magazine Complete Works CD located in
INFORM\2002\JUN\DI200206LS.
Leo Seaman is an independent consultant who does training for Web Tech Training
and Development (http://www.WebTechCorp.com). The company has offices in the
United States and United Kingdom and specializes in Delphi training and development. Readers may reach Leo at leseaman@bellsouth.net.
Informant Spotlight
By Jerry Coffey
A Mature Market?
Delphi Informant Magazine
Readers Choice Awards 2002
ne has to ask the question. Things do seem to have settled down. New Delphirelated companies and products used to come out of the woodwork. True, some
would disappear just as quickly, leaving only a forlorn Web grave site, but many
would go on to thrive in the rough-and-tumble Delphi marketplace.
It was nearly impossible to keep up; putting together
each years ballot was a major learning experience,
trying to identify who was new and who had vanished. Even the number of categories used to expand
rapidly each year. The first ballot, in 1996, had 12
categories including Best VBX, if you can believe
it while this year only one new category, Best
Scheduling/Calendar, has been added.
The pace of change has slowed. This isnt necessarily a bad thing, of course; its a natural stage of any
BEST ACCOUNTING PACKAGE
BS/1 - 29%
Bravo - 21%
Informant Spotlight
BEST ADD-IN
Others - 11%
StarTeam - 5%
Class Explorer
6%
CodeRush
41%
ExpressOrgChart
Suite - 20%
VssConneXion
7%
teeChartPro
69%
Multi-Edit - 10%
Best Add-in
Then there are categories where little has changed over the years
at least in first place. CodeRush from Eagle Software won
easily (this time with 41% of the votes) for the fourth straight
year. However, a newcomer to the category, ModelMaker Code
Explorer from ModelMaker Tools, took runner-up with 20%,
while last years runner-up, Multi-Edit from American Cybernetics, fell to third with 10%.
You really like Steema Softwares teeChartPro! Its won in this category since the category itself was introduced in 1998 this year
with 69% of the votes. Thats five consecutive first-place finishes.
ExpressOrgChart Suite from Developer Express repeats as runner-up
for the second year in a row, with 20%.
BEST BOOK
Others - 18%
Building Kylix
Applications
7%
IP*Works!
Delphi Edition
11%
Mastering
Delphi 6
50%
Async Professional
75%
The Tomes
of Delphi:
Algorithms
and Data
Structures - 25%
IB Objects - 25%
Others - 35%
ASTA - 19%
Direct Oracle Access
Component Set - 10%
Informant Spotlight
IP*Works! Delphi Edition from /n software emerges from the
field to finish a distant second with 11%.
MultLang Suite - 8%
MULTILIZER
VCL Edition - 37%
Localizer - 25%
DBISAM
Database System
27%
FlashFiler - 20%
Apollo - 15%
Advantage
Database Server
27%
InfoPower - 36%
Rubicon Full
Text Search
8%
ForeHelp - 9%
RoboHELP Office
17%
HelpScribble - 26%
QuickDesk - 8%
Others - 17%
IB Expert - 13%
ExpressDBTree
Suite - 18%
ImageEn - 12%
LEADTOOLS
Imaging Toolkit - 29%
ImageLib
Corporate
Suite - 42%
Informant Spotlight
settle for third with 17%. Help & Manual must be something
special, because HelpScribble and RoboHELP had been one and
two for three consecutive years.
ModelMaker
40%
CDK - 13%
Others - 5%
DeployMaster - 7%
InstallShield
47%
with 40%, and Rational Rose from Rational Software was runner-up
with 21%. Last year, however, the numbers were 27% and 22% respectively, so ModelMakers doing something right for Delphi developers.
BEST REPORTING TOOL
Wise for
Windows
Installer - 41%
Others - 8%
ExpressPrinting
System - 7%
ReportPrinter
Pro - 11%
ReportBuilder
54%
FastReport - 20%
BEST LIBRARY
EIPack - 10%
SysTools
39%
ExpressBars Suite
36%
Others - 16%
Best Library
TurboPowers SysTools has topped this category since the categorys inception in 2000. Its margin of victory, however, has narrowed significantly.
In 2000 it won with 74% of the votes, and in 2001 with 53%. This year
it beat runner-up, ExpressBars Suite from Developer Express (in second
place again!), by just three percentage points, 39% to 36% respectively.
Jazmine
Calendar/PIM
Widgets - 18%
TMS
TPlanner/TDBPlanner
51%
Informant Spotlight
Best Scheduling/Calendar
This is a brand new category; in fact it wasnt even added until a week
into the voting. Not surprisingly therefore, it was the least popular with
only 9% of you voting in the category (actually its in a virtual tie for
last place with Accounting Package). TMS TPlanner/TDBPlanner from
TMS Software took inaugural honors handily with a dominating 51%
of the votes. Jazmine Calendar/PIM Widgets from Jazmine Components
made a respectable second-place showing with 18%.
BEST TESTING/DEBUGGING TOOL
reACT - 2%
Others - 19%
ASPack - 30%
VMware
Workstation
6%
Xceed Zip - 6%
FinalBuilder - 9%
AQtest - 9%
OnGuard - 18%
Beyond Compare - 12%
AQtime - 10%
NuMega
BoundsChecker
10%
BEST UTILITY
Sleuth QA
Suite - 45%
one with 31%, and ASPack number two with 24%. Beyond Compare
from Scooter Software made a good showing in third with 12%.
Developer Express tops this crowded category for the second straight
year with its ExpressQuantumGrid, getting 39% of the votes. Only
TurboPowers Orpheus managed to win in this volatile category two years
in a row, in 1998 and 1999, so its an impressive achievement. (That
was before the Best VCL Component Set category was created in 2000).
The runner-up is TAdvStringGrid from TMS Software with 18%, and
Abbrevia from TurboPower finished a close third with 16%.
BEST VCL COMPONENT
ExpressQuantumGrid
39%
Others - 20%
BEST TRAINING
TRichView - 7%
Others - 25%
Jensen Data
Systems - 26%
Abbrevia - 16%
TAdvStringGrid - 18%
The DSW
Group - 9%
InfoCan
Management
26%
Best Training
Heres something new. Jensen Data Systems came from nowhere
to tie past-winner InfoCan Management with 26% of the vote.
This was all the more remarkable because Jensen Data Systems has
never even placed before, while InfoCan has taken the top spot
the last four years running. Web Tech Training & Development
finished a decent third with 14%.
Best Utility
More action! ASPack Softwares ASPack took top honors with 30% of
the votes, while TurboPower Softwares OnGuard took runner-up with
18%. In other words, they traded places; last year, OnGuard was number
27 June 2002 Delphi Informant Magazine
Informant Spotlight
BEST VCL COMPONENT SET
ExpressQuantumPack
22%
Others - 29%
TMS Component
Pack - 16%
Raize Components
9%
1stClass - 11%
Orpheus - 13%
Conclusion
Hats off first to the creative, hard-working Delphi third-party
vendors who are the subject of these awards. The products they
create and the risks they take to bring them to the market are
what these awards are all about. Congratulations to every company on the ballot; without you there would be no Delphi community. And if your product wasnt on the ballot this year, please
let me know. This is intended as an exhaustive list of all commercial Delphi-related software. Freeware or open source software,
however, does not qualify for this ballot at least not until we all
start programming for free.
And finally, thank all of you in the Delphi community for voting.
These awards belong to you; we just tabulate the results and
reflect them back. Im honored to be part of the process, and a
member of the Delphi community the best bunch Ive worked
with in my 20+ years of programming.
Jerry Coffey is Editor-in-Chief of Delphi Informant Magazine. You can reach him at
jcoffey@DelphiZine.com.
Informant Spotlight
Contacting the Winners
Best Accounting Package
Accounting for Delphi
ColumbuSoft
Phone: (800) 692-2150
Web Site: http://www.columbusoft.com
BS/1
Davis Business Systems Ltd.
E-Mail: info@dbsonline.com
Web Site: http://www.dbsonline.com
Best Add-in
CodeRush
Eagle Software
Phone: (310) 441-4096
Web Site: http://www.eagle-software.com
Best Book
Mastering Delphi 6 by Marco Cant
Sybex
Web Site: http://www.sybex.com
Best Charting/Mapping Tool
teeChartPro
Steema Software
E-Mail: info@steema.com
Web Site: http://www.steema.com
Best Communications Tool
Async Professional
TurboPower Software
Phone: (800) 333-4160
Web Site: http://www.turbopower.com
Best Database Connectivity
IB Objects
Jason Wharton
E-Mail: jwharton@ibobjects.com
Web Site: http://www.ibobjects.com
Best Database Engine
Advantage Database Server
Extended Systems
E-Mail: info@extendsys.com
Web Site: http://www.extendsys.com
DBISAM Database System
Elevate Software
E-Mail: sales@elevate.com
Web Site: http://www.elevatesoft.com
Best Scheduling/Calendar
TMS TPlanner/TDBPlanner
TMS Software
E-Mail: info@tmssoftware.com
Web Site: http://www.tmssoftware.com
Best Training
InfoCan Management
Phone: (888) INFOCAN (463-6226)
Web Site: http://www.InfoCan.com
OP Tech
Data Storage / Resource Files / Delphi 2-6
By Bill Todd
surprisingly common question from Delphi programmers is: How can I store
data in an EXE? With resource files, Microsoft anticipated this need in the original design of Windows. Using a text editor and the Borland Resource Compiler, you
can add any kind of data to a Windows resource file, and then link that file into an
EXE. Once you have the data in an EXE, Delphi provides all the tools you need to use
it in your application.
You may be familiar with storing strings, icons,
cursors, or other Windows objects in resource files.
In this article, well look at some less traditional
types of data, including a ClientDataSet, the
contents of a ListBox, an integer array, bitmap and
JPEG images, sounds, and any type of ASCII data.
You can store any data in any format in a resource
file, and access it from your application using the
same techniques covered in this article.
OP Tech
procedure TfrmMain.btnSaveArrayClick(Sender: TObject);
var
A: array[0..9] of Integer;
AFile: TFileStream;
begin
A[0] := 1;
A[1] := 2;
A[2] := 3;
AFile := TFileStream.Create('IArray.dat', fmCreate);
try
AFile.Write(A, SizeOf(A));
finally
AFile.Free;
end;
end;
lowed the end of the array regardless of what those bytes contained.
Using the SizeOf function ensures that youll always write the whole
array, and nothing but the array, even if you change the arrays size.
To demonstrate using other types of data, the sample application
also includes a WAV file, a bitmap image, and a JPEG image. I
didnt need a Delphi program to create these files. They came
from other sources.
The next step is to create a Windows resource file that contains all
of the data files. First, you have to create the resource contents file
shown in Figure 3. This file, DEMO.RC, is an ASCII text file, so
you can create it with the Delphi editor, Notepad, or any other text
editor. Each line consists of three parts. The first is the resource name
or identifier number. I prefer names, but you can use an integer
number if you prefer.
The second item on each line identifies the type of data. Figure
4 shows the resource types defined by Microsoft. The values in
the left column are the names of the predefined constants for the
resource types. For example, the constant for the RCDATA type is
RT_RCDATA. In each case, the name of the constant is the name of
the resource type with RT_ attached. The third part of each entry is
the name of the file that contains the resource.
To compile the resource file, open a command prompt window and
enter the command:
BRCC32.EXE DEMO.RC
Value
Meaning
RT_ACCELERATOR
Accelerator table
RT_ANICURSOR
Animated cursor
RT_ANIICON
Animated icon
RT_BITMAP
Bitmap resource
RT_CURSOR
Hardware-dependent cursor
resource
RT_DIALOG
Dialog box
RT_DLGINCLUDE
RT_FONT
Font resource
RT_FONTDIR
RT_GROUP_CURSOR
Hardware-independent cursor
resource
RT_GROUP_ICON
Hardware-independent icon
resource
RT_HTML
HTML
RT_ICON
RT_MANIFEST
RT_MENU
Menu resource
RT_MESSAGETABLE
Message-table entry
RT_PLUGPLAY
RT_RCDATA
RT_STRING
string-table entry
RT_VERSION
Version resource
RT_VXD
VXD
the same directory as the RC file. The compiled resource file will have
the same base name as the RC file and the extension RES.
With the compiled resource file in hand, the next step is to create
an application that uses the resources. The sample application that
accompanies this article is called USEDATA.DPR. To get Delphi
to link the resource file into your program, use the $R compiler
directive shown in Figure 6. Figure 6 shows the beginning of the
implementation section of the main forms unit. Any unit associated
with a form will already contain a $R directive that links the forms
DFM file into the application. Instead of a full file name, this $R
directive uses the * wildcard to stand for the name of the unit. The
{$R *.dfm} directive tells Delphi to include the resource file that has
the same base name as the unit and the extension DFM. Figure 6
also shows that a second $R directive has been added to include the
DEMO.RES file. The order of the $R directives doesnt matter.
Figure 7 shows the main form of the USEDATA sample application
with the ClientDataSet and ListBox items resources loaded. Figure 8
shows the code for the Load CDS buttons OnClick event handler, which
declares an instance variable named RStream of type TResourceStream. A
ResourceStream is a stream object that reads data from a resource.
The first statement in the event handler creates an instance of the
TResourceStream class. The constructor takes three parameters.
The first is the Windows instance handle of the EXE or DLL
whose resources you want to read. This allows you to put your
OP Tech
implementation
uses
MainD, ImageF, MMSystem;
{$R *.dfm}
{$R Demo.res}
OP Tech
procedure TfrmMain.btnLoadListBoxClick(Sender: TObject);
var
RStream: TResourceStream;
begin
RStream := TResourceStream.Create(
HInstance, 'ListBox', RT_RCDATA);
try
ListBox1.Items.Clear;
ListBox1.Items.LoadFromStream(RStream);
finally
RStream.Free;
end;
end;
Loading a JPEG image is a bit more complex. The OnClick event handler for the View JPEG button is shown in Figure 15. To display a JPEG
image in an Image component, you have to create a TJpegImage object
and assign it to the Graphic property of TImage. The Jpeg variable used
in Figure 15 is declared as type TJpegImage in the forms type declaration.
The TJpegImage class has a LoadFromStream method, so a ResourceStream
object is created and the image is read from the ResourceStream by a call
to the TJpegImage.LoadFromStream method. The JpegImage instance
is assigned to the Image1.Picture objects Graphic property to make the
image visible in the Image object.
Playing a WAV file stored in a resource requires a different approach.
The Windows API function that plays sound files is SndPlaySound. It
takes two parameters. The first is a string that can hold a registry key or
33 June 2002 Delphi Informant Magazine
OP Tech
procedure TfrmImage.btnViewBitmapClick(Sender: TObject);
begin
Image1.Picture.Bitmap.LoadFromResourceName(
HInstance, 'Bitmap');
end;
contains the length of the string, the strings reference count, and the
string data. Since you want to load the contents of the resource into the
data area, the first parameter of RStream.Read must be S[1], i.e. the first
character of the string variables data.
Conclusion
procedure TfrmMain.btnPlaySoundClick(Sender: TObject);
var
FindHandle, ResHandle: THandle;
Sound: Pointer;
begin
// Get the location of the resource in the EXE file.
FindHandle := FindResource(
HInstance, 'Sound', RT_RCDATA);
if FindHandle <> 0 then begin
// Load the resource into memory.
ResHandle := LoadResource(HInstance, FindHandle);
if ResHandle <> 0 then begin
// Get a pointer to the location of
// the resource in memory.
Sound := LockResource(ResHandle);
// Play the sound.
if Assigned(Sound) then
SndPlaySound(Sound, SND_ASYNC or SND_MEMORY);
end; // if
end; // if
end;
In Development
Compiler Construction / Delphi 2-6
By Fernando Vicaria
his is the third part of my compiler construction and concepts series. Last month,
we added a few fundamental parts to the parser, such as embedded white spaces,
multi-character tokens, and code optimization. This month, in Part III, well give the
compiler the ability to recognize command-line parameters and introduce some
fundamental keywords to the language. Well also make it able to read the source
program directly from a file on disk, as opposed to line-by-line via the keyboard.
Delphis inline assembler, also known as BASM
(for built-in assembler), doesnt allow us to
reserve any memory storage directly, or at least
not in a safe way. Therefore, it will become
a little harder to debug the generated code
without adding more code manually, so well
use Delphi to debug and test the assembly code
generated by the compiler as much as possible.
var
x: Integer = 2;
i: Integer;
function foo: Integer;
asm
// Get whatever is in x...
MOV EAX, x
// and add 2 to it.
ADD EAX, 2
end;
function RunAsmCode: Integer;
asm
MOV EAX, 5
PUSH EAX
CALL foo
POP EBX
ADD EAX, EBX
MOV
i, EAX
end;
Lets consider, for example, the debugging routines shown in Figure 1. To debug a statement
such as:
i := 2 + foo;
In Development
procedure SkipBlank;
begin
// Ignore blanks.
while IsWhite(Lookahead) do begin
LookBack := Lookahead;
Read(F, Lookahead);
if (Lookahead = LF) then
Inc(LineNo);
// Handle comments.
if Lookback = '{' then begin
while Lookahead <> '}' do begin
Read(F, Lookahead);
if (Lookahead = LF) then
Inc(LineNo);
end;
Read(F, Lookahead);
if (Lookahead = LF) then
Inc(LineNo);
end;
end;
end;
Command-line Parameters
As you might have noticed in the new code for SkipBlank, weve
shifted from reading the code directly from the screen to providing the compiler with a source file. A typical call to the compiler
would look like this:
C:\Temp\fvc3.exe source.txt /s output.txt /o /t /c
Comments
Lets start by making the compiler recognize and ignore comments in the
source code. Keep the traditional Pascal format for multi-line comments:
begin
{ A comment that extends
through one or more lines. }
end;
The comments can appear at any point in the source file, even
between tokens:
x := { assignment to x } 3 + 5;
You can easily add more options to the list of parameters. The
code necessary to deal with the command-line parameters
contains one new enumerated type, a constant that maps the enumerated type, and a case statement inside the Initialize function,
shown in Listing One (on page 38). To list all available options,
just call the compiler from the DOS prompt with no parameters
(see Figure 3).
In Development
{ Procedure for the nonterminal ProgramBlock. }
procedure ProgramBlock;
begin
GetNextToken;
case GetTokenIndex(NextToken) of
tVar : begin
VarList := TStringList.Create;
VarDecl;
GetNextToken;
if GetTokenIndex(NextToken) = tConst then
begin
if not Assigned(ConstList) then
ConstList := TStringList.Create;
ConstDecl;
end
else if GetTokenIndex(NextToken) <> tBegin then
Error('Unexpected token or character');
end;
tConst: begin
ConstList := TStringList.Create;
ConstDecl;
GetNextToken;
if GetTokenIndex(NextToken) = tVar then
begin
if not Assigned(VarList) then
VarList := TStringList.Create;
VarDecl;
end
else if GetTokenIndex(NextToken) <> tBegin then
Error('Unexpected token or character');
end;
tProc : Error('"procedure" keyword not accepted yet');
tFunc : Error('"function" keyword not accepted yet');
tBegin: { Do nothing. };
else
Error('Unexpected token or character');
end;
MainBlock;
end;
program MyProg;
var
a, b: Integer;
c: string;
const
d = 'Hello world!';
e = 22;
begin
{ This is a multi-line
comment. }
a := 8 / 4 + e; { Another comment. }
b := { Mid-statement comment. } 1 + 4 * foo();
end.
A quick test in the main ProgramBlock has been added to debug and
37 June 2002 Delphi Informant Magazine
check the contents of the lists. Just make sure you set and compile
the project with the DEBUG condition defined.
Allocating Memory
Memory allocation will be done directly in the generated assembly
code, using place holders in the templ.asm file and the TStringList
objects (VarList and ConstList). There are many other ways to do
this, such as using streams or even keeping the whole template file
hard-coded inside the compilers code. Some of these alternatives are
more efficient than what were using, but the approach is to provide
the user a way to alter the template file so it can assemble under any
assembler. Dont worry too much about the contents of the template
file; it will be explained in more detail next month.
IMPORT32.LIB contains information concerning Windows symbols and APIs (its found in the include directory of TASM). Make
sure make.bat is in the same directory as the compiler and replace
FILENAME with the name and path of your program source.
Youll use TASM in all of the code in this series, but it shouldnt
be too difficult to convert it to NASM, or even Microsofts Macro
Assembler (MASM). Just make sure the template file is compliant
with your favorite assembler, and modify the batch to point to the
correct programs.
NASM is a free assembler that can be found at http://nasm.octium.net.
Its a portable assembler for the Intel 80x86 microprocessor series,
which uses the traditional Intel instruction mnemonics and
syntax. A list of other free assemblers and linkers can be found at
http://www.thefreecountry.com/developercity/asm.html.
A pause statement is included at the end of the batch file, so you can
see the results of the assembling and linking processes over the .asm
In Development
file. This will prevent the DOS window from closing until you hit a
key on your keyboard (this technique was used during debugging).
Once the compiler has been fully tested, you can remove it.
Conclusion
Things are taking shape with our compiler. Weve added a few command-line options, and some new keywords (program, begin, end, var,
and const) to the language syntax. The compiler can also understand
comments embedded in the source code, and read the source program
from a file on disk. In addition, the main output files are now the assembly code (the .asm file), and a 32-bit, protected-mode, DOS executable.
The language is finally starting to look a little more real. Next month,
well add some control structures, such as if and while, as well as some
basic I/O routines to get a fully operational compiler.
The project referenced in this article is available for download on the Delphi
Informant Complete Works CD located in INFORM\2002\JUN\
DI200206FV.
Inside OP
Currency Type / Floating-point Format / Delphi 2-6
By Vsevolod Ciumaciov
verybody knows that comparing numbers stored in floating-point format can lead
to inaccurate results especially when the numbers represent money. To remedy
this, Delphi 2.0 introduced a new type named Currency.
The idea is that Currency isnt susceptible to
rounding errors the way floating-point types are,
and should therefore be used in place of Single,
Real, Double, and Extended where money is
involved. Unfortunately, theres very little information about the Currency type in the Delphi
documentation. Ive asked colleagues and looked
through professional literature, and I have yet
to find a complete and reasonable explanation.
So I did some investigating on my own, and this
article is the result.
Floats
Now set one more breakpoint at the first statement reading: dCounter := dCounter + 0.05;
and re-run the procedure. After execution has
stopped at the first breakpoint, open the CPU
window by pressing CAC, and the FPU
window by pressing CAF. Make these
two windows easy to view and activate the CPU
window as shown in Figure 2.
Inside OP
Take off the fractional part and
obtain the following:
2-6 x112 = 310/6410=0.04687510
Repeat the same steps with different levels of precision:
2-10 x1100112=5110/
102410=0.049804687510
2-14 x11001100112=81910/163841
=0.0499877929687510
0
The greater the number of digits
participating in the calculation,
the more precise your result.
Figure 2: The
CPU window
at the first
breakpoint.
Currency
Figure 3: Loading .05 into the ST0 register as a Double.
(Visit developer.intel.com/design/pentium4/manuals/245470.htm
for detailed information on how FPU stores floating-point numbers.)
Now, transform the hexadecimal number located in the ST0
register into decimal format using the above formula. Microsofts
calculator, in Scientific mode, might be very helpful. Figures 4
and 5 can assist you with this procedure.
Hexadecimal
Binary
3FFA
0011.1111.1111.1010
CCCC
1100.1100.1100.1100
CCCC
1100.1100.1100.1100
CCCC
1100.1100.1100.1100
CCCD
1100.1100.1100.1101
Figure 4: ST0
register values in
hexadecimal and
binary.
Sign
02 = 010
Exponent
0111111111110102 = 1637810
Mantissa
1.100110011001100110011001100110011001
1001100110011001100110011012
Inside OP
in Currency format are stored as integers. Consequently, all calculations are executed with integers as well. Therefore, if you use the
Currency format rather than the floating-point format, you will
not face any problems concerning the accuracy of calculations.
However, Currency only allows the fractional part of four digits,
maximum, to be stored.
Conclusion
Should you calculate a satellites take-off-path, a quantity of atoms in
air cubic meters, or a soccer field in square inches, the floating-point
format would be best. However, should you want to develop a balance sheet for accounting purposes or develop a financial-reporting
package, the floating-point format is problematic. In such cases, the
fixed-point format type Currency is essential.
The project referenced in this article is available for download on the Delphi
Informant Complete Works CD located in INFORM\2002\JUN\
DI200206VC.
Vsevolod Ciumaciov is a programmer and analyst. He has been using Delphi for
almost six years. Readers may reach Ciumaciov at Ciumaciov_vs@yahoo.com.
ts rare these days to discover a rich Windows-client application that is not Internetenabled in some way. Sometimes an application simply has a hyperlink in an About
dialog box. Other times, a product has a complex feature, such as a newsgroupreading application that contains an optional routine to send the programs creator
SMTP e-mails when the application has encountered a bug. Whatever the feature, the
Internet has invaded the traditional client-application space.
To shield developers from the low-level complexities of writing interfaces to the WinSock library,
/n software has published its own solution. They
surveyed the Internet-protocol landscape for the
most popular applications and communication
methods, then wrote wrappers that make what once
was in the domain of TCP/IP experts available to, in
this case, Delphi developers who are trying to solve
business problems. With that objective in mind, /n
softwares fifth attempt provides the companys most
comprehensive Internet-component library yet.
Component Cornucopia
IP*Works 5.0 supplies developers with 34 Internetenabled components that assist with everything
from HTTP file transfers to telnet sessions (see
Figure 1). Version 5.0 has a few more component
objects than past versions, most notably for Simple
Object Access Protocol (SOAP) messaging and
Extensible Markup Language (XML) parsing.
Because most developers are familiar with the
basic services by now, such as FTP, MIME, and
Ping, this review will focus on the more complex and intriguing components provided in the
IP*Works collection. Incidentally, there are no
Figure 1: The IP*Works tab installed into the Delphi IDE offers 34 components.
42 June 2002 Delphi Informant Magazine
Internet-communication-encryption components
in the standard IP*Works package. Programmers
searching for Secure Sockets Layer (SSL) support will need to upgrade to the more expensive
IP*Works SSL edition. Although the SSL edition contains plenty of secure communication
components, I was dismayed with the omission
of a Secure Shell (SSH) component, especially as
insecure public telnet servers are being replaced
by SSH-only services. Nevertheless, almost every
other secure communication-standard component
is available in the SSL edition.
Figure 3: The
rudimentary
and overused
(but functional)
XMethods.net
StockQuote Web
Service, made
easily accessible
via IP*Works
SOAP component.
If your application calls for powerful, cross-platform Internetapplication clients and servers, IP*Works 5.0 will help you build
your solution faster. Its fast; it has 34 easy-to-use components
with comprehensive properties, methods, and events; its scalable;
it provides useful, easy-to-follow demos in Delphi-specific code
for every component in the package; and its the only package
(except for the components bundled with the Delphi 6 Enterprise
edition) that provides a commercial-grade SOAP-implementation
component. On the down-side: it requires two DLLs for run-time
deployment; close to 70 percent of the packaged components are
available for free or in shareware form; it offers no SSH components (theyre also absent in IP*Works 5.0 SSL edition); the Kylix
edition is a separate, considerably more expensive (US$545) package; and the XML-parser component is rudimentary.
/n software Inc.
P.O. Box 13821
Research Triangle Park, NC 27709
Phone: (919) 544-7770
Web Site: http://www.nsoftware.com
Price: US$345 for a single-developer license.
Mike Riley is a chief scientist with RR Donnelley, one of North Americas largest
printers. He participates in the companys emerging technology strategies using a
wide variety of distributed network technologies, including Delphi 6. Readers may
reach him at mike_riley_@hotmail.com.
IntraWeb
Delphi Web Development Made Easy
ntraWeb is a set of VCL objects that extend the capabilities of Delphi so you can
develop Web applications much as you would standard executables. Create Web
forms, add components, write events, and your project is done. In brief, AToZed
Softwares IntraWeb allows true RAD Web development.
IntraWeb installs smoothly. The process is
automated and includes putting all components
onto the Component palette and making necessary modifications to Delphi. A full installation
requires about 7MB of disk space. The uninstall
is just as clean, and removes all traces of IntraWeb
from Delphi without any manual cleanup.
I was impressed by IntraWebs demos, but
anyone can write good-looking demos. After
all, demos are sales pitches, and we all know
how much a sales pitch speaks about the actual
product. I was genuinely amazed when I saw the
source code to the demos, and began to understand why IntraWeb is so special.
Testing It Out
To create a new IntraWeb application, select File
| New | Other | IntraWeb | Stand Alone Application.
Complex Capabilities
Although the demo we just looked at is very simple, dont let
this fool you into thinking IntraWeb can do only simple things.
IntraWeb contains many demos, but my favorite is the dynamic
chart shown in Figure 5. This chart is live in the browser. The user
can play with the data without needing to request data from the
server. A user can choose columns, rows, and even functions. The
currently supported functions are Sum, Count, Minimum, Maximum, and Average. The dynamic chart is limited to bar charts and
has other limitations. For more advanced charting needs, IntraWeb
has support for TeeChart as well.
46 June 2002 Delphi Informant Magazine
Conclusion
With IntraWeb, you can leverage your existing Delphi skills to
develop fully functional Web applications and deploy them to a
standard browser with no more effort than developing a normal
Delphi application. In short, IntraWeb is Delphi.Web. If youre
involved in Web development or plan to be, you need to try
IntraWeb and see if it fits your needs. The product is compatible
with Delphi 5 and Delphi 6, both Professional and Enterprise. Kylix
and C++Builder versions are expected soon.
AToZed Software
P.O. Box 126
Erindale Center
ACT 2903
Australia
Phone: +61 2 62912702
Fax: +61 2 62912702
Web Site: http://www.atozedsoftware.com
Price: Developer Edition, US$499; Enterprise Edition, US$599.
There are no run-time, royalty, or deployment fees.
Paul Stockton has been a programmer with Barwick Systems Limited for 15 years.
He develops database applications for many large organizations in the United
Kingdom using both IBM AS/400s and PC systems. He is a jack-of-all-trades with
PCs, whether hes building them, configuring networks, doing Web or graphic
design, or dabbling with Delphi.
var
fs: TECLFileStream;
inBuf, outBuf: PChar;
offset, inSize, outSize: Integer;
begin
// Allocate buffers; set offsets and sizes.
// Create compressed, encrypted file.
fs := TECLFileStream.Create('test.dat', fmCreate,
'Password', eclFastest);
// Write some data.
fs.WriteBuffer(outBuf^,outSize);
// Seek to needed position.
fs.Seek(offset, soFromBeginning);
// Read some data.
fs.ReadBuffer(inBuf^,inSize);
// Close file.
fs.Free;
end;
Figure 1: Simply replace TFileStream with TECLFileStream to add compression and encryption capabilities.
Small Footprint
ECL compiles into executables, so no DLL or OCX files are
required. The minimum footprint of the library is 45K. When all
the encryption features and compression algorithms are enabled,
the ECL footprint is approximately 100K. Users of the version
with the source codes can easily configure the library to turn off
unnecessary features. That would reduce the size of the library to
its minimum size in case the encryption and compression eclGood
and eclMax algorithms arent activated.
ECL makes it possible to work with standard files in the eclNone
mode, which allows users to switch easily between the ECL format
with data compression (encryption) and the standard format. To
turn on the compression mode, you only need to open the existing
file and set the appropriate value of the CompressionLevel property.
In the same way, you can switch from the compression mode to the
usual format by setting the CompressionLevel property to eclNone.
The library works in Delphi 4, 5, and 6 and in C++Builder 4 and 5.
One of the most attractive features is progress indication; ECL offers
progress indication of any time-consuming operation with streams.
You only need to write the event-handler procedure and assign it to the
OnProgress event of the necessary stream. Progress is measured in the
floating-point format. ECL stream classes make it possible to display
the progress of both single Read and Write operations, and potentially
slow, complex operations such as the LoadFromStream, SaveToStream,
LoadFromFile, and SaveToFile operations.
An Example
Lets look at some examples of using ECL that illustrate its many
features, and the advantages of using this product in application
development. One example of the librarys use is for TFileStream with
encryption and compression. Assume you used the TFileStream for data
access, and now youd like your file to be compressed and encrypted. As
you can see in Figure 1, the required changes are minimal.
Another example is for low-memory consumption. Assume you
used TMemoryStream for working with some object in memory.
Using TECLMemoryStream consumes less memory.
Conclusion
Easy Compression Library is a superb compression and
encryption library based on the unique One Stream technology
that allows the same stream for compression and decompression.
This is the library of TFileStream, TMemoryStream, and other
TStream descendants with compression, decompression,
and encryption capabilities. Compression is transparent. All
the methods, properties, and behavior of TFileStream and
TMemoryStream are supported, so its very simple to replace them
in the application code.
TextFile
TextFile
Brian Burton
aspects of Linux API programming from a Kylix developers perspective. The first of eight chapters
serves as an introduction to Linux
and Kylix. Thereafter, each chapter illustrates a specific aspect of
Linux programming. The chapters
begin with introductory text and
example programs to explain the
main concepts. Most chapters
conclude with an API Reference
section with examples.
Some chapters contain a larger
sample program that illustrates
the concepts discussed. For
example, Chapter 3, Linux Input
and Output, includes a dBASE
file reader that lets you import
a dBASE file into a TClientDataset. At the end of Chapter 4,
Processes, Stephens presents a
daemon sample, the Linux equivalent of an NT service.
TextFile
File | New
Directions / Commentary
legend in the Borland development community, Jeff Duntemann has had an impressive and varied career as
a writer of fiction and non-fiction, editor, and publisher. After creating and editing Borlands magazine Turbo
Technix in the late 1980s, Jeff took over the Structured Programming column in Dr. Dobbs Journal, which he
wrote from 1989-1993. In 1989 he co-founded The Coriolis Group to publish PC Techniques magazine, and later
a line of popular technical books on programming, networking, and certification. PC Techniques became Visual
Developer Magazine in 1996, with Ray Konopka writing a regular column, first on Turbo Pascal and later on Delphi.
Jeff has written numerous popular technical books, including Complete Turbo Pascal, Borland Pascal from Square
One, and Assembly Language Step-by-Step, and was the lead author for The Delphi Programming EXplorer. He has
written and published science fiction for many years, and was on the final Hugo ballot in 1981. You can learn more
about Jeffs ideas on a variety of topics and contact him through his Web site at http://www.duntemann.com.
Delphi Informant: You worked as a technical writer at Borland in
the early years. Please share some of your history at the company,
how you came to join Borland, and some of your more memorable
experiences.
Jeff Duntemann: Philippe Kahn recruited me away from PC Tech
Journal, the Ziff-Davis magazine where I was Technical Editor from
1985 to early 1987. I interviewed toward the end of 1986, and he
roared me around Santa Cruz in his Porsche, talking too fast while I
left nailprints in the armrests. The job was actually to create a slick
programming magazine for him; I didnt do technical writing for
them until after I left the company. The Santa Cruz area is gonzo
city; I was constantly bumping elbows with Communists and nudists
and radical feminists and anarchists and many species of druggie. The
local radicfems regularly burned men in effigy in the town square. It
was street theater par excellance, especially on Halloween.
Doing Turbo Technix was great fun. It was big (160 pages), slick, full
of source code, and hosted some of the best writers in the business.
Borland sent it free to all registered users of its developer products,
and it wasnt sold in stores. This sent their registration rate through
the roof, and at the end we were mailing almost 225,000 copies of
the damned thing, all without a nickel of subscription revenue. Alas,
it was expensive, and what ads we could sell didnt float it. After just
six issues (and with a seventh finished and ready to print) they killed
it. I mourn that un-printed seventh issue; the cover story was a preemptive multitasking engine written in Turbo Pascal.
It was by far the best job I ever had. I used to eat lunch out on the
patio with Anders Hejlsberg, Gary Whizin, and the rest of the Turbo
gang, and argue about things like whether reserved words should be
bolded or capitalized. Anders allowed me to look at the source code
54 June 2002 Delphi Informant Magazine
File | New
JD: I remember Delphi 1 so poorly that Im not entirely sure what it
didnt have. The repository, probably. Maybe action lists. Maybe WinSight. What this means, of course, is that Borland got it almost precisely
right the first time. This is rare; the only other product I can say that
about is Visio. No one feature or group of features has dominated its
evolution since V1.0. The awesome integration of the entire package will
probably always remain Delphis best new feature.
Im still exploring D6, which is very deep. The new stuff on the high end
thats gotten so much buzz is still pretty immature in my view, so Im
still waiting to see how it changes over time. Im not a big champion of
doing things through the Web protocols, and that colors my view a little.
HTTP is a lousy way to move data around, and HTML is a lousy way to
show data. So Delphi features like WebBroker dont make the cut for me.
DI: What are your favorite Delphi add-ons: components, libraries, or tools?
JD: Orpheus 4, hands down. TurboPower rocks always has, always
will. Ditto Elevate Softwares DBISAM database engine. I also like the
Dream Company InfoTree components, which are at the heart of my
Aardmarks utility. And while its unclear whether its a tool or not, Id be
hard pressed to do any significant Delphi work these days without Torrys
Delphi Pages on the Web.
DI: You have been involved as a writer and editor of Visual
Developer Magazine and other popular technical journals. It seems
that such periodicals are becoming an endangered species. What
is your assessment of the current situation and what advice would
you give magazine publishers?
JD: Sigh. Magazines are in trouble, in part because programming
has gotten so interconnected and so deep. Doing DOS was easy,
and you could write articles with source code that both taught
something useful and could be covered at magazine length. Thats
no longer the case. Really gutsy technical coverage of programming has to be done at book length. The little stuff we used to put
in magazines is now free on the Web.
But its worse than that: Magazines need huge ad bases to prosper,
and products with user bases smaller than VBs cant always support the ad base that a dedicated magazine requires. The generalinterest programmer rags (like mine) died because programmers
specialize these days. They have to; there arent enough hours in
the day to be good at more than one or two languages or RAD
environments. As for advice, I dont know. If I felt I knew how to
win at the magazine game, Id still be playing it.
Finally, the very best way to be sure youve learned something yourself is to try to explain it to someone else!
DI: As a writer and editor, I suspect you are also a voracious
reader. What have you been reading lately? Do you have any
recommendations in the must-read category?
JD: Steven Pinkers How the Mind Works and The Language Instinct.
Kevin Kellys seminal Out of Control (I read that every couple of
years). The Lord of the Rings (ditto.) I read a good mix of science/
technology, theology (mostly Catholic theology), history, and
interdisciplinary works like Guns, Germs, and Steel and The Wealth
and Poverty of Nations. I read way less science fiction than I used to,
because publishing consolidation has tilted the field toward shallow,
interminable TV-like series that simply bore me.
Recommendations? Bowling Alone. Five Great Catholic Ideas. The Tipping
Point. Papal Sin. The Deep Hot Biosphere. A Distant Mirror. The Emperors
New Mind. The Bad Popes. Rare Earth. I also subscribe to Wired, Sky
& Telescope, and The Atlantic, and pick up odd books on odd topics
here and there, from herbs to the paranormal to the fourth dimension
to garage bands of the 60s. When Im not writing (or building things,
which is writing on a different kind of paper) Im reading. TV isnt even
on the radar.
DI: To conclude, are there any areas Ive not touched upon you
would like to talk about additional information you would like
to share with readers?
JD: Ive gone on way too long as it is. All Id like to do is make a suggestion: That Delphi people create small utilities and post them on Tucows
and places like that, with source code. Free or not free it doesnt
matter. (TurboPower has always shipped source with their products,
which are not free.) The idea is to remind those who download the
utilities that Delphi exists, and that you can create useful things with it in
almost no time compared to command-line C/C++ development, which
takes forever. When Aardmarks goes into general release, it will include
the source for my portions of it, at least (much of it is components). If
Delphi is to survive, it needs as broad a user base as we can give it. The
things that Delphi produces are the best advertisement that Delphi can
ever have. Programmers respect nothing so much as results. Exposing the
Big Lie (that Pascal is a kiddie language without power or utility) is the
single most important task for any Pascal/Delphi programmer. If we can
do that, we win. If we cant (or wont) do that, nothing else matters, and
we might as well hand the world to Bill Gates in a shopping bag.
This is a drastically abridged version of this interview. Please visit
http://www.DelphiZine.com/opinion to read the full-length version.
DI: As weve discussed, you have done a great deal of writing and editing.
What advise would you give an aspiring technical writer?
JD: First: Read! Read lots. Read the sorts of things you want to
write. Reading helps you develop a voice and shows you how the
other guys do it. Second: Write! Write lots. You dont become a
programmer by not programming, and writing is just programming
for the semantic compiler inside the human brain. Selling technical writing is tougher than it used to be (we have fewer magazines
now) but its way easier than selling, say, science fiction. People who
have a specialty should contact the magazines serving that specialty
and ask the editor what he or she needs. Thats how I started. Thats
how many other people have started. Writing is an excellent way
to get street cred in the industry, speaking gigs, and other perks.
Alan Moore is a professor at Kentucky State University, where he teaches music theory
and humanities. He was named Distinguished Professor for 2001-2002. He has
developed education-related applications with the Borland languages for more than
15 years. Hes the author of The Tomes of Delphi: Win32 Multimedia API (Wordware
Publishing, 2000) and co-author (with John Penman) of an upcoming book in the
Tomes series on Communications APIs. He also has published a number of articles in
various technical journals. Using Delphi, he specializes in writing custom components
and implementing multimedia capabilities in applications, particularly sound and
music. You can reach Alan on the Internet at acmdoc@aol.com.