Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Delphi 7
Web Services
UDDI
Browser,
SOAP
Attachments,
Custom
Headers,
and More
Cover Art By: Arthur A. Dugoni Jr.
ON THE COVER
5
First Look
22
Delphi 101
Showing by doing, Bill Todd introduces the expanded Web Service features of
Delphi 7 including its UDDI browser, and SOAP attachment and custom SOAP
headers capabilities by sharing demonstration client and server applications.
Rick Spence makes it easy to start writing components with this two-part
series. This month he explains how to create a component based on an
existing class, general rules for component writing, tips, pitfalls, and more.
FEATURES
REVIEWS
10
27
On the Net
Borland R&D Engineer Corbin Dunn shows how you can easily write a program
that submits a URL to search engines, ads to classified sites, shareware to
shareware sites, or anything else that involves filling out a form on a Web site.
13
31
34
Greater Delphi
17
Sound+Vision
WPTools Version 4
Exploring the design, database, and statistical issues, Fred Edberg shares his
BVStatClientDataSet component, which provides Delphi developers with an easy
way to incorporate basic statistical functions into their applications.
AQtime 2.0
DEPARTMENTS
2
Delphi Tools
35
Symposium
DelphiZineNOW
Your subscription to Delphi Informant just got more valuable, more timely,
more varied, and more fun. Thats because weve launched an e-newsletter companion to our print magazine. Its named DelphiZineNOW, and
if youre a subscriber to DI and we have your e-mail address youve
already received the premiere issue.
DelphiZineNOW will be sent free to all subscribers including trial subscribers. It will be monthly to begin with, and I plan to produce it twicea-month as soon as I can. In each issue Ill point readers to links of interest
on www.DelphiZine.com. Much of the content will appear in the print
magazine; however, some material will never appear in DI. Everything, of
course, will be published at www.DelphiZine.com.
Its free, but dont miss out. Just because youre a subscriber doesnt
mean that we necessarily have your correct e-mail address or any email address for that matter. To check, just go to www.DelphiZine.com
and select Change of Address from the menu on the left-hand side of
the page. That way youll get DelphiZineNOW delivered to your mail
box every month.
So what can you expect from DelphiZineNOW? For one thing, its a great
way to find out about new content on the DelphiZine Web site. And, via
DelphiZineNOW, youll receive links to material before anyone else. Theres
a lot going on in the Delphi/Borland universe, now and in the near future,
so its a particularly important time to stay current.
Take Delphi 7 Studio for instance. It was first discussed in the July 2002
issue of DI in my editorial in which I shared the startling amount of
information made available at the annual Borland conference. Delphi 7
Studio got the full-length article treatment by Cary Jensen in the October
issue. DelphiZineNOW readers would have had that material available to
them up to a month earlier.
Now Delphi for .NET (not its real name) is just around the corner.
Weve already had in-depth articles about this exciting new departure
for Delphi and Borland outstanding among them was Jon Shemitz
Borlands .NET Plans which appeared in the August 2002 DI. Our
coverage has continued ever since, and weve got some great .NETrelated material coming up, from an in-depth series that introduces the
.NET Framework to Delphi developers (by Alexei Fedorov and Natalia
Elmanova), to an example-laden exploration of the Delphi for .NET
Compiler Preview (by Jani Jrvinen).
Which brings up an interesting aside. Alexei and Natalia (who are Russian), and Jani (a Finn) are excellent examples of just how international
the Delphi developer community has become. In fact, when we mailed
the December 2002 Delphi Informant, 49% were sent to non-U.S. subscribers. If youre an international subscriber, DelphiZineNOW is even
more valuable. Depending on the vagaries of the worlds various postal
services, you could be getting your content a full two months earlier, or
even sooner.
And although you can always get in touch with me Ive long contended that Im all-too-easy to contact the e-newsletter gives you yet
another opportunity (excuse?) to send an e-mail my way. Im always
keenly interested to hear what you have to say, positive and/or negative,
about our humble publications. I dont hear from you often enough, so
please let me know what you think.
Thanks for reading.
Delphi
T O O L S
New Products
and Solutions
Book Picks
Windows XP Under the Hood
Brian Knittel
QUE
development environment. It
will support connector roles
and add markup components
to use when mapping pattern
strategies; expand and
document the Source Code
Markup Language (SCML)
capabilities; extend support for
Struts 1.1; add Customer Tag,
XML Page, and HTML Page
TurboPower announced
Memory Sleuth 3, a major new
upgrade to its debugging tool
for professional Windows
programmers. Memory
Sleuth 3 works by watching
a Windows application while
it runs. It then reports any
problems it finds during
the applications execution.
Unlike some other debugging
products, Memory Sleuth
requires no changes to a
programs source code; a
Memory Sleuth user simply
support, an updated
SDL_Mixer with an Effects API,
and demo directories split into
2D- and 3D-related subdirectories.
The JEDI-SDL project is also
available on Source Forge
(http://sf.net/projects/jedi-sdl/).
Project JEDI
Price: Free
Contact: Dominique@
SavageSoftware.com.au
Web Site: www.delphi-jedi.org/
Jedi:TEAM_SDL_HOME
Delphi
T O O L S
New Products
and Solutions
Book Picks
.NET Development for
Java Programmers
Paul Gibbons
Apress
ISBN: 1-59059-038-4
Cover Price: US$49.95
(432 pages)
www.apress.com
Hacking Exposed
Web Applications
Joel Scambray and Mike Shema
McGraw-Hill Osborne
ISBN: 0-07-222438-X
Cover Price: US$49.99
(386 pages)
www.osborne.com
MySQLDAC, RemObjects
SDK, ReportBuilder, and
SourceConneXion+. The
retail price of all of these
components is over US$4,000.
Project Dionysus offers all
these tools for US$1,795.
Project Dionysus
Price: US$1,795
Contact: support@projectdionysus.com
Web Site: www.projectdionysus.com
eHelp Corp.
Price: See Web site for pricing.
Contact: robohelpinfo@ehelp.com
Web Site: www.ehelp.com
First Look
Web Services / UDDI / SOAP / Delphi 7
By Bill Todd
elphi 6 provided pioneering support for Web Service clients and servers. Delphi 7
expands those capabilities with important new features, starting with a UDDI browser.
UDDI is an acronym for Universal Description, Discovery, and Integration. UDDI provides a
standard way to register Web Services in any of the public registries available on the Internet.
The UDDI browser is integrated into the WSDL
Import Wizard in the Object Repository. To use
it, just select File | New | Other to open the Object
Repository, then click the Web Services tab.
Double-click the WSDL Importer icon to display the
WSDL Import Wizard shown in Figure 1. Then
click the Search UDDI button to open the UDDI
Browser shown in Figure 2.
Drop down the Registry combo box to choose one of
the pre-defined public registries. To use a registry that
isnt listed, just type its name into the combo box. This
lets you use a private UDDI registry on your local
network, or any other registry with which you want to
work. Since UDDI registries are organized hierarchically by business name and then service, you need to
enter the name of the business you want to search for
in the Business Name edit box. This is a starts with
Using Attachments
Delphi 7 adds support for SOAP attachments via
the TSOAPAttachment object, a descendant of
TRemotable. The best way to understand SOAP
attachments is by using them in an application. Begin
by creating a SOAP server application following the
steps in my article Building Web Services: Part I
in the July 2002 Delphi Informant Magazine (also
available online at http://www.delphizine.com/
features/2002/07/di200207bt_f/di200207bt_f.asp).
Name the server AttachmentDemo. This server will
be able to send and receive both binary and text files
as SOAP attachments. Figure 3 shows the invokable
interface declaration from the AttachmentDemoIntf
unit, with four methods added.
First Look
the TSOAPAttachment object
are passed as parameters. The
declarations for the GetTextFile
and SendTextFile methods
are identical, although their
implementations are different.
Figure 4 shows the
implementation of the
GetImageFile method. All
you have to do is create the
TSOAPAttachment object and
call its SetSourceFile method
to specify the name of the
file that contains the data
to be sent as an attachment.
Calling SetSourceFile causes the
TSOAPAttachment object to
load the file and send it to the
client. The TSOAPAttachment
object is freed automatically
after the client has received the
attachment.
First Look
that will handle the encoding and decoding
for you. You can use TSOAPAttachments
Encoding property to tell the client how the
attachment is encoded. Valid values are:
BASE64
QUOTED-PRINTABLE
8BIT
7BIT
BINARY
x -EncodingName
TObject);
var
Attachment: TSOAPAttachment;
begin
try
// Get the file.
Attachment := (RIO as IAttachmentDemo).GetImageFile(
FileNameEdit.Text);
// Save the file.
Attachment.SaveToFile(FileNameEdit.Text);
// Show the attachment headers in the memo.
Memo.Lines.Clear;
Memo.Lines := Attachment.Headers;
// Show the file name and size.
ShowMessage('File ' + FileNameEdit.Text +
' successfully saved. ' + 'File size = ' +
Attachment.Headers.Values['Content-Length'] +
' bytes.');
finally
Attachment.Free;
end;
end;
Figure 11 shows the Send File buttons OnClick event handler. This
method creates a TSOAPAttachment instance and assigns the Memo
First Look
procedure TMainForm.GetTextBtnClick(Sender: TObject);
var
Attachment: TSOAPAttachment;
StrStream: TStringStream;
begin
try
Attachment := (RIO as IAttachmentDemo).GetTextFile(
TextFileNameEdit.Text);
StrStream := TStringStream.Create('');
try
Attachment.SaveToStream(StrStream);
Memo.Lines.Clear;
Memo.Lines.Add(StrStream.DataString);
finally
StrStream.Free;
end;
finally
Attachment.Free;
end;
end;
type
TSecurityHeader = class(TSOAPHeader)
private
FSecurityCode: Integer;
published
property SecurityCode: Integer
read FSecurityCode write FSecurityCode;
end;
Figure 10: The text Get File buttons OnClick event handler.
procedure TMainForm.SendTextBtnClick(Sender: TObject);
var
Attachment: TSOAPAttachment;
begin
Attachment := TSOAPAttachment.Create;
try
Attachment.SourceString := Memo.Text;
(RIO as IAttachmentDemo).SendTextFile(
TextFileNameEdit.Text, Attachment);
ShowMessage('File ' + TextFileNameEdit.Text +
' sent successfully.');
finally
Attachment.Free;
end;
end;
Figure 11: The text Send File buttons OnClick event handler.
that string along with the value of the SecurityCode property of the
TSecurityHeader object shown in Figure 12. The first line simply
assigns the Msg parameter to the Result string.
Access to the message headers is provided by the ISOAPHeaders
interface. ISOAPHeaders is implemented by TInvokableClass. The class
created by the SOAP server application wizard, THeaderDemo in this
example, descends from TInvokableClass. This means you can get a reference to ISOAPHeaders by casting Self as ISOAPHeaders. In Figure 14, the
ISOAPHeaders interface reference is assigned to the Headers variable.
The next step is to get a reference to the header in which youre
interested. Two variables, ClientHeader and ServerHeader, are used to
hold references to TSecurityHeader objects. To get a reference to the
header received from the client as part of the call to the ReturnHeader
method, the ISOAPHeaders Get method is called with two parameters.
The first is the class name of the header to which you want a reference,
and the second is the variable to which the reference will be assigned.
If the header isnt found, the call to Get will set ClientHeader to nil.
The if statement ensures that ClientHeader has been assigned a value
before using it to get the value of the SecurityCode property. The value of
First Look
The next line in the OnClick event handler calls the servers ReturnHeader
method and passes the string "Header from client: " as its parameter.
The remaining code clears the Memo control and displays the header
returned by the server. The call to ISOAPHeaders.Get receives a reference
to the TSecurityHeader returned by the server and assigns it to the
ClientHeader variable. If a TSecurityHeader was returned, its SecurityCode
property is converted to a string and displayed in the Memo. As you can
see, custom headers provide a convenient way to pass any number of
values back and forth between the client and server.
Conclusion
In addition to the major new features described here, there are
many other new features to help you implement SOAP services in
Delphi 7. These include new events on several components, the
ability to implement your own routines to handle the conversion
of remotable objects to and from their SOAP representation, and
ERemotableException exceptions. See Whats new in Delphi 7 in
the online help for details.
The sample server and client projects referenced in this article are
available on the Delphi Informant Magazine Complete Works CD
located in INFORM\2002\DEC\DI200212BT.
Bill Todd is president of The Database Group, Inc., a database consulting and
development firm based near Phoenix. He is co-author of four database programming books, author of more than 90 articles, a contributing editor to Delphi Informant Magazine, and a member of Team B, which provides technical support on the
Borland Internet newsgroups. Bill is a nationally known trainer and is a frequent
speaker at Borland Developer Conferences in the United States and Europe. He has
taught Delphi programming classes across the country and overseas. Readers may
reach him at bill@dbginc.com.
On the Net
HTML Forms / Indy / Delphi
By Corbin Dunn
ow many times have you visited a Web site and had to fill out the same
information again and again? Probably more times than you wanted. Luckily, as
a programmer, you can automate the process of submitting forms on the Web.
You can easily write a program that submits a
URL to search engines, ads to classified sites,
shareware to shareware sites, or anything else
that involves filling out a form on a Web site.
In this article, youll see how to submit a simple
guest-book entry on my Web site at http://
www.cruzio.com/~seaweb/corbin/guestbook.html.
On the Net
procedure TForm1.FormCreate(Sender: TObject);
begin
SetLength(FTempFileName, MAX_PATH + 1);
GetTempFileName(PChar(ExtractFilePath(
Application.ExeName)), 'crb', 0,
PChar(FTempFileName));
SetLength(FTempFileName, StrLen(PChar(FTempFileName)));
{ Delete the temp file that Windows just created... }
DeleteFile(FTempFileName);
{ ...and make it an HTML temp file. }
FTempFileName := FTempFileName + '.html';
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if FileExists(FTempFileName) then
DeleteFile(FTempFileName)
end;
Note the question mark that separates the URL from the encoded string.
Once the post is done, the code saves the HTML response
into a string for easy searching. By looking for some keywords
on the resulting HTML page, you can determine if the post
was successful or not. In this case, if you find the title of my
page, Corbins Guestbook and Treehouse Comments Page, it
was successful. Otherwise, this simple example will show the
resulting HTML page using ShellExecute.
Quite often, a CGI program will redirect the user to a new page,
instead of sending a response back directly. With the TIdHTTP
component, you can handle this easily by following the redirect in
the OnRedirect event. Add the code shown in Figure 4 to do this.
Thats It!
Now, you have the power to submit any HTML form by writing
some simple code. Feel free to download the associated code
for this article. It contains the complete source and a few more
tidbits of information. From search engines to your favorite
informational sites, youve probably already thought of a few
time-saving ways to apply this technique...
The example project referenced in this article is available on
the Delphi Informant Magazine Complete Works CD located in
INFORM\2002\DEC\DI200212CD.
On the Net
Begin Listing One
Generating the Request; Reading the Response
if MessageDlg(
'Thanks for submitting your entry!' +
#13#10 +
'Would you like to see resulting page?',
var
ShellExecute(Handle, 'open',
PostStream: TStringStream;
PostStr: string;
SW_NORMAL);
ResponseStream: TMemoryStream;
end
ResponseStr: string;
else
begin
begin
'comments=' + URLEncode(memComments.Lines.Text);
begin
PostStream := TStringStream.Create(PostStr);
{ Length of <title>. }
try
StartPos := StartPos + 7;
ResponseStream := TMemoryStream.Create;
ResponseStr := Copy(ResponseStr,
try
MessageDlg(
FResponseStream := ResponseStream;
httpClient.Post(
#13#10 + ResponseStr,mtError,[mbOK],0);
'http://www.cruzio.com/~seaweb/corbin/guestbook.cgi',
end
PostStream, ResponseStream);
ShellExecute(Handle, 'open',
search it. }
SetLength(ResponseStr, ResponseStream.Size);
SW_NORMAL);
ResponseStream.Position := 0;
end;
ResponseStream.ReadBuffer(
end
ResponseStr[1], ResponseStream.Size);
else
ResponseStream.SaveToFile(FTempFileName);
if httpClient.ResponseCode = 200 then
begin
{ Successful HTTP request. See if post worked; if
it took us back to main Guestbook page, then it
worked. Otherwise, something else happened.
Generally, you can look for success keywords
such as "Thank You" }
if Pos('Corbin's Guestbook and Treehouse ' +
'Comments Page', ResponseStr) > 0 then
begin
Greater Delphi
Statistical Analysis / MIDAS / Database / Delphi 6
By Fred Edberg
Statistically Enable
Your Delphi Apps
Linear Regression, Clustering, Variance, and More
raditionally, database applications provide extensive capabilities for adding, editing, storing, and reporting data. Few, however, have extended such functionality
to make it easy to understand or interpret that data. In this article, Ill demonstrate
methods to incorporate basic but useful statistical functionality into data-based applications. Often, you can add powerful functions with just a few lines of code.
Greater Delphi
public
QueryStatRec: TBVRegStatRec;
X: TDataArray;
Y: TDataArray;
Residuals: TDataArray;
PredictedY: TDataArray;
CIUpper, CILower: TDataArray;
PIUpper, PILower: TDataArray;
function CalcRegression: TRegStatRec;
function BVRegressionMemo: string;
...
property N: Integer read fN write fN;
property DF: Integer read fDF write fDF;
property R: Extended read fR write fR;
property XMean: Extended read fXMean write fXMean;
property YMean: Extended read fYMean write fYMean;
property Beta0: Extended read fBeta0 write fBeta0;
property Beta1: Extended read fBeta1 write fBeta1;
property SSXX: Extended read fSSXX write fSSXX;
property SSYY: Extended read fSSYY write fSSYY;
property SSXY: Extended read fSSXY write fSSXY;
property SumX: Extended read fSumX write fSumX;
property SumY: Extended read fSumY write fSumY;
property SumXY: Extended read fSumXY write fSumXY;
property SumX2: Extended read fSumX2 write fSumX2;
property SumY2: Extended read fSumY2 write fSumY2;
property XVariance: Extended
read fXVariance write fXVariance;
property YVariance: Extended
read fYVariance write fYVariance;
property XStdDev: Extended read fXStdDev write fXStdDev;
property YStdDev: Extended read fYStdDev write fYStdDev;
property XMin: Extended read fXMin write fXMin;
property XMax: Extended read fXMax write fXMax;
property YMin: Extended read fYMin write fYMin;
property YMax: Extended read fYMax write fYMax;
property T: Extended read fT write fT;
property RegressionPrepared: Boolean
read fRegressionPrepared write fRegressionPrepared;
...
published
property SignificanceLevel: Integer
read FSignificanceLevel write FSignificanceLevel;
property XField: string read fXField write fXField;
property YField: string read fYField write fYField;
...
end;
Statistical Analyses
Before going into more specifics of the technique, let me identify
some important assumptions. First, the data should consist of
samples taken randomly from a larger population of observations (in the examples here, observations are synonymous with
records in a data set). Second, the data values (field values, to
be technically precise) must be of numerical type, e.g. of type
Float or Integer and must represent interval-scale data. Another
important assumption is that the data are independent no data
point should rely upon, or be tied to, other data points in a serial
or other manner. Also, the data shall exhibit a roughly normalshaped distribution. A graphical scatterplot display of the X and
Y data points is often useful to assess the degree to which the data
conforms to the normality assumption.
14 December 2002 Delphi Informant Magazine
Greater Delphi
Example 1
The first example is a very simple one to find the correlation coefficient
r, using a simple function call and a ShowMessage dialog box. This
example will introduce you to the most basic steps to use the component. You will find the degree to which two fields are associated the
BETA and CUR_PRICE fields from the data set master.xml.
Here are the steps:
Create a new application (File | New | Application).
Add a Button component to the main form, then double-click
it to create an OnClick event handler.
Add a BVStatClientDataSet component to the form, set its
FileName property to master.xml, and set the Active property to
True.
Select the BVStatClientDataSet, and enter BETA for the XField
property, and CUR_PRICE for the YField property.
Insert the following code into the Button1.OnClick event
handler:
procedure TForm1.BVStatClientDataSet1CalcFields(
DataSet: TDataSet);
begin
with DataSet as TBVStatClientDataSet do begin
FieldByName('PredictedY').AsFloat :=
PredictedY[RecNo-1];
FieldByName('Residual').AsFloat :=
Residuals[RecNo-1];
end;
end;
Example 2
In the second example, you will create a new application project
and add DBGrid, DataSource, and BVStatClientDataSet
components to the main form to display the contents of the source
data set. To make things more interesting, youll add columns to
the data set and a DBGrid to contain the residuals and predicted
values, using the same X and Y input fields. That way, you expose
to your user other important information in the same data grid.
Here are the steps: First, create a new application. Second, add
these components to the main form: BVStatClientDataSet
(named BVStatClientDataSet1), DataSource (DataSource1),
DBGrid (DBGrid1), DBNavigator (DBNavigator1), and Button
(Button1).
Third, assign the component properties. Set the DataSource1.DataSet
property to BVStatClientDataSet1. Set the DataSource property of
both DBGrid1 and DBNavigator1 to DataSource1. Set the FileName
property of BVStatClientDataSet1 to master.xml (youll use an XML
data set for this example). Specify the fields to use in the regression
procedure. Set the XField property to BETA and set YField to CUR_
PRICE. (You must type these field names into the corresponding box
in the Object Inspector). Double-click Button1 to create an OnClick
event handler for it. Next, create calculated fields to expose both the
predicted values of BETA and the associated residuals (the predicted
value of Beta minus the actual value of BETA) for each record.
Double-click BVStatClientDataSet1 to view its Fields editor.
Then, right-click and select Add Field. Add both the BETA field
and CUR_PRICE fields. Next, create two new calculated fields.
15 December 2002 Delphi Informant Magazine
Right-click and select New Field to display the New Field dialog
box. In the dialog box, enter PredictedY for Name, then select
Float for Type and Calculated for Field Type. In the same way, create
a second field and enter Residual for Name, then select Float for
Type and Calculated for Field Type.
Now, add an OnCalcFields event handler to the data set and insert a
few lines of code, as shown in Figure 3. This code will populate the
new fields with the corresponding array data, calculated internally by
the component using this code.
Fourth, you can add the main method, CalcRegression, into the desired
event handler to compute the summary statistics. In this case, you
Greater Delphi
will use the Button1Click event handler youve
already defined. To ensure that your view of the
data is consistent, add a reference to the First
method to force you back to the top of the data
set. Then run the application to view the results.
Figure 4 shows the example at run time.
Finally, you can add a few additional lines
of code into the example to expose other
interesting capabilities. Add a doubleclick handler for the data grid. Within
the handler, you will find and display the
confidence and prediction intervals on the
currently selected records predicted Y value.
You will display these values using a few
Label components and format functions.
Do these steps: Select DBGrid1. Set its
options to allow SelectedRows, and define
a DblClick event handler. Add three Label
components onto the main form in the
lower, left corner. Insert the following code
into the DBGrid1.DblClick event handler:
Figure 6: This example demonstrates several of the concepts outlined in this article. Here, I
have provided options for the user to select among significance levels and to filter the data set.
The residuals and confidence intervals are displayed graphically using a TeeChart component.
Now, run the application. Your result should look similar to the form
shown in Figure 5.
This additional code simply shows the predicted value of Y for the
selected record, and displays the upper and lower bounding intervals
(prediction and confidence) on that predicted Y value. The current
SignificanceLevel property value is used in computing these intervals.
Three fixed significance levels are available: .90, .95, and .99. Greater
values produce wider intervals, and smaller levels produce narrower
intervals. Note that the prediction and confidence intervals are
defined by a minimum and maximum value, and calculated results
are stored in separate but corresponding arrays.
Finally, let me offer a demonstration that might suggest other interesting
ways to use the component. Figure 6 illustrates the concepts outlined
previously, with additional functionality, as well. In this example, I
show the basic regression concepts combined with a graphical display
of the regression line and confidence or prediction intervals. Figure 6
also uses a simple TeeChart to provide a non-tabular view of the data.
Here, the input X and Y data points are displayed as a series of points in
a TeeChart. Both confidence and prediction intervals were added as line
series, as well. (Predicted Y values are displayed as a dark green line, for
reference.) The display conveys the overall distribution of the data points
and the fit of your regression line to the data points. Such displays often
can be easier to interpret than a table of numbers.
Here, Ive included several other summary statistical properties to display. I also have allowed the user to choose a significance level. I have
provided the user the ability to compare a subset of records, using a
filter applied to the data set.
16 December 2002 Delphi Informant Magazine
Conclusion
There is tremendous potential for improvement. Displaying the
statistical summary fields in the Object Inspector would be an
improvement. Adding fields to the data set to contain the summary
statistics as the residuals, the predicted values, confidence intervals,
and prediction intervals generated from the regression summary
would eliminate the need to do this manually. The logic used to set
the statistical summary properties could be enhanced through the
use of more complete get and set methods.
From here, the next step would be to address the more complex statistical techniques, such as multivariate regression, analysis of variance, and
clustering. But that, as they say, will have to wait until another day.
I used the following sources as references when writing this article:
Applied Regression Analysis and Other Multivariable Methods, Second
Edition by Kleinbaum, Kupper, and Muller (PWS-Kent Publishing
Company, Boston, MA, 1988).
Delphi 6 Developers Guide (Borland Software Corporation, Scotts
Valley, CA, 2001)
The components and projects referenced in this article are available
on the Delphi Informant Magazine Complete Works CD located in
INFORM\2002\DEC\DI200212FE.
Fred Edberg is a Delphi developer living and working in the Portland area. He is a
former Windows developer with a regional health-maintenance organization, where
he maintained and enhanced a client-server practice-management application that
processed accounts receivable, billing, claims, etc. Earlier, he worked for the US Army as
a geographic information systems (GIS) specialist. He developed a GIS for planning and
managing natural resources at a domestic military installation. Fred earned a bachelors
degree and a masters degree in the geosciences, specializing in digital mapping
technologies. He completed his thesis research with the development and comparison
of statistical classification algorithms for satellite image data. He is interested in data
analysis, statistics, and image processing, especially using Delphi. Fred plays Spanish
and South American tunes on the classical guitar, when time allows.
Sound+Vision
Genetic Algorithms / Shortest Path / Delphi 5-7
By Fernando Vicaria
Genetic Algorithms
Or, What Does Natural Selection Have to Do with a
Traveling Salesperson?
enetic algorithms have been around for the past three decades, but theyre only now
finding a place in the mainstream of application development. In the past few years
in fact, the use of genetic algorithms in software engineering has increased dramatically.
In this article, well go through the most important points of the theory behind genetic
algorithms and find out how we can apply that theory to real-life problems.
For the finale, well test everything weve learned
by building a Delphi project to solve the wellknown problem of the traveling salesperson, using
a genetic algorithm. The idea is to determine the
shortest path for a salesperson visiting multiple
cities, without the salesperson passing through the
same city more than once.
Genetic algorithms are search algorithms based on
the mechanics of natural selection and genetics.
They combine a survival-of-the-fittest approach to
string structures, with a structured-yet-randomized information exchange. Genetic algorithms
are stochastic by nature. They supply sub-optimal
solutions without any boundaries on their quality,
and do not necessarily provide polynomial running
times. Empirically, however, genetic algorithms
often provide a successful trade-off between optimality and speed.
Genetic algorithms are part of evolutionary
computing, which is a rapidly increasing area of
artificial intelligence. In the last decade, genetic
algorithms have been applied to a variety of computational problems in place of more classic algorithms. The main goal behind the development
of genetic algorithms is robustness that is, the
balance between efficiency and efficacy necessary
for survival in many different environments.
Imitation of Life
The idea of a genetic algorithm is to imitate life
and the way it evolves toward the form thats
most suitable for a specific set of conditions, e.g.
17 December 2002 Delphi Informant Magazine
Sound+Vision
to look for the exact solution, but many methods are available to help
us find a suitable one, i.e. not necessarily the best solution. Solutions
include hill climbing, simulated annealing, and genetic algorithms.
The main advantage of genetic algorithms when compared with
other search algorithms is that genetic algorithms arent closely tied to
the problems they solve, and therefore can be easily adapted to solve
a different problem with little or no modification. This could reduce
or even eliminate costs in redesigning systems. Genetic algorithms
are ideal for situations in which higher levels of adaptation and
robustness are the main areas of concern. Genetic algorithms are
theoretically and empirically proven to provide both adaptability and
robustness in complex search spaces.
Once they learn how to encode the solutions of a given problem into
chromosomes, nearly everyone can benefit from genetic algorithms
and compare their relative performance (fitness). An effective geneticalgorithm representation and meaningful fitness evaluation are the
keys to success in genetic-algorithm applications.
As a general rule, genetic algorithms are useful and efficient when:
The search space is large, complex, or poorly understood.
Domain knowledge is scarce, or expert knowledge is difficult to
encode to narrow the search space.
No mathematical analysis is available.
Traditional search methods fail.
The advantages of using genetic algorithms in such situations are that:
They require no knowledge of the search space.
Discontinuities in the search space have little effect on the overall
optimization.
They resist becoming trapped in local optima.
They provide an excellent (and easy-to-tune) balance between
speed and efficiency for large-scale optimization problems.
This represents only the idea behind evolutionary and genetic algorithms; there are no hard and fast rules. You are free to modify it to
fit the details of a particular problem you want to solve. For example,
your test condition can be based on a local best, or on a predetermined number of generations to which to evolve.
The representation or encoding of the variables being optimized
has a large impact on search performance because the optimization
is performed on this representation of the variables. Most of the
developmental work of the genetic-algorithm theory was performed
using a binary-encoded genetic algorithm historically, the most
widely used representation. In a binary encoding, each chromosome
is vector comprised of zeroes and ones, with each bit representing a
gene. Well see how that works soon.
Before we start building our solution to the traveling-salesperson
problem, we need to go over some important genetic-algorithm
concepts. Ive mentioned some of them already:
Organism. One of the elements in the population. Each organism or individual is a complete solution to a problem. Each
individual is represented by a finite string of symbols known as a
genome, which encodes the solution.
Natural selection. The process we use to eliminate the weaker
organisms in a population. Natural selection is responsible for
killing a specific percentage of the weaker individuals. The process used for choosing these individuals can vary.
Elitism. For our purposes, elitism is the process in which we
use the fitness criteria to determine which organisms should die
and which ones should generate offspring. Elitism is also used to
decide if a particular mutation is beneficial. Elitism can increase
performance of genetic algorithms very rapidly because the best
solutions are never discarded.
Crossover. A genetically inspired operator applied to a population (solution set). Crossover is performed with a probability
of pi (the crossover probability or crossover rate) between two
selected individuals (parents) by exchanging parts of their
genomes (encodings) to form new individuals (offspring). This
operator tends to enable the evolutionary process to move toward
promising regions of the search space.
Mutation. As with crossover, mutation is also an operator used
during the generation of the offspring. The mutation operator is
introduced to prevent premature convergence to local optima by randomly sampling new points in the search space. Mutation is carried
out by flipping bits at random, with some (small) probability of pj.
A Simple Example
Because encoding the chromosomes is usually the trickiest part of
building a successful model for a genetic algorithm, well start with a
very simple example. The problem were going to solve, using derivatives, is probably something you can remember from your calculus
classes in high school. We need to find the maximum value of the
following function:
y = -x2 + 8x + 15
To simplify this example, well assume that the maximum is between
0 and 25 (the actual maximum is x = 4), and that the maximum is an
integer value.
For simplicitys sake, were also using a quadratic function that contains only one local maximum, which is also the absolute maximum
of the function. Local maximums and minimums are easy to find by
looking at the places where the derivative of the function is 0, that
Sound+Vision
is y=0. For functions of
higher order, a local maximum or minimum isnt
necessarily the absolute.
Chromosome
Decoded Value
f(x)
Fitness
Number of
Selections
00001
00101
00010
27
0.35
22
00111
22
0.34
10110
22
-293
0.008
01011
11
-18
0.3
10110
Chromosome
f(x)
Relative Fitness
00101
25
0.04
01101
13
169
0.25
10110
22
484
0.71
Population
00010
00111
10110
01011
Population
00010
00111
Population
00011
00110
01010
10
00011
3
We usually check at the
end of each generation
Figure 7: The new set of organisms
for some predefined
(or solutions).
termination criteria thats
based on the quality of the solution found, or the number of
generations evolved. Its up to you to decide which one will suit
your problem best.
Sound+Vision
TSolution = class(TObject)
private
FProblem: TTSP;
FCount: Integer;
FTogo: Integer;
FCities: array of Integer;
FPath: array of Integer;
procedure InitSolution;
procedure Swap(i, j: Integer);
function FirstAvail: Integer;
public
constructor Create(const Problem: TTSP);
destructor Destroy; override;
procedure RandomRoadTrip;
procedure ForwardRoadTrip(City: Integer);
procedure BackwardRoadTrip(City: Integer);
procedure RandPath;
procedure Mutate;
function Distance: TDistance;
function Visited(City: Integer): Boolean;
function Complete: Boolean;
function GetCity(Index: Integer): Integer;
function GetPath(City: Integer): Integer;
function PlaceCity(City, Index: Integer): Boolean;
end;
TGA = class(TObject)
private
FProblem: TTSP;
FPopulation: array of TSolution;
FPopSize: Integer;
FRankings: array of TOrganism;
FGen: Integer;
FMutRate: TPercentage;
FNatSelRate: TPercentage;
FBestSol: TDistance;
FWorstSol: TDistance;
FBestOverall: TDistance;
FWorstOverall: TDistance;
FOnNewGenComplete: TNewGenEvent;
procedure BigBang;
procedure Genocide;
procedure Rank;
procedure NaturalSelection;
procedure Reproduce;
function Crossover(Mom, Dad: Integer): TSolution;
procedure Mutate;
procedure NewGenComplete(Gen: Integer);
public
constructor Create(const Problem: TTSP;
Pop, Gen: Integer; const MutRate: TPercentage = 20;
const NatSelRate: TPercentage = 30);
destructor Destroy; override;
procedure Evolve(var StopFlag: Boolean);
property BestSol: TDistance read FBestSol;
property WorstSol: TDistance read FWorstSol;
property BestOverall: TDistance read FBestOverall;
property WorstOverall: TDistance read FWorstOverall;
property OnNewGenComplete: TNewGenEvent
read FOnNewGenComplete write FOnNewGenComplete;
end;
Once you create your TGA object, you must attach an event handler
to its NewGenComplete event. This event will fire at the end of each
generation, and provide you with information about the generation
just created. Well use this to extract the current generation number
and the best solution of that generation.
Our fitness function will be the total distance our salesperson travels.
The shorter the distance, the fitter the organism will be. Well also
use elitism to choose which organisms will reproduce, and which
ones will die. This way, we can guarantee that the best solutions will
survive to the next generation.
Sound+Vision
{ Evolves the population through the generations. }
procedure TGA.Evolve(var StopFlag: Boolean);
var
i: Integer;
begin
for i:=1 to FGen do begin
NaturalSelection;
Reproduce;
Mutate;
Rank;
NewGenComplete(i);
if StopFlag then
Break;
end;
end;
Conclusion
Figure 13: The user interface for our traveling-salesperson solution showing the best path for our traveling salesperson.
TSolution = class(TChromosome)
// Specific code goes here...
end;
and:
TGASolver = class(TGA)
// Specific code goes here...
end;
Further Reading
Genetic Algorithms in Search, Optimization and Machine
I hope this article gives you a feel for what can be done with
genetic algorithms. The main difficulties in developing genetic
algorithms are probably encoding the chromosomes and the way
we build the analogy between the natural and artificial systems.
However, after a few tries, this process becomes easier.
Fernando Vicaria is a quality-assurance engineer at Borland Software Corporation in Scotts Valley, CA. Hes also a freelance technical author for Delphi and
C++Builder issues. Fernando is specialized in VCL and CLX frameworks, and,
when hes not at work, hes probably surfing at some secret spot in Northern
California. He can be reached via e-mail at fvicaria@borland.com.
Delphi 101
Classes / Components / Delphi 4-7
By Rick Spence
n July, I showed you a class that I use to manage the SQL statements in my applications. I
keep the SQL statements external (in a database), and the class gives me access to these
statements (see Separate, but SQL in the July 2002 issue of Delphi Informant). In that
article I mentioned that it was a trivial matter to convert the class to a component. Since
then, Ive received requests to supply the component, hence this article.
First Ill explain the general approach to converting
a class to a component, why you would want to do
so, some rules for component writing in general,
and some things to be careful of. Then Ill take the
SQL class from the previous article and transform
it into a non-visual component. After that, Ill
show how to sub-class TQuery to allow it to use the
external SQL statements. Next month, Ill show
you how to write a property editor that will allow
the component user to select the appropriate stored
SQL statements.
Why Components?
Why would you want to convert a class to a
component? Im certainly not proposing that you
blindly take all your classes and remake them as
components. Some classes are fine as they are,
and you wont gain anything by making them
components. In general, components offer three
advantages over classes:
1) Delphi takes care of creating and destroying
components for you, whereas classes must be
explicitly instantiated and destroyed using
code.
2) You can set properties at design time, rather
than only using code at run time.
3) With visual components, you can position and
size the component at design time.
22 December 2002 Delphi Informant Magazine
So the benefits arent as great with non-visual components, such as a Query, or FileOpenDialog. Since
youre not concerned with positioning or sizing the
component, the only advantages are that you can set
properties at design time, and that Delphi will create
and destroy the component for you.
However, dont underestimate the advantages of
setting properties at design time. In addition to
the ease with which you can set properties (and
thereby link components), you can also write
property editors that allow only valid values. As an
example of a property editor, consider a data-aware
control such as DBEdit. After you have set the
DataSource property, you must set the DataField
property to reference a field from the associated
data sources data set. The property editor will list
only valid fields, preventing you from entering a
bad field name. Thats a great advantage, and next
month Ill show how to do that with our TQuery
subclass. Well link it to our external SQL manager,
and allow users to select the SQL statement to use,
restricting them to entries in the table that stores
the SQL statements.
Its also important to note that developing
components can be a time-consuming process,
and most of the time someone will have created a
Delphi 101
component that fills your need, or is close to filling your need. These
may be commercial components such as those you see advertised in
this magazine, or even freeware. Whether you create components
yourself, use others components, or modify others components for
your purposes, this article will get you started.
Rule Four. To integrate your components into the IDE, they must
be stored in design-time packages. You must create the package, add
your components (Pascal files) to it, compile the package, and install
it into the IDE.
There are five hard and fast rules you must adhere to when writing
your own components. There are also some conventions you should
follow, which Ill discuss in the next section.
Rule One. All components must descend from the VCLs TComponent
class. They dont have to directly descend from TComponent; your
component could descend from another class which ultimately
descends from TComponent. For example, you would inherit
from TWinControl for a visual control that can receive focus. The
class your component inherits from is called your components
ancestor, and your first decision when creating a new component
is to decide its ancestor.
Rule Two. If you write a constructor for your component, the constructor must be declared using the override directive, it must accept
one parameter (of type TComponent), and it must call its ancestors
constructor. This is because your constructor is overriding the constructor in TComponent, which is declared as:
constructor Create(AOwner: TComponent); virtual;
Rule Five. The final rule concerns the placement of your components
in the IDE. You must decide which Component palette page they
will appear on, either an existing page (such as Samples), or (preferably) your own page.
Heres how this works. When you install your component into
the IDE, Delphi calls a procedure in your components file named
Register (note the initial uppercase R, this is case sensitive). You
must declare the Register procedure in your programs interface
section so its visible to other units.
You must write this Register procedure to call another routine named
RegisterComponents. You must pass RegisterComponents the name of
the page on which you want your components installed, and an array
of components (if youre registering just one component your array
will have only one entry). Heres an example:
procedure Register;
begin
RegisterComponents('Samples', [TWTExternalSQL]);
end;
Conventions
The first convention concerns naming your component. You already
know (from writing classes) that all class names should be prefixed
with the letter T. When you write components, theres an additional
convention to prefix the component name with a few letters to indicate
the author/company responsible for writing the component. Component
names must be unique, so this helps avoid the possibility of duplicate
names. I use the letters WT (for WebTech), for example, while
Woll2Woll Software (developers of InfoPower and 1stClass) use WW.
The Delphi Prefix Registry Web site (http://www.delphiprefixregistry.net)
is an attempt to centralize the registration of component prefixes (and I
just realized my prefix is not registered there).
Another convention concerns the names of properties, the fields
used to store those properties, and the get/set functions you can
optionally use to read from or write to those properties. If the
property you are publishing is named DataSet, the convention is to
name the field (instance variable) which actually stores this property
as fDataSet, and to store this in the class private or protected section, as shown here:
TWTExternalSQL = class(TComponent)
private
All material onTDataSet;
this CD-ROM 1995-2001 Informant Communications Group, Inc. Elk Grove, CalifDataSet:
fornia, United States of America. Informant is a registered trademark of Informant Communications
published
Group, Inc. Delphi is a trademark of Borland Software Corporation.
property DataSet: TDataSet read fDataSet write fDataSet;
end;
Delphi 101
TWTExternalSQL = class(TComponent)
private
fdataSet: TDataSet;
protected
function GetTDataSet: TDataSet; virtual;
procedure SetTDataSet(const Value: TDataSet); virtual;
published
property DataSet: TDataSet
read GetTDataSet write SetTDataSet;
end;
type
TExternalSQL = class
public
constructor Create(dbName, tblName: string);
function GetSQL(SQLName: string): string;
destructor Destroy; override;
protected
fTable : TTable;
end;
implementation
{ TExternalSQL }
constructor TExternalSQL.Create(dbName, tblName: string);
begin
fTable := TTable.Create(nil);
fTable.DatabaseName := dbName;
fTable.TableName := tblName;
fTable.Open;
end;
destructor TExternalSQL.Destroy;
begin
fTable.Close;
fTable.Free;
inherited;
end;
function TExternalSQL.GetSQL(SQLName: string): string;
begin
if not fTable.Locate('SQLName', SQLName,
[loCaseInsensitive]) then
Raise Exception.Create(SQLName + 'Not found');
Result := fTable.FieldByName('sqlCode').AsString;
end;
Some developers prefer to place the field in the protected section, so its visible to subclasses. I prefer to place it in the private
section, then use protected virtual accessor functions so those
functions can be accessed and/or overridden by subclasses. If you
use accessor functions (functions which Delphi calls to read/write
the properties) rather than a simple redirection (as we used in the
previous example), the convention is to prefix the read function
name with Get, and the write function name with Set, as
shown in Figure 1.
To use this class, the user must first instantiate it, passing the name of the
database and the name of the table which stores the SQL statements:
externalSQL :=
TExternalSQL.Create('ExternalSQL', 'SQLStatements.db');
Converting to Component
The first issue to consider is which class our component should
inherit from. This ones easy; because were creating a non-visual
component, well inherit directly from TComponent, thus:
TWTExternalSQL = class(TComponent)
Delphi 101
type
TWTExternalSQL = class(TComponent)
private
fdataSet : TDataSet;
protected
function GetTDataSet: TDataSet; virtual;
procedure SetTDataSet(const Value: TDataSet); virtual;
public
function GetSQL(SQLName: string): string;
procedure Notification(AComponent: TComponent;
Operation: TOperation); override;
destructor Destroy; override;
published
property DataSet: TDataSet
read GetTDataSet write SetTDataSet;
end;
procedure Register;
implementation
{ TExternalSQL }
function TWTExternalSQL.GetSQL(SQLName: string): string;
begin
if not fDataSet.Active then
fDataSet.Open;
if not fDataSet.Locate('SQLName', SQLName,
[loCaseInsensitive]) then
Raise
Exception.Create(SQLName + 'Not found');
Result := fDataSet.FieldByName('sqlCode').AsString;
end;
function TWTExternalSQL.GetTDataSet: TDataSet;
begin
Result := Self.fDataSet;
end;
procedure TWTExternalSQL.SetTDataSet(
const Value: TDataSet);
begin
fDataSet := Value;
end;
procedure TWTExternalSQL.Notification(
AComponent: TComponent; Operation: TOperation);
begin
inherited;
if (AComponent = Self.DataSet) and
(Operation = opRemove) then
DataSet := nil;
end;
procedure Register;
begin
RegisterComponents('WebTech', [TWTExternalSQL]);
end;
into memory and remains there while the application runs. (Code
accompanying this article is available for download; see end of
article for details.)
Subclassing TQuery
Now that we have ExternalSQL as a component, we need to create
a subclass of Delphis TQuery class. The new subclass must be able
to access the ExternalSQL object, and allow the user to specify the
name of the external SQL statement to use. Initially well develop it
so the user can enter any name for ExternalSQL, i.e. we wont check
for validity. In next months article, Ill show you how to develop a
property editor that will display the names of the SQL statements
from the table and even allow users to change and test the SQL
statements from within the form designer.
The previous section showed how to create a component from
scratch. What I think is more common is to inherit from an existing
component and change it. And thats exactly what were going to do
in this section. Well create a new component based upon TQuery,
and add two new properties: one named ExternalSQL of type
TWTExternalSQL, and another named SQLName of type TSQLName.
In other words, ExternalSQL will be a reference to our
TWTExternalSQL object, and SQLName the name of the SQL
statement stored in the database table. Note that I declare its type as
TSQLName, which in turn I declare as:
type
TSQLName = string
So, if we can have our code execute when Active is set to True, we
can ignore Open. The question is how can we get our code to execute
when the Active property is set? Further digging in TDataSet reveals
the public property Active declared as:
property Active: Boolean
read GetActive write SetActive default False;
Delphi 101
Its protected, which means we can override it. Its also virtual,
so if we do override it our version will still be called even when
it is referenced from within its own class (as when Open calls
it). Thats a key point you need to understand regarding the
virtual directive. If SetActive wasnt declared as virtual, our
version wouldnt be called when Open sets active to True. This
is because the call would be statically bound (i.e. the compiler
would generate code to directly access the SetActive method in
TDataSet), whereas we need it to call the method in our subclass.
We need the call dynamically bound, which is what we get when
we declare it as virtual. Refer back to the section on conventions
earlier in this article, and note that I recommend you declare your
own properties this way.
Our class, then, only needs to override the SetActive method,
and when called, retrieve the external SQL and load it into the
components SQL property. Listing One shows the complete
type declaration and implementation. As you can see, theres not
much to it. We declare the two new properties as private and
write protected accessor functions. We also need to override the
Notification event, so we can reset the wtExternalSQL property if
the user deletes it. And we override the SetActive method, so we
can copy our SQL from the database table into the SQL property.
Summary
This article demonstrates that its a fairly straightforward task
to convert simple classes into components. The biggest problem
youll encounter is changing the class constructor. There are
some rules you must follow when creating components, and some
conventions you should follow. These were discussed in detail.
We also saw how to create a new component based on an existing
component. We were fortunate in that the ancestor component
declared the method we needed to override as virtual. Sometimes
we arent as lucky; either a key piece of information we need in an
ancestor class is private (rather than protected), or a method we
need to override isnt declared as virtual. In these cases I suggest
you locate the component author and send him or her threatening
e-mail, or make disparaging comments about his or her parents
(just kidding about the e-mail).
Next month, Ill show you how to create a property editor that will allow
the component user to select the SQL statement from a list, and to test
the statements from inside the IDE. See you then.
The package and demonstration project referenced in this article are
available on the Delphi Informant Magazine Complete Works CD
located in INFORM\2002\DEC\DI200212RS.
procedure TWTExternalQuery.SetWTExternalSQL(
const Value: TWTExternalSQL);
begin
fwtExternalSQL := Value;
end;
AQtime 2.0
Profiler Makes You Look Like a Guru
ext time Im in Las Vegas, I have an obligation: Ill be buying lunch for the team
at AutomatedQA. In the April 2002 issue of Delphi Informant, I had great things
to say about AQtest, now known as TestComplete (see http://www.delphizine.com/
productreviews/2002/04/di200204rl_p/di200204rl_p.asp). AQtest has completely revolutionized my testing processes. Im also quickly becoming dependent on AQdevTeam,
although its still in beta at the time of this writing. Similarly, I sense that it will change
my project management methods. And, the subject of this review, the second version of
AQtime, is quickly making me look like an absolute guru to my clients.
Are your ordering procedures taking too long? Is
your legacy application leaking memory? Do you
want to make sure your program works with Windows XP? AQtime is the answer for positive results.
Im also using it behind the scenes to improve and
watchdog many other performance areas of my
current projects, to great advantage.
AQtime is a profiler what exactly is that? The
quick answer is that a profiler monitors code performance. A function profiler can track how much
Overview
Getting started with AQtime is straightforward.
For Delphi applications and modules, your
project must be compiled with certain compiler
settings that make your module transparent to
AQtime. Then you need to open your compiled
executable within AQtime and select one of its
19 profilers. Some profilers will require your
module to be executed, so they can trace the
executing code. Others get what they need from
the debug information embedded in the module.
Some profilers can seriously degrade the performance of an application, and you may not
wish to profile your entire application at once.
AQtime offers the ability to specify which parts
of your module will be profiled and at what
points during execution the profiler will be
active. The interface provided to make these
specifications is found on the Setup tab of the
Figure 2: AQtimes Results page after a profiling session using the Function Profiler.
Nineteen Profilers
The current version of AQtime (2.04) contains 19 profilers (up from 17
in version 2.0). The following is an abbreviated look at these profilers.
Static Analysis: This profiler analyzes the debug information in the
profiled module to discover information about sizes and addresses of
routines and units, binary code, number of source lines per routine, etc.
It doesnt actually run the module to perform this discovery, so its quick.
Besides the obvious uses of such information, this particular profiler is
helpful when your application raises an exception with nothing but an
address; you can run this speedy profiler to find the offending method.
Function Coverage: This profiler simply checks to see whether each
routine set to be profiled actually executed during the profiling
session. The Report panel displays a red or green dot for each listed
routine, indicating whether it executed.
Line Coverage (Grouped by Function): Similar to Function Coverage,
this profiler goes much deeper and determines whether each line of code
for the profiled routines executes during the profiling session. In the
Report panel, the results are grouped by source functions.
Line Coverage (Grouped by File): Similar to the previous profiler,
this profiler runs a little quicker since it groups by source file rather
than function. This profiler is an excellent way to quickly see if
sections of your code are executing.
Function HitCount: A full function profile can sometimes be
overkill, so use Function HitCount when you simply want to get
29 December 2002 Delphi Informant Magazine
tines that execute quickly will go unnoticed, while slow routines will
be caught. Since this profiler doesnt noticeably slow the executing
module, its a good profiler to run over the whole application, prior
to running the Function Profiler.
Line Sampling: Similar to Function Sampling, the Line Sampling profiler counts the number of times each line of code is executing when the
profiler polls the profiled module.
Platform Compliance: This profiler runs quickly (the profiled application need not be executed) and provides information about whether the
profiled application can work on a specific operating system.
Memory and API Resource Check Profiler: Run this profiler to track
your modules calls to API functions and memory allocation. It will
check whether the profiled application correctly creates and releases allocated resources, analyze the use of GetMem, FreeMem, ReallocMem, New,
and Dispose, and trace other memory-related functionality.
BDE SQL Profiler: The BDE SQL Profiler times the execution of SQL
queries and stored procedures through the BDE.
ATL Reference Count Profiler: This last profiler isnt available for
Delphi applications because it applies only to applications compiled
using Borland C++Builder or Microsoft Visual C++.
Conclusion
AQtime will change the way you approach application performance. If
youre concerned with improving the speed of your applications, or their
memory management, the profiling power of AQtime and its robust
data presentation should make it an obvious choice for your development toolkit. If youre still not sure AQtime will revolutionize your
development, AutomatedQA is they offer a 60-day unconditional
money-back guarantee.
Robert is a Delphi developer who has been writing code since he discovered AppleBasic on his Apple II+. He now provides custom creative solutions and Delphi tool
training via his company, Thoughtsmithy (http://www.thoughtsmithy.com). He is
working on his Masters degree in Music Theory at the University of North Texas
and currently resides in Texas with his wife and daughters.
WPTools Version 4
Text Manipulation Library for Delphi and C++Builder
Component
Purpose
TWPRichText
TDBWPRichText
TWPEdit
TWPRichTextLabel
TWPPreview
An Arsenal of Word-processing
Components
WPTools consists of about 20 components that
provide everything you need to add modern
word-processing support to any application.
At the heart of the library are the five RTF
components summarized in Figure 1. All
of these display, and most allow editing of,
formatted text.
Chief among the main RTF components and
at the heart of any word processing support
is TWPRichText. Since it relies on WPTools
TWPToolCtrl
TWPToolsBasicCustomAction
TWPComboBox
TWPToolButton
TWPValueEdit
TWPShadedPanel
Figure 4: An example program showing various text formats and integration with Addict
Spelling Checking library.
32 December 2002 Delphi Informant Magazine
Conclusion
WPTools is an outstanding word processing library for Delphi
or C++Builder. It contains everything you need to add word
processing support or to work with formatted text in any
application. I recommend it highly.
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 been named
the Project JEDI Director for 2002-2003. 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.
TextFile
File | New
Directions / Commentary
here might be something to numerology the notion that numbers have mystical meaning or power. For example,
in some Asian cultures the number four is considered unlucky. Recalling the initial problems with Delphi 4, especially
its premature release with those annoying bugs, numerology seems worth considering. What about the number seven?
It seems that seven is considered auspicious in many cultures. So, will Delphi 7 prove to be a great success for Borland?
Its much too soon to say for sure. But with that question in mind, well explore Delphi 7s new features, its expanded
component library, and a few early user reactions.
Delphi and Windows. One important issue that comes up with each
new version of Delphi is its level of support for current Windows
incarnations and other important new technologies. Delphi 7 supports many of the new features in recent Windows versions, such as
XPs Themes and common controls. In terms of emerging technologies, support for Web Services seems particularly important. As in the
previous version, Delphi 7 includes seven Web Services components.
However, there are additional enhancements and even more example
programs (something I find especially helpful). As many readers are
aware, Simple Object Access Protocol (SOAP) is one of the most
popular technologies for packaging Web Services messages. Delphis
SOAP support has increased considerably; Delphi 7 now includes
classes and interfaces to support reading or inserting headers into the
SOAP envelopes that transmit messages between clients and servers.
And thats just one of several enhancements.
Providing Web Services is also one of the main elements of Microsofts
.NET strategy. Not only does Delphi 7 provide good support for Web
Services in general, it provides incipient support for .NET, including
a Migration Kit for moving to that platform. There is also an early
version of the Delphi for .NET compiler; unfortunately, this compiler
is apparently not integrated into the development environment. When
I shared this with Borlands John Kastner, he informed me that there
is an unsupported OTA (Open Tools Add-in) for using the Delphi for
.NET compiler preview thats available on BDN (Borland Developer
Network). The Delphi dcc32 compiler itself has also been enhanced with
.NET in mind. That compiler includes three additional compiler warnings to help developers prepare code for porting to .NET: Unsafe_Type,
Unsafe_Code, and Unsafe_Cast.
Of course, Delphi continues to provide strong support for its three
traditional development areas: Windows GUI desktop applications,
database applications, and Web applications. Be aware that in the latter
two categories some of the new and continuing support is limited to the
high-end versions; a small subset is found only in the Enterprise Edition.
There are many changes in the area of database development, with some
components being enhanced and others being depreciated. DbExpress
is becoming increasingly important with each new Delphi version. Its
drivers have been updated to support Informix SE, Oracle 9i, DB2
7.2, InterBase 6.5, and MySQL 3.23.49, and a driver has been added
to support MSSQL 2000. There are also new and enhanced database
components. A new unit, DBClientActns, includes three new action
components for working with client datasets: TClientDataSetApply,
TClientDataSetUndo, and TClientDataSetRevert.
35 December 2002 Delphi Informant Magazine
File | New
The Enterprise Edition includes ModelMaker, a UML-based add-on to
simplify working with classes and interfaces (see Robert Leaheys review
of ModelMaker in the August issue of Delphi Informant Magazine).
This is one of my favorite new features, and something I expect to be
working with a great deal. Although there is less printed documentation than in earlier Delphi versions, electronic documentation in the
form of PDF, HTML, and INT files has mushroomed. In fact, theres
so much that Borland had to put it on one of the Companion Tools
CDs. There are now two!
Should you upgrade? This was a particularly hot topic in some of the
discussions I saw online. Some developers complained about the price
of Delphi compared to Microsoft products. Others pointed out that
upgrade prices are much less than purchasing it new. And when the
question of bugs came up, most developers indicated they had not
encountered anything serious.
Quite a few folks were interested in comparing Delphi with Microsofts
Visual Studio .NET. One developer pointed out that upgrades to
Microsofts development tools occur much less frequently than those
to Borlands tools. (Im not convinced that less frequent upgrades are
really desirable; I think it makes a lot of sense to keep up to date with
the newest technologies.) Another developer talked about positive
experiences with C#. Another Delphi columnist suggested that Borland
should consider making Delphi for .NET a plug-in that could be used
in that environment. Again, Im not convinced this would be a good
business move for Borland, although it has a certain superficial appeal. A
few in the discussion groups were quite candid about their past Delphi
upgrade histories; here are two examples: 1, 2, 3, 5 and 1, 3, 5, with a
strong intention of upgrading to Delphi 7. It struck me that this second
individual might be a student of numerology who believes in the magic
of odd-numbered Delphi versions.