You are on page 1of 60

BLAISE PASCAL MAGAZINE

ALL ABOUT DELPHI AND DELPHI PRISM(.Net) , LAZARUS & PASCAL


AND RELATED LANGUAGES

Pascal

19
Procedure Speech(Sender:TMonkey)
Begin
Monkey := 'Hello World';
End;

DataSnap connectivity for iOS using Delphi XE2 and FireMonkey


By Anders Ohlsson
Creating a simple webserver in Lazarus Michael van Canneyt
Using Amazon S3 in Delphi XE2 By Marco Cant
Creating a Database program from scratch 2 By D.Overbeek
Writing New Components in Lazarus, Part 4 Howard Page-Clark
TMS MultiTouch SDK By Bruno Fierens
FireMonkey Containers and Frames By Bob Swart
Introduction to Databases Part 6 By Cary Jensen

November 2011
Publisher: Foundation for Supporting the Pascal Programming Language
in collaboration with the Dutch Pascal User Group (Pascal Gebruikers Groep)
Stichting Ondersteuning Programmeertaal Pascal

BLAISE
PASCAL MAGAZINE 19
ALL ABOUT DELPHI AND DELPHI PRISM(.Net), LAZARUS & PASCAL AND RELATED LANGUAGES
CONTENTS

Volume 19, ISSN 1876-0589

Articles

Editor - in - chief

DataSnap connectivity for iOS


using Delphi XE2 and FireMonkey
By Anders Ohlsson page 6
Creating a simple webserver in Lazarus
Michael van Canneyt pag 16
Using Amazon S3 in Delphi XE2
By Marco Cant page 21
Creating a Database program from scratch 2
By D.Overbeek page 29
Writing New Components in Lazarus, Part 4
Howard Page-Clark page 37
TMS MultiTouch SDK
By Bruno Fierens page 46
FireMonkey Containers and Frames
By Bob Swart page 53
Introduction to Databases Part 6
By Cary Jensen page 56

News and Press Releases


email only to editor@blaisepascal.eu
Authors

Detlef D. Overbeek, Netherlands


Tel.: +31 (0)30 890.66.44 / Mobile: +31 (0)6
21.23.62.68

A
B
C
D
F
G
H
J
L
M
N
O
P
R
S

Alexander Alexeev
Peter Bijlsma,
Michal Van Canneyt, Marco Cant,
David Dirkse, Daniele Teti
Bruno Fierens
Primo Gabrijeli,
Fikret Hasovic
Cary Jensen
Wagner R. Landgraf, Sergey Lyubeznyy
KIm Madsen, Felipe Monteiro de Cavalho
Jeremy North,
Tim Opsteeg, Inoussa Ouedraogo
Howard Page-Clark, Herman Peeren,
Michael Rozlog,
Henk Schreij, Rik Smit, Bob Swart,

Editors
Peter Bijlsma, Rob van den Bogert, W. (Wim) van
Ingen Schenau,

Correctors
Howard Page-Clark, James D. Duff
Copyright Page 118
Trademarks All trademarks used are
acknowledged as the property of their respective
owners.
Caveat Whilst we endeavour to ensure that what
is published in the magazine is correct, we cannot
accept responsibility for any errors or omissions. If
you notice something which may be incorrect,
please contact the Editor and we will publish a
correction where relevant.
Subscriptions ( 2011 prices )
1: Printed version: subscription 60.-(including code, programs and printed magazine,
4 issues per year including postage).
2: Non printed subscription 35.-(including code, programs and download
magazine)
Subscriptions can be taken out online at

www.blaisepascal.eu
or by written order, or by sending an email to

office@blaisepascal.eu

Advertisers
Advantage Database Server page 3
AnyDac page 27 / 28
Barnsten page 20
Bauer + Kirch page 5
BitTime page 36
Cary Jensens newest book page 52
Components for Developers page 60
Database Workbench Pro- Upscene Productions page 45
FastCube page 25
FastReport page 26
LAZARUS Book page 19 / 42

Subscriptions can start at any date. All issues


published in the calendar year of the subscription
will be sent as well.

Subscriptions run per calendar year.


Subscriptions will not be prolonged without notice.
Receipt of payment will be sent by email. Invoices
will be sent with the March issue. Subscriptions can
be paid by sending the payment to:
ABN AMRO Bank Account no. 44 19 60 863
or by credit card: Paypal or TakeTwo
Name: Pro Pascal Foundation-Foundation for
Supporting the Pascal Programming Language
(Stichting Ondersteuning Programeertaal Pascal)
IBAN: NL82 ABNA 0441960863
BIC ABNANL2A VAT no.: 81 42 54 147
(Stichting Programmeertaal Pascal)

Subscription department
Edelstenenbaan 21 / 3402 XA IJsselstein,
The Netherlands / Tel.: + 31 (0) 30 890.66.44 /
Mobile: + 31 (0) 6 21.23.62.68

office@blaisepascal.eu

COMPONENTS
DEVELOPERS

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

Foreword
This issue specially finished for the First Belgian
Delphi event: Be-Delphi - is as we hope, the first start
of
a long series of events for Belgium.
In the next issue (December) we will let you know all
about the happenings over here. Meanwhile we have
published issue No.18, (the Dutch issue 100)
containing 126 pages. A real milestone.
There is another major milestone to be celebrated
next year. Blaise Pascal Magazine will have been
created 25 years ago, and part of the celebration
during the year 2012 will be enjoyed by making it
even more attractive to our readers.
We will publish our Essential Pascal Learning and
teaching book - Marco Cant is one of the Authors for the first time in English. We will create an Adobe
Pdf version and a Loose Leaf version.
There is a very special thing about this:
there will be about a hundred sample programs
coming with it and, because we do the complete IDE
of Delphi as Lazarus, you will be able to regularly
update your issue with the newest version of Delphi
or Lazarus.
And of course it will be in full colour. You can
subscribe to it and it will cost 50 Euros per year.
Full details will be published next year on our
website.
I hope you all will find it very attractive.

There is a little rumour about Pascal: Android for


Delphi as well Android for Lazarus. I 'd love that to
happen.
The final Version 1 of Lazarus will be finished next
year - another celebration. Our current version of
Lazarus (a stable release) is extended with many new
components, including those. Even from third
parties. So in this version you won't have to install
components, but: "yes you can". It is very important
to have this stable version, especially for starters,
otherwise people will end the game before they even
tried...
In the new edition we would like to start a new
service: Job Advertising. For the Netherlands we
found the Delphi Company to help us with that
subject. It will be a service that is free for
Programmers, but Companies will have to pay a
small advertising price. Please send your ideas and
questions to editor@blaisepascal.eu.

Hopefully you were surprised by the illustration of


the cover: Fire Monkey.
Actually we thought it might be nice to have a real
and cuddly little monkey and what is better than a
little Orang-utan. It's a species that needs to be
protected and therefore we would like to suggest that
we make the little beast our Delphi and Fire Monkey
symbol.
(You can of course order it and we will use it as a
trophy for events etc.)

COMPONENTS
DEVELOPERS

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

COMPONENTS
DEVELOPERS

DataSnap connectivity for iOS using Delphi XE2 and FireMonkey


By Anders Ohlsson
starter

expert

The files are as follows:


dsproxybase (directory of 56 files)
sbjson (directory of 13 files)
DSProxyBase.pas
AnonClassDefinitionsDsproxybase.pas
AnonClassDefinitionsSbjson.pas

DELPHI XE2

This article discusses how you can connect to


existing DataSnap servers from an iOS application
using RAD Studio XE2

Parsing the headers of the ObjectiveC


connector

Problem: Data connectivity for iOS?

Let's start with a disclaimer. There is currently no data


connectivity in FireMonkey for iOS in the currently
shipping version of RAD Studio XE2. Data connectivity
for iOS is of course very important, and it is being
planned, prioritized and road mapped. Please see
edn.embarcadero.com and blogs.embarcadero.com for
more information about roadmaps.
Solution: Mobile DataSnap connector for ObjectiveC!
This article discusses how you can use the ObjectiveC
mobile DataSnap Connector that does ship with RAD
Studio XE2.
First, my grateful recognition of Phil Hess's contribution
here. He was of great assistance in helping me understand
ObjectiveC as opposed to Pascal. He parsed all the
ObjectiveC header files for the mobile DataSnap
connector and sent them to me. The result is a collection
of about 70 files.

Phil took the existing ObjectiveC to FreePascal parser


from here:
http://web.me.com/macpgmr/ObjP/Xcode4/iOS_Parsing_St
atus.html (in the pdf version you can click on this addres)

He had to patch it to fix a few things in order to parse


SBJson and DSProxyBase properly. The resulting parsed
Pascal files then needed some manual editing to alter parts
that the parser does not yet handle correctly.
Instead of providing all the steps on how to reproduce the
parsing, I'm simply providing the files as parsed and edited
by Phil Hess here, along with the entire project (server and
client) as demonstrated in this article:
http://cc.embarcadero.com/item/28579 (in the pdf version
you can click on this addres)
Writing the DataSnap server

We will start by creating a DataSnap server by selecting


File | New | Other | Delphi Projects | DataSnap Server
| DataSnap REST Application.

Figure 1: We go through the expert and select Stand-Alone VCL application in this case:
6

COMPONENTS
DEVELOPERS

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

DataSnap connectivity for iOS (continuation 1)

Figure 2: We then select a


port and test it:

Figure 3:On the next page of the


expert we simply accept the defaults
(sample methods and sample web files)
for simplicity:

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

COMPONENTS
DEVELOPERS

DataSnap connectivity for iOS (continuation 2)

Figure 4; On the following expert


page we'll pick TDataModule as
the ancestor

Figure 5: then finish up and save


the project as
EmployeeServer.dproj
somewhere on disk.
8

COMPONENTS
DEVELOPERS

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

DataSnap connectivity for iOS (continuation 3)


Adding our business logic

Now we need to add useful methods that actually


implement our business logic. In this case we will provide
two main methods - GetRecordCount and GetRecord.
We'll discuss GetRecords a bit later.
Let's first add a TSQLConnection and a TSQLDataSet to
our ServerMethodsUnit1 (default name):

type
TServerMethods1 = class(TDataModule)
MyDB: TSQLConnection;
MyDS: TSQLDataSet;
private
{ Private declarations }
public
{ Public declarations }
function GetRecordCount: Integer;
procedure GetRecord(RecNo: Integer;
var FirstName, LastName: String);
function GetRecords : TJSONArray;
end;

The implementation of GetRecordCount is very simple:


function TServerMethods1.GetRecordCount: Integer;
begin
MyDS.Open;
Result := MyDS.RecordCount;
MyDS.Close;
end;

We're simply going to connect to one of the


demonstration databases that ship with RAD Studio XE2
(EMPLOYEE.GDB) in the Samples directory. The
TSQLConnection and the TSQLDataSet will have the
following parameters set, respectively:

The implementation of GetRecord is also fairly simple.


It's also very inefficient. For now we'll accept the
inefficiency for the sake of producing an easy-to-follow
example method that obtains a specific record by record
number, without any complicating optimisations.
procedure TServerMethods1.GetRecord(RecNo: Integer;
var FirstName, LastName: String);
var i: Integer;
begin
MyDS.Open;
for i := 0 to RecNo-1 do MyDS.Next;
FirstName := MyDS.FieldByName('FIRST_NAME').AsString;
LastName := MyDS.FieldByName('LAST_NAME').AsString;
MyDS.Close;
end;

Finally, for comparison, I include another method GetRecords - that returns all of the records as a
TJSONArray.
It also has a comment section that describes how to get the
data back out of the array on the Win/Mac side (different
code applies to iOS).

Figure 6:

function TServerMethods1.GetRecords : TJSONArray;


var
NewArr : TJSONArray;
NewObj : TJSONObject;
Val
: TJSONValue;
Pair
: TJSONPair;
begin
NewArr := TJSONArray.Create;
MyDS.Open;
while not MyDS.EOF do begin
NewObj := TJSONObject.Create;
NewObj.AddPair
('LastName',MyDS.FieldByName
('LAST_NAME').AsString);
NewObj.AddPair('FirstName',MyDS.FieldByName
('FIRST_NAME').AsString);
NewArr.AddElement(NewObj);
MyDS.Next;
end;
MyDS.Close;
Result := NewArr;
// You get the data back by doing this:
// TJSONObject(NewArr.Get(index)).Get('LastName').JsonValue.Value;
// TJSONObject(NewArr.Get(index)).Get('FirstName').JsonValue.Value;
end;

Figure 7: Here's the declaration of our TServerMethods1


(default naming again).
We add GetRecordsCount, GetRecord and GetRecords.
NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

COMPONENTS
DEVELOPERS

DataSnap connectivity for iOS (continuation 4)


Testing the DataSnap server

We can now compile and run the DataSnap server. It pops


up on the screen as below:

Figure 8: If we click the Open Browser button we get


presented with the test harness. Notice that we haven't
written a client yet. The appearance of a test harness
depends only on our writing the above server.

Figure 9: Here we can expand the nodes for the server


methods which do the actual work of the server. You can
see below that when GetRecordCount is executed the result
is 42 records:

Figure 10: Testing GetRecord gives the following results.


Notice that we provide the input value (RecNo = 0), and
we get back Robert Nelson as our record data.
10

COMPONENTS
DEVELOPERS

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

DataSnap connectivity for iOS (continuation 5)

Figure 11: Finally, executing GetRecords gives


us all the records.

Figure 12:

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

COMPONENTS
DEVELOPERS

11

DataSnap connectivity for iOS (continuation 6)


Writing the iOS client
Now, let's get on with writing the iOS client that will talk
to our server. We create a new FireMonkey HD
application for iOS.
The GUI is the simple part here, and it looks like this:

We'll declare a connection variable first:


var
Connection : DSRESTConnection;

Then we write the code for GetRecordCount:


function GetRecordCount: Integer;
var
cmd : DSRESTCommand;
begin
cmd := Connection.CreateCommand;
{$IFDEF FPC}
cmd.setRequestType(GET);
cmd.setText(NSSTR(PChar('TServerMethods1.
GetRecordCount')));
cmd.prepare(
NSArray.arrayWithObjects(
DSRESTParameterMetaData.
parameterWithName_withDirection
_withDBXType_withTypeName(
NSSTR(PChar(String(''))),
4{ReturnValue},Int32Type,NSSTR(PChar
(String('Int32')))), nil
)
);
{$ENDIF}
cmd.execute;
{$IFDEF FPC}
Result := cmd.getParameterByIndex(0).getValue.
GetAsInt32;
{$ENDIF}
end;

Let's break this one down. We declare a REST


Figure 13: command variable first:
var
cmd : DSRESTCommand;

Now we have to write the actual code behind the button


that retrieves the data... Disclaimer: There may very well
be much more elegant ways of writing this, especially the
string conversions between FPC/ObjC and Delphi.
The first thing that is necessary at the top of the unit is
the following directive to the FreePascal compiler in order
to compile our FPC/ObjC type conversion code.

We create the command:


cmd := Connection.CreateCommand;

We then set the request type


(REST request types include GET/POST/etc):
cmd.setRequestType(GET);

Next, we set the text of the request.


This is the fully qualified name of the method we're going
to call on the DataSnap server:

unit Unit1;
{$IFDEF FPC}
{$modeswitch ObjectiveC1}
{$ENDIF}

cmd.setText(NSSTR(PChar('TServerMethods1.GetRecordCou

We then prepare the command.


Prepare takes one parameter.
This parameter is an array of DSRESTParameterData.
In this case the only parameter is the return value from
GetRecords. Since it is a return parameter, it is nameless
(an empty string). We pass it as Int32Type. We also pass
'Int32' as a string for the last parameter.

In the interface section we'll add iPhoneAll, SBJson and


DSProxyBase. The iPhoneAll unit is part of FPC as
shipping with RAD Studio XE2 and gets installed when
you install the Mac parts for iOS development.
The SBJson and DSProxyBase units are the ones Phil
parsed for me, and are necessary in order to work with
JSON and communicate with the DataSnap server.
interface
uses
SysUtils, Types, UITypes, Classes, Variants,
FMX_Types, FMX_Controls, FMX_Forms, FMX_Dialogs,
FMX_Edit, FMX_Layouts, FMX_Memo, FMX_ListBox
{$IFDEF FPC}
,iPhoneAll, SBJson, DSProxyBase
{$ENDIF};

12

COMPONENTS
DEVELOPERS

cmd.prepare(NSArray.arrayWithObjects(
DSRESTParameterMetaData.
parameterWithName_withDirection
_withDBXType_withTypeName(
NSSTR(PChar(String(''))),4{ReturnValue},
Int32Type,NSSTR(PChar(String('Int32')))),
nil
));

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

DataSnap connectivity for iOS (continuation 7)


We then execute the command:
cmd.execute;

Finally, we extract the return value and return it from our function:
Result := cmd.getParameterByIndex(0).getValue.GetAsInt32;
end;

Much in the same way, GetRecord is implemented as


follows. Notice that we now have one input parameter
(RecNo), two output parameters (FirstName and LastName)
and no return value (since it's a procedure, not a function).
procedure GetRecord(RecNo: Integer; var FirstName, LastName: String);
var
cmd : DSRESTCommand;
begin
cmd := Connection.CreateCommand;
cmd.setRequestType(GET);
cmd.setText(NSSTR(PChar('TServerMethods1.GetRecord')));
cmd.prepare(
NSArray.arrayWithObjects(
DSRESTParameterMetaData.parameterWithName_withDirection_withDBXType_withTypeName(
NSSTR(PChar(String('RecNo'))),1{Input},Int32Type,NSSTR(PChar(String('Int32')))),
DSRESTParameterMetaData.parameterWithName_withDirection_withDBXType_withTypeName(
NSSTR(PChar(String('FirstName'))),2{Output},WideStringType,NSSTR(PChar(String('String')))),
DSRESTParameterMetaData.parameterWithName_withDirection_withDBXType_withTypeName(
NSSTR(PChar(String('LastName'))),2{Output},WideStringType,NSSTR(PChar(String('String')))),
nil
)
);
cmd.getParameterByIndex(0).getValue.setAsInt32(RecNo);
cmd.execute;
FirstName := String(PChar(cmd.getParameterByIndex(1).getValue.GetAsString.UTF8String));
LastName := String(PChar(cmd.getParameterByIndex(2).getValue.GetAsString.UTF8String));
end;

Now for the "easy" part: actually calling our methods and
getting that data displayed in our client. Notice that the
first thing we do here is create and set up the DataSnap
connection. We provide the host address, the port, and the
protocol to be used.
We then call GetRecordCount and iterate over all records
to get them displayed in our UI.
procedure TForm1.GetDataButtonClick(Sender: TObject);
var
i : Integer;
FirstName, LastName : String;
begin
Connection := DSRESTConnection.alloc.init;
Connection.setConnectionTimeout(5);
Connection.setHost(NSSTR(PChar(String(HostEdit.Text))));
Connection.setPort(StrToInt(PortEdit.Text));
Connection.setProtocol(NSSTR(PChar(String('http'))));
for i := 0 to GetRecordCount-1 do begin
GetRecord(i,FirstName,LastName);
Memo1.Lines.Add(LastName+', '+FirstName);
end;
end;
end.

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

COMPONENTS
DEVELOPERS

13

DataSnap connectivity for iOS (continuation 8)


Compiling the client

Once the client is created. We run dpr2xcode to generate


the extra information needed for Xcode.
We then open the project in XCode.
Testing the client

Here is an actual screenshot of the client running on my


iPhone 4:
Comparing the implementation to Windows

If you have experience with JSON and DataSnap on


Windows, then you'll be familiar with the following.
This is how GetRecordCount could be implemented on
the client side:
var
GetRecordCountParams : array[0..0] of TDSRESTParameterMetaData =
((Name : ''; Direction : 4{ReturnValue};
DBXType : TDBXDataTypes.Int32Type; TypeName : 'Int32'));
function GetRecordCount: Integer;
var
cmd : DSRESTCommand;
begin
cmd := Connection.CreateCommand;
cmd.RequestType := 'GET';
cmd.Text := 'TServerMethods1.GetRecordCount';
cmd.prepare(GetRecordCountParams);
cmd.execute;
Result := cmd.Parameters[0].Value.AsInt32;
end;

Figure 14/15: Just as a side-note, I also wrote code that allows


you to test the client under Win32. Here's a screenshot of
what the client looks like under Win32.

14

COMPONENTS
DEVELOPERS

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

DataSnap connectivity for iOS (continuation 9)


GetRecord could be implemented as follows:
var
GetRecordParams : array[0..2] of TDSRESTParameterMetaData =
((Name : 'RecNo'; Direction : 1{Input};
DBXType : TDBXDataTypes.Int32Type; TypeName : 'Int32'),
(Name : 'FirstName'; Direction : 2{Output};
DBXType : TDBXDataTypes.WideStringType; TypeName : 'String'),
(Name : 'LastName'; Direction : 2{Output};
DBXType : TDBXDataTypes.WideStringType; TypeName : 'String'));
procedure GetRecord(RecNo: Integer; var FirstName, LastName: String);
var
cmd : DSRESTCommand;
begin
cmd := Connection.CreateCommand;
cmd.RequestType := 'GET';
cmd.Text := 'TServerMethods1.GetRecord';
cmd.prepare(GetRecordParams);
cmd.Parameters[0].Value.SetInt32(RecNo);
cmd.execute;
FirstName := cmd.Parameters[1].Value.AsString;
LastName := cmd.Parameters[2].Value.AsString;
end;

Finally, the button event handler that gets the number of


records and iterates over the dataset to get all records
looks almost exactly the same as the iOS equivalent:
procedure TForm1.GetDataButtonClick(Sender:
TObject);
var
i : Integer;
FirstName, LastName : String;
begin
Connection := TDSRestConnection.Create(Self);
Connection.Host := HostEdit.Text;
Connection.Port := StrToInt(PortEdit.Text);
Connection.Protocol := 'http';
for i := 0 to GetRecordCount-1 do begin
GetRecord(i,FirstName,LastName);
Memo1.Lines.Add(LastName+', '+FirstName);
end;
end;

Resources

Download the whole project (server and client) from here:


http://cc.embarcadero.com/item/28579

(in the pdf version you can click on this addres)


ObjectiveC to FreePascal parser:
http://web.me.com/macpgmr/ObjP/Xcode4/iOS_Parsing_Status.html

(in the pdf version you can click on this addres)


Contact

Please feel free to email me with feedback to


aohlsson at embarcadero dot com.

(you need to transform the address!)


About the author:
Anders Ohlsson
Anders Ohlsson is part of Embarcadero's Developer
Relations Team. When he's not in
the office, he can be found in an aisle seat on an
airplane, at some developer
conference, trade show, user group or seminar
evangelizing to the world why everyone
should use the Embarcadero suite of developer and
database tools.

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

COMPONENTS
DEVELOPERS

15

Creating a simple webserver in Lazarus By Michal Van Canneyt


starter

expert

FreePascal / Lazarus 9.31 and above

Free Pascal - and therefore Lazarus - can


compile many TCP/IP stack implementations:
Synapse, Indy and lnet. You may not realise,
however, that Free Pascal also ships with some
simple networking components of its own,
including a simple webserver. This article
shows you how to use it.
Introduction

There are applications which need a small


webserver as part of their functionality, for
instance, if you may need to configure and manage
your application through a web interface. This is
especially useful when writing service applications
(daemons).
There are several TCP/IP networking component
suites available to Pascal developers: Indy and
Figure 1: Lazarus support for HTTP server applications with the
Synapse are well known to people coming from a
LazWebExtra package installed
Delphi environment.
L Net was written specifically for FPC, and uses an event- The latter component is in fact integrated in the
development version of Lazarus (0.9.31 and above).
driven, non-blocking, approach.
If you have installed the LazWebExtra package you can
For a long time Free Pascal has included several simple
choose HTTP server application as a project option in the
TCP/IP classes, which have been used recently for a set of
New dialog accessed via the File New menu, as shown
components that implement both the server and client
in
Figure 1.
sides of the HTTP 1.1 protocol.
A simple webserver

Combining these classes with existing fcl-web components The simplest webserver can be implemented with very few lines of code:
allows you to create a fully-fledged HTTP server, which can program myserver;
serve files, implement a web application, or host a web
service for WST (the Web Services Toolkit as explained in uses
SysUtils, fphttpapp, fpwebfile;
the book, Lazarus The Complete Guide). All this can be
achieved with a standard Free Pascal and Lazarus (latest
Const
MyPort = 8080;
version only) installation.
MyDocumentRoot = '/home/michael/public_html';
These networking components are architecturally simple,
MyMiMeFile = '/etc/mime.types';
so they may not be suitable to build high-load, scalable
web servers. But they are perfectly suitable for simple web begin
RegisterFileLocation('files',MyDocumentRoot);
services or embedded use.
MimeTypesFile:=MyMimeFile;
There are two basic components available:
TFPHTTPServer This is the component that does the
actual work. It offers various properties for configuring
the server. The most important ones are the TCP/IP
port number to listen to (obviously named Port), and the
Active property, which starts the server. It also offers an
event to actually handle requests, named OnRequest
(what else?). When embedding an HTTP server in an
application, this is the component you should use.
This is a TCustomApplication
descendant which uses a THTTPServer component to
listen to requests, and then hands the requests over to
the fcl-web request dispatcher mechanism. This
component should be used when creating a web service
hosting application. It offers the same properties as the
THTTPServer component for configuring the server's
behaviour.

THTTPApplication

16

COMPONENTS
DEVELOPERS

Application.Initialize;
Application.Port:=MyPort;
Application.Title:='My HTTP Server';
Application.Run;
end.

This source code is not very different from what Lazarus


generates if a HTTP Server application project is started.
Note the fpwebfile unit in the uses clause. This unit
contains a complete and ready-to-use fcl-web module
(TFPCustomFileModule) that serves files. It is not necessary
to create an instance of this module explicitly since fclweb takes care of this. All that needs to be done is to tell
fcl-web which location should be mapped to an actual
directory. This is the purpose of the first line of code:
RegisterFileLocation('files', MyDocumentRoot);

This tells fcl-web that the location


http://localhost/files/myfile.html

should be translated to a disk file named


/home/michael/public_html/myfile.html

This is similar to the 'Alias' directive in an Apache


webserver configuration.
NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

Creating a simple webserver in Lazarus (Continuation 1)


Obviously, the actual directory must exist on the computer
where the program is run. It is possible to register several
locations.
The second line of code tells the file serving module
where the mime.types file is. This file (installed by default on
all Unix systems) is used to map file extensions to mimetypes when setting the Content-Type HTTP header.
This HTTP header tells the browser what kind of file it is
getting from the server - so it can decide what to do with
it. On Windows, you will have to provide such a file for
the program. A typical Windows Apache installation
contains such a file, and it can be used for
TFPCustomFileModule as well.
If you run the program above, you can use a browser to
display files in the registered file locations, as can be seen
in Figure 2.

The opening lines of this method are identical with the


first example. The last two lines create the module and tell
it from what location it should serve files. Note the use of
the CreateNew constructor: this prevents the runtime
library from trying to find a form file for the module.
Since there is no form file, the use of Create would raise
an exception. If everything were simple, the Start button
would create a TFPHTTPServer instance and set Active to
True:
procedure TMainForm.BStartClick(Sender:
TObject);
begin
MLog.Lines.Add('Starting server');
FServer:=TFPHTTPServer.Create(Self);
FServer.Port:=8080;
FServer.OnRequest:=@DoHandleRequest;
FServer.Active:=True;
end;

Unfortunately, this would freeze the application as


soon as you clicked the [Start] button, because the
line
FServer.Active:=True;

does not return until the server stops. This would


mean that the GUI becomes unresponsive, which is
not very desirable.
Instead, the server needs to run in its own thread,
and the [Start] button needs to create this thread.
The following thread implementation will run the
HTTP server:
THTTPServerThread = Class(TThread)
Private
FServer : TFPHTTPServer;
Public
Constructor Create(APort : Word;
Const OnRequest : THTTPServerRequestHandler);
Procedure Execute; override;
Procedure DoTerminate; override;
Property Server : TFPHTTPServer Read FServer;
end;

Figure 2: Testing the simple server


Embedding a webserver

The above example is very simple, but is also very limited.


Other than serve web-requests, it cannot do anything else
at all. Using the THTTPServer component, however, it is
possible to embed webserver functionality in an existing
application. This is not harder to do than it was to write
the previous example.
To demonstrate this, a GUI application will be built with a
memo component and two buttons. One button (named
Bstart) starts the server, and the other (named BStop)
stops the server. The memo (named MLog) logs the
requests.
This application will simply serve files, so a
TFPCustomFileModule instance is needed to actually serve
the files. The required instance can be created and
configured in the OnCreate event of the main form
procedure TMainForm.FormCreate(Sender: TObject);
begin
RegisterFileLocation('files','/home/michael/public_
html');
MimeTypesFile:='/etc/mime.types';
FHandler:=TFPCustomFileModule.CreateNew(Self);
FHandler.BaseURL:='files/';
end;

The constructor creates the TFPHTTPServer instance and


sets the Port property, and assigns the onRequest handler:
constructor THTTPServerThread.Create(APort: Word;
const OnRequest: THTTPServerRequestHandler);
begin
FServer := TFPHTTPServer.Create(Nil);
FServer.Port := APort;
FServer.OnRequest := OnRequest;
Inherited Create(False);
end;

The Execute methods simply sets Active, and waits till it


returns. After that, it frees the server instance.
procedure THTTPServerThread.Execute;
begin
try FServer.Active:=True;
Finally FreeAndNil(FServer);
end;
end;

When the thread is terminated (by an external thread), it


should stop the server:
procedure THTTPServerThread.DoTerminate;
begin
inherited DoTerminate;
FServer.Active:=False;
end;

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

COMPONENTS
DEVELOPERS

17

Creating a simple webserver in Lazarus (Continuation 2)


Using this thread, the OnClick handler of the [Start]
button becomes simply the following:

The method saves the URL, and then calls ShowURL.


Because the DoHandleRequest is called by the thread, and it
must update the GUI, it is necessary to protect it with
Synchronize, which will ensure that ShowURL is called in
the context of the main (GUI) thread. The ShowURL
method is extremely simple:

procedure TMainForm.BStartClick(Sender: TObject);


begin
MLog.Lines.Add('Starting server');
FServer:=THTTPServerThread.Create
(8080,@DoHandleRequest);
end;

procedure TMainForm.ShowURL;
begin
MLog.Lines.Add('Handling request : '+FURL);
end;

Note that it passes a form method: DoHandleRequest to


handle the requests. You have to be careful here. This
event handler will be called in the context of the server
thread.
Likewise, the OnClick handler of the [Stop] button just
stops the thread:

With this, the demonstration application is finished. It can


now be run, and pressing the [Start] button starts the
HTTP server. Figure 3 shows what happens when a URL
is entered in the browser. The browser shows the page,
and a log entry is shown on the form.

procedure TMainForm.BStopClick(Sender: TObject);


begin
MLog.Lines.Add('Stopping server');
FServer.Terminate;
end;

All that needs to be done is to implement the


DoHandleRequest method:
procedure TMainForm.DoHandleRequest(Sender: TObject;
var ARequest: TFPHTTPConnectionRequest;
var AResponse: TFPHTTPConnectionResponse);
begin
FURL:=Arequest.URL;
FServer.Synchronize(@ShowURL);
FHandler.HandleRequest(ARequest,AResponse);
end;

Note that the URL which is entered, does not contain the
location 'files' as in the first demo. The reason is that
the application does not need to decide which fcl-web
module is needed to handle the request, because all
requests are handled by the file-serving module. When this
module was created in the OnCreate handler of the main
form, the line
FHandler.BaseURL:='files/';

told the module that it should look in the registered


location called 'files'.

Figure 3: The embedded server at work


18

COMPONENTS
DEVELOPERS

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

Creating a simple webserver


in Lazarus (Continuation 3)
Conclusion

In this article, we have demonstrated the use of standard


FPC functionality for the HTTP protocol. You can easily
create an application that can act as a webserver with just a
few lines of code. We have not covered all aspects of the
available functionality. A future article will look at the
client side (getting files) and support for web services
(using WST).

About the Author:


Michael van Canneyt
After an education in Theorethical Physics, Michael Van
Canneyt became the leading software architect in a
small software company which creates administrative
software for Flemisch schools. He has been developing
professionally with Delphi since 13 years, and has been
involved in Free Pascal development since its
beginnings, now 15 years ago.

LAZARUS
THE COMPLETE GUIDE
Simple Package 35,00
(this excludes shipping costs 17.50)
- Just the book and a Windows installer CD
for Lazarus value 12.50
- 50 additional sample projects
- extra: additional prenstalled component suites

Complete Package 60,00


(Hardcover) or 50,00 (Paperback)
(this excludes shipping costs 17.50).
Total value of the package 112,50
Blaise Magazine offers a complete package
The complete package contains
Lazarus the Complete Guide value 50.00
- a Windows installer CD for Lazarus value 12.50
- Lazarus Portable, a prenstalled copy Lazarus on
a free 4GB USB stick, value 25.00
- 50 additional sample projects
- extra: additional prenstalled component suites on both:
CD and USB Stick.
- free DVD Blaise Pascal Magazine Library, value 25.00

WELCOME PACKAGE FOR NEW SUBSCRIBERS


(You need to become a subscriber first)
If you take out your first subscription to Blaise Pascal Magazine
you will be eligible for one half-price copy of "Lazarus The Complete Guide
(this excludes shipping costs).
A top-quality book for 50% off!
You pay for your subscription the normal price for what

ever subscription you want plus 25,00 for the book...

Blaise Pascal Magazine


www.blaisepascal.eu

Barnsten B.V. - Embarcadero Technology Centre


Postbus 5010 2000 GA Haarlem Nederland
Tel.: +31 23 542 22 27 / Fax.: +31 84 755 52 60
COMPONENTS
SEPTEMBER
BLAISE
PASCAL MAGAZINE 18
NOVEMBER 2011
19
Web: www.barnsten.com
Info:
info@barnsten.com
DEVELOPERS

barnss en
DEVELOPMENT &
DATABASE

28
42
20
TOOLS

Using Amazon S3 in Delphi XE2 By Marco Cant


starter

expert

DELPHI XE2

If you've followed my past articles on this


magazine, you know I've been building an Amazon
S3 client in Delphi in the past. Now Delphi XE2
support this and a few other Amazon Web Services
out of the box, thanks to the extended and
augmented Cloud Service clients architecture. This
new set of classes, as we'll see in this article,
extends the Azure support classes already in XE,
providing a more open approach, introducing native
support for Amazon services, and making it much
easier to process the data returned by these calls
due to a large collection of helper classes and
records. In other words, you can now write an
Amazon S3 client in a fraction of time. In my case,
that was even faster, since I already had the client
user interface, and all I need to do was remove
my(limited) core classes and replace them with the
equivalent features in XE2.

In parallel, Azure support is now in the unit


Data.Cloud.AzureAPI (while the Delphi XE Azure units still
exists mostly for backwards compatibility):
TAzureTableService is used to access Azure's Table
service (a NoSQL database similar to SimpleDB, not to
be confused with Azure SQL Server service, which
offers Microsoft's relational database in a cloud
configuration)
TAzureQueueService is a class interfacing the Queue
service
TAzureBlobService is the service for binary or file
storage, like S3.
Figure 1 has the a summary of the names of theese
services for the two offerings supported by Delphi XE2:

BOX: What is AWS?

S3 is one of the many services that are part of AWS,


Amazon Web Services, the collection of services for
storage, bandwidth, CPU, messaging and more that
Amazon started offering to developers many years back
and have become a sort of reference implementation for
most web services, now generally called cloud services.
Other well known services offered by AWS are Elastic
Cloud Computing (or EC2), a virtual machine hosting
service; the non-relational database Amazon SimpleDB;
the Simple Queue Service (SQS); a few e-commerce and
payment services; Alaxe web monitoring services; and a
few others. You can find detailed information on Amazon
Web Services at: http://aws.amazon.com/
The Delphi XE2 Cloud Architecture

Compared to the simpler structure in Delphi XE, the


latest version of the development tool has an extended
library for Cloud services. This portion of the VCL has
two visual components for holding connection
information (TAmazonConnectionInfo and
TAzureConnectionInfo). These are the components
where you can set you account key and name.
The actual Cloud Service classes take these connection
components as parameters. The Data.Cloud.AmazonAPI
unit defines the following classes:
TAmazonStorageService is a mapper for Amazon's S3,
Simple Storage Service
TAmazonTableService is a mapper for Amazon's
SimpleDB API (a NoSQL database)
TAmazonQueueService is a class for using Amazon's
Queue Service

While the various services share a base abstract class,


called TCloudService and defined in the unit
Data.Cloud.CloudAPI), this is more of an infrastructure
class, as there are too many differences among the services
themselves to have a single base class with the same virtual
methods for, say, listing, getting, and uploading files.
In any case, this is the class hierarchy for Clioud Services
in Delphi XE2:
TCloudService
TAmazonService
TAmazonStorageService
TAmazonBasicService
TAmazonTableService
TAmazonQueueService
TAzureService
TAzureTableService
TAzureQueueService
TAzureBlobService

Processing Results in Practice

As I mentioned, the most significant feature beside


directly supporting AWS is the ability to simply the
processing of the result. Let me show this with an
example, comparing the usage of the older Azure API to
the new one.
The TAzureTableService class in Delphi XE has the
method QueryEntities returning an XML document. So
you'd generally have to load it in an XMLDocument and
proicess it directly or create an XML mapping interface
for it, as I did in this code I wrote last year:

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

COMPONENTS
DEVELOPERS

21

Using Amazon S3 in Delphi XE2 (Continuation 1)


strXml := TableService.QueryEntities(tablename);
XMLDocument1.LoadFromXML(strXml);
XMLDocument1.Active := True;

The program starts by loading the user's access key and


secret access key from an INI file. Next, it passes the
connection information object to the constructor of the
TAmazonStorageService, initializing the key object used in
the application:

iFeeed :=
AzureTableXmlInterfaces.Getfeed(XmlDocument1);
for I := 0 to iFeeed.Entry.Count - 1 do
begin
// process XML node by reading XML elements
end;

Now in XE2 the same method returns a list of


TCloudTableRow objects, so we don't need the XML
document nor the XML mapping interface, but we can
write:
var
rowsList: TList<TCloudTableRow>;
aRow: TCloudTableRow;
begin
rowsList := TableService.QueryEntities(tablename);
for aRow in rowsList do
begin
// process row by reading field values
end;

If you look at the actual code or start to write it, the


difference will be even more striking than this show demo
indicates. In fact, when I had to rewrite the S3 client
(starting with my own custom classes for XE and migrating to the
built-in classes in XE2) the simplification was even more
significant.

procedure TS3ClientForm.FormCreate(Sender: TObject);


var
iniFile: TMemIniFile;
begin
// load keys from INI file, if available
iniFile := TMemIniFile.Create(GetHomePath +
PathDelim + 'amazon.ini');
try
AmazonConnectionInfo1.AccountName {AccessKeyID}
:= iniFile.ReadString ('keys', 'AccessKey',
'');
AmazonConnectionInfo1.AccountKey {SecretAccessKeyID}
:= iniFile.ReadString ('keys',
'SecretAccessKey', '');
finally
iniFile.Free;
end;
Caption := AmazonConnectionInfo1.StorageEndpoint;
s3Service :=
TAmazonStorageService.Create(AmazonConnectionInfo1);
end;

The first actual operation on the storage service object is


extracting a list of buckets (or folders). There are two
version of the method, one retuning an array of records
and the second a string list, with name/value pairs. In both
cases, you received the names of the buckets and their
creation date:

The S3 Client Application Revisited

AWS Simple Storage Service is one of the oldest and most


used service from Amazon. As examples, Twitters uses it
for storing user's images and DropBox relies on it for the
actual storage. One of the key reasons is that Amazon S3
offers unlimited web space and bandwidth for a very
limited fee. You pay 14 cents (in US dollars) for GB each
month, plus a very low per-request fee and a data transfer
fee. The big advantage, is that the infrastructure can
handle peaks of requests that very few sites could sustain.
You can find more information on the service (and a signup module) at: http://aws.amazon.com/s3/

procedure TS3ClientForm.btnListBucketsClick(Sender:
TObject);
var
sBucketsList: TStrings;
I: Integer;
item: TListItem;
begin
lvBuckets.Clear;
sBucketsList := s3Service.ListBuckets;
for I := 0 to sBucketsList.Count -1 do
begin
item := lvBuckets.Items.Add;
item.Caption := sBucketsList.Names[I];
item.SubItems.Add(sBucketsList.ValueFromIndex[I]);
// CreationDate
end;
end;

Let's now move to the central topic of this article,


rewriting the Amazon S3 client I introduced last year, this
time using the Amazon S3 classes available in Delphi XE2. The output should be like the one of the topmost list view
This client application is a simple front end for browsing
in Figure 2. The obvious next step is to list the content of
and uploading files.
a given bucket, as the bottom portion of the same image
demonstrates.

Figure 2: The list of S3 buckets and the contents of one of them.


22

COMPONENTS
DEVELOPERS

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

Using Amazon S3 in Delphi XE2 (Continuation 2)


This time the method call received the bucket name as
parameter and (given you have permission) returns a
rather complex array:
procedure TS3ClientForm.lvBucketsDblClick(Sender:
TObject);
var
strBucket: string;
bucketInfo: TAmazonBucketResult;
objectInfo: TAmazonObjectResult;
listItem: TListItem;
begin
strBucket := CurrentBucketName;
bucketInfo := s3Service.GetBucket(strBucket, nil);
lvObjects.Clear;
for objectInfo in bucketInfo.Objects do
begin
listItem := lvObjects.Items.Add;
listItem.Caption := objectInfo.Name;
listItem.SubItems.Add(IntToStr
(objectInfo.Size));
listItem.SubItems.Add(objectInfo.LastModified);
listItem.SubItems.Add(objectInfo.StorageClass);
listItem.SubItems.Add(objectInfo.OwnerDisplayName);
listItem.SubItems.Add(objectInfo.ETag);
end;
end;

As you can see the code is quite simple and linear, and
(again) this is very different from the manual processing
code I used earlier, based on some sophisticated XML
processing. Now I don't want to continue listing the
remaining methods in such detail, but only cover the key
features. The program, in fact, has quite a few features.
One area is buckets management, with the possibility of
creating new buckets and deleting
existing ones. The calls are quite simple:

var
FileContent: TBytes;
fReader: TBinaryReader;
begin
fReader := TBinaryReader.Create(
OpenPictureDialog1.FileName);
FileContent :=
fReader.ReadBytes(fReader.BaseStream.Size);

Once you have this structure in memory, it is a matter of


sending it to the service along with some meta data inside a
string list:
meta := TStringList.Create;
meta.Add('Content-type=image/jpeg');
s3Service.UploadObject (CurrentBucketName,
edFileName.Text,
FileContent, False, meta);

The last operation is the download of a given file (in this


case the program supposes it is an image and displays it,
without checking the object properties first). Downloading
is even simpler as you can pass a stream to the service and
it will fill it:
mStream := TMemoryStream.Create;
s3Service.GetObject(CurrentBucketName,
lvObjects.Selected.Caption, mStream);

Notice that if the bucket is public, you can download the


file also with a simple HTTP request with a proper URL.
An example of viewing one of the remote JPG files is
visible in Figure 3.

s3Service.CreateBucket(edBucketName.Text);
s3Service.DeleteBucket (CurrentBucketName);

Notice that buckets creation is subject to


many rules, since bucket names are part of
the URL used to refer to your files
(so they are subject to URL encoding rules) and
they are global names, so different users
cannot have buckets with the same name.
If you try to do so you'll receive a rather
generic error message.
Another feature of the program is the ability
to upload and download files. The Delphi
implementation of the service works in terms
of TBytes, and this is how a file is loaded in
a TBytes structure:

Figure 3: With the S2 client application you


can see a remote image.
NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

COMPONENTS
DEVELOPERS

23

Using Amazon S3 in Delphi XE2 (Continuation 3)

Figure 4: The repository hosting the code of the projects covered in this article.
The global subversion repository and the project specific one (these are the URL you can put in Delphi's File | Open
From Version Control command) are at the following URLs:
http://code.marcocantu.com/svn/marcocantu/delphirestclients
http://code.marcocantu.com/svn/marcocantu/delphirestclients

You can also browse the project source code online, if all you are interested in is looking at it. Use the Source | Source
Tree commands from the initial URL above and follow the specific project, called AmazonS3_XE2.
At the same repository you can also find other Delphi REST clients I wrote, including the AmazonS3Client project, the
older version of the same client, based on a custom component. Other projects include a simple RSS feed, a Twitter
search client, and a Google Translate client.
Remember you need to create your own Amazon Web Services account and prepare the INI file with the account key
and secret key to see the program up and running.
About the Author:
Marco Cant is the author of several best-selling
Delphi books, including the Mastering Delphi series
and the recent Delphi Handbook series (the last
being a Delphi XE Handbook) all available in the
Delphi Handbooks Collection ebook. He's also an
active user and developer in the Web 2.0 world and
social web, like Twitter and Google+. You can reach
him on www.marcocantu.com or on
blog.marcocantu.com, and email him at
marco.cantu@gmail.com.

24

COMPONENTS
DEVELOPERS

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

COMPONENTS
DEVELOPERS

25

26

COMPONENTS
DEVELOPERS

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

COMPONENTS
DEVELOPERS

27

AnyDAC is an unique set of Universal Data Access Components for developing database applications on Delphi,
C++Builder and FreePascal. With its powerful common architecture AnyDAC enables native high-speed direct
access from Delphi to Firebird, SQLite, MySQL, SQL Server, Oracle, PostgreSQL, DB2, SQL Anywhere,
Advantage DB, Interbase, Access, Informix and more.
Based on 10 years of experience writing native drivers for the database back-ends, AnyDAC was built as
powerful access layer that supports all that the features needed to build real-world high-load applications.
AnyDAC provides a common API for accessing different database back-ends, without giving up access to unique
database-specific features, or compromising on performance.

DA-SOFT Technologies Services


At DA-SOFT Technologies, we believe IT consulting is all
about assessing technologies from a lot of options and
selecting solutions which will best meet business objectives
and ensure long term value. Our solutions help you
organize business and technology strategies - cost effectively.

DA-SOFT Technologies provides IT Services in the global market,


with prime emphasis on data access technologies.
We are continuously expanding our expertise and capabilities,
to be a leading IT Service provider. Our range of services includes:
Technology Consulting
Software Development
Application Maintenance
Testing / QA Services
Software Re-engineering

Technology Consulting
The main objective is to provide the customers with competent technology advice that provides an optimal
approach to implementation and deployment of the software system.
Software Development
DA-SOFT Technologies is a company that creates, designs and develops technology to deliver measurable
results with direct impact on your bottom line. After an analysis of your unique needs and operations, we
employ the right tools to manage your task.
Application Maintenance
As clients strive to integrate their business and IT strategies, our application maintenance services help them
achieve their business goals and objectives, by providing on-time support and value adding services. Business
demands and technology challenges create the need for migration of applications or databases from one
environment to another, either to improve operational efficiency or to manage risk. We provide custom
application development services.
Testing / QA Services
In software development, validation and verification take up a significant percent of the effort, time and
resources. Testing, as a major component of the development process, must ensure quality and costeffectiveness to provide a good return on investment over the long term. The quality of testing can be
improved by adopting a systematic testing approach, whereas cost-effectiveness can be improved by test
automation and by using offshore services.
Software Re-engineering
Existing software systems need to move with the times too, evolving into state-of-the-art IT solutions that
cater to expanding business needs, while preserving business rules and practices in the current system. The
benefits for companies are two-fold: they can leverage their investments in the current system, as well as
move out of proprietary and outdated technologies.

Creating a Database program from scratch 2


starter

expert

by Detlef. D. Overbeek

DELPHI XE and above (Win32)

In part one of this series we started summarizing


the needs for the creation of a database program
and finding additional tools that can help in this
process. We started with Database Workbench in
order to create the first designs and to learn how
this program works. This is the continuation of the
same subject:
Database Workbench in practice
Since we have already created a very simple database,
(actually nothing other then a large flat file created with
a client dataset) without any referential connections, I
thought it might be helpful to reuse all smart ideas that
we already used for our program, but also rewrite the
whole program from scratch. So no strict rebuild, but a
reinvention of all of it. I want to show in detail the
process and creation of something that is in essence
nothing else but a shop with internet connections /
services / bookkeeping and registration. Client Server,
or a modern more fancy name like Cloud(?). Anyway;
the service should be performed on a client "anywhere"
and registered at a server of choice; at home or even
better: at a special server hosted by some hostingcompany.
Database

So let's start with the database.


Since our database choice is Firebird, we need to realize
we have to change all filtering (Client Dataset) to SQL.
This will become quite a large part of the undertaking. All
filters need to be rewritten and transformed to SQL. All
reports have to be redesigned, and here comes another
hurdle: we want the best report generator we can find and
so we decided to use Fastreport. But first things first.
By now we have ideas about how to build the program,
but the design of the database itself needs to be dealt
with. And this seems to become rather complex.

Time to find a specialist who can help build the structure.


So the first question is: can we build a database in a way
that we start with it simply and then extend it?
Or would it be more appropriate to do the whole design at
once?
I can hardly imagine that would be possible, there is no
future to foresee, so I suppose we will have to build the
structure during the process and make changes whenever
necessary.
The Client Dataset we use now will be the model to start
from.
And again, here we need to make a few choices about the
structure: where will we start, and what model do we
want to use the company model or a personal model?
So here is how I want to make the first design of the
database: since we have our Client Database Tables,
we already have a structure, so we will start to design this.

Where to start?

In Database Workbench there are two choices: start


building a table immediately and then transform that
information into a diagram, or which I prefer - start
creating a Diagram. Most people are far better using visual
information , so I think that's best to do...
For that reason we will go to Database Workbench and
find the diagram editor. Go to the Tools menu | and at
the 7th item you will find the Diagram Editor.
Once available there are again some choices to be made:
create a new diagram or load one you already created
and/or saved. To create a new one you will have to add
some objects: I must say, the many options that are given
in the listings are quite confusing, so again I search visually
and find a little icon that looks like a table:Figure1

Creates Diagrams
Creates Objects

Figure 1: The icons are a quick way to become acquainted with the program...
NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

29

Creating a Database program from scratch 2 (continuation 1)


Drag the object to the grid and scale it in the way you want it. Right-click on the object and another list will become
available. Here you can give the object a name, create field sizes etc See Figure 2, 3, 4, 5

Figure 2: The object (Table) shows its properties

Figure 3: Change
the name of the Entity

Figure 4: You can find the possiblity


to change the Datatypes according to
your needs

Figure 5: add or delete attributes

30

COMPONENTS
DEVELOPERS

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

Creating a Database program from scratch 2 (continuation 2)


These are all options we will need to find out but, as before the interface is very user-friendly in itself and within a very
short time, you will get to know it quite well.
Now I want to show what we did in first step: lets get the diagram I already created and look at the details.
One thing I could not find in the IDE main items bar was the option to reopen a once saved diagram. But at closer
inspection I found the icon: all the aspects and abilities are listed here in the icon bar.

Figure 6: Open a diagram file

Figure 7: After selecting the file the diagram was opened:

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

COMPONENTS
DEVELOPERS

31

Creating a Database program from scratch 2 (continuation 3)

File 8: See the details of the articles table

File 9: You also can zoom to see the total work you have created after a while.

32

COMPONENTS
DEVELOPERS

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

Creating a Database program from scratch 2 (continuation 4)

It was quite a job to create all this. Making dependencies and trying to forecast what we will need in future...
Especially the idea of creating the ability to have some kind of a warehouse: if you want to sell anything you'll have to
register these sales, make invoices, eventually create a table for the web shop and so on... It can become very complex:
don't underestimate the time you will need to do all this;
it's not only the creation but redefining goals for your company, asking specialists to get advice and eventually
changing your opinion which in itself is not very easy.
NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

COMPONENTS
DEVELOPERS

33

Creating a Database program from scratch 2 (continuation 5)

I think it is worthwhile to explain how a structure in itself works and we will show you how the details evolved. We
will do so when we come to the program development details: it is of course not meant to be complete we would
have to write a book about this but some details must be understood before you could start your own shop and
registration. So in figure 11 you can see some details emerging: the Contact table is actually the centre of all other
dependencies. The next is the Company table. As you can see in this figure, the address table is created apart from the
Contacts table because of multiple relationships with Invoices, Warehouse Registratio and so on.
34

COMPONENTS
DEVELOPERS

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

Creating a Database program from scratch 2 (continuation 6)

The Private table keeps the fields for name of the Person, and the Company was chosen to be less important than the
Contact: its a choice you canof course make your own descions. In figure 12 you can see the connections designed for
bookkeeping: Invoices / Correspondence / Articles an orders. Of course this is not the complete information, but you
can get an idea about what complex task it can be to create a simple shop handling some articles, and then try to do
some bookings and orders. In the next article we will dive into the details of how to create a table, make
dependencies and how they sometimes need to be very carefully handled in these very complex relationships.
The Unicode ability of modern tables needs to be explained as well: especially if you want to create a shop
that has an international audience.
NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

COMPONENTS
DEVELOPERS

35

Writing new components in Lazarus (3)


starter

expert

By Howard Page - Clark

Lazarus 9.31 and above

The previous article presented a step-by-step


guide to using Lazarus Package Editor to
create, register and install a somewhat trivial
new component in the Component Palette.
This article assumes you understand those
component creation basics, and moves on to
the design and implementation of an edit
component that will accept only numeric input.
The Component Palette contains a good generalpurpose edit component in TEdit, but has no edit
specialised for accepting numbers apart from
TSpinEdit and TFloatEdit (on the Palettes Misc
page) which suit some (but not all) applications. If
you want a normal GUI edit field that accepts only
numbers, you have to roll your own.
Design specification

A good place to start with component design is to list the


features you need in your new component without
worrying yet about how they will be implemented. Here is
the desired feature list for a component to be named
TNumberEdit. It would:
Accept integers or floating point numbers
Accept negative numbers
Adapt to the local decimal separator (. or ,
depending on locale)
Validate both individual keystrokes entered, and the
final number
Have an option to give explanatory error messages
and/or beeps for invalid keystrokes
Have an option to apply minimum and maximum
allowed limits
Have properties (integer and float) providing the
numeric value of the number entered
Have an optional descriptive label accompanying
the edit field
Give user feedback by highlighting the edit
background when the component is focused
Have an option to treat [Enter] as if it were [Tab] to
facilitate moving between successive TNumberEdit
fields (for users accustomed to older-style GUIs)
It is surprising how many issues such a seemingly
innocuous list of requirements can raise! The spinedits on
the Misc page of the Palette had been designed as two
distinct components keeping integer and float
functionality separate. Would it be possible to have a single
numeric edit that covered both types of number (integer
and float) within the one component? This led me to
realise that such a component would also need:
A property to specify whether integer or whether
floating point numbers were allowed
A default value property which would be returned
in case of invalid input

The resulting list of properties (beyond those of a


standard TEdit) looked like this:
public
property
property
property
property
property
property
property
property
property
property
property

BeepOnError: boolean
DefaultValue: double
EnforceMinMaxLimits: boolean;
FloatValue: double
FocusColor: TColor
IntegersOnly: boolean
IntegerValue: int64
MaximumValue: double;
MessageOnError: boolean
MinimumValue: double;
TreatEnterAsTab: boolean

The requirement to allow (or disallow) negative values


could be covered by the MinimumValue property, since
setting that to a value less than zero would allow negative
values, and vice versa.
Immediate ancestor

The important decision about which class in the LCL


hierarchy to pick as this new components ancestor was
determined by the requirement to provide an optional
descriptive label accompanying the numeric edit field. This
meant descending from TCustomLabeledEdit, a readymade component which provides exactly this functionality.
TLabeledEdit(from the Additional Palette page) is found
in the ExtCtrls unit. So the new unit numberedit.pp for
this evolving component had a skeleton as follows:
Unit numberedit;
{$mode objfpc}{$H+}
Interface
Uses Classes, SysUtils, ExtCtrls;
type
TCustomNumberEdit = class(TCustomLabeledEdit)
public
property BeepOnError: boolean
property DefaultValue: double
property EnforceMinMaxLimits: boolean;
property FloatValue: double
property FocusColor: TColor
property IntegersOnly: boolean
property IntegerValue: int64
property MaximumValue: double;
property MessageOnError: boolean
property MinimumValue: double;
property TreatEnterAsTab: boolean
end;
TNumberEdit = class(TCustomNumberEdit)
published // first we publish properties inherited from TLabeledEdit
property Alignment;
property Anchors;
property // etc.
property BeepOnError;
// here we publish the publishable properties we have added
property EnforceMinMaxLimits;
property FocusColor;
property IntegersOnly;
property MessageOnError;
property TreatEnterAsTab;
End;
Implementation
End.

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

COMPONENTS
DEVELOPERS

37

Writing new components (3) in Lazarus (continuation 1)


A testing ground for the new component

A general test-bed code skeleton

To develop a new component of any complexity, it is not


necessary or even helpful to start, as we did in the
previous article, by getting Lazarus to create a new
package for the component. In the development phase the
component will not be in any state to register with or
install in the IDE. We need a testing ground where we can
try out the component both visually and programmatically,
to check whether the code we write achieves its desired
purpose. We dont want to crash Lazarus in the process by
linking a potentially buggy component into the IDE. So
we set up a new project (ptestnumberedit.lpi) in a new
directory (NumberEdit) with a main form unit
uTestNumberEdit saved in a file named utestnumberedit.pp.
To this project we add (FileUnit) the Pascal unit
numberedit saved in a file named numberedit.pp which
contains the skeleton code given above. Alternatively, you
can create a new package and package directory for the
new component, and comment out the Register
procedure Lazarus inserts, so that you avoid IDE
registration issues at this early component development
stage.
The main form unit uTestNumberEditwill be the testing
ground for our new component as we add functionality to
it and test it. So to the empty unit skeleton written by
Lazarus we add numberedit to the uses clause, and a
private field FNumberEditof type TNumberEdit to the
TForm1 class. Additionally we add an OnCreate event
handler for the form (by double-clicking on the
appropriately named fields of Form1sEvents page in the
Object Inspector), and insert basic creation code for the
FNumberEdit instance of TNumberEdit that we will
instantiate and test in this project. At this stage our
skeleton testing code looks like the following:

This kind of test-bed unit is a pattern that can be adapted


for testing any new component. Having instantiated the
component with a call to Create() we have to remember
to set a few basic properties, including Parent (to get the
component to display correctly on the form) and position
properties. At this stage we will accept the default values
for Width and Height, but of course these and many
other properties could be set specifically in the
FormCreate handler. We will need to add a number of
private fields and setter/getter methods for the properties
we are adding. Once this is done the class declaration has
expanded to look like this:

Unit uTestNumberEdit;
{$mode objfpc}{$H+}
Interface
Uses
Classes, SysUtils, FileUtil, Forms, Controls,
Graphics, Dialogs, ExtCtrls, numberedit;
Type
{ TForm1 }
TForm1 = Class(TForm)
Procedure FormCreate(Sender: TObject);
Private
FTestEdit: TNumberEdit;
Public
{ public declarations }
End;
Var Form1: TForm1;
Implementation
{$R *.lfm}
{ TForm1 }
Procedure TForm1.FormCreate(Sender: TObject);
Begin
FTestEdit := TNumberEdit.Create(Self);
FTestEdit.Parent := Self;
FTestEdit.Top := 10;
FTestEdit.Left := 10;
end;
End.

38

COMPONENTS
DEVELOPERS

TCustomNumberEdit = class(TCustomLabeledEdit)
Private
FBeepOnError: boolean;
FDefaultValue: double;
FEnforceMinMaxLimits: boolean;
FFocusColor: TColor;
FIntegersOnly: boolean;
FMaximumValue: double;
FMessageOnError: boolean;
FMinimumValue: double;
FTreatEnterAsTab: boolean;
Function GetFloatValue: double;
Function GetIntegerValue: int64;
Procedure SetFloatValue(AValue: double);
Procedure SetIntegerValue(AValue: int64);
public
property BeepOnError: boolean read FBeepOnError
write FBeepOnError;
property DefaultValue: double read FDefaultValue
write FDefaultValue;
property EnforceMinMaxLimits: boolean
read FEnforceMinMaxLimits
write FEnforceMinMaxLimits;
property FloatValue: double read GetFloatValue
write SetFloatValue;
property FocusColor: TColor read FFocusColor
write FFocusColor;
property IntegersOnly: boolean
read FIntegersOnly write FIntegersOnly;
property IntegerValue: int64
read GetIntegerValue write SetIntegerValue;
property MaximumValue: double read FMaximumValue
write FMaximumValue;
property MessageOnError: boolean
read FMessageOnError write FMessageOnError;
property MinimumValue: double read FMinimumValue
write FMinimumValue;
property TreatEnterAsTab: boolean
read FTreatEnterAsTab write FTreatEnterAsTab;
end;

Lazarus will generate the required private fields and


methods bodies in the implementation section when we
press [Ctrl] [Shift] [C]. This saves a lot of typing!
(Removing this Editor functionality from the Starter
Edition of Delphi XE was a mean-spirited step, in my
view. Beginners also need to write classes, and appreciate
such Editor auto-completion features). In order for this to
compile we need to add Graphics to the uses clause of
the component unit, so the compiler can find the
declaration of TColor for FFocusColor. Note that we
have no private integer or float fields to support the
FloatValue or IntegerValue properties. These will be
accessed solely via suitably declared methods, which use
the existing TEdit.Text field as the basis for a type
conversion to obtain or set the desired value.
NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

Writing new components (3) in Lazarus (continuation 2)


A component testing framework

To recap what we need to do to set up a suitable test-bed


application for our new component (before we eventually
add it to a dedicated package to register it with the IDE):
create a new Lazarus project whose main form will
carry the dynamically instantiated component
add the new component's unit name to your test
project's uses clause, and use the Project Inspector's
[+] button to add the new component's unit to the
test project
create the new component in the main form's
OnCreate (or OnActivate) event handler, setting at
a minimum the component's Parent, position and
size properties
ensure you set the Owner parameter of the
Create()call to the containing form (Self in the
code above), so that Lazarus' automatic destruction
mechanism will then see to the component's eventual
disposal.

(where ErrorMessage an error-handling or exceptionraising routine).


The LCL does have a built-in messaging system, and there
is an analogous LCL method to the above VCL example.
It is declared in TWinControlas follows:
procedure WMChar(var Message: TLMChar);
message LM_CHAR;

However, the LCL messaging system does not emulate the


VCLs, since Lazarus is cross-platform and obviously
cannot just clone Windows messages in the hope they will
apply to other platforms, since they dont. Indeed there are
no equivalents on Unixes to many Windows messages
(and vice versa). The conclusion is that using the messaging
system to customise components is not the best way to go
in Lazarus, unless you understand the innards of the LCL
pretty well. This is one reason why (even when you have
the full source) porting complex Delphi components to
Lazarus often fails. The Windows API is, well, Windowsspecific. The Windows messaging system is not emulated
on other platforms. When writing a Lazarus component it
is often better to start from scratch with cross-platform
Validating user input
The major motivation for developing this component is to considerations in mind, rather than try to get a Windows
way of doing things to work on other platforms. However,
encapsulate reliable validation functionality in an edit
if
we are to avoid Windows message-response methods in
widget. We want both to restrict users to entry of valid
customising
our component, what do we use instead?
numeric keystrokes (digits, +/-, decimal separator), and we
want to validate the resulting string. The string
Overriding the KeyPress method
+123.45.678 contains only valid characters, but is not a
The LCL provides just the method we need to override
valid number, because the validity of a 'valid' numeric
for
filtering and validation of keyboard input. It is the
character may depend on its position in the string.
KeyPress
method declared in TWinControl as follows:
Compare -123 (valid) with 12-3 (invalid).
Our validation algorithm must therefore operate not only procedure KeyPress(var Key: char); virtual;
at the level of individual key entry, but also operate on the
entire string-entered-so-far to reject values like 12-3 whose So our component needs to include:
individual characters will have already passed a onecharacter-at-a-time validator.
protected
procedure KeyPress(var Key: char); override;
If you come from a Delphi background you may be
familiar with overriding message handlers. You can create
It's all too easy to forget the override instruction! And the
a new message-response method which will respond to a
implementation section needs the following
given message before the form or default components
procedure KeyPress(var Key: char);
message handlers respond to it. You can handle the
Begin
Windows wm_Char message that occurs whenever the
if ReadOnly then Exit;
if FTreatEnterAsTab and (Key = #13) // #13 = [Enter]
user presses a non-system key. Your component class
then begin
declaration would include:
Key := #0;
public
procedure WmChar(var Msg: TWmChar); message
wm_Char;

and for our numeric component we might implement the


message-response method as follows:
procedure WmChar(var Msg: TWmChar); message wm_Char;
begin
if not Char(Msg.CharCode) in ['.', '+', '-',
'0'..'9'] then ErrorMessage
else inherited;
end;

PerformTab(True);
Exit;
End;
if not (Key in [#8,#9,'+','',DefaultFormatSettings.DecimalSeparator,'0'..'9'])
then begin
ConditionalErrorMsg('The character ''%s''
cannot be part of a number',[Key]);
Key := #0;
End
else
if ValidInContext(Key)
then Inherited KeyPress(Key)
else Key := #0;
End;

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

COMPONENTS
DEVELOPERS

39

Writing new components (3) in Lazarus (continuation 3)


If the control has been made read-only we exit
immediately since no key processing needs to be done.
Otherwise in overriding KeyPress, we treat [Enter] as if
it were [Tab] if that option is set, and filter out unwanted
keys by setting Key := #0. Acceptable keys (valid digits
etc.) are passed to the ValidInContext() function which
validates the entire potential edit string (that is, any
existing Text plus the Key being tested). If the key
passes this test it is processed by the inherited KeyPress
procedure which passes it on through the LCL widget
system to appear in the edit; otherwise it is set to #0. You
will see that Key is a var parameter which allows this
Key-behaviour.
Note the use of
DefaultFormatSettings.DecimalSeparator to cover
use of either , or . for the decimal separator (depending
on the users locale).
Note also the use of ConditionalErrorMsg() to handle
invalid input. Many people advocate using exceptions for
all cases of this sort, and you often see code in a function
which validates numeric input from the Text of an edit
something like:
try
i := StrToInt64(Text);
except
result := False;
// some procedure to handle the error here
Exit;
End;

This has the advantage that the validation is carried out by


a RTL function (StrToInt64()), which is likely to be far
better tested and debugged than any code we write. You
might prefer to alter the validation routines in this
component along those lines, which is fine. However, my
preference is to save exceptions for handling unexpected
errors, and situations that are basically the programmers
fault. We know already that users will, perhaps
inadvertently, enter invalid characters. The whole point of
this component is to provide for exactly that kind of
invalid input. So I prefer either silently to ignore such
errors letting the user type away until something valid is
entered, or (with the option set to beep and/or provide an
informative error message) to give the user helpful
feedback if, say, she enters in the middle of a number.
Exceptions seem overkill for this, and I believe incur more
overhead than a simple procedure call. You can see the
code for ConditionalErrorMsg() in the source, which
is available to subscribers for download from the Blaise
Magazine website.
Lastly note the inclusion of #8[BackSpace] in the set of
filter characters. KeyPress monitors not only
alphanumeric key characters but the control characters at
the beginning of the ASCII code list such as [BackSpace]
and [Enter]. We definitely want to allow [BackSpace] for
editing characters, so we have to test for it specifically to
ensure it passes and is processed by the inherited
KeyPress (which passes it to the underlying widget).
System keys such as the arrow keys, [Home], [End] and
[Delete] do not trigger KeyPress() so we can ignore
them as potential values for Key.
40

COMPONENTS
DEVELOPERS

A number validation algorithm

Another reason I have avoided trapping exceptions by


using RTL type conversion routines as a means of
validating input is to demonstrate a deterministic finite state
machine (DFSM) to readers who have not come across this
very useful algorithm, which is also applicable in situations
beyond parsing and validation. A DFSM is also known as
a deterministic finite automaton (DFA). These ghastly names
may serve to put beginning programmers off using a very
versatile idea, applicable whenever a system can be in a
number of mutually exclusive states, each requiring
different program responses. The algorithm simply checks
to see which is the current state, and responds accordingly,
changing the state if necessary. So at base it is very simple
indeed, consisting of a case statement (or the equivalent)
which directs program execution based on the current
state.
You may well have coded such an algorithm intuitively,
even if you have never heard of the label computer
science academics give it. Using the algorithm involves
analysing the program scenario into a finite number of
states (see where the name comes from?), and then writing
a diagram showing comprehensively how the different
states are related, or in computer science lingo, what are
the valid transitions from one state to another. This state
diagram is then translated into code which provides for all
valid transitions, and flags invalid transitions with an
exception or other error routine.
We have two slightly different situations to analyse: valid
entry of characters to form an integer, and valid entry of
characters to form a floating point number. Well look in
detail here at the integer scenario since it is slightly simpler
(code for the floating point DFSM is also in the
downloadable source).
A finite state machine describing how an
integer is built from characters

Our KeyPress(Key) routine has already ensured that


only number-building characters are allowed. (See the line
if not (Key in [#8,#9,'+','',DefaultFormatSettings.DecimalSeparator,
'0'..'9']) above). Our subsequent validation routine
(function ValidForInteger(Key: Char): boolean)

will look not merely at the single character just entered,


but consider the whole history of characters entered so
far, to check that the entire string (including the current
character, Key) is still valid. It needs to reject strings such
as 123+45 and allow strings such as +12345. Before we
can draw a state diagram, we need to analyse all the
possible valid states. Simple deterministic state machines
start with a single initial state, and conclude with a single
final state. Fortunately ours is a simple state machine. We
will declare an enumeration type for the possible valid
states. The initial state on entry will be stStart, and the
final state will be stInteger. We also must allow a sign
state, lets call it stSign. This leads to an enumerated
type declaration as follows:

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

Writing new components (3) in Lazarus (continuation 4)


type
TStateEnum = (stStart, stSign, stInteger);

Now we draw a state diagram, showing all possible valid


transitions from state to state. This will look like Figure 1.
digit
stInteger
digit

stStart

digit

stSign

Figure 1: A state diagram for parsing an integer string value


Function TCustomNumberEdit.ValidForInteger
(Key: Char): boolean;
type TStateEnum = (stStart, stSign, stInteger);
var state: TStateEnum = stStart;
p
: integer; tmp: string;
procedure ConstructStringToProcess;
begin // this code checks where the insertion cursor is
if (SelStart = 0) then tmp := Key + Text
else if (SelStart = Length(Text))
then tmp := Text + Key
else
begin
tmp := Copy(Text, 0, SelStart) + Key;
tmp := tmp + Copy(Text, SelStart+1, MaxInt);
end;
end;
Begin
Result := True;
ConstructStringToProcess;
for p := 1 to Length(tmp) do // the finite state machine begins here
case state of
stStart: if IsSign(tmp[p])
then state := stSign
else state := stInteger;
stSign: case IsDigit(tmp[p])
of True: state := stInteger;
False:
begin
ConditionalErrorMsg
('''%s'' is not allowed here',
[tmp[p]]);
Result := False;
Exit;
end;
end;
stInteger: if not IsDigit(tmp[p])
then begin
ConditionalErrorMsg
('''%s'' is not allowed
here',[tmp[p]]);
Result := False;
Exit;
end;
end; // case

The arrows show all possible valid transitions between all


possible states. Transitions not described by an arrow are
invalid. We always enter this scheme in the stStart state.
There are three valid transitions from this state we can
either accept a digit (taking us straight to stInteger) or
we can accept a '+'or a '-' sign taking us to stSign.
Thereafter the current state is either stSign or
stInteger. Whichever state is current, the only valid
character that can be accepted is a digit. Since stInteger
is the final acceptable state it is usually drawn with a
double border round it, while the intermediate stSign
has just a single border. It is very straightforward to
translate this scheme into Pascal code. We set the initial
state to stStart, and then use a case statement listing all
possible states to handle acceptance (or rejection) of the
current Key value, and any required change of state. The
resulting function looks like the code sample under Figure
1 at the left.
Youll see that three helper functions (with selfexplanatory names) are used: IsSign(), IsDigit() and
ConditionalErrorMsg(). A few lines of set-up code are
required, to ensure that the string to be validated, tmp, is
assembled correctly. This task is put into a sub-procedure
ConstructStringToProcess. It is not sufficient simply
to assume that tmp is equal to Text + Key. This is
because the latest Key character may not be inserted at
the end of the Text currently in the edit field. Keystrokes
such as [Home] or the arrow keys may have moved the
insertion point away from the end of the Text. Hence
we have to test for this possibility to correctly construct
the string to test (tmp). In the stStart state any
validated Key is acceptable, so all we have to do is change
to the correct state in moving away from stStart. In the
stSign state we have to ensure that only a digit is
accepted, and then change to the stInteger state. In the
stInteger state we dont have to change state ever,
merely ensure that only digits are accepted.
It is left to readers to construct their own finite state
diagram for the slightly more complex situation of
validating a floating point number. Suffice to say that two
additional states are possible (called
stDecimalSeparator, and stFloat in the example
code provided), and there are several valid state-to-state
transitions, so the required case statement is slightly more
complex. The set-up code required is identical. If you
produce a state diagram for this floating point validation
scenario, you can then try translating your diagram into
Pascal code, and see how it compares with the source I
came up with, available from the Blaise website. The
relevant function is called
TNumEdit.ValidForFloat(Key: Char): boolean.
Note the local declaration of the TStateEnum type (since

it is not needed elsewhere), and the useful option Free


Pascal offers of initialising local variables when they are
declared:
var state: TStateEnum = stStart;

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

COMPONENTS
DEVELOPERS

41

LAZARUS
THE COMPLETE GUIDE
Simple Package 35,00
(this excludes shipping costs 17.50)
- Just the book and a Windows installer CD
for Lazarus value 12.50
- 50 additional sample projects
- extra: additional prenstalled component suites

Complete Package 60,00


(Hardcover) or 50,00 (Paperback)
(this excludes shipping costs 17.50).
Total value of the package 112,50
Blaise Magazine offers a complete package
The complete package contains
Lazarus the Complete Guide value 50.00
- a Windows installer CD for Lazarus value 12.50
- Lazarus Portable, a prenstalled copy Lazarus on
a free 4GB USB stick, value 25.00
- 50 additional sample projects
- extra: additional prenstalled component suites on both:
CD and USB Stick.
- free DVD Blaise Pascal Magazine Library, value 25.00

WELCOME PACKAGE FOR NEW SUBSCRIBERS


(You need to become a subscriber first)
If you take out your first subscription to Blaise Pascal Magazine
you will be eligible for one half-price copy of
"Lazarus The Complete Guide
(this excludes shipping costs).
A top-quality book for 50% off!
You pay for your subscription the normal price for what
ever subscription you want plus 25,00 for the book...

Blaise Pascal Magazine www.blaisepascal.eu

58

COMPONENTS
DEVELOPERS

COMPONENTS
SEPTEMBER
2011 BLAISE PASCAL MAGAZINE
Pag 18
87
DEVELOPERS

Writing new components (3) in Lazarus (continuation 5)


Properties and methods
Most of TNumberEdit's property-setting and propertygetting code is fairly trivial, except for the conversion
methods that get numeric values to and from the edits
Text. Functionality to highlight the edit field when it
receives the focus (depending on the FocusColor
property) is programmed by overriding the OnEnter
event handler. The minimum and maximum limits for
entered values are enforced (if the
EnforceMinMaxLimits property is set) in an overridden
OnExit event handler. Suitable default values for the
added properties are set in the component's constructor
(though you may wish to set them to other values that
better fit your needs).
Since several methods show error messages (if the
MessageOnError property is set), the text of these was
entered as resourcestrings, and the i18n page of the
Project Options (and eventually the resulting component
package) was set to a languages subdirectory. Setting the PO
output directory and checking the Enable i18n checkbox of
the Project Options i18n page is all that is required for
Lazarus to automatically produce a projectname.po file in the
specified PO directory. This file can then be copied, and
the copied .po files used to produce translations of the
listed strings (the example code available for download
contains just the original English .po file no translations
have been attempted).
A few lines of code to load the
correct set of translated strings in an
application that uses the component
will then localise the application so
that users of the component get
error messages in an appropriate
translated language. This requires
you to add the Translations unit to
your project's uses clause, and use
the functions it provides (and also
of course to translate the strings
into a new .xx.po file). This
straightforward internationalisation
facility is a boon for Lazarus
developers who live in a culture
where English is not the main
language, however useful English is
as a lingua franca in programming
communities (as seen in Niklaus
Wirth's choice to make the original
Pascal language a subset of English,
rather than of Swiss-German or
even French as might have been
expected from a native of
Winterthur).

Comprehensive component testing


Properly testing GUI components is not an easy task,
since they require user interaction in testing that is
complex to simulate and automate in code. It is far easier
to write test routines for procedural-style programs
where each procedure/function has clearly defined
purposes, parameters, limits, return values and so on. I
settled for a pretty simple testing application that allows
user input to a single TNumberEdit instance (the field
originally named FTestEdit above was renamed to
FTestEditRadius) whose validated value is processed
and passed to two read-only TNumberEdits which display
the area of a circle and the volume of a sphere of the
given radius (named FTestEditArea and
FTestEditVolume). You can see the main form of the
application in Figure 2.
Because there are many boolean properties to test, and
keeping track of their names and manually writing code to
create checkboxes for them is very tedious, the main form
of the test application creates these many test controls
dynamically, using two routines, GetPropList() and
PropIsType(), from the typinfo unit to obtain the
necessary published information to name each required
checkbox, and assign its initial Checked property and
OnChecked event handler appropriately.

Figure 2: The test application user interface


NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

COMPONENTS
DEVELOPERS

43

Writing new components (3) in Lazarus (continuation 6)


The code to accomplish this dynamic creation and
placing of checkboxes is as follows:
procedure TForm1.CreatePropertyCheckboxes;
procedure CreateNumEditPropertyAccessComponents
(numEdit: TNumberEdit);
var
cmpCB: TCheckBox;
k: integer;
i: integer;
retValue: integer;
pi: TPropInfo;
ppl: PPropList;
function GetNamePrefix: string;
begin
if numEdit = FTestEditRadius then result :=
'cbR'
else if numEdit = FTestEditArea then result
:= 'cbA'
else result := 'cbV';
end;
begin
try
k := 0;
retValue:=GetPropList(numEdit, ppl);
if (Assigned(ppl) and (retValue > 0)) then
begin
for i := 0 to retValue-1 do
begin
pi := ppl^[i]^;
if PropIsType(numEdit, pi.Name, tkbool) then
begin
cmpCB := TCheckBox.Create(self);
cmpCB.Caption := pi.Name;
cmpCB.Name := GetNamePrefix + pi.Name;
cmpCB.Parent := Self;
cmpCB.Left := numEdit.Left;
cmpCB.Top := succ(k)*26 +
numEdit.BoundsRect.Bottom;
cmpCB.OnClick := @DynamicChkboxClick;
TCheckbox(cmpCB).Checked :=
Boolean(GetOrdProp(numEdit, pi.Name));
if (numEdit <> FTestEditRadius)
then
begin
if (pi.Name = 'ReadOnly') then
begin
numEdit.ReadOnly:= True;
TCheckbox(cmpCB).Checked := True;
TCheckbox(cmpCB).Enabled := False;
end;
TCheckbox(cmpCB).Enabled := False;
end;
InsertControl(cmpCB);
inc(k);
end
end
end
finally
Freemem(ppl);
end;
end;

Packaging the finished component

You might hope that this component would be placed in a


custom package, ready for installation in the IDE as was
available for the previous article's TCompanyLabel. At
first I refrained from doing this (though there is nothing
to stop readers from so doing) because this is very much
beta code, only just developed. I want to test it out in
several applications where different stresses and contexts
may show up shortcomings or bugs before I am happy to
package it and regard it as worthy of a place on the
Component Palette. Readers may also give helpful
feedback on how to improve this component. Do contact
Blaise with your ideas and comments. The test application
containing the unit with component code is available for
download from the usual subscriber section of the Blaise
website. Actually, at the Editor's insistence, a
numbereditpackage.lpk is included in the downloadable code
which you can install in Lazarus. It is not recommended,
and certainly unsuitable for production code, but let me
know how you get on if you install it for your own use!
The author
Howard Page-Clark lives near Poole, Dorset in the
UK. He is a hobby programmer who first made use
of Delphi to develop database programs when
working as a book keeper for a disability charity.
After a period as a teacher of science to teenagers,
he now works as a volunteer with charities and
church groups. He is married to Jodi, and they have
four adopted children, now all grown-up.

begin
FCurrentTestEdit := FTestEditRadius;
CreateNumEditPropertyAccessComponents(FTestEditRadius);
FCurrentTestEdit := FTestEditArea;
CreateNumEditPropertyAccessComponents(FTestEditArea);
FCurrentTestEdit := FTestEditVolume;
CreateNumEditPropertyAccessComponents(FTestEditVolume);
FCurrentTestEdit := FTestEditRadius;
end;

44

COMPONENTS
DEVELOPERS

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

Pag 45

productivity software building blocks

TMS MultiTouch SDK By Bruno Fierens


method and structures are all declared. So, in Delphi, if
The first multitouch experience offered by Microsoft we'd want to handle multitouch input, this is typically done
was the Microsoft Surface introduced in May 2007.
in following way:
The Microsoft Surface consisted of quite
sophisticated hardware made up of a projector and 5
infrared cameras. The software consisted of an SDK TMyMultiTouchControl = class(TCustomControl)
private
and drivers for Windows Vista. The SDK targetted
procedure WMTouch(var Message: TMessage);
message WM_TOUCH;
the WPF framework in the first place. The hardware
end
;
was very expensive and the experience to create apps
with WPF and the SDK was daunting. With Windows procedure TMultiTouchRegion.WMTouch(var Message:
TMessage);
7, Microsoft added built-in support for multitouch
var TouchInput: array of TTouchInput;
and we saw the introduction of low cost hardware
i: integer;
like the Dell ST220T multitouch monitor or the HP
begin
SetLength(TouchInput, message.WParam);
TouchSmart machine. This means that at this point,
GetTouchInputInfo(Message.LParam,
the only remaining obstacle to create multitouch
Message.WParam, @TouchInput[0],
applications is writing the software. With Microsoft
sizeof(TTouchInput));
technologies, this mainly still means going the WPF
for i := 0 to message.wParam - 1 do
route and this can be cumbersome, time-intensive
outputdebugstring(PChar(IntToStr
(TouchInput[i].x)+':
and complex at times.
Multitouch background

+IntToStr(TouchInput[i].y)));

Making an application multitouch-aware typically means:


1) Handling multitouch events
2) Manipulating objects synchronously with
the multitouch events
3) Optionally handling inertia
In the next 3 paragraphs, each of these steps will be
explained.

CloseTouchInputHandle((Message.lParam)
inherited;
end;

Manipulating objects

To handle the synchronous manipulation of objects with


movement of touch points, the Windows 7 SDK
Handling multitouch events
introduces the IManipulationProcessor COM interface.
From Windows 7, a new message is defined:
This interface is declared in the Delphi unit
WM_TOUCH. This new window message is the main
Manipulations.pas. For each object that we want to have
starting point to begin handling multitouch. The definition manipulated, it is required to create an instance of the
of this message is:
IManipulationProcessor and connect it with a Delphi
WM_TOUCH: Notifies the window when one or more touch
object that implements the _IManipulationEvents
points, such as a finger or pen, touches a touch-sensitive
interface. The task of the ManipulationProcessor is to
digitizer surface.
convert the incoming touch point information to
The WM_TOUCH message returns following parameters:
transformations in terms of translation/rotation/scaling
Low-order word of the wParam parameter contains the
of objects.
number of touch points associated with this message.
The IManipulationEvents interface is defined as:
The lParam parameter contains a handle that can be used
_IManipulationEvents = interface(IUnknown)
with the new call GetTouchInputInfo(). With this
[SID__IManipulationEvents]
method, detailed information about the touch points can
function ManipulationStarted(
be retrieved. The definition of the new
x: Single; y: Single):
HRESULT; stdcall;
function ManipulationDelta(
GetTouchInputInfo() API function is:
BOOL WINAPI GetTouchInputInfo(
__in
HTOUCHINPUT hTouchInput,
__in
UINT cInputs,
__out PTOUCHINPUT pInputs,
__in
int cbSize
);

with: hTouchInput, the handle that was passed in the


lParam parameter of the WM_TOUCH message. cInputs
contains the number of structures passed via the pInputs
parameter. pInputs is a pointer to an array of
_TOUCHINPUT structures. Note that when the
GetTouchInputInfo() was called, this should be followed
by a call to CloseTouchInputHandle(). Fortunately, from
Delphi 2010, this new message identifier, the new API

x: Single; y: Single;
translationDeltaX: Single;
translationDeltaY: Single;
scaleDelta: Single;
expansionDelta: Single;
rotationDelta: Single;
cumulativeTranslationX: Single;
cumulativeTranslationY: Single;
cumulativeScale: Single;
cumulativeExpansion: Single;
cumulativeRotation: Single): HRESULT; stdcall;
function ManipulationCompleted(
x: Single; y: Single;
cumulativeTranslationX: Single;
cumulativeTranslationY: Single;
cumulativeScale: Single;
cumulativeExpansion: Single;
cumulativeRotation: Single): HRESULT; stdcall;
end;

productivity software building blocks

46

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

productivity software building blocks

TMS MultiTouch SDK (Continuation 1)


So, basically, to manipulate objects, for each touchpoint,
identify the object under the touchpoint, call the
IManipulationProcessor's ProcessDown, ProcessMove,
ProcessUp methods and then the
IManipulationProcessor will call the object's
_IManipulationEvents interface methods to update the
transformation matrix we can associate with the object to
set its translation, rotation and scale.
Handling inertia

To create natural user interfaces, handling inertia is also a


critical part. The definition of inertia is: the resistance of
any physical object to a change in its state of motion or
rest, or the tendency of an object to resist any change in
its motion. In terms of manipulating objects on the
screen, that means that when moving an object with a
given velocity on the screen, moving the finger will not
immediately cause the object to stop but instead slowly
decrease speed due to so called resistance of the surface
till it eventually stops. Technically, handling inertia works
in a similar way as manipulation. Each object creates an
instance of the IInertiaProcessor COM interface and
connects this interface with the object's
IManipulationEvents interface.
In the ManipulationCompleted() method, we retrieve
the parameters of the touchpoint movement, i.e. the
velocity in X and Y direction and the angle of velocity and
initialize the IInertiaProcessor with these parameters.
It is then the IInertiaProcessor that will be responsible for
converting these velocities in further manipulations of the
object after the actual manipulation with touch ended.

Via a timer, the further inertia based manipulation


is requested with:
InertiaProcessor.Process(Completed);

When the inertia effect is finished, it will set the


parameter Completed to true, indicating no further
processing of the inertia effect is required.
Entering the TMS MultiTouch SDK

You might have come to the conclusion from the above


technical description of multitouch handling that this is far
from trivial. At TMS, we thought it would be way more
convenient to encapsulate all these complexities in a VCL
component: TMultiTouchRegion that handles all this
technical stuff in the background for you and just expose
the capabilities by a set of properties and events. In addition
to the TMultiTouchRegion that is the core component for
handling multi-touch, the TMS MultiTouch SDK comes
with over 30 additional touch supporting controls as typical
touch based interfaces require much more possibilities than
just manipulating objects with multi-touch.
TMultiTouchRegion component architecture

The TMultiTouchRegion is built-up of 4 layers. The


background or the region is the bottom layer. Note that
the background can also be moved, scaled, rotated. This
means that all items that will be placed on the background
will be transformed along with the transformation of the
background layer. Note that this also means that the
background can be much larger than the actual control
size and multi-touch interaction will allow to scale or
perform movements on this region. Immediately on top
of this background layer is a layer of control items.

productivity software building blocks

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

47

productivity software building blocks

TMS MultiTouch SDK (Continuation 2)


Control items are items that cannot be moved, sized or
rotated. The items have a fixed position and the only
interaction with this item type is a click, exposed by the
event OnControlItemClick. Control items in this layer are
added via the collection
TMultiTouchRegion.ControlItems. Typical purpose
of background control items is for example a button to
exit the application, rotate the screen etc.. The next layer
consists of regular items. Regular items can be
manipulated via multi-touch to scale, rotate or translate
the items. The regular items, added via the
MultiTouchRegion.Items collection, will as such always
be displayed on top of the background of background
control items.
The regular items are a collection of items of the type
TMultiTouchItem. The published properties of
TMultiTouchItem are:
TMultiTouchItem
published
CanMoveHorizontal: boolean;
CanMoveVertical: boolean;
CanRotate: boolean;
CanScale: boolean;
DetailItem: TDisplayInfo;
EnableFlip: boolean;
ItemHeight: integer;
ItemWidth: integer;
ItemX: integer;
ItemY: integer;
LinkedItem: TMultiTouchItem;
MainItem: TDisplayInfo;
Resizable: boolean;
ResizeHandleSize: integer;
StackIndex: integer;
end;

Getting started with the TMultiTouchRegion

To show how easy and fast immersive multitouch user


interfaces can be created with TMultiTouchRegion, this
sample code snippet will add all JPEG files from a folder
and add these to the TMultiTouchRegion and add a static
control item to leave the application when clicked:
begin
// sets the default size of items in the multitouch region
MultiTouchRegion1.DefaultItem.ItemWidth := 100;
MultiTouchRegion1.DefaultItem.ItemHeight := 150;
// Add all image files in the specified folder to be loaded by a thread by the component
MultiTouchRegion1.AddFileLocationsFromFolder
('\My Pictures\*.jpg');
// Add a control item of size 32x32 pixels in the top left corner of the control
MultiTouchRegion1.ControlItems.Add.Text := 'Exit';
MultiTouchRegion1.ControlItems[0].Tag := 1;
MultiTouchRegion1.ControlItems[0].Top := 0;
MultiTouchRegion1.ControlItems[0].Left := 0;
MultiTouchRegion1.ControlItems[0].Width := 32;
MultiTouchRegion1.ControlItems[0].Height := 32;
end;
// handle the closing from the static control item click
procedure
TForm1.MultiTouchRegion1ControlItemClick(Sender:
TObject;
ControlItem: TMultiTouchCustomItem);
begin
if ControlItem.Tag = 1 then
Close;
end;

With just simple boolean


properties (CanMoveHorizontal,
CanMoveVertical, CanRotate,
CanScale), it can be controlled

what type of movements of items


are possible, if the item can be
rotated or scaled. An item itself
has a foreground (MainItem) and
a background (DetailItem) side.
Having a main side and detail side
allows effects just as clicking on an
item to make it flip between main
side & detail side. The item main
or detail side can display an image,
text or the combination of text
and images.
An item can be linked to another item via the property
LinkedItem. When two items are linked, this means that
translations, rotations, scaling on one item will have the
same effect on the second item. Note that it is possible to
create a chain of linked items.
On top of the regular items is a last layer of static control
items. Similar to background control items, these items
cannot be moved, scaled, rotated. The only interactioin is
also clicking on an item.

The AddFileLocationsFromFolder will loop through


the matching JPG files in the folder and add these to the
MultiTouchRegion.Items collection. Note that it is a
background thread in the TMultiTouchRegion
component that will perform the actual loading of the
images as the call to AddFileLocationsFromFolder will
immediately return after all file names have been retrieved.

productivity software building blocks

48

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

productivity software building blocks

TMS MultiTouch SDK (Continuation 3)


It is also possible to add items one by one
programmatically. This can be done either by setting the
item's FileName property and the components internal
background thread will perform the actual loading:
MultiTouchRegion1.Items.Add.MainItem.FileName :=
myfilename;

that will save and restore the full item's setup and matrices.
This sample code snippet loads the images as a stack.
With the property MultiTouchRegion.OpenStackMode
set to dmGrid, this causes that when the stack is clicked,
the TMultiTouchRegion will automatically open the stack
with animation to show the images in dmGrid mode:

or the image can be loaded under application code control:


MultiTouchRegion1.Items.Add.MainItem.
Picture.LoadFromFile(myfilename);

TMultiTouchRegion display modes

As each regular item can be moved, scaled, rotated, the


TMultiTouchRegion keeps track of these transformations
via transform matrices. As each item can have a
background rectangle and the actual content rectangle, two
transforms are always associated with an item:
MultiTouchRegion.Items[index].
MainItem.BackgroundTransform: TD2DMatrix3x2F;
MultiTouchRegion.Items[index].
MainItem.ContentTransform: TD2DMatrix3x2F;

The background of an item is an optional rectangular area


that is displayed as placeholder for the content
(image/text) and has its own transfer. When items are
manipulated via multi-touch, it is these transform matrices
that will be updated. The transform matrices are
read/write properties. This means that the position of an
item can also be set under programmatic control via these
matrices and the TMultiTouchRegion itself will also
initialize these matrices dependent on the chosen display
mode.
MultiTouchRegion.DisplayMode: TMultiTouchDisplayMode

TMultiTouchDisplayMode is defined as:

dmRandom:
matrices of items are initialized in a random way by the
MultiTouchRegion as items are added
dmGrid:
matrices are setup in such a way that items are
displayed as a grid using the Cols/Rows properties
dmCascade: matrices are setup in such a way that items
are displayed in cascade style, using
HorizontalSpacing,VerticalSpacing properties
dmPosition:
matrices are setup to display items in a not rotated and
not scaled way at position Item.ItemX, Item.ItemY.
dmMatrices:
items will be displayed with the matrices
configured as is.
dmStacked:
items are displayed in grid mode organized in stacks.
All items with the same Item.StackIndex
property value will be displayed on top of each other
to form a stack.
It is the DisplayMode = dmMatrices that can be used to
persist the last location & manipulation of items.
The TMultiTouchRegion provides the built-in method

Item controls

Sometimes it is desirable to have controls associated with


an item. This could be a button to delete an item, a button
to open a software keyboard on screen to enter text for an
item etc...
It is expected that such a button control will move, rotate
and scale along with an item as it is manipulated.
To make it easy to add such controls, the item exposes a
Controls collection on Item.MainItem and
Item.DetailItem level. A control has a normal state and
a down state and the appearance of these 2 states can be
set via:

MultiTouchRegion.SaveToFile() /
MultiTouchRegion.LoadFromFile()
productivity software building blocks

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

49

productivity software building blocks

TMS MultiTouch SDK (Continuation 4)


Visualizers

MultiTouchRegion.Items[index].MainItem.Controls
[controlindx].Fill

and
MultiTouchRegion.Items[index].MainItem.Controls
[controlindx].FillDown

The Fill class can for example be used to specify a PNG


image used for the button.
The position of the control relative to the item is set with:
MultiTouchRegion.Items[index].MainItem.Controls
[controlindx].Position

Finally, handling a click of such a control is handled via the


event TMultiTouchRegion.OnItemControlItemClick(). This
event returns an instance of the ControlItem in the
Item.MainItem.Controls collection and an instance of the item
itself.

By default, an item in the TMultiTouchRegion can display


an image in various file formats: BMP, JPEG, PNG, GIF
or just text or a combination of image and text.
To make the component flexible to show other types of
files or formats, a visualizer virtual class is provided.
When a visualizer is associated with the
TMultiTouchRegion, it is the visualizer's protected virtual
method LoadImage that will get called whenever the
TMultiTouchRegion component needs it to draw a visual
representation of the item. The LoadImage() override is
supposed to return this via a TD2DPicture, a Direct2D
image. It is very easy to create a custom visualizer.
The minimum implementation is a class that descends
from and implements LoadImage().
The TMultiTouchRegion comes default with 3
visualizers. The built-in visualizer for the most common
image file formats, a

This sample code snippet shows how to add a Delete control in


the top right corner of an item that is only visible when the item TMultiTouchRegionPreviewVisualizer
is selected. The OnItemControlItemClick event will then delete to use the shell preview handler to display the shell
the item:
preview image for a given file and also a
procedure TForm1.FormCreate(Sender: TObject);
var
ctrl: TMultiTouchCustomItem;
begin
MultiTouchRegion1.Items.Add;

that is able to
render pages of a PDF file. On the next page is a
screenshot of such TMultiTouchRegion where the
PDFVisualizer renders the cover page of a number of
magazines. Note also the 4 control items (green circular
arrows) at each side of control from where screen rotation
is performed and the control item in the top right corner
to exit the application and the control item in the top left
corner to line up the items in grid mode.
TMultiTouchRegionPDFVisualizer

ctrl :=
MultiTouchRegion1.Items[0].MainItem.Controls.Add;
ctrl.Location := ilTopRight;
ctrl.Width := 16;
ctrl.Height := 16;
ctrl.Fill.Picture.LoadFromFile("cancelpic.png");
ctrl.Visible := false;
end;
TMyVisualizer = class(TMultiTouchRegionVisualizer)
protected
procedure TForm1.MultiTouchRegion1ItemSelectedChanged
procedure LoadImage(APicture: TD2DPicture;
(Sender: TObject; Item: TMultiTouchItem);
D2DItem: TDisplayInfo; AThread: TMultiTouchThread);
begin
end;
Item.MainItem.Controls[0].Visible := Item.Selected;
end;
procedure TForm1.MultiTouchRegion1ItemControlItemClick
(Sender: TObject; ControlItem: TMultiTouchCustomItem;
Item: TMultiTouchItem);
begin
Item.Free;
end;

productivity software building blocks

50

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

productivity software building blocks

TMS MultiTouch SDK (Continuation 5)

Summary

With Windows 7, Microsoft provides the software


infrastructure to create multitouch applications.
The provided APIs are unfortunatately still quite
complex and daunting to start with. With the TMS
MultiTouch SDK, TMS software encapsulates all these
complexities in a TMultiTouchRegion component and
makes it accessible and very fast to create immersive
touch applications.
The TMS MultiTouch SDK also comprises the
full range of TMS touch-centric smooth controls with
which graphically attractive full-blown point of sales,
multimedia, educative software can be created.
On top of that, TMS software also offers flexible
multi-touch hardware solutions, starting with a 32" 6
point multitouch table or wall model till solutions up to
60".
Or, you can also outsource your entire multi-touch
project as TMS software offers the services to create
this for you.

About the author


Bruno Fierens
He started doing several small projects in the mid-eighties
in GWBasic and soon after discovered Turbo Pascal v3.0
and got hooked to its fast compilation, clean language and
procedural coding techniques.
Bruno followed the Turbo Pascal releases and learned
object oriented programming when it was added to the
Pascal language by Borland. With Turbo Pascal for Windows
and Resource Workshop, he could do his first steps in
Windows programming for several products for the local
market.
TMS software became Borland Technology Partner in 1998
and the team grew to 4 persons in the main office in
Belgium and developers in Brazil, Uruguay, India, Pakistan
doing specific component development.
TMS software is now overlooking a huge portfolio of Delphi
components and looks forward to strengthen this product
offering in the future. With Delphi 2010, Embarcadero now
offers a very rich and powerful environment for creating
fast and solid Windows applications using the latest
technologies in Windows 7 such as touch.
Bruno said he will watch the announced cross-platform
development tools from Embarcadero closely and TMS
software is hopeful this will bring exciting new
opportunities for Embarcadero, Delphi and our
components. We live indeed again in very interesting times
for passionate Delphi developers.

Contact us for more information and details.

Website page: http://www.tmssoftware.com/site/multitouchsdk.asp


productivity software building blocks

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

51

52

COMPONENTS
DEVELOPERS

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

FireMonkey Containers and Frames By Bob Swart


starter

expert

which is clearly incorrect (there is no FireMonkey Frame


wizard in the Object Repository at all). See also

DELPHI 5 - XE2

TPanels play an important role in Delphi and


C++Builder VCL Forms Application, since they are
often used as visual containers for other visual VCL
components. Panels can also be placed on Panels in
parent-child relations, using the Align property to align
the panels on the Top, Bottom, Left, Right, whole
remaining Client area (or none).
Using FireMonkey, any component can act as a
container for other components. If you use the Tool
Palette to quickly add controls to a FireMonkey form,
then each new control will become the child of the
previous control, placed in the center of the active
control (which was the previous control if you only
keep on using the Tool Palette to add new controls).
However, clearly a TButton or TEdit is not the ideal
replacement for a container like the VCL TPanel.

http://qc.embarcadero.com/wc/qcmain.aspx?d=100
883 for my bug report about this documentation issue.

Without a real FireMonkey Frame, we can only simulate


the behaviour. This is done by creating a new FireMonkey
HD or 3D Form (instead of a Frame), placing
components on the Form just as if it was a Frame, and
then assigning the parent of the new Form to point to
some kind of container of the main Form, so the new
Form will be displayed inside the main Form.
This chain of thought may not be immediately clear, so
here are a few steps to reproduce the effect.
Create a new FireMonkey HD Application Delphi. This
will provide you with one main form already. Let's give the
form the name MainForm, and save it in uMainForm.pas,
so it's clear that this will be the main form, hosting the

TRectangle

FireMonkey offers the TRectangle as


most TPanel-like control that we can use
for this purpose. We'll use the
TRectangle in the next example, to
demonstrate just how it's similar (and
different) compared to the VCL's TPanel.
Apart from the fact that just about all
FireMonkey controls can act as
Containers, the Align property is also
extended with far more possible
alignment values. Apart from the wellknown alNone (the default), alTop,
alBottom, alLeft, alRight and
alClient, the FireMonkey Align

property can get one of the new values:


alCenter, alContents, alFit,
alFitLeft, alFitRight,
alHorizontal, alHorzCenter,
alMostBottom, alMostLeft,
alMostRight, alMostTop, alScale,
alVertCenter, and alVertical.
There is no alCustom that we have at

the VCL side, but the other alXXX


values more than make up for that.
FireMonkey Frames
Delphi and C++Builder VCL Forms
applications offer the functionality of
Frames: a reusable GUI container that
we can fill with visual and non-visual
components, and place on Forms (or
other Frames). However, Frames do not
(yet?) exist for FireMonkey application
(even if the Help seems to mention them at
http://docwiki.embarcadero.com
/RADStudio/en/Wizards_for_Creati
ng_Cross-Platform_Applications
_and_Components

frame forms that we'll design shortly. I've saved the project itself as
FrameDemo.dpr by the way. Now, do File | New Other, and in the
Object Repository go to the Delphi Files section, where you won't find
the FireMonkey Frame option (as mentioned in the help). Instead, we can add
a FireMonkey Form (HD or 3D): For our FireMonkey HD example, just
select a new FireMonkey HD Form. Give it the name FrameDemo and
save it in uFrameDemo.pas. To demonstrate that this Form will actually
be treated as a FireMonkey Frame.
Note that the Caption of the new Form can be left unchanged, since it
will not be shown (when using the Form as a Frame). I've changed the
Caption to FrameDemo only for the sake of the screenshots.
On the FrameDemo Form, we can start by placing a TRectangle
component, with the Align property set to alClient. That way, if we
place the FrameDemo as a Frame inside a Container at the main form,
it will completely fill that container (the client area).
On the TRectangle of the FrameDemo, we can place the components
that we would normally use in reusable Frames, like TEdit, TLabel, etc.

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

COMPONENTS
DEVELOPERS

53

FireMonkey Containers and Frames (Continuation 1)


Demo Frame

As an example, let's build a Frame where a user with a


Name can leave a Comment, for example on a weblog. To
design this frame, place a TLabel (called lbComment) and
a TMemo (called mmComment) as well as another
TRectangle with a TLabel (called lbName) and a TEdit
(called edName). The Align property of the first
TRectangle should be set to alClient, as mentioned before.
The second TRectangle, a child of the first, should have
its Align property set to alTop, and a height just a bit more
than the TEdit height. The TLabel on the second
TRectangle should get an Align of alLeft, and the TEdit
an Align of alClient. This leaves the label on the first
TRectangle, which can get an Align property value of
alTop, leaving the TMemo which can get an Align of
alClient.
The Structure pane (with the parent-child relationships) as
well as the design of the FrameDemo can be seen in the
screenshot below:

The actual trick is using this FrameDemo in the


MainForm, and allowing it to act like a Frame (instead of a
Form). For this, we need to return to the MainForm, and
place one or more TRectangles on the MainForm. Each of
them will be the container of the FrameDemo frame.
Then, we need to add the uFrameDemo unit to the uses
clause, so the MainForm knows about the TFrameDemo
type.

implementation
uses
uFrameDemo;
{$R *.fmx}
procedure TMainForm.FormCreate(Sender: TObject);
var
FrameDemo: TFrameDemo;
begin
FrameDemo := TFrameDemo.Create(Self);
FrameDemo.Rectangle1.Parent := Rectangle1;
end;

The downside of this approach is that the FrameDemo is


created and its Parent is assigned, but after the
FormCreate method is finished, we can no longer easily
access it, since the member FrameDemo itself has gone
out of scope. There is no memory leak (Self is the owner,
so the memory will be cleaned up), but it's not easy to get
our hands on the FrameDemo.edName.Text for example.

Frame as property

A better approach is to place the FrameDemo variable as a


field in the TForm class definition (and add the
uFrameDemo unit to the uses clause of the interface
section instead of the implementation section), leading to
the following code for two frames:

Finally, in the FormCreate event of the MainForm, we can


create an instance of the TFrameDemo form and place it
as a frame inside one of the TRectangles on the
MainForm. This can be implemented as follows:

54

COMPONENTS
DEVELOPERS

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

FireMonkey Containers and Frames (Continuation 2)


unit uMainForm;
interface
uses
System.SysUtils, System.Types, System.UITypes,
System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs,
FMX.Objects, uFrameDemo;
type
TMainForm = class(TForm)
Rectangle1: TRectangle;
Rectangle2: TRectangle;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
FrameDemo: TFrameDemo;
FrameDemo2: TFrameDemo;
end;

Summary

In this article, I've explained how we can mimic the


behaviour of VCL Frames in FireMonkey using
TForms and TRectangles. This and more FireMonkey
topics can be found in my Delphi XE2 Development
Essentials courseware manual, available from my
webshop at
http://www.eBob42.com/courseware and
free for my Delphi and RAD Studio XE customers.
Bob Swart
Bob Swart Training & Consultancy (eBob42)
http://www.DelphiXE.eu

var
MainForm: TMainForm;
implementation
{$R *.fmx}
procedure TMainForm.FormCreate(Sender: TObject);
begin
FrameDemo := TFrameDemo.Create(Self);
FrameDemo.Rectangle1.Parent := Rectangle1;
FrameDemo2 := TFrameDemo.Create(Self);
FrameDemo2.Rectangle1.Parent := Rectangle2;
end;

The resulting output is as follows (note that the two


TRectangles are a different shape, but the TFrameDemo
inside is using alClient and displayed correctly):
And we can use both the FrameDemo and the
FrameDemo2 to get to their edName and mmComment
fields and values when needed.

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

COMPONENTS
DEVELOPERS

55

Introduction to Databases Part 6


starter

expert

by Cary Jensen

DELPHI 5 - XE2

The powerful Delphi TClientDataSet


This is the sixth article in a series designed to
introduce you to Delphi database development. Th
previous article in this series discussed how to
configure OLE DB Providers and ODBC drivers
using Delphi's dbGo components.
This article looks at the basic use of the
ClientDataSet component, including how to load
data into it from a database, as well as how to write
changes made to a ClientDataSet's data back to
that underlying database. There is a general
discussion of powerful ClientDataSet features,
including the change cache, cloned cursors, and
nested datasets.

Loading and Saving ClientDataSet Data

A ClientDataSet can get its data from a number of


different sources. In many cases a ClientDataSet obtains
its data from an underlying database. Once this data is
loaded, it can be examined, modified, and eventually
written back to the underlying database from which it was
loaded (or even to another database, but that approach is less
common).
In the scenario in which the ClientDataSet's data
originates from an underlying database, the ClientDataSet
is typically used in conjunction with a DataSetProvider.
The DataSetProvider is responsible for obtaining data
from another TDataSet (such as an ADODataSet), and
providing that data in a form that the ClientDataSet can
load.
When you have finished editing the contents of the
An overview of ClientDataSets
ClientDataSet, and are ready to write those changes to the
A ClientDataSet is a TDataSet descendant that stores its
underlying database, you invoke the ClientDataSet's
data entirely in memory. Because this data is in memory,
ApplyUpdates method, which sends the change log to the
operations on that data (including filters, ranges, searches,
DataSetProvider. In response, the DataSetProvider
calculations, and the like) are very fast.
creates an internal SQLResolver, which it uses to
Another feature of the ClientDataSet is that it supports a generate SQL delete, insert, and update statements.
change cache. As long as a ClientDataSet's LogChanges
By default, these SQL statements are executed against the
property is set to True (the default), a ClientDataSet notes
same connection from which the DataSetProvider's
when a record is inserted, deleted, and updated.
TDataSet obtained the original data.
In addition, for those records whose data is changed,
It is also possible to use a ClientDataSet without an
the ClientDataSet remembers which specific fields are
underlying database. In this scenario, the ClientDataSet
modified, and stores both the original data and the
can receive data that is entered either programmatically or
updates.
data that arises from user input.
This data can then be stored for later use. In the simplest
The change cache is used for two purposes. First, it can be case, you can call SaveToFile to store this data in the
used to review individual changes, allowing unwanted
local file system. A subsequent call to LoadFromFile
changes to be discarded. For example, those records that
restores this data, permitting review or additional data
have been deleted can be reviewed and, if necessary,
entry.
restored. The second purpose of the change cache is to
When using a stream, you use the SaveToStream and
maintain sufficient details about those changes to permit
LoadFromStream methods.
them to be applied to the underlying database.
To read and write a ClientDataSet to a string, use the
Another significant feature of ClientDataSets is that they XMLData property. Similar to the situation described
can be persisted (preserved or stored). Both the data held above where the data is obtained from an underlying
in memory by a ClientDataSet, as well as the contents of
database, data entry and data edits, regardless of the
the change cache, can be written to a string, saved to a
source, can be reviewed and selectively discarded,
stream, or stored to disk. If, at some later time, that string, if necessary, when reading and writing from a file, stream,
stream, or file is loaded back into another ClientDataSet,
or string.
the original state of the persisted ClientDataSet is
There are a couple of significant differences between
restored. The process of storing and later restoring the
using a ClientDataSet with an underlying database and
ClientDataSet is achieved without any loss, and the
without. The first is that when used with an underlying
restored ClientDataSet is indistinguishable from the
database, a ClientDataSet obtains its structure, that is, the
original, no matter how long the ClientDataSet's data was definition of its columns and their types, from the
in storage.
TDataSet which loaded the data via the DataSetProvider.
This combination of high-performance, update caching,
When there is no underlying database, you must specify
and persistence make the ClientDataSet a component that the ClientDataSet's structure using either FieldDefs or
can be used in a wide range of situations. For example, a
TFields. This can be done manually at design time or
ClientDataSet can be used as an abstraction layer in an
programmatically at runtime.
otherwise traditional database client application. Or, it can The second significant difference is in how the change
be used to transfer binary files from one service to
cache is flushed. When used with an underlying database,
another. It can even be used to persist a complex
a call to ApplyUpdates which successfully applies all
relationship resulting from an analytical process into a
cached changes will result in the change cache being
blob field of a database for use at a later time.
emptied.
56

COMPONENTS
DEVELOPERS

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

Introduction to Databases Part 6 (Continuation 1)


When persisting to a string, stream, or file, the change
cache must be managed manually. For example,
after approving all changes in the change cache, and
before storing the ClientDataSet's data in a string, stream,
or file, a call to MergeChangeLog has the effect of
emptying the change cache, effectively making those
changes permanent. Alternatively, you can call
CancelUpdates to clear the change cache, thereby
reverting all records to their initial state.
With the exception of the differences noted above, nearly
all remaining uses of a ClientDataSet are identical,
regardless of where the data is sourced or stored
persistently. Even the management of the change cache
has more similarities than differences. For example,
reverting records to their previous state, testing the status
of a particular record (determining whether the current record is
an inserted record or a modified one), and undoing the last
change applied, is the same regardless of the data's origin.
In the remainder of this article, I want to touch lightly on
the general features ClientDataSets support in order to
give you a better overall view of a ClientDataSet's
capabilities. I hope that this overview will give you
incentive to try out this powerful component, if you
haven't done so already.

ClientDataSets support Locate and Lookup. The primary


difference between the FindKey methods and Locate and
Lookup, is that FindKey requires you to set an index in
advance, while Locate and Lookup do not require an
index.
The same can be said of ranges and filters. ClientDataSets
support SetRange and the corresponding ApplyRange
methods. They also fully support filters, and a
ClientDataSet's filters are applied faster than other Delphi
TDataSet descendants' filters, simply because a
ClientDataSet's data is entirely cached in memory.

Beyond TDataSet
In addition to the aforementioned features which
TClientDataSet inherits from TDataSet, it also
supports a number of valuable features that are not
available in other TDataSet descendants. For example,
TClientDataSet supports aggregation, and grouping.
An aggregate is a calculation (such as a sum, average, or
count) which can be calculated automatically for record
collections. These collections of records are defined by
groups. A group is a collection of records sharing a
common value on an indexed field. Using groups and
aggregates, you can count the total number of sales to
customers in a particular city, calculate the average salary
of employees by department, or determine which
products generate the most revenue all without using
Using a ClientDataSet
The first area I want to address is navigation in which the code. An example of aggregates can be seen in Figure 1,
where each customer's total sales is calculated
ClientDataSet is similar to functionality found in other
TDataSet descendants. For example, you move to the next automatically using an aggregate. In addition, the
record using the Next method, and move to the last record suppression of repeated values in the CustNo and
CustomerTotal fields is performed using the
by calling Last. You can also call the MoveBy method or
GetGroupState method, which lets you determine where a
set the RecNo property to make another record the
given record lies within a group. Another feature unique to
current record. Bookmarks are also supported, which
ClientDataSets
is the cloned cursor. A cloned cursor
enables you to identify records to which you want to
creates
a
new
reference
to the data and change cache of an
return later.
existing
ClientDataSet.
Importantly,
the original
One navigational feature of a ClientDataSet that deserves
ClientDataSet
and
its
clone
can
differ
in a number of
mention is that it supports bidirectional navigation, which
critical
ways.
For
example,
although
they
reference the
is something that many, but not all, TDataSet descendants
same
memory
store,
a
ClientDataSet
and
its clone can
support. This is particularly important since a
point
to
different
current
records,
employ
different filters,
ClientDataSet can load data from one of these
use
different
ranges,
and
even
have
different
ReadOnly
unidirectional TDataSet descendants, such as a
property
values.
Nonetheless,
when
changes
are
made to
dbExpress SQLDataSet, making it possible to support
bidirectional navigation and grid viewing of data obtained the underlying data store, both a ClientDataSet and its
clone are instantly aware of any changes applied to the
from one of these more limited TDataSets.
data
store.
Speaking of unidirectional TDataSets, these TDataSets
It's
actually
more powerful than it sounds since you can
tend to be readonly. The data in a ClientDataSet, however,
have
many
cloned
cursors on a single data store. In fact,
is always read/write (unless you specifically set its ReadOnly
once
a
ClientDataSet
has been cloned, the clone and the
property to True). As a result, the ClientDataSet again
original
are
equal
in
every
way, with respect to their data
permits you to actually edit data obtained from a
store.
For
example,
the
original
can then be closed and the
unidirectional, readonly TDataSet. (This feature was a primary
clone
will
continue
to
maintain
the
data and change cache.
motivating factor in including ClientDataSets in the Professional
Furthermore,
you
can
have
any
number
of clones of a
version of Delphi, beginning with Delphi 6 and Kylix. This version
single
data
store,
giving
you
the
chance
to
have three, ten,
coincided with the introduction of dbExpress, whose TDataSets are
fifty, or even more current records for that store.
all unidirectional and readonly).
Another significant feature that is unique to
With respect to searching, here again ClientDataSets
ClientDataSets is the nested dataset. A nested dataset is a
support all of the features of the TDataSet interface.
In addition to FindKey and FindNearest (and their GotoKey set of zero or more records, embedded in a single field of
a ClientDataSet's records. For example, you can have a
and GotoNearest counterparts),
NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

COMPONENTS
DEVELOPERS

57

Introduction to Databases Part 6 (Continuation 2)

Figure 1. A form displaying a ClientDataSet's aggregated calculations


ClientDataSet that stores individual customer records, with one record for each customer in the
database. One of the fields in this ClientDataSet can be a set of all that particular customer's orders.
An example of exactly this type of nested dataset is shown in Figures 2 and 3. Figure 2 shows a
DBGrid displaying the contents of a ClientDataSet that holds customer records where the nested
OrdersTable field has been selected. When a nested dataset field is selected you can click the displayed
ellipsis button to show the default nested dataset form, which looks like Figure 3. Alternatively, you can
point another ClientDataSet to this nested dataset field, in which case you can configure your own data
aware controls in which to display the nested dataset.

Figure 2. The Customer ClientDataSet contains a nested dataset field, named OrdersTable.
58

COMPONENTS
DEVELOPERS

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

Introduction to Databases Part 6 (Continuation 3)

Figure 3. The nested dataset OrdersTable is displayed in the default form.


Nested datasets are extremely powerful, in that a nested
dataset itself can contain one or more nested datasets, and
so on and so on. Couple this with the ability to persist a
ClientDataSet, and the combination provides a very
effective mechanism for maintaining, communicating, and
retrieving information about complex data relationships.
Summary

ClientDataSets are the most flexible of Delphi's


TDataSet descendants. They implement more of the
TDataSet interface than any other Delphi TDataSet,
and some features, such as cloned cursors and nested
datasets, make TClientDataSet a potent component on
its own, even in applications that are not associated
with a database. Moreover, its ability to hold data
obtained through other TDataSet components make it
a perfect addition to almost any database application.

NOVEMBER 2011 BLAISE PASCAL MAGAZINE 19

About the Author


Cary Jensen
is Chief Technology Officer of Jensen Data
Systems, a consulting, training, development,
and documentation and help system company.
Since 1988 he has built and deployed database
applications in a wide range of industries. Cary
is the best-selling author of more than 20 books
on software development, and winner of the
2002 and 2003 Delphi Informant Reader's
Choice Award for Best Training. A frequent
speaker at conferences, workshops, and
seminars throughout much of the world, he is
widely respected for his self-effacing humour
and practical approaches to complex issues.
Cary's latest book, Delphi in Depth:
ClientDataSets, is available from
http://www.JensenDataSystems.com/cdsbook.

COMPONENTS
DEVELOPERS

59

COMPONENTS
DEVELOPERS

Now available for DELPHI XE2 -32 and 64


The Delphi Starter Edition now also has the
ability to do multi tier development
for no cost at all.
kbmMW CodeGear Edition v. 3.52.00 contains full
support for Delphi Starter Edition, and provides
access to SQLite in addition to the standard
Delphi Starter Edition database connectivity
frameworks, BDE and IBExpress.

For kbmMemtable now available:


full XE2 support (32/64/osx)

For kbmMW there are multiple new items:


Remote Desktop
Client/Server/Proxy Support,
NEW JSON support, JSON streaming of
kbmMemTable datasets,
Delphi Starter Edition also lacks proper XML
bugfixes and more.
support and the local TClientDataset, which is
Improved HTML template support,
also to be found as part of kbmMW CodeGear
improved Cookie Support etc.
Edition in the form of TkbmMWDOMXML and the
Lots of improvements related to using
high performance TkbmMemTable.
kbmMW as a web server producing dynamic
web pages. kbmMemTable on Win64.
In addition its possible to upgrade to
kbmMemTable Standard Edition for only US$30.
The upgrade includes full source for
kbmMemTable, and includes kbmSQL which
provides a SQL frontend to kbmMemTable.

Tests done:
10 million records in memory
with multiple fields and indexes...

ESB, SOA,MoM, EAI TOOLS FOR INTELLIGENT SOLUTIONS. kbmMW IS THE PREMIERE N-TIER PRODUCT FOR DELPHI /
C++BUILDER BDS DEVELOPMENT FRAMEWORK FOR WIN 32 / 64, .NET AND LINUX WITH CLIENTS RESIDING ON

You might also like