You are on page 1of 126

BLAISE PASCAL MAGAZINE

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


AND RELATED LANGUAGES

Pascal

18

's the code I need to call?

irr

or
,m

irr

or

on

th

wa

ll,

wh

at

SPECIAL ISSUE 126 PAGES

Cylindrical anamorphosis / Chinese Nightingale

Book review James Duff


Writing New Components in Lazarus, Part 2 Howard Page-Clark
Delphi XE2 is the future - theme XE2 Fikret Hasovic
Cylindrical anamorphosis Peter Bijlsma
Web Service Toolkit as a rich type framework for Plug-in development
Inoussa Ouedraogo
Polygon colouring and area calculation David Dirkse
Creating a Database program from scratch - part 1 Detlef Overbeek
An Android client for a DataSnap Server Part 2 Daniele Teti
Anti-freeze for the VCL Alexander Alexeev
Delphi XE2 New Features - theme XE2 Bob Swart
Learning to use FastReport 4 for VCL - Part 3 Sergey Lyubeznyy
Question and Answers Henk Schreij
Creating 64-bit applications with Delphi XE2 - theme XE2 Jeremy North
kbmSQL Structured Query Language for your memory table Kim Madsen
High Level Multithreading PrimozGabrijelcic
TMS Scripter Studio Pro Wagner R. Landgraf & Bruno Fierens
FreePascal vectorial Felipe Monteiro de Cavalho
The tale of the Nigthtingale Detlef Overbeek
Using the Advantage Database Client Engine Michal Van Canneyt
Configuring OLE DB Providers and ODBC Drivers Cary Jensen
Delphi XE2 LiveBinding - theme XE2 Bob Swart

September 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 18
ALL ABOUT DELPHI AND DELPHI PRISM(.Net), LAZARUS & PASCAL AND RELATED LANGUAGES
CONTENTS

Volume 18, ISSN 1876-0589

Articles

Editor - in - chief

Book review
James Duff page 4
Writing New Components in Lazarus, Part 2
Howard Page-Clark page 6
Delphi XE2 is the future - theme XE2
Fikret Hasovic page 13
Cylindrical anamorphosis
Peter Bijlsma page 17
Web Service Toolkit as a rich type framework
for Plug-in development
Inoussa Ouedraogo page 21
Polygon colouring and area calculation
David Dirkse page 24
Creating a Database program from scratch - part 1
Detlef Overbeek page 29
An Android client for a DataSnap Server Part 2
Daniele Teti page 34
Anti-freeze for the VCL
Alexander Alexeev page 39
Delphi XE2 New Features - theme XE2
Bob Swart page 46
Learning to use FastReport 4 for VCL - Part 3
Sergey Lyubeznyy page 53
Question and Answers
Henk Schreij page 16
Creating 64-bit applications with Delphi XE2 - theme XE2
Jeremy North page 64
kbmSQL Structured Query Language
for your memory table
Kim Madsen page 72
High Level Multithreading
PrimozGabrijelcic page 76
TMS Scripter Studio Pro
Wagner R. Landgraf & Bruno Fierens page 81
FreePascal vectorial
Felipe Monteiro de Cavalho page 90
The tale of the Nigthtingale
Detlef Overbeek page 96
Using the Advantage Database Client Engine
Michal Van Canneyt page 101
Configuring OLE DB Providers and ODBC Drivers
Cary Jensen page 107
Delphi XE2 LiveBinding - theme XE2
Bob Swart page 113

Advertisers

Detlef D. Overbeek, Netherlands


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

News and Press Releases


email only to editor@blaisepascal.eu
Authors
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
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.

Advantage Database Server page 3


AnyDac page 70
Barnsten page 98
Be-Delphi page 124
BitTime page 36
Cary Jensens newest book page 106
Components for Developers page 71
Database Workbench Pro- Upscene Productions page 45
Embarcadero page 99, 100
FastCube page 63
FastReport page 62
LAZARUS Book page 60
TMS Software page 89

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 info page 61, 115

office@blaisepascal.eu

COMPONENTS
DEVELOPERS

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

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Book Review James D (Jim) Duff


1: The Architecture of Lazarus
The Pascal language and the tools available to enhance the
writing and correction of source code; the meaning and
descriptions of projects, components and packages
2: Installing Lazarus
Installing Lazarus for Windows, Linux, FreeBSD and
MacOS X, plus installation of version management
systems: TortoiseSVN and Subversion
3: The IDE
Using the Lazarus IDE (Integrated Development
Environment) for rapid application development,
explained in detail
4: Projects
Developing different types of projects - GUI (Graphical
User Interface), console and unit test applications, and
using packages to develop new components
LAZARUS The Complete Guide
Background

This book is an English translation of the German


original, written by several authors who are part of the
Lazarus design team. Following the conversion into
English by several translators, a final overall grammatical
review was undertaken by Howard Page-Clark, creating a
uniform style throughout. The translation and production
of this English version was managed by Detlef Overbeek,
Editor-in-Chief of Blaise Pascal Magazine.
Following a knowledgeable Foreword by Detlef, the
opening chapter contains a very descriptive and
competitive opening paragraph about this free, open
source software development product:

5: Target Platforms
Understanding the differences among the platforms
Lazarus supports - Windows 32 and 64, Windows CE,
Unix Systems (FreeBSD) and MacOS X
6: The Class Libraries
The class libraries available: RTL, FCL and LCL, and a
description of the functionality supplied with Lazarus in
its two hundred components.
7: Porting Delphi Components
How to port existing Delphi components to Lazarus
(where possible)

8: Files and Devices


Working with files, directories, serial and parallel ports, and
"Lazarus is an integrated development environment (IDE) printers.
for the Object Pascal Language. The Free Pascal Compiler
provides the backend for Lazarus. This combination of
9: Graphics Programming
backend engine and integrated development environment Lazarus graphics programming - working with images and
is comparable to Visual Studio for C++ or Borland
icons, the necessary basics for making attractive interfaces
Delphi. It yields efficient, highly optimised native source
in this day and age
code. Whereas Java and C# development environments
produce bytecode that must be interpreted in a runtime
10: Processes and Threads
environment or converted into poorly optimised code by a Operating system processes, and programming with
JIT compiler."
multiple threads - functions that highlight the ability to
make the compiled software operate efficiently
Go Pascal!
11: Network Programming
Review
Web services, client/server and TCP/IP network
This is a large book that contains 735 pages comprising 12 programming
chapters. A CD containing the source code is also
provided with the book, as well as a free USB stick that
12: Database Access
contains an installed version of Lazarus - just plug it in
Working with flat-file and client/server databases (via a
and go.
unified database interface to DBF files, Firebird, Interbase,
MySQL, Oracle, PostGreSQL, SQLite and ODBC), SQL
The book's chapters are listed here together with a small
and reporting (based on FastReports)
summary of each to give an idea of the book's coverage.
4

COMPONENTS
DEVELOPERS

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Book Review
LAZARUS The Complete Guide (continuation)

Each chapter contains very well described topics along


with relevant screen shots and source code. The level of
detail is relevant and correct, given that the authors of the
book were involved in the development of Lazarus.

USB STICK

There is also an additional Lazarus USB STICK. It


contains a preinstalled copy of Lazarus on a free 4GB
The chapters are presented in a logical courseware type
layout and sequence for beginners to the Pascal language, USB stick.
50 additional sample projects
one of the key target audiences of Lazarus. It also
The book includes the folowing example programs:
provides sufficient details for experienced Pascal/Delphi
B: borderstyle\ borderwidth \ bouncer
programmers wishing to utilise the benefits of Lazarus.
D: dbftrack\ dbfviewer\ dialogdemo\ docking
dragdrop\ dragdropobject\ dynamicforms
Summary
E:
editordemo\ eventsdemo
One has a feeling of the good old days when manuals
F:
filedragdrop\
formevents
provided the reader with a logical and educational theme
G:
gentrackdata\
griddemo
for learning about particular software products, such as
I:
ibtracker\
initialwindowposition\
inputquery
Turbo Pascal or Delphi.
L: lazreport\ listviewdemo
M: menudemo\ messagdlg\ morecontrols
This book has been generated by a group of highly skilled
P: pacman \ paramsdemo \ pinguin
contributors and, together with the other positive benefits
Q: questiondlg
mentioned above, is highly recommended for use by both
S: showmessage\ sizing-samples\ sizingwindows
beginners and experienced Pascal developers.
splitterdemo\ splitterpairdemo\ statusbardemo
T:
toolbardemo\ traffic light \ treeviewdemo
Am I going beyond the subject of reviewing the book
W:
windowmenu\ windowstate
when I say many of its topics make the end product very
attractive? Also, the ability to have an efficient
development tool delivered on a USB stick, ready to go, is Additional preinstalled component suites
on the USB Stick:
another positive element. Hmmm, I think this book has
Additional /
done its job on me.
Chart / Common / Controls /
DataAccess / DataControls / Dialogs /
The Book
Indy Misc - Core / Intercepts- Core /IO Handlers /
LAZARUS The Complete Guide
Servers Core / Clients core / Misc Protocols Decoder /
Including CD
Misc Protocols Encoder / Misc Protocols /
IO Handlers Protocols / IO SASL Protocols /
Authors:
IO Intercept Protocols / IO Servers Mapped Port / IO
M. van Canneyt, M. Grtner, S. Heinig, F. Monteiro de
Servers Protocols (nz) / IO Servers Protocols (am) /
Cavalho, I. Ouedraogo
Clients Protocols (am) / Clients Protocols (nz) Inet /
Development Editor, Production Editor and Cover
Ipro / LazControls/ Lazreport / Misc / RTTI /
Designer: Detlef Overbeek
Copyright 2010 by C&L Computer und Literaturverlag SQLDB / Standard / SynEdit System / VE
Copyright 2011 All rights for the English version
reserved by Blaise Pascal Magazine
Update Service
there is an update service available that ensures you have
ISBN: 978-94-90968-02-1 for the paperback
the latest version. It cost only 25, for a maximum of
ISBN: 978-94-90968-03-8 for the hardcover
four updates.. This is an affordable way to ensure you have
a complete, fully updated Lazarus program.
The Update service wil be available at the end of
September 2011.
You will be notified by web and email as soon as there
is an update available.

To receive a printed copy of the magazine


you need to go to our website to place your order

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

Writing New Components in Lazarus, Part 2 By Howard Page-Clark


starter

expert

Lazarus 9.30

A little bit of work up-front in creating and saving the package,


setting the component's properties and recompiling the IDE
The first article in this series looked at the Lazarus
will save you a lot of repetitive work in the future (until you change
component hierarchy, and some properties of TControl
company, that is; although the component will continue to benefit other
and TComponent that are important for a background
employees).
understanding of component development. This
If you've never created a new component in Lazarus before,
subsequent article focuses on the stepwise development this is a good way to familiarise yourself with the process,
of a specific simple component.
even if such a customised label appears to be rather a trivial
component to you. Frankly, it is, because this is an early article in
You write a new component to fill a gap in the functionality of
a series. There's plenty of scope for component complexity
the components available to you. You need some GUI (or other) ahead but let's begin simply. If you don't want this component
functionality that you can't find on the Lazarus Component
installed long-term, it is easy enough to remove it from the IDE
Palette, so you wonder about creating a new component that
later. You can walk through the construction stages simply as a
does meet your need. The default Component Palette found on learning process. Subsequently you can remove the component
a newly installed latest-release Lazarus (0.9.30) has 13 pages
by recompiling Lazarus without the newly added package (and
containing 186 components in total. But that is not enough!
delete the CompanyLabel package directory altogether if desired). Note
Perhaps you find a component that almost but not quite
that in Lazarus (unlike Delphi) new components can be
does what you want.
introduced into a Component Palette page only by recompiling
Say you are working primarily on Windows and want to use
the Lazarus IDE, since the new component is linked statically
buttons that can be shown in subtly different colours. Although with the rest of the IDE code. There is no provision (currently)
a TButton's Color property can apparently be 'changed' for a dynamic package system of component dlls such as Delphi
in the Object Inspector to something other than clDefault, uses (these are .dpl and .bpl files together with .dcp files, which in
you discover that it always gets reset to clDefault!
versions from Delphi 2 up to Delphi XE have all been Win32-specific,
Checking the Object Inspector's Restricted page confirms that a and depend on compiler 'magic' to work dynamically).
Windows widgetset restriction applies: the native Windows
The basic steps of new component
button-widget's colour cannot be changed. You search beyond
the Standard page. But neither TBitBtn, TSpeedButton creation/installation
We will illustrate the basic steps involved in installing a new
(Additional page) nor TColorButton (Misc page), nor
Lazarus component from scratch by creating the simple
TTIButton or TTIColorButton (TTI page) suit your
TCompanyLabel component suggested above, and then
purpose, in spite of their hopeful button-containing names.
consider
how these basic stages need to be amplified when
In fact in this case there is a custom drawn component that fits
writing
a
more sophisticated, more widely useful component. If
the bill exactly. Felipe Monteiro de Carvalho and JiXian Yang
you
don't
want to have your component installed in the IDE
have written several custom drawn Lazarus components.
(employing its functionality merely by adding its unit to your
These include a TCDButton, which provides button colourchanging functionality on all Lazarus-supported platforms inclu- application's uses clause and instantiating it through code) that is
ding Windows. For more information on these components see certainly possible. We will look at that scenario in due course.
http://wiki.lazarus.freepascal.org/Lazarus_Cus However, the full process of installing a visual component into
the IDE that can be manipulated by both the Object Inspector
tom_Drawn_Controls
and the Form Designer requires registration of the component
In a standard Lazarus Windows installation you'll find the
with the IDE. To accomplish that, you need to follow these
package from which you can install these components at
steps:
C:\lazarus\components\customdrawn\
Decide on your new component's ancestry and name
customdrawn.lpk
(Optionally) design an icon to identify your component on
In this particular case you can take advantage of the work of
the Palette
other developers which they have made freely available. But
Create a new folder for the new component package
supposing we can't find the component functionality we need?
Then we have to roll our own.
Create a new component package
Write (or adapt) the component unit as appropriate to
Setting properties to make a 'new'
provide new functionality
component
Test the new component's functionality before installation,
The simplest way to customise an existing component to better
debugging as required
suit your purpose is to pre-set the default properties of that
Check the new package's integrity by doing a test compilation
component, and save this altered component as a 'new'
Install the package in the Lazarus IDE by recompiling the
component on the Lazarus Component Palette (with a slightly
IDE
altered name, of course, since component names must be unique).
Test the component's IDE functionality after installation,
If you find yourself time and again dropping a TLabel on a
debugging if needed
form and setting its Caption to your company's name, its Name
to lblCompanyName, its Font, Color and Layout to match
your company's livery, its Autosize, Anchors, and so on to the
same regular values then it is time to create a
TCompanyLabel as a descendant of TCustomLabel (or
TLabel), set its properties accordingly, and save it in a new
package. You can then install it by recompiling Lazarus to get it Figure 1: Two indistinguishable icon-less components added to the
Common Controls page
to appear as a new item in the IDE's Component Palette.
6

COMPONENTS
DEVELOPERS

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Writing New Components in Lazarus, Part 2 (continuation 1)


Creating TCompanyLabel by descending
from TWhat?
Which class should our new component descend from?
This is always an important question in component
development. Finding the best answer to this question requires
familiarity with the LCL class hierarchy. Potential ancestors will
usually include at least the following possibilities:

TGraphicControl

TWinControl

TCustomControl

TCustomxxxx
TGraphicControl (or a TGraphicControl descendant)
is the best ancestor for visual components that do not require
user interaction, but just provide information to the UI;
components such as labels, shapes, and progress indicators.
On the other hand, components that must respond to mouseclicks or gestures or keystrokes (components that will receive and lose
focus) will require a window handle (or its equivalent in non-Windows
operating systems) and must descend from TWinControl or
one of its many descendants. Often the best choice in either
case is a TWinControl or TGraphicControl descendant
named TCustomXXX where XXX might be Label, Edit,
StringGrid and so on. Nearly all the standard LCL controls such
as TLabel, TEdit, TStringGrid and so on have an
immediate ancestor named TCustomLabel,
TCustomEdit,or TCustomStringGrid.The immediate
TCustom ancestor has all the designed functionality of its
descendant, but has few, if any, published properties or events.
This is a deliberate LCL (and VCL) architectural feature, which
keeps controls as small and sparing of memory footprint as
possible. The actual, everyday control such as TLabel is
nothing more than a TCustomLabel with suitable properties
and events published for availability in the Object Inspector.
Our new TCompanyLabel component is just a customised
TLabel. In this particular case there is little to be gained by
descending from TCustomLabel, so we will descend directly
from TLabel, thereby accepting all TLabel's published
properties and events. It is often the case that a new component
does not need all the published properties of its ancestor. Here
there is a distinct advantage in descending from a TCustom
class, since unneeded publishing of properties and/or events
can thereby be avoided.
Component Identity
To identify the new component we need a name for it, and a
unique icon (if it is to be distinguishable from nearly 200 other
components on the Component Palette). Both name (text) and icon
(picture) deserve careful consideration, and of the two, the name
is more important because you must give a component a name,
and it must be unique. If you do not provide an icon for a
registered component Lazarus will supply a default icon for it in
the Component Palette (see Figure 1). Provided there are no other
icon-less components on that Palette page, this will suffice.
However, if you have several such components on a single
Palette page, the icons will indicate how many such components
there are, but obviously won't identify which is which.
Users who have enabled the Hints for component palette
checkbox in the Hints section of the Environment
Desktop page of the IDE Options dialog will see the
component's name in a hint box if they move the mouse over
the icon. Users who don't have this checkbox enabled will be
shooting in the dark. (You reach the IDE Options dialog via the
ToolsOptions menu).

The requirement for a unique name needs careful consideration.


A long-standing convention names new components with a twoor three-letter prefix to identify the component's author or
owning company, such as the TkbmSQL component featured in
this issue from Components4Developers (see page 60). A good
name will be descriptive, unique, and preferably short.
Of the 186 components that are installed in a default Lazarus
installation the name lengths range from the shortest (TDbf,
TEdit, TMemo) up to the longest,
TDateTimeIntervalChartSource, a 28-character
name, which is hardly short, though it is descriptive and unique.
One of the beauties of a language such as Object Pascal is its
self-documenting propensity, provided you choose identifier
names carefully and intelligently. Two other related names need
careful thought, as well. What will you call the Pascal unit where
your component's code is stored, and what will be the name and
location of the package used to register the component? There
is the possibility of a name clash here if you are not careful,
because if you name the package MyComponent.lpk,
Lazarus will automatically generate a file in the package
directory called MyComponent.pas which is used to
compile the package. There is potential here for this file to
unwittingly overwrite your component's source file if you have
not thought about a possible name clash. So avoid giving the
package and the component source file exactly the same name.
Make them similar, but not identical.
In addition to intelligent choice of names throughout the
development process it is also a good idea to set up a
component development directory structure along the following
lines:
..\ComponentPackageDirectory\source\
\images\
\languages\
\tests\
\docs\

If you are not planning on internationalising your component,


you won't need the \languages subdirectory, and many
people manage without an \images or a \tests
subdirectory. Sometimes a \lib\ or \bin\ subdirectory is
useful. Obviously how you set up your development file storage
structure is up to you. Lazarus will not enforce anything, except
requiring a unique component name, and auto-generating the
yourPackageName.pas file in your package directory.
Nevertheless, a considered, tidy approach may help you avoid
unnecessary and frustrating errors later in the development
process. I can testify that the careless bung-everything-in-asingle-folder approach is a recipe for later name conflicts!
Creating a new component package
Lazarus' package system is quite different from Delphi's, and the
identical term used by the two development tools can be
misleading since the basic concept of a Lazarus package differs
from the Delphi concept. Lazarus uses packages to modularize
projects in a cross-platform way. Lazarus allows you to deal with
only one project at a time, but you can have any number of
packages open simultaneously. Each package has its own
directory, its own compiler configuration and its own language
files, and packages are versioned.

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

Writing New Components in Lazarus, Part 2 (continuation 2)


Packages are not restricted to containing source code: you can
add any type of files you wish to a package, such as a .wav or a
.png file. A package developed in a Linux environment such as
Ubuntu can be copied and recompiled in Mac OS X or
Windows without needing any changes to paths or settings (the
folder separators '\' and '/' are converted transparently as needed).
In particular, Lazarus uses packages to implement components
that are registered with the IDE and show up on the
Component Palette. As ever, cross-platform considerations
mean that you are best advised to keep all file names lowercase
only (you can't know who might one day want to use your work, or what
platform they will be working under assuming, that is, you'll not be
proprietary about your code). Lazarus packages are created and
maintained by the Package Editor. This is accessed either from
the File New menu, selecting the Package item from the
treeview on the left of the New dialog
Figure 4: The newly created (empty) companylabel.lpk
(Figure 2), or you can access it from the PackageNew package
package open in the Package Editor
menu (Figure 3).
Notice that the package has already had the FCL added by
Lazarus as a mandatory requirement, so we don't need to add
this ourselves. To customise the package for our purposes we
need to add other content and requirements, so we click on the
[+ Add] toolbutton in the toolbar at the top of the Package
Editor. This brings up the Add to package dialog (Figure 5):

Figure 2: The New dialog accessed from the FileNew menu

Figure 5: The Package Editor's Add to package dialog


The Add to package dialog has four pages: New File, New
Component, New Requirement, and Add Files, and opens by
default at the first page, New File. Our package is a component
package, so we click on the New Component tab whose opening
appearance is as follows (Figure 6):

Figure 3: The Package New package menu

Whichever method you use to access the Package Editor,


Lazarus first presents you with a Save Package NewPackage 0.0
(*.lpk) dialog, waiting for you to name the new package
(default name NewPackage.lpk) and save it. Although you
can dismiss this dialog by pressing [Esc], it is far better to give
the new package its long-term name now, and save it in a new
directory created specifically for the creation of the new
component.
If you're following along, do that now, and you will see the
Package Editor with the newly created, newly named package
open. For me the initial view of the Package Editor dialog
appeared as follows, entitled Package companylabel with the full
name of the new package shown in the status bar at the bottom
of the window (Figure 4).
8

COMPONENTS
DEVELOPERS

Figure 6: The New Component page of the Add to package dialog

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Writing New Components in Lazarus, Part 2 (continuation 3)


If you're familiar with Delphi's New Component wizard, you'll
Lazarus then opens the newly created companylbl.pp file
appreciate that analagous pages from that wizard are compressed in the Source Editor. You'll find that it contains the following
here (sensibly) into a single page of required information.
skeleton code written for you by Lazarus:
The Lazarus version of its Delphi equivalent lacks the colourful unit CompanyLbl;
designs that have been lovingly lavished on the Delphi wizard,
{$mode objfpc}{$H+}
but it also saves you from having to click through lots of pages
of a wizard when one page suffices. There are five pieces of
interface
mandatory information that the component writer has to supply,
uses
and optionally you can specify a sixth, an icon. When you drop
Classes, SysUtils, LResources, Forms, Controls,
down the Ancestor Type combobox's list, you'll find that it lacks
Graphics, Dialogs, StdCtrls;
any TCustomXXX entries apart from TCustom Control.
So so if you wanted to use say, TCustomLabel, you'd have to type
TCompanyLabel = class(TLabel)
type it in manually (you're not restricted to classes only found in the dropprivate
down list), and if it exists Lazarus will find it without any
{ Private declarations }
problems (unless you spelled it incorrectly). We want to use the
protected
standard TLabel, so can just select it in the drop-down list.
{ Protected declarations }
public
Lazarus then fills in the other fields with some defaults, which
{ Public declarations }
you will probably want to change. If you type the name of a
published
non-existent Palette Page: entry, Lazarus will create that page for
{ Published declarations }
you, and place your new component on the newly added page.
end;
To add an icon you click on the ellipsis[]button labelled Icon
(maximum 24x24) which lets you locate a .png icon file. Note procedure Register;
that this must be named with the new component's class name
implementation
(so tcompanylabel.png in this case).
The completed New Component page looked as follows, before procedure Register;
begin
clicking the [OK] button:

{$I Companylbl_icon.lrs}
RegisterComponents('Additional',[TCompanyLabel]);
end;
end.

You'll see that this is just a standard Object Pascal unit, including
compiler directives for objfpc mode (rather than delphi mode) and
for treating the keyword string as meaning ansistring ({H+}).
Eight units which you are likely to need are included in the uses
clause, the new component class declaration is written out as a
skeleton descending from the ancestor you specified earlier,
and a Register prodecure is declared and implemented for you.
Notice that this two-line Register implementation associates the
icon you specified with the new component via a {$I }
Figure 7: The information needed to complete the New Component page directive which has inserted a Lazarus-generated resource file
(based on the icon you specified) named Companylbl_icon.lrs,
When specifying the Unit File Name: information there is an
and places the registered component on the Palette page given
intruigingly labelled [ <> ]button beside the ellipsis [ ]button earlier via the RegisterComponents()procedure.
(that brings up a Save As dialog). The [<>] button is a Shorten or
Of course you could write all this code yourself, but it is easier
expand button which usefully shows either the short filename
and less error-prone to use the New Component page of the
(source\companylbl.pp here) or the expanded filename
Package Editor to do it for you. If we look at the Package Editor
with the full path. This affects the name display only.
now (via the Window Package CompanyLabel* menu) we see
Lazarus saves all files with fully qualified paths, however much
that Lazarus has now added the LCL in the Required Packages
of that path is actually displayed in the dialog. Clicking the
treeview node (TLabel obviously needs the LCL) and the
[OK] button creates a new companylbl.pp in the location
component file \source\companylbl.pp as a package
you have specified. If the package has not registered any of the
file, as we would expec (see Figure 9).
paths you've just entered an intermediate dialog will pop up
If we highlight the companylbl.pp file you'll see in the File
asking if you want the package to add the paths (to which you
Properties section near the bottom of the Package Editor
should agree).
window that two new checkboxes have appeared (Register
unit and Use unit) which are both now (correctly) ticked
(Figure 9). The checked Register unit checkbox indicates that
companylbl.pp contains a Register procedure in its interface
which will be called by the IDE to place the component on the
Component Palette. The checked Use unit checkbox indicates
that this unit will be compiled automatically when the package is
compiled, which is what we want to happen. The asterisk at the
end of CompanyLabel* in the Package Editor's title indicates
that the new package has not yet been saved.
Figure 8: A confirmation dialog for changing the unitpath

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

Writing New Components in Lazarus, Part 2 (continuation 4)


Customising the component code
For this example we shall use the hypothetical company Timeless
Ltd. If this is a useful component for you it would obviously be
customised appropriately for your situation.
We proceed to alter the skeleton code to fashion
TCompanyLabel's appearance as Timeless Ltd. might
require.
For this simple customised TLabel component we don't need
3 of the 8 units Lazarus had added to companylbl.pp,
so we remove SysUtils, Forms, and Dialogs from the uses clause.
(You can leave them there, redundant, of course, but my preference is to issue
only needed instructions to the compiler, even though it ignores unneeded ones).
To set various default properties as the component is constructed
we simply override the constructor, supplying our own, which first
calls the inherited constructor and then calls a SetDefaults
procedure we write, where the customisation takes place.
The amended class declaration then looks like the listing at the left.

It is time to click on the Save toolbar icon. This saves the


package, and the asterisk appended to the package name in the
Editor's title bar disappears (until you make some other change to the
package). Notice also that the Registered plugins area at the
bottom of the Package Editor is empty. This indicates that our
new component is not yet registered with the Lazarus IDE.

Figure 9: Highlighting the companylabel.pp file


in the Package Editor
If we compile and install TCompanyLabel at this stage it
will register a duplicate TLabel component, identical in every
respect to a default TLabel, except for its name and
alternative icon. So we next need to customise our component
before use. Component writing is a non-visual task we have to
write the customisations in code, since the component-to-be
cannot be manipulated yet in the Form Designer nor can it be
customised yet using the Object Inspector.

Setting properties in code is obviously more tedious than


clicking around in the Object Inspector, but once it's done and
debugged, that property-setting code can be recalled anytime
thereafter simply by dropping this new component on a form. It
then comes with all the properties already set the way you want
them.
So we save the CompanyLbl.pp file, click on the Compile
toolbutton in the Package Editor to check that the component
compiles correctly, and if so click then on the [Use >>]
toolbutton, to begin recompiling the IDE (Figure 10).

TCompanyLabel = class(TLabel)
private
procedure SetDefaults;
public
constructor Create(TheOwner: TComponent);
override;
end;
The new constructor and SetDefaults procedure are as follows:
Constructor TCompanyLabel.Create(TheOwner:
TComponent);
Begin
Inherited Create(TheOwner);
SetDefaults;
End;

Figure 10: Selecting the Package Editor's Install menu option

Procedure TCompanyLabel.SetDefaults;
Begin
:= 'Timeless Limited';
Caption
Autosize := True;
Alignment := taCenter;
:= clMoneyGreen;
Color
ParentColor := False;
:= clMaroon;
Font.Color
:= False;
ParentFont
:= 'Calibri';
Font.Name
:= [fsBold, fsItalic];
Font.Style
Font.Height := -19;
:= 2;
BorderSpacing.InnerBorder
:= 1;
BorderSpacing.Around
:= 23;
Constraints.MinHeight
:= 140;
Constraints.MinWidth
Hint:='Timeless can meet all your software needs';
:= True;
ShowHint
Transparent := False;
End;

10

COMPONENTS
DEVELOPERS

This brings up a confirmation dialog, reminding you that to


register and install a component in the Lazarus IDE requires
recompiling the whole IDE (a somewhat time-consuming operation
first time round, so it checks first that this is indeed what you want to do).

Figure 11: The Rebuild Lazarus? confirmation dialog

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Writing New Components in Lazarus, Part 2 (continuation 5)


Finishing touches and removal
We have done no testing of our component, except to iron out
the one property-setting glitch encountered above. However, in
the case of this simple, straightforward component that does
nothing more than alter a few property values in code there is
no point setting up a test program. Hundreds of thousands of
Lazarus users 'test' the TLabel component every day, and we
can be fairly sure it is thoroughly debugged! Our minor
adaptations of the component are not likely to introduce errors.
Of course a more complex component with major functionality
we have coded ourselves will need much more thorough testing
before it gets added to the Component Palette. Registering a
buggy component with the IDE (even if it compiles and installs) will
quite likely lead to a buggy IDE, and unexplained Lazarus
Figure 12: The Additional page of the Component Palette
crashes.
sporting a new component icon
If we open the component package now in the Package Editor
Well, almost all the properties you want. Disappointingly, not
and highlight the companylbl.pp source file, we see that
every property we set via SetDefaults in the component's
the previously empty Registered plugins region is now updated
constructor has come through. The Caption, which we set
to reflect the registration of this new component with the IDE.
in code to be 'Timeless Limited' has been overridden by Lazarus The name of the registered plugin is shown
to become 'CompanyLabel1' (see Figure 13).
(TCompanyLabel)with its icon, as well as the Component
Palette page where it is located (the Additional page).

Clicking the [Yes] button proceeds to recompile the IDE. The


Messages window keeps you informed of progress until Lazarus
closes and restarts, whereupon you should find a new icon in the
Component Palette's Additional page (as specified earlier) which
can be seen as the rightmost hourglass icon of Figure 12.
Now a TCompanyLabel instance can be dropped straight
onto a form, and it has all the properties that previously you
would have had to set by hand, all of them pre-set ready for use.

Figure 13: The newly dropped component with the 'wrong' caption
What is going on here? Well it turns out that after construction
Lazarus does some housekeeping, which includes setting the
default name of new components if the component's
ControlStyle property contains the csSetCaption
flag (see the previous article for more detail). The Caption is set to
match the name. Since all new components have the
csSetCaption flag set by default, we have to override this
Lazarus behaviour to get our constructor's Caption-setting code
to persist. So we must add a line to the SetDefaults procedure
given earlier as follows:
Procedure TCompanyLabel.SetDefaults;
Begin
ControlStyle := ControlStyle - [csSetCaption];
Caption := 'Timeless Limited';
// etc.
End;

Figure 15: The Package Editor showing the newly registered plugin
Moreover, if we click the [Use >>] toolbutton when the
component package is open in the Editor we now see an
additional Uninstall menu option available.
Lazarus recognises that not all registered plugins are there to stay.
If we change our mind about TCompanyLabel, we now have
the option to uninstall it. After putting up a confirmation dialog,
Lazarus lets us uninstall the component.

If we recompile and install our TCompanyLabel component,


we now get the behaviour we want.

Figure 16: Getting rid of an unwanted component plugin


As when the component was first installed, so now the
uninstallation of a statically-linked package requires
recompilation of the Lazarus IDE to remove it.
So a further confirmation dialog intervenes before the
recompilation is allowed to proceed.

Figure 14:
TCompanyLabel at
design time and runtime
SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

11

Writing New Components in Lazarus, Part 2 (continuation 6)

Figure 17: Confirmation is required before component


removal and IDE recompilation
The freshly compiled Lazarus IDE now has an
Additional Component Palette page with no sign of the
TCompanyLabel icon that was there previously.
The ability to uninstall components is, of course, as
important as the ability to install them.
The Lazarus Package Editor
The Package Editor is a sophisticated tool, whose functionality
is worth fully exploring. There is not space to document it all
here, but several pages of the Compiler Options for Package
XXXX dialog are worth a brief mention. You access this from
the [Options] toolbutton (sixth from the left) of the Package
Editor's toolbar. The Description page allows you to briefly
document your component and add copyright and licence
information, as well as set the version number (see Figure 18):

Figure 19: The Package Options's IDE Integration page


Next time we will write from scratch a more widely useful
component to fill a gap in Lazarus' current provision.
This will require much more extensive customisation of an
existing component.

The IDE Integration page has radiobutton options set for you
by Lazarus at installation, but the preferences can be changed if
for some reason you want to override Lazarus' guess as to
whether the component is runtime only or runtime and design
time (Figure 19).
If you have FPDoc documentation files (which for a component of
any complexity you ought to take the trouble to provide, so users have local
access to important details of usage and behaviour) you specify their
location here too.

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.

Figure 18: The Description page of the package's Compiler Options


12

COMPONENTS
DEVELOPERS

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Delphi XE2 is the future by Fikret Hasovic


starter

expert

DELPHI XE2

Since I first learned Basic, C and Pascal in the early


1990s I have always preferred Pascal's clear language
combined with its low-level abilities (if needed). Object
Pascal has been continuously developed since then
through the introduction of the VCL framework to
wrap Windows widgets and API calls, while Borland
and then CodeGear adapted both the language and the
Delphi IDE to keep pace with technological
developments. Embarcadero's acquisition of CodeGear
was a further important milestone. Now we have its
upcoming star product: RAD Studio XE2, in which
Pascal takes two further leaps forward: Delphi's ability
to compile for non-Windows target platforms, and the
introduction of a new cross-platform framework,
FireMonkey (FMX) to complement the cross-platform
compiler capabilities. This release brings other
enhancements as well.

Figure 1 Th splash screen of Delphi XE2


For newcomers Delphi XE2 has everything to get you started.
For experts, there are many new features to raise your coding
experience to a higher level. This release provides key new
features for developing applications using both Delphi and
C++Builder.

- Cross-platform application development provided by


Macintosh OS X cross compilers: Delphi (DCCOSX.exe)
and C++ (BCCOSX.EXE) and new cross-platform
component libraries.
- 64-bit application development for Windows with a new
64-bit Delphi compiler and a 64-bit Delphi RTL.
We finally get 64-bit support in our favourite development
tool! And for a first version, it is very impressive.
- DataSnap Connectors for mobile devices - take aa look at
Bob Swarts article on page 111 - (Android, Blackberry, iOS, and
Windows Phone) have been introduced.
- LiveBindings,
A entirely new feature is LiveBindings, the new data binding
feature that simplifies your programming work with both the
VCL and the new FMX FireMonkey library.
So, Delphi XE2's supported platforms are now: Mac OS X
(Delphi and C++Builder), 32-bit Windows (Delphi and
C++Builder), 64-bit Windows (Delphi only).
What does Cross-Platform Mean?
Cross-platform applications that you develop with the RAD
Studio IDE will run on a remote target machine such as a Mac
running OS X, or a PC running a Windows 64-bit OS.
The VCL is not supported on the Mac OS X platform, so a
VCL application has no direct migration path to the Mac or to
FireMonkey.
If you have a VCL application that you want to migrate to the
Mac OS X platform, you start by creating either a crossplatform console application or a FireMonkey application.
You install and run the Platform Assistant on the Mac, then
create a remote profile on the development system to describe
the target platform (Mac OS X), and redesign your Windows
application with the requirements of the target platform in
mind. For example, you cannot use any Windows function calls
in an application for the Mac. If you want to reuse your original
application's logic, you must refactor your Windows application
and cut-and-paste sections of the code into the new application.

To create Cross-Platform applications you


follow these steps
Compiling and building, but not running, cross-platform
Key features of the XE2 release:
applications is similar to the same operations for building native
- The FireMonkey Application Platform for cross-platform Windows applications, with a few additions. Running and
applications that run on both 32-bit and 64-bit Windows, Mac debugging a true cross-platform application requires that the
OS X, and iOS. The VCL now supports 64-bit and 32-bit
development system be connected to the target platform, where
Windows, and the Delphi RTL now supports Mac OS X and the Platform Assistant (the remote application server) is running in
both 32-bit and 64-bit Windows.
listening mode.

Figure 2 RAD Studio now supports Windows 64-bit Cross-Platform applications


SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

13

Delphi XE2 is the future (continuation 1)


RAD Studio now supports Windows 64-bit Cross-Platform
applications! To create a Delphi 64-bit cross-platform
application, you need to add the Win64 target platform to your
project and then activate the Win64 target platform. For Win64
you can use either VCL or FireMonkey. Here are the steps:
In the Project Manager, any project type that potentially
supports cross-platform applications now has a Target
Platforms node. When you create a new project, only the native
platform (Windows 32-bit) is listed as a target platform:

To use a specific platform, you need to activate your chosen


target platform: Either double-click the platform (such as OS X),
or right-click the platform and select Activate.
Note: To add OS X as platform, you need to create an
application using the FireMonkey Application Platform.
A comparison of the VCL and FireMonkey
When you create a new GUI project using either the VCL or
FireMonkey, you'll notice a few differences from previous
Delphi versions. XE2 introduces consistent use of unit scope
prefixes. The Windows related units have the Winapi prefix,
and the VCL units have the Vcl prefix. FireMonkey units have
the FMX prefix.
In what follows you can see that the two frameworks generate
basically similar skeleton code. Apart from referring to different
framework units, the resource include statements are also
different: "*.dfm" for VCL applications as opposed to "*.fmx"
for FireMonkey applications.
An example VCL uses clause:
uses
Winapi.Windows, Winapi.Messages, System.SysUtils,
System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs;

An example FireMonkey uses clause:

Figure 3
The name of the active platform is boldfaced in the Project
Manager. The native Win32 platform is the default active
platform.
To make the current project a crossplatform project
Add a target platform (right-click the Target Platforms node,
and click Add Platform):

uses
System.SysUtils, System.Types, System.UITypes,
System.Classes, System.Variants, FMX.Types,
FMX.Controls, FMX.Forms, FMX.Dialogs;

The two framework's designers are fairly similar, with a few


differences between them.
The VCL designer (as we know and use it already in Delphi):

Figure 6 The FireMonkey designer (cross-platform):

Figure 4
On the Select Platform dialog box, select the platform you want to add,
and click OK:

Figure 5
14

COMPONENTS
DEVELOPERS

The FMX designer looks gray at design time, but at runtime


FMX adapts to the OS theme where it is running - so it looks like
a Windows application on Windows, and like an OS X
application on the Mac. FireMonkey (or FMX) is a completely
new framework, not based on the VCL, which has been designed
with cross-platform considerations in mind (unlike the VCL which
was designed solely to wrap Windows controls and Windows API calls).
The illustration below shows the appearance of a selection of
FMX controls:
SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Delphi XE2 is the future (continuation 2)

Figure 7
DataSnap Mobile Connectors
in RAD Studio XE2

A very interesting new feature in XE2


is the DataSnap extension called Mobile
Connectors. The first version of XE
allowed you to create ad hoc JSON
messages and manually parse the
returned JSON messages from an
Android connection using a DataSnap
REST service. With RAD Studio XE2
you no longer need to do it that way. If
you have a DataSnap REST service,
you can automatically generate a proxy
connector for the major mobile
platforms. DataSnap XE2 version now
supports four mobile platforms:
Android (Using Java)
BlackBerry (Using Java)
Windows Phone (Using C#)
iOS 4.2 (Using ObjectiveC)
To enable your DataSnap server for
the Mobile Connectors you have to
check that feature in the New DataSnap
Server wizard.
SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

15

Delphi XE2 is the future (continuation 3)


The same basic connectivity elements are associated with
each type of deployment.
To deploy applications, you use the Deployment Manager
which is accessed from the Project Deployment menu.
Your deployed Mac OS X application is not just the
LiveBindings in RAD Studio XE2
executable, but a set of object files and shared libraries
LiveBindings is a data-binding feature supported by both (dylibs).
the VCL and FireMonkey in RAD Studio. LiveBindings is I am impressed with how stable and good this XE2 release
expression-based, meaning that it uses expressions to
feels...
bind objects to each other, by means of their properties.
Thumbs up! We have a very exciting time ahead of us!
LiveBindings is based on relational expressions, called
binding expressions, that can be either unidirectional or
bidirectional. LiveBindings uses the concept of control
objects and source objects.
By means of binding expressions, any source object can be
bound to itself (it becomes both a source and a control object) or
to any other control object, simply by defining a binding
expression involving one or more properties of the
objects you want to bind together. For example, you can
bind a TEdit control to a TLabel so that when the
edit's text changes, the label's caption is automatically
changed to the value your binding expression evaluates.
Another example would be binding a tracker control to a
progress bar so that the progress indicator rises or falls as
you adjust the track bar.
In the same way you can live-bind to a database, alter one
or more properties of an onscreen object, and have these
changes reflected in the connected database. Because
QUESTIONS AND ANSWERS
#4 How to #show a hint
LiveBindings propagate, you can even alter properties of
on an disabled button
Q&A
objects that are connected to other objects that are bound
& USEFUL CODE SNIPPETS
to a control object. This type of functionality is no longer
Author: Henk Schreij
restricted just to data-aware components.
The generated proxies support all the standard Delphi
types and map them to the native target language. The
functionalities of the various Delphi classes are not-oneto-one with the Delphi version, but similar.

We have changed the name of this column. It was UCOS


USEFUL CODE SNIPPET ... Readers chose to change the
name...

Delphi XE2 Compiler improvements

I develop principally in Delphi, so this area is very


important to me.
Eight new DEFINEs have been added:
ALIGN_STACK
CPUX86
CPUX64
MACOS (Mac operating system)
MACOS32
PC_MAPPED_EXCEPTIONS
PIC
WIN64
Lastly, how do we deploy cross-platform
applications?

Deployment can have two different meanings in RAD


Studio:
Deployment is a required step that the IDE
automatically performs when you run your crossplatform application in the debugger.
Deployment is the final step that you perform in
successfully delivering any completed project.

If you change a BitButton's Enabled property to False, in


Delphi the Hint is no longer shown (though it is in Lazarus).
How can we get round this?
It is often unclear to a user why a button should be
disabled (grayed out). A working Hint (or tooltip) would be an
excellent way to explain the reason for the button being
unresponsive. For example, with a delete-button you could
show a hint message:
"You require administrator rights to remove the
selected client."
However, if this Hint will never be shown for a disabled
button any such explanation via a Hint is useless.
A matter of chicken and egg?
A possible solution to this dilemma in Delphi is simply to
use a SpeedButton, and format this to (75x25) to match
the default size of a BitButton.
The disadvantage is the SpeedButton cant get focus.
If you need the button to get focus, there is a further
solution. Take a Panel component, sized to match the
BitButton (75x25) and drop the button on the Panel.
Now if the button is disabled, it will show the Panel's Hint.
So enter the required text into the Panel's Hint and your
Chicken will have laid the egg.

For the code of the complete project see downloads:

www.blaisepascal.eu

16

COMPONENTS
DEVELOPERS

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Cylindrical Anamorphosis by Peter Bijlsma


starter

expert

DELPHI 3 and above (Win32)

Cylindrical anamorphosis is the technical term for using


a cylinder as a mirror to reshape an image.
When you look at Figure 1, you see a heavily distorted
photograph. To discover what is depicted, you'll have to
place a shiny cylinder (silver or chromium) on the circle
in the centre. Now when you look at the reflection of the
image on the cylinder, you will notice that the
undistorted photograph is revealed on the shiny cylinder
face. This article describes a method for constructing
such anamorphic images and the program you need to
produce them.

Figure 2: A possible construction method


So artists of earlier generations had a lot of geometrical work to
do to construct their hidden images.

Figure 1: An anamorphic image


Constructing anamorphic pictures
Cylindrical anamorphosis was in use as early as the 16th century.
Morphed images were produced from originals varying from
painted landscapes to naughty pen drawings. Imagine the big
grins on the faces of the lord of the manor and his guests when
the secret of his mysterious drawing was revealed by placing a
cylinder on top of it! If you are curious about the subject of my
example in Figure 1, see the end of the article. Hint: it's happy
and wears no clothes!
There are several ways to construct anamorphically distorted
images. It is possible for an artist to paint an anamorphic image
directly by copying the picture details accurately onto paper
while looking at the reflection in the cylinder. The image can
also be constructed mathematically. One way to do this is to
position the original picture inside the circle representing the
cylinder base and, for each point on the drawing, to construct a
corresponding point outside the circle (see Figure 2). Point P1 of
the original picture is transformed to a point I1 on the resulting
image by determining the mirror point using the tangent at the
cylinder face. You have to use a great many points, since straight
lines on the original picture are not straight lines on the
anamorphic image. Because the lines of sight in this method are
parallel, you can construct this type of morphed drawing fairly
easily using a ruler and a pair of compasses.

Nowadays we have computers to perform this task.


For my program I decided to wrap the original picture around
the cylinder and calculate the reflected points
on the image to be constructed by looking down at an angle of
45 degrees from the viewing point to the cylinder (see Figures 3
and 4).

Figure 3: Calculation of horizontal component Ihor

The vertical position of a point on the original picture to be


displayed on the cylinder is called Pv, and the angular position
of this point on the circle's circumference is called Pa.
The horizontal projection of the reflected line on the image is
called Ihor. You can calculate the x and y values of the image
point [Ix,Iy] by applying a number of goniometric
formulae.
This is done in the procedure ConvCylToImg: (next page)
COMPONENTS
SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18
17
DEVELOPERS

Cylindrical Anamorphosis (continuation 1)


procedure TForm1.ConvCylToImg;
var Hdis, Vdis, A1, A3, A4, A5, A6, Ia, Ib: double;
{convert cylinder coordinates to image coordinates on paper}
begin
if Pa<0 then Pa:= 360+Pa;
if Pa<PaMin then Pa := PaMin;
if Pa>PaMax then Pa := PaMax;
Par := DegToRad(Pa);
Cx := R * sin(Par);
Cy := R * cos(Par);
Hdis := Ed + Cy;
Vdis := Eh - Pv;
Ihor := Ed * Pv / Vdis;
A1 := ArcTan(Cx/Hdis);
if Cx =0 then begin
A3 := Pi/2;
A4 := Pi/2;
end else begin
A3 := Abs(ArcTan(Cy/Cx));
A4 := Abs(ArcTan(Hdis/Cx));
end;
A5 := Pi - (A4+A3); if A1<0 then A5 := -A5;
A6 := A5 - A3; if A1<0 then A6 := A3 + A5;
Ihx := Ihor * Cos(A6); if A1<0 then Ihx := -Ihx;
Ihy := Ihor * Sin(A6); if A1<0 then Ihy := -Ihy;
Ix := Ihx + Cx;
Iy := Ihy + Cy;
end;

So the only thing we have to do is to map the pixels of the


original picture to Pa/Pv coordinates on the cylinder and
subsequently convert them to Ix/Iy coordinates on the
anamorphic image. This brings us to:

Figure 4: Calculation of Image points Ix and Iy


The Program
The program is designed as a
demonstration, which means it is
not really suitable for end users.
Just to demonstrate how many
points are necessary to make a
drawing, I added a [Hello]
button (see Figure 5 at the left).
Once clicked, an anamorphic
picture is produced which causes
the word HELLO to be reflected
on the cylinder. For this simple
word 156 points were needed to get
a reasonable image. They are
contained in a separate unit,
UnCoords.pas, and consist of an
array (named MirrPnts) of
record variables of type
TCylPnt:

Figure 5: The user interface after Hello has been clicked


18

COMPONENTS
DEVELOPERS

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Cylindrical Anamorphosis (continuation 2)


The loaded picture is shown in the TImage area.
Since you can load high resolution photographs,
the picture is stretched so you can verify that the correct picture
is being used. Don't be alarmed by this stretching which distorts
what you see. For the conversion process the unstretched
bitmap
(called BitmapOrg) is used, of course. Prior to the
Each coordinate pair Pa/Pv is converted to a plotting point
morphing
process, a calculation is made to fit the picture
(Ix/Iy) by calling the procedure ConvCylToImg. The
efficiently
on
the available cylinder space. Using the tangents
drawing is then made by connecting the points on the image
from
the
eye
to
the cylinder (Ihor then forms a straight line from the
using LineTo commands.
viewing direction in Figure 4),
When the Boolean MirrPnts.New has been set to True
a maximum and minimum angle for Pa can be calculated and
for any given point, a MoveTo command is executed to start a
therefore also the maximum width (Wmax) of the picture
new line.
wrapped around the cylinder.
Magazine subscribers will find the details in the source code
These values are displayed on the interface.
listing, which is available on the www.blaisepascal.eu
The picture to be converted is centred on the available area in
website.
such a way that for portrait type pictures the height is decisive
and for landscape type pictures the width is decisive (see Figure
I added a [Demo] button to demonstrate the reflection
6 down left).
process. It displays a simplified image of Figure 4, and clicking
the [+] and [-] buttons enables you to see the position
of the line Ihor at various angles Pa (Pv is kept constant).
The procedure CheckFormat calculates the high and low values
for the angular position Pa (Pahi and Palo), and for the
The most interesting part of the program is its capability of
vertical position Pv (Pvhi and Pvlo):
automatically converting an existing picture (e. g. a photograph)
into its anamorphic image.
procedure TForm1.CheckFormat;
To do this you load a picture (jpg or bmp) by clicking the [Load var Temp, Mid, HVRatio, BMRatio: double;
Pict] button, and then convert it by clicking [Make
begin
HVRatio := Wmax/PvMax;
Image].
BMRatio := BitmapOrg.Width/BitmapOrg.Height;
You can alter several conversion parameters in the provided
if HVRatio<BMRatio then begin
EditBoxes: the Cylinder radius (R),
Pahi := PaMax;
the Cylinder height (PvMax)
Palo := PaMin;
and the Eye distance (Ed and Eh, equal values).
Mid := PvMax/2;
You save the converted image by clicking the [Save Image]
Temp := PvMax*HVRatio/(BMRatio*2);
button.
Pvhi := Mid + Temp;
To print the image you'll have to make use of a separate
Pvlo := Mid - Temp;
program.
end else begin
TCylPnt = record
Angle : double; //angular position Pa
Height: double; //vertical position Pv
: Boolean; //beginning of a new line
New
end;

Pvhi := PvMax;
Pvlo := 0;
Mid := 180;
Temp := (Pamax-PaMin)*BMRatio/(HVRatio*2);
Pahi := Mid + Temp;
Palo := Mid - Temp;
end;
end;

Figure 6: Fitting the picture in


the available area
SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

19

Cylindrical Anamorphosis (continuation 3)


When the conversion process is complete, the TImage area
displays the anamorphic image and other UI elements display
the original picture's file name, and its resolution on the cylinder
(calculated in dots per inch, dpi).
Using Scanlines
The most complicated task has to be accomplished first, namely
making a bitmap of the anamorphic image.
I decided to use TBitmap's Scanline property, as I did in earlier
articles (see Blaise Pascal Magazine #8). Remember that
Bitmap.Scanline is a read-only array of pointers to a horizontal
row of pixels. To manipulate the pixels in these rows we must
copy the scanlines to an array where we can access the separate
pixels. Each pixel needs a number of bytes, dependent upon the
pixel format (or colour depth). In this instance I'm using a 24-bit
pixel format, and therefore I've introduced the following types
and variables:
type
TPixCol = record //sequence is BGR in scanlines
B : byte;
G : byte;
R : byte;
end;
TPixArray = array[0..6000] of TPixCol;

//a length of 6000 bits is sufficient


pPixArray = ^TPixArray; //scanlinepointers
var
ScanlCyl: array of pPixArray;

Now the following code snippet :


NumbScl := BitmapOrg.Height;
SetLength(ScanlCyl,NumbScl);
for I:=0 to NumbScl-1 do
ScanlCyl[I] := BitmapOrg.ScanLine[I];

ensures that ScanlCyl[y][x].R points to the Red


component of pixel number x in row number y of
BitmapOrg. By the way, we don't need the colour
component in this program, but it's there for free. Similarly, we
must create pointers to access the pixels in the resulting
anamorphic image. In the following procedure (called when the
[Make Image] button is clicked) this is done by creating a
temporary bitmap BitmapTmp. For each pixel in the
original bitmap a new position is calculated in the temporary
bitmap via procedure ConvCylToImg and the pointers of the
temporary bitmap are assigned to the pointers of the original
pixels. For high resolution pictures this can take several seconds.
When all pixels have been converted, the temporary bitmap is
copied to Image1's bitmap, and the result is instantly visible.

procedure TForm1.BtMakeImgClick(Sender: TObject);


var I, J, NumbPix, NumbScl: integer;
DegStep, VertStep : double;
BitmapTmp: TBitmap;
ScanlTmp: array of pPIxArray;
begin
if BitmapOrg.Empty then begin
ShowMessage('First load a picture');
exit;
end else begin
ClearScreen;
//makes Image1 white
Checkformat;
BitmapTmp := TBitmap.Create;
BitmapTmp.PixelFormat := pf24bit;
BitmapTmp.Height := BitmapImg.Height;
BitmapTmp.Width := BitmapImg.Width;
SetLength(ScanlTmp,BitmapTmp.Height);
for I:= 0 to BitmapTmp.Height-1
do ScanlTmp[I] := BitmapTmp.ScanLine[I];
//copy scanline pointers
NumbPix := BitmapOrg.Width;
NumbScl := BitmapOrg.Height;
SetLength(ScanlCyl,NumbScl);
for I:= 0 to NumbScl-1 do
ScanlCyl[I] := BitmapOrg.ScanLine[I];
//copy scanline pointers
DegStep := (Pahi-Palo)/NumbPix;
VertStep := (Pvhi-Pvlo)/NumbScl;
Pv := Pvlo;
try
for J:=NumbScl-1 downto 0 do begin
Pa := Palo;
for I:=0 to NumbPix-1 do begin
ConvCylToImg; //retrieve Ix, Iy
ScanlTmp[Yim(Iy)][Xim(Ix)] :=
ScanlCyl[J][I];
Pa := Pa + DegStep;
end;
Pv := Pv + VertStep;
end;
except
on Exception do begin
Label6.Caption := 'error'; //further ignored
end;
end;
end;
Image1.Picture.Bitmap := BitmapTmp;
with Image1.Canvas do begin
Pen.Color := clBlack;
{draw the Cylinder base}
Ellipse(Xim(-R), Yim(R), Xim(R), Yim(-R));
TextOut(10,Image1.Height-20,'Cyl. diameter: '
+ FloatToStr(2*R));
end;
BitmapTmp.Free;
Label6.Caption := 'resol. '
+ IntToStr(round(2.54*BitmapOrg.Height/
(Pvhi-Pvlo))) + 'dpi';
end;

In this listing the functions Xim and Yim translate the


calculated positions of the pixels to the coordinates of Image1.
The cylinder base circle is also drawn on the image together with
the selected cylinder diameter (in cm).
The resolution of the original picture on the cylinder is
displayed on the user interface in dpi. If this resolution is too
low (i.e. the number of pixels is insufficient to fully cover the image area)
you see white interference lines in the image.
If that happens you need to increase the resolution or size of
the original in a photo editing program.

20

COMPONENTS
DEVELOPERS

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Cylindrical Anamorphosis (continuation 4)


Conclusion
I've not told you the identity of the image subject used in Figure
1. It's Lhamo, one of my Tibetan Mastiffs. See Figure 7.
I must admit that for quite a long time I didn't know whether
my program was functioning correctly because I couldn't find a
shiny cylinder anywhere at home; but eventually one of my
wife's lipsticks came to my rescue.

So now you can surprise your guests by showing your homemade anamorphic pictures, provided you have a shiny cylinder
(or lipstick). If you want to improve the program, feel free to do
so and let me know. Maybe you can speed up the processing by
using the SinCos function in the math unit (which
works only with variables of type extended).
I also didn't try whether the Canvas.Pixels property is
faster or slower than the Scanline property.
So download the source code and try it out!
It's written in Delphi 7, and therefore usable by the many
hobbyists among us.
There's only one thing that annoys me:
sometimes I get an Access violation in the try except
loop. I couldn't discover the cause, but as the program appears
to continue without problems, I just ignore the error. Any
suggestions for a solution?
See you next time.
peter @blaisepascal.eu

Figure 7: The image of Figure 1 revealed

Web Service Toolkit By Inoussa Ouedraogo


- a framework offering rich potential for Plug-in development
starter

expert

Lazarus

The Web Service Toolkit [1] (WST for short) is a


software package for FPC/Lazarus and Delphi
developers which is designed to facilitate web services
consumption (consumer side) and authoring (provider
side). Its Web Service name reflects its design goal of
offering a Pascal framework to integrate and simplify all
aspects of providing internet-capable web services.
However, it also has rich potential for defining both
simple and complex types (through its built-in Type
Library Editor) that can be used in non-web services
settings such as the client-server communication
required for developing application plug-ins. WST frees
developers from struggling with the 'plumbing details'
of message formats (SOAP, XML, RPC, JSON) and
transport protocols (HTTP, TCP or as another
possibility, a binary protocol specific to WST). Please
refer to Chapter 11 of the book Lazarus, the Complete
Guide[2] for extensive information about WST and
Lazarus.
The example presented here demonstrates how you can
use a dynamic-library-based Application Server as a way
of offering additional functionalities (plug-ins) to an
application using the rich type of framework supported
by WST.

A Library-based Application Server

Figure 1: Client and server modules in a client process


WST web services can be hosted in a dynamic library
either a Windows dynamic linked library (.dll) or a Unix
shared object (.so). At runtime the library is loaded into
the client process so that the client and server code are
then running in the same process. Library-based
application servers offer superior performance since the
server and the client share the same address space and no
remote protocol stack is needed. The library transport
protocol uses the local process address space to share
client request buffers and server response buffers. Every
library-based application server exports a function
wstHandleRequest located in the library_server_intf unit,
that is used by the client library protocol implementation
to invoke the server code.

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

21

Web Service Toolkit (continuation 1)


The following code shows the function signature.
The ARequestBuffer parameter is the one used to share request
and response buffers.
Library-based application servers are well suited for plug-in
development since the plug-in interface is defined in a high level
language and the developer does not have to worry about low
level library code.

Figure 2: Plug-in Main Program


Plug-in example
WST library-hosted services can be used as a framework
offering rich potential for plug-in development.
The main application defines its API interface through a WSDL
(Web Services Description Language) schema that plug-in modules
use to interact with the application.
The main application (the consumer) consumes the services
provided by the plug-in libraries (the service providers).
<Code unit=library_server_intf.pas>
function wstHandleRequest(ARequestBuffer:IwstStream;
AErrorBuffer : Pointer;
var AErrorBufferLen : LongInt ):LongInt;
</Code>

Figure 3: Architecture
22

COMPONENTS
DEVELOPERS

The example illustrated here is a program that lets the user


choose a mathematical function to perform calculations on
parameters the user has entered. The function list from which
the user makes a choice is provided by the currently selected
plug-in module. The plug-in list is built from the modules
(dll/so) found in the program folder's modules sub-folder.
This example contains :
The main GUI program that is being
extended with the plug-in modules.
The program source code is located in
the sources\web-services\
plugins folder. The application's only
(main) form lets the user select a plug-in
module. For each module a description is
shown, along with a drop-down list of the
functions available in that module.
A module named defaultmodule.
This module contains
common
mathematical functions such as exp, ln,
and the inverse function (1/x).
The module source code is located in
the folder sources\web-services\
plugins\modules\default.
A module named linearmodule.
This module contains simple linear
functions such as the Absolute function,
the Identity function x, and the Double
function 2x. The module source code is
located in the
sources\webservices\
plugins\modules\linear folder.
The plug-in interface is specified by
sources\web-services\
plugins\pluginservice.WSDL.
The function that is used by the main
application to invoke the plug-in calculation
for a selected operation on given
parameters. The Object Pascal translation of that file
contains principally three classes together with the service
interface as follows:
TPluginDesc :
This class describes a particular plug-in module
implementation. It contains the module's caption, its
description and the list of available functions.
TFunctionDesc :
A function description class which contains the function
identifier (Name) and a human-readable caption.
TFunctionDescList :
This utility class is used by the plug-in description object
class to specify the module's function list.
IPluginService :
The interface of the service provided by the plug-ins.
This interface is the only way the main application can
communicate with the plug-in implementation modules.
The interface contains two methods :
GetDescription :
Every plug-in describes itself by returning a
TPluginDesc instance filled with its identifying and
informational items.
ExecuteFunction :
The function that is used by the main application to invoke
the plug-in calculation for a selected operation on given
parameters.

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Web Service Toolkit (continuation 2)


<Code unit=pluginservice.pas>
TPluginDesc = class(TBaseComplexRemotable)
published
property Identifier : string read FIdentifier write FIdentifier;
property Description : string read FDescription write FDescription;
property Caption : string read FCaption write FCaption;
property Functions : TFunctionDescList read FFunctions write FFunctions;
end;
TFunctionDesc = class(TBaseComplexRemotable)
published
property Name : string read FName write FName;
property Caption : string read FCaption write FCaption;
end;
TFunctionDescList = class(TObjectCollectionRemotable)
public
class function GetItemClass():TBaseRemotableClass;override;
function Add(): TFunctionDesc; {$IFDEF USE_INLINE} inline;{$ENDIF}
function AddAt(const APosition : Integer) : TFunctionDesc;
property Item[AIndex:Integer] : TFunctionDesc Read GetItem;Default;
end;
IPluginService = interface(IInvokable)
['{26FEB177-55C3-4D59-A5C6-3CF7AD651E2A}']
function GetDescription():TPluginDesc;
function ExecuteFunction( const AOperation: string; const AValue : Double ):Double;
end;
</Code>

When the application's main form is created, the


LoadPlugins method builds the plug-in list (which is
pointed to by the FPlugins field). This method iterates
through the plug-in modules and for each module it
creates the appropriate service and asks for its description
using the function GetDescription. Note that the service's
proxy is created using the proxy class and an unambiguous
library file name.
The actual calculation is invoked by the actCompute
action's event handler actComputeExecute. Again, the
proxy is manually created and the correct library must be
specified.
This example shows how easy and simple it is to transfer
complex data structures via the plug-in API. In this regard
there is no difference between an HTTP-based remote
service and the library- based service.

SUMMER OFFER:

LAZARUS

the complete guide

Blaise Magazine is making a summer deal available


to all our subscribers. If you purchase the newly
published book (Lazarus the Complete Guide)
we will include with it the following bonus items at no extra
charge:
- a Windows installer CD for Lazarus version 0.9.30
- a preinstalled copy Lazarus on a free 4GB USB stick.
- 50 additional sample projects
- Blaise Pascal Magazine Library
17 complete issues on a 4GB USB stick
850 pages of articles
very fast search facility
comprehensive index covering all issues
locate the article of interest with one click
separate code index
separate index by author
overall index for complex searches covering all articles
all code completely accessible, linked to the relevant article
- extra: additional preinstalled component suites

PAPER BACK 50,00 + postage


HARDCOVER 60,00 + postage

Notes :
[1]
http://wiki.lazarus.freepascal.org/Web_Service
_Toolkit,

WST can be checked out at


https://lazarus-ccr.svn.sourceforge.net/
svnroot/lazarus-ccr/wst/trunk

[2]
Lazarus The Complete Guide,
ISBN 978-94-90968-02-1, Blaise Pascal Magazine,

COMPONENTS
DEVELOPERS

Pag 87

http://www.blaisepascal.eu/index.php?actie=./s
ubscribers/lazarusbookinfoEnglish

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

23

Polygon colouring and area calculation By David Dirkse


starter

expert

DELPHI 3 and above (Win32)

Introduction
This articles describes both how to colour a polygon
and how to calculate its area. These two tasks are
actually related since both require that you first divide
the polygon into its constituent triangles. This
triangulation is the hardest part of the task.

Figure 2
In the case of two intersecting vectors (red and blue) there is a point
S where they meet. Since S is on both the red and the blue vector,
we may calculate the f values (f1 for red, f2 for blue) for S. f1 and f2
then give an indication of the relative positions of the vectors.

We can distinguish three types of polygon:

Figure1 1 is a convex polygon, where each internal angle is less


than 180 degrees.
2 has an internal angle greater than 180 degrees.
3 has intersecting edges.

Figure 3

In this application type 3 is illegal, and if encountered will cause


the program to raise an error message. Types 1 and 2, however
complex they might appear, can always be broken down into
constituent triangles. These individual triangles can then be
coloured, or their areas can be calculated and totalled.
'Outside' and 'inside' angles
A polygon is made up of a sequence of points interconnected
by lines. There is an area internal to the polygon, and an external
area outside the polygon.
When decomposing the polygon into triangles, the biggest
problem is to classify an angle as being either 'outside' or 'inside'.
In case 1 you can take any three consecutive points on the
polygon's perimeter to construct a triangle, and that triangle will
always be internal to the polygon. All the angles of this polygon
are therefore 'inside' angles, giving rise to internal (inside)
triangles. Triangulation may also be accomplished by drawing
lines from one point to all the others.
In the case of polygon 2 however, we can identify an angle
formed by joining three consecutive points on which a triangle
can be constructed which is external to the polygon. We must
avoid colouring this triangle, which lies outside the polygon, and
the angle giving rise to this external triangle will thus be termed
an 'outside' angle. To differentiate between these angle types I
use some vector-geometry mathematics. I will explain the
general method here, and give its details further on in this article.

The red and blue vectors may be parallel or may coincide. In


that case there is no intersection. An fvalid flag is set to false if
this is the case. Using this type of vector geometry we are able
to distinguish between 'inside' and 'outside' angles in the case of
simple polygons. Consider the following figure, and examine the
angle at B.

Figure 4
In case (1) B is an internal angle of less than 180 degrees (an
'inside' angle) because the forward extension of AB does not
intersect other polygon vectors.
In case (2) the forward extension of AB intersects DE at point
S, so B forms an internal angle greater than 180 degrees (an
'outside' angle).
In case (1) we may colour triangle ABC. In case (2) with the
'outside' angle, we must not colour triangle ABC, since it is
external to the polygon.
However look at the following figure (5):

A line (edge) has a starting and ending point. Such a line is called
a vector, because more than one number is required to describe
it (two numbers are required in 2D geometry).
The figure below shows line l with vector AB.
An arbitrary point on line l can be characterized by a single
number, a factor which I call f.
Point A, the starting point, corresponds to f = 0.
Point B, the ending point, corresponds to f = 1.
To the right of B (the forward extension of AB)
are points that correspond to f > 1
To the left of A (the backward extension of AB) are points that
correspond to f < 0.
(You can refer to Appendix 1 at the end of the article for a fuller
mathematical description.)
24

COMPONENTS
DEVELOPERS

Figure 5
Diagram (1) shows polygon ABCD. At point B there is an
internal angle of less than 180 degrees (apparently an 'inside'
angle), but triangle ABC may not be coloured because there is a
polygon vertex (D) inside triangle ABC. Diagram (2) shows how
to recognize this situation. Construct vectors CA and BD and
investigate if the forward extension of BD intersects CA.

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Polygon colouring and area calculation(continuation 1)


Of course this must be repeated for all points (D) other than A, Vector start and end points may, or may not, lie on the extended
B and C. For simple polygons like bars and arrows, this algorithm vector AB. In each case we compute a score, the crosscount (see
suffices.
the figure left bottom (8)) which indicates whether just one end, or
whether both ends, of the crossing vector span the line, taking
The complete algorithm
direction into account as indicated, to give the sign of the
Below is a more complex polygon for which the algorithm we
crosscount.
have worked out so far fails to distinguish 'outside' angles
Now the simple rule for an 'inside' angle is:
correctly.
inside' angles have a crosscount sum of zero.
Note: a parallel vector has a crosscount of zero, which adds
nothing to the crosscount total.
Please refer to the following illustration (of an F-shaped
polygon tipped on its side) which shows crosscount values for
AB.

Figure 6
The extended vector AB intersects FG at S1 (and HI at S2)
so angle B (on the basis of the simple arrow-case algorithm above) is a
forbidden 'outside' angle. However, B is less than 180 degrees
and triangle ABC is internal to the polygon.
So, the 'outside' angle test needs refining.
We notice that the extension of AB intersects two other polygon
vectors, not one. That is the important clue towards a correct
solution. If there are an odd number of intersections then we
have an 'outside' angle. If there are an even number of
intersections then we have an 'inside' angle. But there still is a
problem to be solved, which can be seen in the following figure:

Figure 9
The sum for all AB crosscounts is 1 -1 + 2 = 2.
This is not equal to zero so B is not an 'inside' angle.
All that remains is to design a method that will recognize a
positive or negative crosscount.
Clearly we should measure the angle between the vectors to do
this. We shift the vectors parallel-wise until their starting points
coincide.
We notice
RIGHT...0 < angle < 180 degrees ...or
-360 < angle < -180 degrees (relative to AB)

Figure 7
The extension of AB intersects two other vectors (CD and DE)
exactly at their starting and ending point D.
In case(1) B is an 'outside' angle, but
in case (2) B is an 'intside' angle.
In case (1) we should colour triangle ABC whereas
in case (2) we should not colour ABC.
What then should we do? I present the following solution,
having evaluated all possibilities:

Figure 10
The angle between two vectors is the difference of their
directions.
Rather than using degrees (0..360) ,we calculate in radians
(0..2*pi), where pi = 3.14,
the ratio of a circle's circumference to its diameter.
The directions are shown in the following diagram, in which
clockwise movement is positive:

Figure 8
Position yourself at point A and look beyond B.
Vectors intersect the extension of AB and they may cross to the
right considered positive crossings (CD, EF, GH) or to the
left considered negative crossings (IJ, KL, MN).

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Figure 11
COMPONENTS
DEVELOPERS

25

Polygon colouring and area calculation(continuation 2)


This completes discussion of the algorithm.
Let's consider the implementation details.
The following data structures are defined:

The trianglelist is the final table produced. Working from this


array, individual triangles may be coloured or a triangle's area can
be calculated. To limit the size of this article the code for
triangle-colouring is available for subscriber download, but is
not reproduced here in full.

// the maximum number of points allowed


const maxpolypoint = 100;

The area of a triangle, given the coordinates of the angles, is


calculated using Heron's theorem:

Points are defined in a points array:

area = sqrt(s*(s-a)*(s-b)*(s-c)).

var points : array[1..maxpolypoint] of Tpoint;


pcount : byte;

where a, b, c are the lengths of the sides,


and s = 0.5*(a+b+c) // half the triangle perimeter.

// the number of valid points in the points[ ]array

The first and last points in the array must be the same so
that the polygon is "closed".
Using the points array, a vectorlist is generated.

a,b and c are simply calculated using Pythagoras' theorem.


For a proof of Heron's theorem please look here:
http://home.hccnet.nl/david.dirkse/math
/geocalc/geocalc.html#area

type Tvector =
record
x,y : longInt; // the coordinates of starting point
dx,dy : longInt; // the horizontal, vertical lengths
dir : double; // the direction in radians
end;

The structure of the polygon project


All polygon type definitions, variables, functions and procedures are
declared in the poly_unit unit.
The routines provided include:

var vectorlist : array[1..maxpolypoint] of TVector;


vcount : byte; // the number of vectors

From the vectorlist a triangle-list is generated.


type Ttriangle = record
ax,ay,bx,by,cx,cy : longInt;//
end;

(x,y) of A,B,C

var trianglelist : array[1..maxpolypoint] of


Ttriangle;
tcount : byte; //the number of entries in trianglelist

function polyArea(pts:pointer;n:byte):double;//area of polygon

pts points to points[1] and n is the number of valid points in


points[ ].

This function returns the area and draws the polygon on the
previously selected canvas.
procedure
procedure
procedure
procedure

setCanvas(cvs : Tcanvas); //select canvas


setpencolor(pc : longInt); //pen color
setBGcolor(bgc : longInt); //background
setBG(BGmode : boolean); //BG true/false

color

If BGmode is true the background is painted.


If false, only the lines and points are painted.

Coding a vector: (screen coordinates)

procedure geoClear;

//debug purposes initializes all arrays.

//triangles on/off
procedure setShowTriangles(trMode : boolean);

If trMode = true then draw the triangles, which is used only


for test purposes.
function polyResultMessage : string;

//message of code

The global polyResultCode variable measures processing


success zero is OK, nonzero indicates an error.
The PolyResultmessage function returns a string
describing the polyResultCode.

Figure 12: The vectorlist becomes successively shorter


(by removing vectors) as the trianglelist is built.

Global variables in poly_unit are as follows:


var

polyresultcode : byte;

// polyResultcode = 0 if processing is error-free


polyTime : longInt;

// polyTime is the processing time in microseconds

Figure 13
In the figure above vector 1 gets replaced by the sum (AC)
of vectors 1 and 2, and points A,B,C are entered in the
trianglelist.

26

COMPONENTS
DEVELOPERS

The Clock_unit unit contains procedures to measure


execution times using the CPU's clock cycle.
To design and test the algorithm a so-called exerciser is built
using form1/unit1;
This allows polygon generation and modification.
The results are visualised.

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Polygon colouring and area calculation(continuation 2)


There are some low-level procedures which calculate the
vectors. Most calculations are performed by vrsect, which
calculates the f1 and f2 factors of the intersection of two
vectors. (See Appendix 1 for the maths).

Figure 14: The exerciser program


The exerciser
The exerciser operates in either draw or in modify mode.
Draw mode: uses the mouse to draw lines.
Undo or [Backspace] removes the last line.
Modify mode: if you position the mouse pointer on an angle,
you can click to move the point to a new position.
Press [Shift] to increment the mouse pointer pixel by
pixel. Otherwise it uses 10-pixel steps.
The autoproc checkbox enables polygon drawing and
recalculation after each modification.
The fill checkbox paints a background inside the polygon.
The show triangles checkbox draws each individual triangle
within the polygon.
Clicking the [area] button will calculate the polygon's area
and draw it.
The architecture of the exerciser is not discussed in this article.
The poly_unit
This unit only uses the clock_unit, to measure processing
times. Therefore, you can simply add the poly_unit and the
clock_unit to any Delphi project you choose to implement
polygon area calculation. The function that calculates the area is
given below:
function polyArea(pts:pointer; n:byte) : double;
//calculate area, draw polygon
var t1,t2 : Int64;
begin
//clock cycles since power on
getCPUticks(t1);
polyresultcode := 0;
pp := Ppoints(pts); //pointer to points[1]
pcount := n;
//check closed, sufficient points
checkpoints;
if polyresultcode <> 0 then exit;
buildvectorlist;
//crossing edges, duplicate
checkvectors;
if polyresultcode <> 0 then exit;

points

buildtrianglelist;
if polyresultcode <> 0 then exit;

//summarize area of triangles


trianglesums;
//later version: result = area / 400
result := area;
getCPUticks(t2);
polyTime := ProcTime(t2-t1);
//draw polygon on selected canvas
drawpoly;
end;

procedure vrsect(const v1,v2 : TVector);


//calculate intersection of vectors v1,v2
//line1 = (v1.x1,v1.y1) +f1*(v1.dx,v1.dy)
//line2 = (v2.x1,v2.y1) +f2*(v2.dx,v2.dy)
//return f1,f2,fvalid
var d,vx,vy : double;
begin
d := v1.dx*v2.dy - v1.dy*v2.dx;//discriminant
if d = 0
then
begin
fvalid := false;
exit;
end;
fvalid := true;
vx := v2.x - v1.x;
vy := v2.y - v1.y;
f1 := (vx*v2.dy - vy*v2.dx)/d;
f2 := (vx*v1.dy - vy*v1.dx)/d;
if abs(f1) <
if abs(f2) <
if abs(f1-1)
if abs(f2-1)
end;

frnd then f1 := 0; //round to 1e-6


frnd then f2 := 0;
< frnd then f1 := 1;
< frnd then f2 := 1;

Other low-level procedures are:


function outward(vnr : word) : boolean;

//return true if vectorlist[vnr] points outward


function VDir(deltaX,deltaY : double) : double;

//return direction of vector in radians


function Empty3(i1,i2,i3 : word) : boolean;

//check for no point inside triangle i1,i2,i3


//no point : true

are sequential indices to the vectorlist[ ]


For i3, only the vectorlist[i3] starting point is used.
For more details please refer to the source code.
There are various high-level procedures including:
i1,i2,i3

procedure checkpoints;

// tests for sufficient points and a closed polygon


procedure buildVectorlist;

// builds a vectorlist from array points[ ]


procedure checkvectors;

// tests for intersecting edges and duplicate points


procedure buildTriangleList;

// builds trianglelist from vectorlist


procedure TriangleSums;

// sums areas of all triangles

Note: the bitmap has 20 * 20 pixel squares.


Areas are measured in squares (later versions).
The following drawing procedures are included:
procedure geoTriColor(const tr:Ttriangle;
brushcol: LongInt);

This draws the background of a triangle, using horizontal


lines.
Angles are first sorted by ascending y values.
procedure drawline(p1,p2 : Tpoint);
// draws a line from point p1 to p2
procedure drawpoint(p : Tpoint);

// draws point p

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

27

Polygon colouring and area calculation(continuation 3)


The intersection of two lines:
x1
x
dx1
line 1
y = y1 + f1 dy1
x2
x
dx2
line 2
y = y2 + f2 dy2
To know the intersection point following set of equations must
be solved:

Appendix 1

An introduction to the vector geometry used by this


algorithm. In plane geometry, a vector is a line with length
and direction.

()( ) ( )
()( ) ( )

( ) ( )
(
)

dx1
dx2
The notation of vector AB= dy1 BC= dy2
(see figure below)
dx1+dx2
The sum of two vectors AB+BC = dy1+dy2

x1 + f1*dx1 = x2 + f2*dx2
y1 + f1*dy1 = y2 + f2*dy2

The only unknowns here are f1 and f2. As outlined earlier,


these factors indicate the relative position of the vectors.
Calculations are done by procedure vrsect( )
To solve the equations we multiply row 1
by dy2 and row 2 by dx2 :
dy2(x1 + f1*dx1) = x2.dy2 + f2*dx2.dy2
dx2(y1 + f1*dy1) = y2.dx2 + f2*dx2.dy2

Now subtract row 2. from row 1.


dy2(x1+f1*dx1) - dx2(y1+f1*dy1) = x2.dy2 - y2.dx2
...or:
x1.dy2+f1*dx1.dy2 - y1.dx2 - f1*dx2.dy1
= x2.dy2 - y2.dx2 ...or:
f1(dx1.dy2 - dx2.dy1) = (x2-x1)*dy2 - (y2-y1)*dx2

Figure 15: Multiplication of a vector (enlarge, reduce)


dx1
f d y1 =

( )( f.dx1
f.dy1)the vector is multiplied by f.

Note : for negative f the vector changes direction.

let
d = dx1.dy2 - dx2.dy1..and
vx = x2 - x1 ...and
vy = y2 - y1 ...then

f1=

vx.dy1-vy.dx1
vx.dy2-vy.dx2 and similar
f2=
d
d

F or d = 0 the vectors coincide or are parallel.


In this case flag fvalid = false indicating there is no
df . yd 1y 1

intersection point.
Appendix 2

The direction of a vector:


x
of vector d
d y the arctangent
function is used to get the direction.
dy
direction = arctan d x in radians.
Two problems arise:
1. if dx = 0 then the direction is 0.5pi for dy >

()
()

Figure 16 : Before, vector AB is multiplied by 1.5 (yields A'B')


and -0.5 (yields A''B'')
The vector equation of a line (normal coordinates, not screen)

and

1.5pi for dy < 0

division is not possible and would result in a


floating point error.
2. the arctan function returns directions between -0.5pi
(-90 degrees) and 0.5pi (+90 degrees)
To trap all directions, sometimes a correction is necessary.
if dx < 0 then increase direction by pi (180 degrees)
if direction < 0 then increase by 2*pi (360 degrees)
This concludes the description of this polygon project.
And again we realise that nice applications can be made using
only simple math.
Figure 17
x1
x
x
dx1
A point y on the line has y = y1 + f1 dy
1

()

( )( ) ( )

Note:
f=0 for point A, the starting point of the vector.
f=1 for B, the end of the vector.
For 0 < f < 1 a point is located between A and B.
If f > 1 the point is situated on the forward extension of AB.
If f < 0 then a point is on the backward extension
(left of A in this case) of AB.
28

COMPONENTS
DEVELOPERS

David Dirkse
Born 1945 in Amsterdam, David joined Control Data
Corporation in 1968, after studying electrical engineering.
As a hardware engineer, he was responsible for the
installation and maintenance of mainframes at scientific
data centers in the Netherlands.
With the decline of CDC around 1990, he studied
mathematics and became a math teacher.
His hobbies are programming, in particular educational
software, math algorithms, and puzzle solving.
http://home.hccnet.nl/david.dirkse

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Creating a Database program from scratch


starter

expert

DELPHI XE and above (Win32)

In this series of articles I will explain how we are


addressing the magazine's need for a multi-functional
customer service program. The functionality has to
include registration and maintenance of subscriber
accounts, ability to send and receive emails
(respecting individual's privacy), administration of
sales and promotion of a variety of products, and
assistance in planning promotional campaigns. We
need to have direct access to a web-served database,
so any team member can gain access from their own
location. Additionally we need the security of being
able to work using our internal network (if the
internet fails for a period) using direct server access.
The server must make data backups automatically to
enhance the security. We will be open about the
choices we have to make, the tools potentially
available to us, the initial thinking and discussion
stages, the eventual plan and time scale, and how we
implement it in code.
This involves many interesting techniques. I think for
some of our readers it may be a routine task to handle
this type of challenge and to design software to meet
such requirements. I would appreciate discussion
about this, so please respond (to
editor@blaisepascal.eu) with any helpful feedback. I
estimate this will take at least a year of preparing,
designing and coding.

by Detlef. D. Overbeek

The database connection components will be from Kim Madsen


of Components4Developers (from now on C4D). A further - not
very objective - reason for using Delphi would be exploring
Delphi XE2's capabilities during our application development.
Which program will we use for creating and
handling the database?
The next step is to decide on a database administration tool.
What capabilities should it have? Gwan Tan, the owner of
BetterOffice taught me a good lesson: Almost any tool at
all can create projects of up to 100 tables. But if you might have
over 100 tables you would be wise to choose ER
Embarcadero's database tool. Our project is not big enough for
that but we will start a series of articles about ER in October.
First lets summarize the needs:

1. What database?
The database handling tool must be capable of creating
the Firebird database.
2. Auto creation and visual design
It should be able to automatically create tables and allow
you to design them visually.
3. Reengineering
We need reengineering since we already have
many existing tables.
4. The ability to create and then visually
alter the design
We need a tool for SQL scripting that works in two
directions: create and design eventually in visual
alterations (the ones that show in your design overview).
5. Design updates change the underlying tables,
and vice versa
The first hurdle: the Database
6. Support printable overviews
We already have an application in rudimentary form which
Support printable overviews, at least A3 or bigger,
works after a fashion. However, it is not adequate for our
in colour.
current and future needs.
I think it's best to start from scratch, using only some ideas and 7. Create images in a variety of popular formats
Be able to create images with several different
details from our current application. So we have reusable parts
and ideas, but we have not yet settled on a database. Since we are
sorts of extensions.
a user group, we need to use open source where possible to keep 8. Have export and import capabilities to
the price down, which limits our choices. A further issue is
other databases
choice of the operating system on which the database server will 9. Have a data pump
run.
10. Documentation
Will it be Linux, Windows, Mac or Android? How can all our
Last but not least: have very good quality
users have easy access? Linux is cheap (almost free), but then you
documentation.
will need to use an extra helper language for creating a lot of
internet-network functions in something like PHP. Since all the
user group are familiar with Pascal, I suppose it would be best to And now the million dollar question:
Can we as a poor user group get that free? Who might help us?
keep as close as possible to Pascal.
We are of course poor, but not short of fantasy. Why not ask
So the next question is whether we use Delphi or Lazarus
the
manufacturers? And within no time we came up with the
libraries? It doesn't really affect the choice of database, especially
solution. There is a Dutch company that offers such a product,
if we opt for the Interbase equivalent : Firebird. But...
and as chauvinistic as I am - I love that.
If we use some of the best component suites for database
connection (from Components4Developers) as well as other So we gave it a try and they responded positively: we could have
their tool at no cost. (I must admit that they're one of our advertisers:
attractive offerings from TMS Software we should opt for
Upscene). They have created a program called Database
Delphi. (TMS surprises us time and again. At the recent Delphi
Conference in Cologne (Kln), Germany they presented an astonishing new Workbench which meets all of our needs. So let's start testing
product: Desktop - Touch Table. I was excited - like a little boy who had it
just found the ultimate toy when I realised the potential impact it might
have).
Since the TMS Suite is not yet fully available for Lazarus (they
are still working on it) I opted for the Delphi flavour of Pascal,
where TMS provides a full set of reliable components, and
To receive a printed copy of the magazine
Firebird will be our database.
you need to go to our website to place your order

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

29

Creating a Database program from scratch (Continuation 1)

Figure 1: The Splash screen...

Figure 2 : The IDE opening screen

Testing Database Workbench


We already have many Interbase tables and it would be very interesting to see the quality of Upscene's localized SQL (since
Interbase is no longer identical to Firebird). It works most of the time. But Interbase has different SQL abilities compared to
Firebird. So the tool needs to understand both. We have to design, create and connect about 100 tables, and we must be sure...
The next image shows an overview of Database Workbench version 4's IDE. To research the Firebird support and the SQL dialect
it uses I went straight to the help file, which can be seen in Figure 3:

Figure 3:
Firebird's local SQL dialect
30

COMPONENTS
DEVELOPERS

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Creating a Database program from scratch (Continuation 2)


To make sure that we have all the options available: The database visual designer supports bi-directional updating. If you change
anything in the design it will be visible there, and it will also be reflected in updates directly in the table (see Figure 4).

Figure 4: The Database Workbench table editor

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

31

Creating a Database program from scratch (Continuation 3)


Very good, I like that. I must say I am always very curious about
the user-friendliness of a new application's functions when I
first use it. What I term my gut feeling, often based on whether I
intuitively know where to find a feature, acting as if I already
knew it were there.
I was not disappointed. Within 30 minutes I found all the main
items without even using the help once. This was a very
gratifying experience. It is one of those applications where you
can feel at home very quickly.

Figure 5: The IDE and the menu where you can


process reverse engineering
The next thing to go for is the auto creation of the diagram. I
know all database tools do this. But not all have a bi-directional
updating ability. Through reengineering, this all works well in
Database Workbench. And of course since I'm interested in the
ability to create very good presentations (you need that if you
want to sell your database project, but it also demonstrates the
functionality and coherence of your database) it comes in very
handy that you can use colour with the ability to edit your table
design text and annotations as if they were a RichEditMemo.
Great!

Figure 6: A part of the table and permitted alterations

As listed in the requirements above we can print in various


colours, sizes etc. But what makes it feel like home is the little
Exit icon. This tells you the Workbench was written in Delphi!
(See Figure 9)

Figure 7:
You can create and edit annotations.

Figure 9: A sure sign that it was


built in Delphi: the exit icon...
32

COMPONENTS
DEVELOPERS

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Creating a Database program from scratch (Continuation 1)


Returning to the list of requirements: #7 Create images in various formats. We find
bitmap, gif, jpeg, jpg, png, but alas no vector graphics. And then requirement #8, import
and export capabilities: I would have liked the possiblility to import native Excel files, so
I would not have to convert data into CSV files. Of course there should be better import
capablilities... One always wants to dream of more...

Figure 10: The Draw menu


Figure 12: Export capabilities

Figure 11: Import capabilities


The requirement #9 to have a data pump is provided for. I haven't tested it yet. But I am quite confident after all I saw.

Figure 13: The Data Pump...Who ever thought of that name?


And then #10, good documentation. I always like to have a written file. Not only as a help file. With some ingenuity I tried to
create a PDF file through printing to PDF which could be done per chapter. That is too difficult. We need to have a PDF help file,
which I will request. It's not that much work to create one...
In the next issue we will start creating the database, create the application schema and how it should be designed. That will be at the
end of October. Any questions, you know how to find me... editor@blaisepascal.eu

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

33

An Android client for a DataSnap Server Part 2 By Daniele Teti


starter

expert

DELPHI 2007 and above

Specifically, the graphical controls are called widgets and are


defined in the package android.widget. Some of these
In this second article we'll create an Android client
widgets
(buttons, edits and so on) will be used on the app that
application that talks to the previously built DataSnap
we'll
create.
server. Basic knowledge of Java syntax is assumed in
One difference for a Delphi developer is the event handling
what follows.
mechanism is different from that to which a Delphi developer is
used to, but it is very similar to that of the graphics libraries
Introducing Android development
available on Java SE, so a Java developer should not have
As Google says
Android is a software stack for mobile devices that includes an problems in understanding the mechanism.
operating system, middleware and key applications. The Android The application we are going to build will allow us to
SDK provides the tools and APIs necessary to begin developing understand how the listener mechanism works, and how to
parse a string in JSON format.
applications on the Android platform using the Java
I have not structured this article as a step-by-step tutorial.
programming language. (http://developer.android.com/
guide/basics/what-is-android.html)
Rather I will focus on the specific issues involved while
A lot has been written on the web about the pros and cons of
interfacing Android with DataSnap. If you need a step-by-step
Android, so we will not repeat that here. Android is a great
tutorial on Android development, you can browse the amazing
operating system and development platform. To get an
Google documentation, or a white paper that I wrote for
introduction to Android and the motivations that led the way for Embarcadero some months ago. You can download the
Google to undertake the development of Android, you can
whitepaper for free at http://www.embarcadero.com/rad-inconsult Wikipedia at the following URL
action/php-android. The white paper talks about a PHP
http://en.wikipedia.org/wiki/
REST service and Android. However the Android part is almost
Android_%28operating_system%29.
the same. This is the code for the main activity.
Before starting to develop for Android, you must set up the
development environment. Although it is not mandatory to use public class MainActivity extends Activity
a specific development environment, Google has written a very implements OnItemClickListener {
private ListView lv;
useful plug-in for Eclipse called ADT (Android Development
private List<ToDo> todo_list;
Tools), and later in this article we will use Eclipse with ADT.
private final int MENU_REFRESH = 1001;
private final int MENU_CONFIG = 1002;
Google provides an excellent guide on how to configure
private final int MENU_ABOUT = 1003;
everything you need to develop on Android. In Throughout this
article I'll assume that your development environment is
/** Called when the activity is first created. */
configured as recommended in this guide
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
setTitle(R.string.app_title);
lv = (ListView) findViewById(R.id.lv_todo);
lv.setOnItemClickListener(this);
}

(http://developer.android.com/sdk/installing.html)

and that you have successfully tried the HelloWorld tutorial


(http://developer.android.com/resources/tutorials/he
llo-world.html). I am referring to various key components

of Android's architecture. To understand what I'm talking about,


and avoiding a lengthy and boring copy-paste, I suggest you
refer to Application Components. You can Help that you find
more on this topic here:

@Override
protected void onResume() {
super.onResume();
refresh_data();
}
private void refresh_data() {
todo_list = new RESTProxy(this).getToDos();
lv.setAdapter(new ToDoListAdapter
(this, todo_list));
}
@Override
public void onItemClick(AdapterView<?> parent,
View view, int position, long id) {
Intent i = new Intent(this, EditToDoActivity.class);

http://developer.android.com/guide/topics/fundamenta
ls.html#appcomp

Layout
The commonest way to define the appearance of an activity is
using an XML layout file. The structure of the XML used by
Android for layout it is a reminiscent of an HTML web page.
Each element within the XML can be either a View or a
ViewGroup (or one of their descendants). The name of the
XML elements of the layout file correspond to the JAVA class
that represents them. So a <Button/> element creates an
instance of the Button within the GUI and a
<LinearLayout/> element creates an instance of the
LinearLayout ViewGroup. When loading resources,
Android initialises the objects defined in the XML file layout,
creating concrete functional objects. So you can imagine the
layout xml file as a text representation of a complex object tree.
For a Delphi or Lazarus programmer, what comes the closest in
concept to an Android layout file is the DFM or the LFM file.
View and Event Handling
The activities are your application's screens. An activity is the
Android counterpart to the Delphi/Lazarus Form. So,
whatever the user can see, is necessarily contained by an
Activity. Every visible element within Android is called a View
and is drawn on an activity.

// the selected item


ToDo todo = todo_list.get(position);

// fill the intent extra data.


//Will be readed by the EditToDoActivity.
i.putExtra("id", todo.getId());
i.putExtra("completed", todo.getCompleted());
i.putExtra("description", todo.getDescription());
// start the other activity
startActivity(i);
}
public void newTodoClick(View btn) {
Intent i = new Intent(this, EditToDoActivity.class);
// start the other activity
startActivity(i);
}

//Menu management code. Remove for brevity

34

COMPONENTS
DEVELOPERS

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

An Android client for a DataSnap Server Part 2 (continuation 1)


In the onCreate() method (called when the activity is first
created) I do some initialization related to the activity. As the
(famous) Activity LifeCycle says

The proxy uses the same approach.


Running an application with this code on the emulator (or a
device) produces the following error message at the call to the
(http://developer.android.com/reference/androi execHttpRequest method:
d/app/Activity.html#ActivityLifecycle),
Permission denied (maybe missing INTERNET
the onResume() method is called just before the activity runs, permission)
or returns to running. So, in that method I've put the actual data This is because, in Android, every application that uses a
loading from the server. This application is a demo.
network connection, must inform the operating system.
In a real world application you would put all the remote
To inform Android that our application needs access to the
invocation, or more generally, all the time-consuming operations internet, open the file AndroidManifest.xml and add just before
in a secondary thread.
the closing tag </manifest>, the following line
The Android SDK provides strong multithreading support. If
<uses-permission
you need further information about threads in Android, you
android:name="android.permission.INTERNET
should read these articles:
"></uses-permission>
http://developer.android.com/guide/topics/fund Even in this case you can use the visual editor if you prefer. Our
amentals/processes-and-threads.html,
todo application requires this permission.
http://developer.android.com/reference/java/la
ng/Thread.html,
http://developer.android.com/reference/android
/os/AsyncTask.html).

The To Do List Proxy

A ToDo list is simply a list of things to do, without a


specific date on which they must be done. We are using as
The DataSnap proxy
the server the service that we built in the previous
The most interesting part of the application is the proxy. In this article. The interface is minimal but effective. The main
application the DataSnap proxy is written from scratch (although activity just shows a list of all the 'todo' items, and
in Delphi XE2 there is very cool automatic proxy generation for Android
whether they have been completed or not yet completed.
and other mobile platforms). However, if you have Delphi XE and
Clicking on a TODO item, using an Intent, will open a
you need to write an Android client, you will have to write the
proxy by hand. The proxy maps each REST method to a normal new activity that will then allow us to edit it. In addition,
you can also insert a new 'todo' items directly from the
object method that uses a connection to a webserver.
main activity using the "New ToDo" button.
So the URL
/datasnap/rest/TSrvMethodsTODO/Todos
is mapped to the a proxy instance method getTodos() and
so on.
Accessing the web server
It is simple to send a request to a web server from an Android
application. For these this purposes, Android provides the wellknown Java HttpClient library from the Apache Software
Foundation . Precisely for this reason, the documentation on
this is really great and well done. To understand how
HttpClient works, it is useful to use the project's official
documentation which you can find here:
http://hc.apache.org/httpcomponents-clientga/tutorial/html/index.html

As an example, here's the code to send an http request for a


URL and gets its text representation in a String.
private String execHttpRequest(String url)
{
try
{ //Create default http client
HttpClient httpclient = new DefaultHttpClient();

//We want send a GET request, so Create an HttpGet object

Figure 1 : The main activity

HttpGet httpget = new HttpGet(url);

//Execute GET request over the httpclient


HttpResponse response =httpclient.execute(httpget);

//Retrieve the response body or "entity"


HttpEntity entity = response.getEntity();

//Return the entity as String


return EntityUtils.toString(entity);
} catch (Exception e) {

//If something goes wrong, print the stack trace


//and return the Exception message
e.printStackTrace();
return e.getMessage();
}

Studying the code, you will learn how to connect a ToDo list
retrieved from the server to a ListView, how to open a
secondary activity, how to create ing menus for the activity, how
to manage user preferences, and how to use Toast. It is not the
aim of this article to be an Android tutorial, but just
understanding these simple mechanisms you will be able to
develop a simple Android application that does something
really functional.
Let's having take a look at the most significant proxy methods,
starting with in the method that returns the complete ToDo list:

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

35

An Android client for a DataSnap Server Part 2 (continuation 2)

Daniele Teti
Figure 2: The code to get the ToDo list and to convert the JSON array ToDo items into an ArrayList
As can be seen in Figure 2, The ToDo list is returned from the service as an array of JSON objects. I have written a method that
takes care of converting the JSON representation of a ToDo into a real List<ToDo>. At this point the code shown should be
clear. Creating and editing a ToDo item it is just a little bit more complex, because we have to handle the body of the request.

Figure 3: The create and update methods


SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

37

An Android client for a DataSnap Server Part 2 (continuation 3)


Figure 3 shows more on the PUT and POST. In this case, in addition to sending the request with the appropriate method
(PUT to create and POST to update) we also need to send the body of the request in the expected format.
It is necessary to represent the JAVA object in JSON, and In this case the proxy method is responsible for the JSON creation. In a
real world application it is correct to ensure that every object can serialize its internal state independently via Reflection, or by
implementing an interface such as the following:
public interface JSONSerializable {
public String toJSONString() throws JSONException;
public void fromJSONString(String json) throws JSONException;
}

Please notice at line 56 of Figure 3 the URL is built, to identify which ToDo item to edit, using parameters. The same criterion is
used by the deleteToDo method.
When you have to work on a single ToDo, another activity is launched. The code to start another activity, with or without extra
Intent data, is shown in the Figure 4:

Figure 4: How to start a secondary activity.


The secondary activity displays the ToDo data.

Summary
In this quick introduction to the Android world, we have only scratched the surface of
the subject. We but have nevertheless been introduced to several key concepts that
allow readers to start developing DataSnap REST services in Delphi. You can also take
full advantage of mobile device features in allowing an Android application to manage
remote data. You can find the complete application in the sample folder.
If you would like to learn more about it, I would recommend you read a good text on
Android, and above all, I recommend that you study the samples in the SDK provided
by Google. It They can be found under the directory samples \<your-platformnumber> of the SDK .

Figure 5.
38

COMPONENTS
DEVELOPERS

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Anti-freeze for VCL applications By Alexander Alexeev


starter

expert

DELPHI 3 and above (Win32)

Many of you who read this will have developed


applications which perform time-consuming work.
Tasks such as searching through files, loading huge
data files, performing complex computations,
accessing slow networks and so on. When your
application executes such tasks and it starts
thinking it doesn't look good to the user:

There are several undesirable effects:


Your Program (Not responding) appears in the
program window's caption.
The window doesn't respond (or responds poorly)
to any user action such as window minimizing and
dragging, or clicking on a control.
The mouse cursor turns into an hourglass, when moving
over your application's windows.
Your window's client area does not get refreshed.
The client area is drawn as shaded (in Vista and above).
The client area is drawn as filled by a background colour
(typically - white) in XP and earlier.
A system dialog may appear after a timeout, informing you
about the 'hung' application.

The wrong solution


So what can you do? The most frequently encountered advice to
deal with this problem is to interrupt your work from time to
time to give responses to Windows' requests.
This process is known as message pumping or message
processing. For example, if you're copying a file then you are
advised to abandon trying to accomplish this all at once, and
rather to split the copying process into several steps in which the
file gets copied in smaller, same-sized blocks.
In between copying each file block you can do the Windows
message processing. Incidentally, you carry out message
pumping by calling the ProcessMessages method of the
global Application object. In other words, you should divide
your work into relatively small pieces, and mix the work and
message processing in order to keep the user interface (UI)
responsive.
However, this solution has number of serious problems, which
makes it far from optimal:
This is not always a possible solution. If you've called an
external function (such as copy file) then you can't
interrupt it from time to time to perform message pumping.
Heavy work may be performed by a DLL, which has no
idea whether it's a console application which called it or
whether it's a GUI application (which needs to pump messages).
If you call Application.ProcessMessages, when there
are no pending messages to process you're wasting time.
If you call Application.ProcessMessages too often
(unnecessarily) you'll slow down your program, since you'll
frequently spend time doing nothing. In the worst case you
may put more effort into keeping the UI responsive than in
doing actual work!
If you call Application.ProcessMessages too rarely
then your application won't work smoothly. It will appear
jagged.
Note that you can't reliably pick the optimum frequency of
calls to Application.ProcessMessages. Say you're
copying a file from one hard disk to another. You might set
the call frequency to one call per megabyte of file data copied.
However, if the user runs your application and copies the file
over a network your estimates will fail. For a slow network
it's more appropriate to set the call frequency, say, to one call
every for every 10 Kb of copied data. However, if you
implement this slower estimate in your application, then you'll
waste time when copying files between local hard disks.
Calling too often is bad, as is calling too rarely. And what's
worse it changes while you're running. What's just as bad is
that you can't really assess how well your estimate fits until
your users try the application out in their various situations!
Inappropriate calling of Application.ProcessMessages
may introduce recursive cycles and re-entrancy problems
(for example, a user may click a button more than once while your
application is still busy with that button's task which will lead to
running a second task inside the first one. That's probably not what
you're wanting!).
Personally, I believe that correctly coded applications don't need
to call Application.ProcessMessages in the first place.
Anyway, it's good to have another, better solution. And, indeed,
there is one.

All these things underline to the user that your application has
hung even if this might be not true. Your application may just
be busy with processor-intensive work and will revert to full
responsiveness after some delay.
A specific case of this situation is the question (often asked by
newbies): when you do time-consuming work, how can you
implement an immediately effective Cancel button?
The window is not responding so how can a user click on the
button?
OK, let's start with the basic question Why do these things
happen?
It is because Windows doesn't know what you want to show in
your windows. It doesn't know how you want to react to a
button-click.
That's why Windows tells you Please, redraw your windows,
and The user just clicked on your button please, do
something.
Typically, this conversation goes on under the hood of the VCL
library and you deal only with the high level end result
(for example, a Button1Click OnClick event handler).
If your application is busily doing some work it can't answer
these Windows requests, which leads to the appearance of a
hung application.
Threads?
This happens because code running in a single thread can only
do one thing at a time. Either it is working or it is responding The thing is that processes (applications) don't run any code at
all. They are just containers for threads.
to Windows' requests, but not both simultaneously.
COMPONENTS
SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18
39
DEVELOPERS

Anti-freeze for VCL applications (continuation 1)


A thread is a sequence of code running in your application.
When the application starts Windows creates one thread for it,
the so-called primary (or main) thread, which executes your
application's code. Threads are always created in the context of
a particular process, they cannot stand alone.
Their entire life is spent inside the borders of a process. That's
why two or more threads running inside same process can run
the same code, use the same data, and share common data and
objects inside that process. Most applications have only one (the
primary or main) thread. However, you can create more threads on
demand, as needed. Creating additional threads allows you to
perform more work at the same time. Additional threads are
called secondary, worker, additional, helper or
background threads.
As you will have guessed by now, the correct solution to
unfreezing an unresponsive UI lies in the use of additional
worker threads, which will extract some work from main thread,
and execute it simultaneously. One of the threads will perform
constant message pumping, thus keeping the UI alive (refreshing
the UI, reacting to the user's input, etc.). Another thread will do the
actual work. That way your GUI application can run smoothly
whilst doing its work.If you've already been involved in
developing multi-threaded applications, then you will know that
controlling threads and syncing access to shared, common data
are particularly complex tasks.
There are a large number of tools available to help deal with
these tasks (critical sections, events, semaphores, mutexes, and so on).
Even though they all are quite simple by themselves, they can
quickly become a real headache when you develop a proper
synchronization model for your application.
This is an especially hard task for newcomers, who simply want
a smoothly responsive UI. In real-world applications developers
try to reduce coding complexity, minimizing the amount of
synchronization needed, treating it as unavoidable evil only
when they can't see a way to avoid it.
The simplest synchronization is a sync which you don't need to
do. In other words it's the lovely situation where threads do not
share any data at all. In Delphi there is another overriding
issue, namely that the VCL is single-threaded.
This means that you can't access any VCL object (Form1, Label1,
Button1, etc.) from another thread. UI work can be performed
only by main thread and not by any other helper thread.
This also implies that major non-VCL tasks should be
offloaded to secondary worker threads, and all UI work
should be running in the main thread, and not vice versa.
That's why the main thread is often called a UI thread.
Also, this means that you can't access, say, Edit1 from a
secondary thread to discover the file name to process.
And you can't access ProgressBar1 to update the status of
the file's processing because both things mean accessing the
VCL, which you can't safely do from another thread.
All this leads us to unavoidable thread synchronization,
which we were trying to avoid!
Because of all these complex issues, many developers just avoid
threading. They are too lazy to implement a really smoothly
working application. Instead they just scatter calls to
Application.ProcessMessages all over their code, and
sometimes they don't even bother to do that (this is particularly
true if the operation in question is performed quickly in 90% of cases, and
there are only 10% of cases where the operation runs slowly producing the
corner cases where the application hangs).
So, what can you do? Should this simple wish to be so hard to
implement?
40

COMPONENTS
DEVELOPERS

The multi-threaded solution


Well, as it turns out, there is a really good solution to this
conundrum. I've written a TasksEx unit (you can download it from
the registered subscribers area of the Blaise Pascal Magazine website)
which you can use to easily solve this problem, even if you have
no knowledge of threads or synchronisation primitives.
The TasksEx unit uses a bit of a magic (and by magic I mean the
work of Andreas Hausladen the AsyncCalls unit:
http://andy.jgknet.de/blog/?page_id=100 ).
To use the TasksEx unit you just need to follow some simple
rules. The unit offers you only two principal procedures:
procedure EnterWorkerThread;
procedure LeaveWorkerThread;

Any code, which is put between calls to EnterWorkerThread and


LeaveWorkerThread, will be performed as if it were put into a
secondary thread. For example:
// This code runs in the main thread (for example, a Button1Click)
EnterWorkerThread;
try

// This code runs in a secondary thread.


// Even if it's written in Button1Click, it'll still be executed
// by a worker thread, as if it were written in TThread.Execute.
finally
LeaveWorkerThread;
end;

// This code runs in main thread


You can use any global or local(!) variables in code between
and LeaveWorkerThread this
includes the routine's parameters, as well as the parent routine's
parameters. Stated simply: you have access to all the data which
you normally have access to without EnterWorkerThread
and LeaveWorkerThread. And you can write your code as
if there were just one thread in your application (well, almost you
still need to follow a few rules see below).
The typical multi-threaded approach involves creating and using
a thread manager or thread pool, implementing a queue of tasks
for background threads, synchronizing access to common data,
and much else. The advantage of this method is that you don't
need to do anything at all! To be sure, this can't be used for
seriously complex multi-threaded applications, but
EnterWorkerThread and LeaveWorkerThread have
simpler aims. Most often you have a situation in which you just
need to do something without freezing the UI (preferably also
without having to put too much thought into how it will work under the
hood). There are lots of examples out there that demonstrate the
need for this.
One application hangs when doing file searches, because its
author didn't anticipate that there would ever be so many plugins for his application to search. Another application hangs
during file loading, because the author despaired of doing this
smoothly because the task was too complex.
And that's where the TasksEx unit comes in. It's designed to
solve this one programming problem. To make your application
run smoothly you need to detect potentially time-consuming
areas of code, and wrap them with calls to
Enter/LeaveWorkerThread. That's all. There are no
thread priorities to set, no Terminate, no Queue, no
syncing. The unit is designed for transparent programming.
You can write code without thinking about threads at all.
EnterWorkerThread

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Anti-freeze for VCL applications (continuation 2)


You just need to separate your code in two areas which don't
overlap:
a)
Time-consuming code
b)
Code which works with the UI (and accesses the VCL).
Then you wrap both areas in above mentioned calls and you get
your really smooth application. That's all! One possible scenario
for using this unit is when modifying existing code which was
written without any thought of multi-threading. For example,
you have a large routine or method already written. Later you
find that you need to run part of it in a background worker
thread. Instead of rewriting the code, separating different parts
to run in different threads, preparing data to pass to a secondary
thread, passing results back, establishing error handling and
proper synchronization, and so on you can just put
EnterWorkerThread/LeaveWorkerThread around the
problematic area. In short this unit is a very convenient
alternative to other multi-threaded approaches for cases where
you don't want to put much work into applying beneficial
threading to existing non-threaded code.
Usage examples
Here are several examples which show how the TasksEx unit
can be used. Firstly, a simple example: file searching. Let's
assume we have a form with an Edit1, a Memo1, a Button1 and
the following code:
procedure TForm1.Button1Click(Sender: TObject);
procedure EnumFiles(const AFolder: String;
AFiles: TStrings);
var SR: TSearchRec; S: String;
begin
if FindFirst(AFolder + '*.*', faAnyFile, SR) = 0
then
try
repeat
if (SR.Name <> '.') and (SR.Name <> '..') then
begin
S := AFolder + SR.Name;
if DirectoryExists(S)
then EnumFiles(S + '\', AFiles)
else AFiles.Add(S);
end;
until FindNext(SR) <> 0;
finally FindClose(SR);
end;
end;
begin
Memo1.Lines.BeginUpdate;
try
EnumFiles(IncludeTrailingPathDelimiter(Edit1.Text),
Memo1.Lines);
finally Memo1.Lines.EndUpdate;
end;
end;

Incidentally, notice how we're avoiding the magic button antipattern discussed in the previous issue of Blaise Pascal
Magazine. The EnumFiles routine is fully isolated and can easily
be moved somewhere else to allow external calls or even
simultaneous multi-threading.
Run this code, enter any folder with a huge number of files into
Edit1 (say, C:\) and click on Button1.
You'll see that the application hangs for some minutes, while it
searches for files. Experiment by minimizing and restoring the
application, dragging it over the desktop, or clicking on controls
inside the form. You'll see how the application fails to react
properly.

uses

TasksEx;

procedure TForm1.Button1Click(Sender: TObject);


procedure EnumFiles(const AFolder: String;
AFiles: TStrings);
begin
// ... no changes ...
end;
var Str: TStringList;

Folder: String;

begin
Button1.Enabled := False;
try//1
Str := TStringList.Create;
try//2
Folder :=
IncludeTrailingPathDelimiter(Edit1.Text);
EnterWorkerThread;
try;//3
EnumFiles(Folder, Str);
finally;//3
LeaveWorkerThread;
end;//3
Memo1.Lines.BeginUpdate;
try//4
Memo1.Lines.Assign(Str);
finally//4
Memo1.Lines.EndUpdate;
end;//4
finally //2
FreeAndNil(Str);
end;//2
finally //1
Button1.Enabled := True;
end;//1
end;

Okay, so the main searching code was left unchanged, but we've
wrapped it between calls to EnterWorkerThread and
LeaveWorkerThread. Run the application now, and notice how
beautifully it behaves itself: the application doesn't hang, it
allows you to drag it around, minimize, or restore it. It even
accepts clicks on controls inside its window! (That's why we've
locked Button1 for the search duration to prevent the user running the
search again, while first search is still running). All this happens
because code between EnterWorkerThread and
LeaveWorkerThread (i.e. the call to EnumFiles) is now running
in a separate background thread, so main thread is free to do
message pumping and UI processing.
Rules
Even though this was a very simple example, it illustrates several
important rules:
1. Calling EnterWorkerThread/LeaveWorkerThread must
always to be like this:

Obviously, the reason is that EnumFiles takes far too long to


complete. During the file searching no other work (such as UI
repainting, or processing of user input) can be performed.
Let's see how we can fix this:

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

EnterWorkerThread;
try // do work
finally
LeaveWorkerThread;
end;

Pay great attention to this template. It's very important. You


must use the try/finally pattern. You must not insert
any code in the finally block or before try, (apart from the
calls to EnterWorkerThread and LeaveWorkerThread). It's
quite easy to understand why: because these functions
perform thread switching. If there is an exception in the
secondary thread and there is no try/finally, then
reversing the thread switch will be missed.
COMPONENTS
DEVELOPERS

41

Anti-freeze for VCL applications (continuation 3)


Imagine the chaos which will result from that!
If you're used to exception programming, then there is
nothing new for you in this construct, and you'll set it up
automatically. If you're not used to writing exception-catching
code blocks, then you'd better learn quickly, otherwise
2 Since your application now has more than one thread
(i.e. your application is now capable of doing two things at the
same time), then you must design its UI to be resistant to
re-entrance issues. That's why we've inserted the code which
changes Button1's Enabled property. Remove these two lines
and run the application again. Notice that you can click on
Button1 twice, and the second click might occur while the
first search is still running. This will really spawn multiple
background threads, each simultaneously executing a file
search. Probably this is not what you want.
3. The background thread is another thread. If you're using
any thread-affinity objects or states then you must explicitly
transfer them to the secondary thread. For example, any
variable values of the main thread's threadvar block will be
inaccessible in the secondary thread (and vice versa). In order
to use them between threads you must pass them explicitly as
parameters or variables. Another thread-affinity issue that may
arise is with COM initialization. You must initialize COM in
the secondary thread before using it. It's often done
automatically for the main thread by Delphi itself. But this
does not happen automatically for any additional threads, so
you must do this manually as follows:

5. You must not access the VCL from any secondary thread.
In our first example we passed Memo1.Lines to EnumFiles, so
the AFiles.Add(S) line was adding a string directly to Memo1
therefore working with the VCL(since a memo is a VCL control).
Because this is not allowed we must get rid of this code.

That's the most complex part of using secondary threads. Because


Delphi's compiler will not warn you about attempts to use visual
controls from another thread. What's worse if you miss such
code overlaps, your application will work most of the time, but
sometimes it will crash. So, if you have random hangs, crashes or
access violations check your code for access to the VCL from a
non-primary thread.
So, what's the best approach to avoid these pitfalls? Well, there are
many possibilities. Here are a few short examples
As shown in second code example above move any VCL
interaction out of the secondary thread to come either before
or after it. In our example we replaced working with the lines of
Memo1 with working with a TStringList instance instead.
TStringList is a non-visual class. It's not part of the VCL, so we
can easily use this class and share it across secondary threads.
After our work is complete we simply copy the TStringList
results back to Memo1. The common idea here is to collect data
from your form into variables, work with those variables in the
secondary thread (calculating results and so on), and finally
publish the results in the form.
Send a message to the main thread. The background thread
can send a window message to the main window, asking it to
EnterWorkerThread;
perform VCL work (instead of the secondary thread itself
try
performing the VCL work). You can send messages both sync
CoInitialize;
and async (of which async has the least performance penalty).
try // your-code
This is a very good and flexible way to work. Unfortunately,
finally
there are too many potential issues to fully describe all its aspects
CoUninitialize;
in this article. If you have worked with window messages before
end;
finally
you shouldn't have any problems with this approach. Since all
LeaveWorkerThread;
messaging work is done inside a single process, no inter-process
end;
communication (IPC) is necessary. You can see an example of
this approach in the code examples accompanying this article
The same holds true for similar mechanisms.
(available at the Blaise magazine website).
4. This rule is similar to previous one: not all external

Temporarily switch to the main thread. (For the benefit of readers


functions allow simultaneous calls from several threads.
who are familiar with Synchronize and TThread, this is analogous to calling
Some external functions may not allow calls from a nonSynchronize in TThread). You can do this as follows:
primary thread at all. The VCL is the best known example,
procedure TForm1.Button1Click(Sender: TObject);
but we're talking about all sorts of external functions here.
procedure EnumFiles(const AFolder: String;
For example, GDI objects typically have no thread-affinity,
AFiles: TStrings);
but you must guarantee single thread access at one time.
// ... no changes ...
Windows objects have thread-affinity: only the thread which
begin // ... no changes ...
created a window owns it. A further example would be calls
if DirectoryExists(S)
you make to loadLibrary and FreeLibrary.
then EnumFiles(S+'\',AFiles)else
begin EnterMainThread;
Even though they don't formally have thread-limitations, the
try AFiles.Add(S);
DLL they load and free may have implicit expectations about
finally LeaveMainThread;
the loading thread being the application's primary thread.
end;
In such cases you must protect calls to these functions with
end;
critical sections (though you can ignore this if you're sure that you
// ... no changes ...
won't create more than one worker thread). For functions which
end;
don't like calls from other threads at all, you will need to
begin
Memo1.Lines.BeginUpdate;
serialize calls to the main thread. You can either make these
Button1.Enabled := False;
calls before (or after) spawning the secondary thread, or you
try EnterWorkerThread;
can use the technique described in the next item (Rule 5).
try EnumFiles(IncludeTrailingPathDelimiter
A function's description does not always mention the
(Edit1.Text), Memo1.Lines);
finally LeaveWorkerThread;
requirements for multi-threaded calls, which makes life
end;
difficult for inexperienced programmers. So sometimes you
finally
will need to do some study and research before writing code.
Button1.Enabled := True;
Of course, you can risk trial-and-error programming, but I
Memo1.Lines.EndUpdate;
strongly recommend against doing this.
end;
end;

42

COMPONENTS
DEVELOPERS

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Anti-freeze for VCL applications (continuation 4)


This is quite an involved construct for such a simple example,
but it can come in handy for more complex cases.
As you can see, a pair of EnterMainThread and
LeaveMainThread calls must be enclosed in a try
finally block, in just the same way as when using
EnterWorkerThread and LeaveWorkerThread.
Personally I don't recommend this approach. This is because
it's easy to make a mistake here, it has less than optimal
performance and, more important, it is not always possible,
since you can't insert EnterMainThread /
LeaveMainThread calls in external code.
As you can see, the above examples do not all exhibit the same
behaviour. For example, Memo1.Lines.Assign in the first
approach may take a lot of time (if there is a vast number of
files) and thus still give the appearance of a hung application
after all our work.
The solution? Don't pull large amounts of data into the UI in
one go do it partially (as is done in second and third approaches).
Better still use a virtual view.
Implementing a Cancel button
When your application reaches the Put the work into a
secondary thread! level your first natural desire (and need) will
be to implement a Cancel button.
Clicking on the Cancel button lets a user stop and cancel the
current processing.
How can we implement it?
Very easily! You don't need to write much code. Take any of
three examples above
(a: Pulling VCL code out of the secondary thread,
b: Using messages,
c: Serializing access),
and add a Stop button to the form (set its Enabled property to
False at design-time) and make the following changes to your code:
procedure TForm1.Button1Click(Sender: TObject);
procedure EnumFiles(const AFolder: String;
const AFiles: TStrings);
// ... no changes ...
begin
if FindFirst(AFolder + '*.*', faAnyFile, SR) = 0
then
try
repeat
CheckAbort; // <- added
// ... no changes ...
until FindNext(SR) <> 0;
finally
FindClose(SR);
end;
end;
begin
Button1.Enabled := False; // start button
Button2.Enabled := True; // stop button
try
// ... no changes ...
finally
Button1.Enabled := True;
Button2.Enabled := False;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
AbortAllWorkerThreads;
end;

Run the application and start a long search. Pay attention to the
Stop button and how its state changes during the search.
Now, click it while the search is still running the search will be
halted.
How does this work?
The first and most important thing is that you must call the
CheckAbort routine in your secondary thread from time to time
this is a mandatory condition.
That's because there is no clean way to stop the thread from
outside. You can, of course, do it in a dirty way (a forced
Terminate), but doing so will leave a lot of trash in the process.
The correct way to stop the thread's work is to let the thread
itself do so. You let the thread check for a cancel condition and
stop work, if it's appropriate. That's exactly what the
CheckAbort routine does. If there is no cancel command it just
exits without doing anything. If there is a cancel command then
the routine will raise an EAbort exception.
Raising this exception will stop current work in the thread and
pass the exception up to the main thread, where it will be
processed by the Application.HandleException method.
This (by default) just ignores EAbort.
So where is the command to exit issued?
That's done by calling AbortAllWorkerThreads. As you will
have guessed this method stops all running secondary tasks.
Other issues and technical details
A secondary (worker) thread is picked randomly from the system
thread pool (by default via the QueueUserWorkItem function).
If there are no free threads then code execution will be delayed
(queued), until one of the worker threads completes and comes
free. The number of threads in the pool is controlled by passing
flags to the QueueUserWorkItem function (see the description of the
function in MSDN). By default the limit is 512 threads.
While secondary threads run your code, the main thread cycles
the message pump constantly by calling
Application.HandleMessage. The cycle ends with the end of all
worker threads started via EnterWorkerThread. During this cycle
code may be invoked which calls
EnterWorkerThread / LeaveWorkerThread again (for
example, when the user has clicked a button).
Therefore at any moment, there might be multiple code blocks
running (code blocks between EnterWorkerThread and
LeaveWorkerThread), including the case of multiple instances
of the same code running. That's why you may need to explicitly
prevent such code starting again (as we've done in our examples by
disabling the button that starts that code sequence running). Such calls are
termed parallel calls.
The first call to LeaveWorkerThread will continue to run only
after all parallel calls have been completed. In other words the
very first call to EnterWorkerThread will wait until all later
calls to EnterWorkerThread have been completed.
If all such calls have done their work before the first call does its
work there will be no waiting at all. This is the same as in a
single-threaded application, when you call
Application.ProcessMessages and the message invokes
another time-consuming message handler, so
Application.ProcessMessages will not return control to
you (the caller) until that handler has done its work.
This case should not be confused with nested calls to
EnterWorkerThread:

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

43

Anti-freeze for VCL applications (continuation 5)


...
EnterWorkerThread;
try
...
P;
...
finally
LeaveWorkerThread;
end;
...
procedure P;
begin
EnterWorkerThread;//

A call to EnterMainThread/LeaveMainThread is like


calling Synchronize.
Because there can be only one code sequence executed in the
main thread, EnterMainThread will block, if there is already
a thread running inside EnterMainThread. Also, while code
between EnterMainThread and LeaveMainThread is
running the worker thread waits for completion and is not
returned to the thread pool.

does nothing since we're already in


worker thread

try
...
finally
LeaveWorkerThread;

// does nothing, since nearest


EnterWorkerThread
// above didn't do anything too

end;
end;

In other words, you can safely nest calls to Enter/Leave


Worker/Main Thread and only the first call will work,
all other calls will be ignored.
Because of the wait for parallel calls to complete before
returning control, exit from applications with parallel calls will
be delayed until all called secondary threads have completed
(or been cancelled via AbortAllWorkerThreads or a similar routine).
Exceptions in a secondary thread will be caught and re-raised
(and re-thrown) in the main thread.
Delphi's debugger is not able to follow thread switching.
So, if you're debugging an application and want to Step Over
an EnterWorkerThread/LeaveWorkerThread, you must
set an explicit breakpoint right after calls to these functions.
A second (not nested) call to EnterWorkerThread may use
another worker thread, not necessarily equal to the first worker
thread. For example:

When NOT to use this solution


It's good to know when to use this solution. But it's also
important to know, when not to use it.
I remind you that the primary aim of the TasksEx unit is to
simplify development of smoothly responsive GUI applications.
Period.
End of story.
This unit is not suitable for other tasks.
If you need to do something like simultaneously download
using ten threads or anything of that sort then this unit will
not help you.
To be sure, you could try to do that using this unit (and it's
possible), but this would be the moment when a scary monster
program is born. Solving such tasks is discussed in other Blaise
Pascal Magazine articles and many Internet blogs and articles.
There are various approaches at your disposal: BeginThread,
TThread, QueueWorkItem, OTL (Omni Thread Library),
AsyncCalls,

There may be another article about the TasksEx unit and its
additional features in a future issue of Blaise Pascal Magazine.

// This code runs in main thread


EnterWorkerThread;
try

{ This code runs in worker thread #1 }


finally
LeaveWorkerThread;
end;

// This code runs in main thread


EnterWorkerThread;
try { This code runs in

worker thread #2, which could be the very


same worker thread, but also can be a completely different thread. }

finally
LeaveWorkerThread;
end;

To temporarily switch to the main thread use the


EnterMainThread/LeaveMainThread functions. For
example: // This code runs in main thread
EnterWorkerThread;
try { This code runs in worker thread #1
EnterMainThread;
try // This code runs in main thread
finally
LeaveMainThread;
end;

{ This code runs in worker thread #1 }


{ (guaranteed to be the same worker thread) }
finally
LeaveWorkerThread;
end;

To receive a copy of
the printed magazine
you need to
order a subscription
from our website:
www.blaisepascal.eu

// This code runs in main thread


44

COMPONENTS
DEVELOPERS

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

Pag 45

Delphi XE2 New Features


starter

expert

By Bob Swart

DELPHI XE2

This can be used on just about any VCL application (just about,
since some code cannot be migrated to 64-bit the BDE, for example, is a
collection of 32-bit DLLs that will not ever (never!) be migrated to 64-bit
yet another good reason to move away from the BDE today rather than
tomorrow).
Apart from 32- and 64-bit Windows, Delphi XE2 also offers
native Mac OS X as a target platform. The IDE remains a 32-bit
application, however, so running (launching) a 64-bit Windows or
a Mac OS X application from the IDE requires some
tinkering

Early in September 2011, Embarcadero's annual RAD


Studio release cycle offered XE2 editions of Delphi,
C++Builder, Prism (previously known as Delphi Prism)
and RadPHP. This article examines some of the
noteworthy new features in Delphi XE2. Some features
(such as the DataSnap enhancements) are only
available in Enterprise and higher editions, but other
new features are available in all editions.

XE2 Editions
Project Targets
Speaking of Editions, there are now no less than five (5!)
Where previous Delphi projects had a special node in the
different Delphi XE2 editions available to buy. These are listed
Project Manager for the Build Configuration, Delphi XE2
below with pricing details both for first-time buyers and for
upgrading. All prices are shown without VAT, and these editions introduces the Target Platform node. This can have a value of
32-bit Windows, 64-bit Windows, and OS X (sometimes seen as
are available from http://www.bobswart.com for EU
OSX32,
so most likely 32-bit). Since the Delphi XE2 IDE itself
customers, together with a corresponding 12 month
is
still
a
32-bit
Windows application, by default a new project
subscription price.
will be created for the 32-bit Windows target. Depending on the
Delphi XE2
New User Upgrade
Subscription
project type, we can add one or more Target Platforms.
Starter
199
149
n/a
A VCL project, for example, can be targeted for both 32-bit and
64-bit Windows, but not Mac OS X. The VCL is really tied to
Professional
899
499
270
the Windows API, and is just about impossible to migrate to
Enterprise
1999
1299
600
another platform. If you want to create an application for Mac
Ultimate
2999
1999
900
OS X, you have to use either a console application (which can be
Architect
3499
2299
1050
handy at times, but may not be exactly what you have in mind when you
think of a Mac application), or you have to create an application
You can upgrade from Delphi 2007 or later. However, after
using a special cross-platform application framework called
December 31st 2011, you will no longer be able to upgrade from FireMonkey. As you can read in Fikret Hasovic's article
Delphi 2007, and the only eligible versions to upgrade from will elsewhere in this issue of Blaise Pascal Magazine, it's easy to add
be Delphi 2009, 2010 or XE. Delphi XE2 Ultimate is the same
a 64-bit VCL target to a project. Make sure you read Jeremy
as Delphi XE2 Enterprise with the addition of DB
North's detailed coverage of creating 64-bit applications with
PowerStudio. Delphi XE2 Architect is the same as Delphi XE2 Delphi XE2 as well. But for Mac OS X we have to use a
Enterprise with the addition of ER/Studio. These two database different framework, called FireMonkey.
products are both from Embarcadero (now the proud owner of
Delphi). To be honest, as a reseller I mostly sell Professional and FireMonkey
Enterprise editions of Delphi. The Starter edition is rather
In order for an application to look like a Windows application
limited, so check out the conditions and constraints before on Windows, and a Mac application on Mac OS X,
you purchase a Starter edition (especially if you plan to make some Embarcadero had to bring in (read: buy) a whole new set of
money with it). Apart from these five commercial editions, you can visual components a new GUI framework. They actually call it
also get special Academic versions of these editions, but only if the FireMonkey Application Platform, and it is specifically
you are a registered student at a school or university (obviously,
designed for cross-platform application development. Note that
these Academic editions cannot be used for any commercial development).
the VCL is not dead or being replaced. The VCL is still the best
Finally, you can download a 30-day trial edition of Delphi XE2 solution for native Windows applications (32-bit and/or 64-bit).
Architect to get a hands-on feeling of what the product is
But for cross-platform applications that need to move to other
capable of.
platforms like Mac OS X (and in the future probably Linux), we
Subscriptions are offered for the Professional and higher
should use the FireMonkey Application Platform.
editions, and are valid for a period of a year. You can optionally
extend a subscription each year for a further year before the
The FireMonkey Application Platform presents cross-platform
subscription expires. With an active subscription, developers
controls and elements such as forms, dialogs, buttons, menus,
automatically get new editions of Delphi as soon as they are
and so on. It supports 2D and 3D graphics and uses the GPU
released by Embarcadero. They also have the right to three sofor the graphics, freeing the CPU itself for doing the real
called incident support calls with Embarcadero (for example for work (like calculations or database operations).
problems that even your reseller cannot solve for you). A subscription is
The FireMonkey Designer may feel a bit awkward at first, since
especially beneficial if you are a high-end Delphi user, but also
it's not a clone of the VCL Designer. However, remember that
for Professional users if they upgrade at least once every two
FireMonkey is at version 1.0, and there will be many
years.
enhancements (and fixes) in the time ahead.
Major New Feature: X-Platform
The major new feature in Delphi XE2 is the capability for crossplatform development. First of all, the 32-bit Windows compiler
has been extended with a 64-bit counterpart which produces
64-bit Windows applications.
46

COMPONENTS
DEVELOPERS

FireMonkey Example
To demonstrate the FireMonkey Application Platform, let's
create a new application using Delphi XE2. Using File | New
Other, we get into the Object Repository and can see a number
of different FireMonkey project targets for Delphi:

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Delphi XE2 New Features (continuation 1)


The Standard category contains a number
of familiar controls like TEdit, TListBox
and TButton, which can be placed on the
form.
Warning: do not place them on top of
each other (the effect that you get when you
enter Edit in the Tool Palette search box) and
type [Enter], followed by ListBox and
another [Enter]. This places the TListBox
as a child of the TEdit (unlike the way the
VCL works), which is probably not what
you want, and can lead to strange effects,
so if you move the TEdit, the TListBox
will move along!
Speaking of positioning the FireMonkey
controls, you encounter here another area
of difference from the VCL. FireMonkey
doesn't expose a Top or Left property,
but uses a Position property with X and Y
subproperties. There are no Anchors, but
a Margin and a Padding property that
appear to conflict with the way margins
Figure 1: A FireMonkey 3D Application
and paddings work in the VCL or CSS.
A FireMonkey 3D Application is capable of doing more graphic Anyway, after you've placed a TListBox, TEdit and a TButton
stuff than a FireMonkey HD Application. However, as a
on the TForm (at least the control names are the same, although they
consequence, a FireMonkey 3D Application requires more
originate from different units), we can write the well-known event
power from the GPU. The FireMonkey HD Application is the
handler for the OnClick of the TButton:
one that most closely resembles a VCL Forms Application, with procedure TForm1.Button1Click(Sender: TObject);
a 2D Form Designer that can host FireMonkey controls that
begin
almost look and feel like good old VCL controls.
ListBox1.Items.Add(Edit1.Text);
end;
As an example, let's create a FireMonkey HD Application for
Delphi, which creates a new project starting with a new empty
And then we can run the application, which by default will
form with a black caption and border.
show up as a 32-bit Windows application (the default target
Save the project in FireMonkeyDemo.dpr and the form in
platform of any project in Delphi XE2).
MainForm.pas (in case you want to play along, for example using the
trial-edition of Delphi XE2). One thing that you may
immediately notice: if you click on the design area
of a FireMonkey form, you cannot use the Alt+F
shortcut to go to the File menu. Somehow, the Alt
menu keystrokes are disabled here. Also, the nice
IDE short- cut of Edit | Copy to copy the contents
of the VCL Form Designer is missing in the
FireMonkey Designer, so I had to make a screenshot
the hard way, as can be seen in the following picture.

Figure 3

Figure 2
As you can see, this looks like a platform-independent
window, with placeholders for the border icons (on the upper
right, probably because the IDE is still running on Windows), and a
black border and caption. We can now look at the Tool
Palette for a list of FireMonkey controls that can be used.

This sure looks and feels like a regular Windows application.


In fact, it already contains some more-than-regular effects,
like the glow around the TEdit that has the focus (visible in the
screenshot). This effect can take up some processing power (GPU or
CPU), and if the application becomes too slow, you can disable the
effect as follows:
GlobalDisableFocusEffect := true;

In order to run the same project, without any changes to the source
code, on Mac OS X, we need to perform some additional steps.

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

47

Delphi XE2 New Features (continuation 2)


Mac OS X
First of all, you need a Mac running Mac OS X version 10.6 (Snow Leopard) or later. In order to deploy the application from your
Windows development machine to the Mac target machine, you also need to install a separate utility called the Platform Assistant
(paserver) on the Mac. The paserver can be found on your development machine in the C:\Program Files\Embarcadero\RAD
Studio\9.0\PAServer directory. There is a setup_paserver.zip to be used on Mac OS X, and a setup_paserver.exe to be used on a 64bit Windows machine. You need to copy the setup_paserver.zip and open it on the Mac where you need to unzip it and run the
setup_paserver application to launch the installer of the Platform Assistant Server application.

Figure 4
You need to work through a number of screens (leaving the default
settings intact), after which the Platform Assistant Server will be
installed on the machine, in my case in the
/Users/bob/Applications/Embarcadero/
PAServer directory:

Figure 5
Now, as soon as you start the paserver application itself, it will start a terminal window and ask for a password. Don't panic, this is
not a password that you should have remembered or written down somewhere. Rather, it's the password that you can define here
(at the deployment machine) and that you have to specify at the development machine to allow the development machine access to
the deployment machine (and also to prevent any other visitors from accessing the Mac at port 64211).

Figure 6
48

COMPONENTS
DEVELOPERS

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Delphi XE2 New Features (continuation 3)


Once the password is entered, we can leave the paserver running on the Mac, and return to the Windows development machine to
compile and run (with or without debugging) and/or deploy the project as a Mac OS X application.
Mac OS X Development
The next step back at Delphi XE2 involves the addition of a new target platform. In the Project Manager, open the Target
Platforms node to verify that it only contains the default 32-bit Windows target. Then, right-click on the Target Platforms node
and select Add Platform to add a new target platform. In the dialog that follows, select OS X as new target:

Figure 7
A Target Platform also needs a Remote Profile with the information to connect to the remote target machine. If a remote profile
for the selected platform already exists, then Delphi XE2 will assign it as remote profile. Otherwise, you will be prompted to create
a new remote profile as soon as you try to run the project for OS X.

Figure 8
If you click on [Yes], a dialog will show up with all available Remote Profiles for the OS X Platform. Initially, this list will be empty,
so you need to click on the Add button to create a new Remote Profile, or you need to click on the Import button to
import a Remote Profile (in case you saved and exported one from another development machine for example).
The Add button will display the Create a Remote Profile wizard, were we can specify the name of the remote profile. I typically
call the Mac profiles Mac followed by the last part of the IP-address, so I know which Mac I'm talking about. Mac164 is the Mac
mini running OS X Snow Leopard on which we just installed the paserver:

Figure 9
SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

49

Delphi XE2 New Features (continuation 4)


Note the checkbox to set this Remote Profile as the default remote profile for the OS X
platform. Once checked, the next time you add a Target Platform for OS X, this Remote
Profile will be assigned to it automatically.
On the next page, we can specify the Host Name of the Mac OS X machine (or the IPaddress). The Port number is already specified using the default port number 64211. If you
debug and deploy in your own local network, that port number is fine. However, If you plan
to debug or deploy over the internet, I would change that port number to a slightly less
obvious one, since only the password is keeping other visitors from connecting to your Mac
and deploying applications to it.
Finally, do not forget to specify the same password here as the one you specified in the
paserver back on the Mac.

Figure 10
Click on the Test Connection button to ensure you can talk to the Mac. If the connection is
refused, then you may have to configure the firewall on the Mac to allow the incoming
connection.

Figure 11
If you close this confirmation dialog and go to the next page in the Remote Profile Wizard, a
page is shown which is only relevant for C++Builder developers (who need to cache symbolic
information from the remote machine on the local machine). Delphi developers can just skip
that page and click on the Finish button to create the remote profile.

To receive a printed copy of the magazine


you need to go to our website to place your order

50

COMPONENTS
DEVELOPERS

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Delphi XE2 New Features (continuation 5)

Figure 12
This will bring you back to the Select Remote Profile dialog, where you can now finally select the
newly created Remote Profile.

Figure 13
As a result, the Project Manager will now show the Remote Profile next
to the Target Platform in the Project Manager:

Figure 14
SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

51

Delphi XE2 New Features (continuation 6)


Also, if the Remote Profile selection was shown as a result of
the initiative to Run the application, the actual application will
now be running on your Mac OS X machine!
This may not be clear at first, especially if you did Run | Run
without Debugging, but if you switch back to the Mac, you'll see
the FireMonkey demo application running as a native Mac OS X
application!

Figure 15
And this certainly looks like a native Mac OS X application to
me. If you compare this screenshot to the Windows edition of
the FireMonkey Demo, you will see the same ListBox, Edit and
Button, but the actual look-and-feel is now totally different and
Mac-like, while the Windows edition truly feels like a Windows
application. Of course, these are just simple demo applications,
but you should get the idea
Deployment
When it comes to deployment of your application, especially to
the Mac running OS X, you need to perform a number of steps
again. Using Project | Deployment you can get a Deployment
tab in the IDE that shows the files that need to be deployed to
the Mac OS X machine. There is one gotcha: if you compile a
Release Build, then the project .rsm file will not end up in the
OSX32\Release directory, so this file cannot be deployed. The
Debug Build will produce the .rsm file, which causes it to be
displayed in the list of deployment files: If you connect to the
deployment machine, you can see the Remote Status. The green
arrow will actually deploy the project files. On my Mac, they end
up in the
/Users/bob/Application/Embarcadero/PAServ
er/scratch-dir/Bob-Mac164 where Bob is the name
of the remote user (since the local user is called "bob") and
Mac164 is the name of my Remote Profile.
The FireMonkeyDemo is a 17.6 MB archive that contains all
selected deployment files, and can be run as a stand-alone
application on the Mac. We can also copy it to another Mac
(running at least Mac OS X Snow Leopard) and run it from there.

52

COMPONENTS
DEVELOPERS

If you copy it to a USB stick, you'll see a FireMonkeyDemo.app


directory with a Contents subdirectory and a MacOS as well as a
Resources directory inside plus the Info.plist file (see list of files
above). The MacOS directory contains the FireMonkeyDemo,
the FireMonkeyDemo.rsm and the
libcgunwind.1.0.dylib library (only 20 KB), while the
Resources directory contains the
FireMonkeyDemo.icns file. I'm not sure if the
.
rsm file is still required after we've done
a Release build, but that's something to figure out
later. At least it's easy to deploy, and we can now
even run it from the USB stick!
Unit Scope Names
Getting back on track, focusing on some of the new
features of Delphi XE2: in order to prevent VCL
and FMX (FireMonkey) classes and units from
interfering with each other, and to enable both
the VCL and FMX to use one RTL,
Delphi XE2 introduces the concept of scoped unit
names. Where Delphi 7 already introduced the
capability of using dots in unit names (a functionality
mainly used in the .NET flavour of Delphi before it became
extinct), the dot is now being used again to produce
scoped unit names. In summary: all Delphi XE2 units are
grouped into logical categories, and these category names
become the scope or prefix of the actual unit name (as well as the
filename on disk). So SysUtils is now System.SysUtils and can be
found in System.SysUtils.pas
(or System.SysUtils.dcu if you have the trial-edition that
doesn't include the source code).
If you want to know the scoped unit name of a Delphi unit, you
can use the little web application that I wrote at
http://www.bobswart.nl/cgibin/UnitScope.exe?unit=XXX

where XXX is the name of the unit you want to resolve.


In a follow-up article I discuss a new technique that allows
FireMonkey applications to bind data, such as data-aware
controls, without being limited to controls that communicate via
datasources. In addition, I cover the DataSnap enhancements in
Delphi XE2 Enterprise. You can find that article at page 108.
Bob Swart (Bob@eBob42.com)
Bob Swart Training & Consultancy
www.bobswart.com

Figure 16

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Learning to use FastReport 4 for VCL Part 3 Sergey Lyubeznyy


starter

expert

DELPHI 6 and above

In previous articles (Blaise Magazine issues 16 and 17)


I've talked about FastReport (FR) installation and
localization. I also described the process of creating
simple reports both with and without bands (including
creating reports from Delphi code), and showed how to
connect different data sources to the report.
This article begins by showing you how to put images
from files or database fields into a report. The next part
describes FastReport's graphic objects. The final part
shows you how to create a simple report containing a
diagram. To follow along you will need some experience
of working with FR, because I will not describe again
typical simple operations like creating bands and placing
objects on them, which have been covered previously.
Putting images into a report
To work with images in FastReport we need an image database.
We will use a database of several photo images which I took
recently on visits to St. Petersburg (Russia), with accompanying
text which annotates the photos. Most of this information was
taken from Wikipedia articles.
This database is a specific Firebird file, created using Firebird
SQL Server (version 1.5). We will connect to this database using
the Firebird Embedded 1.5 DLL and the InterBase Express
(IBX) components. However we don't need to place these
components on the Delphi form. Instead we can simply use
FastReport 4.0's built-in features to work directly with IBX in
the report Designer.
If you want to try to create this application yourself, you can
download the example archive from the Blaise Pascal Magazine
website. In this archive you will find a folder named Example1.
This folder contains only the database file and the necessary
Firebird Embedded components to connect to this database.
You can completely unpack this folder to your computer and
save your Delphi project there.

A further directory, called Example1_App, contains the same set


of files and the binaries from the completed project that I
created using Delphi 2010.
If you want to create the project using Delphi 2009, it is
important to know that the IBX components shipped with
Delphi 2009 may contain a bug. When you try to open a dataset
containing string fields, a division-by-zero exception is raised.
You can correct this by editing and recompiling the ibsql.pas file.
It is possible that this error might occur when you work with
IBX for FastReport in Delphi 2009. Additional information on
the bug can be found here:
http://qc.embarcadero.com/wc/qcmain.aspx?d
=68103
So, let's start. Create a new Delphi project and save it in the
folder mentioned above, and then place the following
components on your main form:
frxReport1: TfrxReport
Button1: TButton
CheckBox1: TCheckBox

Place them as shown in Figure 1, and set their Captions.

Figure 1: Layout of the example form


Then write the Button1.OnClick event hander, to be able
to test the application during the report's creation.
procedure TForm1.Button1Click (Sender: TObject);
begin
with frxReport1
do begin
PrepareReport;
if CheckBox1.Checked
then ShowPreparedReport
else Print;
end;
Figure 2: The FastReport Data
end;

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

page

53

Learning to use FastReport 4 for VCL Part 3 (continuation 1)


To work with FastReport's built-in IBX components, we also
need to specify the frxIBXComponents module in the
Uses section of the module that contains the TfrxReport
component.
Open the report Designer by double-clicking the
frxReport1 component. Place the first band on the page
(the Report Title band). Then place a Text object on this band
and change its text to My PhotoReport. Centre the text, set
the font size to 14 and make it bold. Set the Text object's
Align property to baClient. Correct the band's height to
place all this text inside the band's working area.
Then you need to place a MasterData band on the page.
We will not set a Datasource for this band right now - let's
do that later. Set this band's height to approximately 5-6 cm
Now look at the region above the Object Inspector. You will see
three text tabs with the names Code, Data and Page1. Click the
Data tab. You will see the report's Data page (Figure 2).(see page
46).
The Data page is an object of type TfrxDataPage. Its
published properties are not useful for us. But we can place
components on this page, although they are not the same
components that can be placed on the printable pages. The Data
page is a container for the data access components. Take a look
at the left vertical toolbar, and you will see the FR data access
component icons. Three of them are IBX components: IBX
Database, IBX Table and IBX Query. You can see these
names in the pop-up hints as you move the mouse over them.
Placing components on a data page is done exactly as for a nondata page. The only difference is that we do not need to adjust
the size of the components.
So, place the IBX Database (IBXDatabase1) and IBX Query
(IBXQuery1) components on this data page.
You can see these objects' types in the Object Inspector:
TfrxIBXDatabase and TfrxIBXQuery.
The TfrxIBXQuery.Database property must point to
IBXDatabase1.
Now you need to configure these components using the Object
Inspector. First let's configure the IBXDatabase1
component. Set the Params property, using the Text editor, as
shown in Figure 3.

Figure 4: Two lines of SQL text need to be entered


It is also useful to verify that the CloseDataSource
property is set to True.
That completes configuration of the IBX components.
Next we look at the Designer's ReportData menu. The data
source list should look like Figure 5

Figure 5: The data source list


Programmers familiar with IBX components may wonder which
FR IBX component is responsible for working with
transactions. I don't know the answer, because my FR Standard
version has no source code included. I can only assume that a
transaction is created and managed fully automatically.
This process is hidden from the user.
Now it's time to return to the Page1 tab. Double-click on the
MasterData1 band and assign the IBXQuery1 as data
source for this band. After that, look at the data tree (Figure 6).

Figure 6: The Data Tree dialog


When the database connection is established, near the dataset
name a [+] icon is shown. If you click this icon, you can see a
list of the dataset's field names. Unfortunately, we can't yet
establish a connection with the database (during the design phase)
because of the peculiarities of the Firebird Embedded engine.
However, we can make our work little easier.
Drag the IBXQuery1 dataset from the data tree to the
Figure 3: Values for the Params property
MasterData1 band. After that, a Text object will appear on
To confirm these settings, click on the button with a green check the band containing the following text:
mark. The text editor window will disappear. Then set the
[IBXQuery1."IBXQuery1"]
LoginPrompt property to False to avoid displaying a
Instead of quoted IBXQuery1 text, we need to put the name
Login dialog box, and set the DatabaseName property by
of the field in quotes. Our text field is called TXT.
locating the database file MYIMAGES.FDB in the file selection Insert this name in our object's text.
dialog (Note: by default this dialog only displays files with a 'gdb' Similarly, when the dataset is active, you can drag a field name
extension, so to find the MYIMAGES.FDB file, you must
from data tree onto the data band to create the text object that
switch the file type to 'All Files'). The SQLDialect property
will display this field's text. Very convenient! This is what we will
must be equal to 3.
do later (see the last part of this article).
Let's go back to our project. We need to configure this Text
So, let's look at the properties of IBXQuery1. Its main
object. Set the text alignment to left justify, the Arial font size to
property is SQL of TStrings type. The SQL query text
a value of 10, and turn off its bold property.
should be entered via the Text editor (Figure 4).
54

COMPONENTS
DEVELOPERS

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Learning to use FastReport 4 for VCL Part 3 (continuation 2)


Place the object in the top-right corner of the band and stretch
its width to half of the page's working area. Set its height to
match that of the band. Assign its stretch mode in the Object
Inspector by setting the StretchMode property to
smMaxHeight.
Now find the Picture object icon in the Designer's left vertical
toolbar. Click on this icon and then on the MasterData1
band. This shows the Picture dialog (Figure 7).

Figure 7: An empty Picture dialog


The toolbar button's captions show as pop-up hints on mouseover. But this dialog's toolbuttons are only used for assigning a
static image to the Picture component. This is not needed in our
case, so we can simply close this window. This places a new
empty Picture1 object of TfrxPictureView type on
the band. Move it to the top left corner of the band's working
area. Stretch it horizontally almost to the border of the Text
object, and vertically to the bottom of the Text object. The
Designer's report page should now look like Figure 8.

Figure 8: The Designer's Report page


Properties of the TfrxPictureView object
First, I will discuss the properties of TfrxPictureView
which define the image.
The main property is Picture - it is a TPicture object,
containing the object's image. When you open this property in
the Object Inspector you see the dialog shown in Figure 7
(above). If you work with images that will be loaded by the
object during the report preparation process, then this property
must be left empty (unassigned). The published FileLink
string property can contain the path and file name of the image
to be loaded when you run the report. In addition, this property
can contain a variable name in square brackets. The value of this
variable (the path and filename) can be assigned, for example, in
the OnGetValue event handler of the TfrxReport
component. If you want to load images from a database field,
you must use two string properties, DataSet and
DataField.

If the dataset is active and correctly configured the desired field


name can be selected in the Object Inspector from the
combobox list at design time. If the dataset is inactive, you can
enter the field name manually.
To generate reports with dynamic image loading, it's very
important to understand the properties of
TfrxPictureView which relate to resizing the images and
objects. These properties are all of type Boolean. If the
Stretched property is True, the image will be resized to the
object's size. The KeepAspectRatio property affects how
the original image's proportions are considered during this
resizing. If it is True, the image will occupy only as much of
the object as is necessary to keep the aspect ratio unchanged.
When you set the Center property to True, the image will be
relocated to the object's centre.
In addition, the object has an AutoSize property. When it's
set to True, the TfrxPictureView object will change its
width and height to the image sizes. Be careful when using this
property, because if the picture is too large, it will be painted
beyond the edges of the page.
Another Boolean property, called HightQuality (not my
spelling mistake - the property is written that way in FR4!) manages the
picture painting with improved quality. This can be useful for
example when printing a report on a high-quality printer.
Once again, let's go back to our example. Set the DataSet
property of Picture1 to "IBQuery1" by selecting that
value from the combobox, and the DataField property to
IMG (enter this text manually). Also set the Picture1 properties
Stretched, KeepAspectRatio, HightQuality and
Center to True, and the MasterData1 Stretched and
AllowSplit properties to True, to make the band divide
over two or more pages,
depending on the height of the
objects placed on it.
I think it's necessary to consider
one shortcoming of FastReport
associated with the algorithm
used to split objects across
pages. The Text object can be
divided across two pages, but
the Picture object cannot be
divided. When the band's
AllowSplit property is
True, if the image does not fit on the page it will be moved to
the next page. The Text object will be divided: some lines will
be placed on the first page, and the remaining lines will be
moved to the next page. This dividing algorithm fails in the case
of a large vertical image which gets placed on the next page
while its accompanying small text stays on the original page. I
hope that FastReport's developers will be able to find and
implement a better solution in a future release.
If the database connection is established, we can see how the
report will look by opening it in the preview window. To do this,
either use the corresponding icon on the Designer's top
horizontal toolbar, or press the key combination [Ctrl]-[P]. But
in our case, we must do it differently. Close the Designer, save
the application, run it, check the preview checkbox and click the
[Generate Report] button. The view of our report in the
preview window is shown in Figure 9.

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

55

Learning to use FastReport 4 for VCL Part 3 (continuation 3)

Figure 9: The report preview window in action


We can see that if the height of a block of text is less than the
image height, the images are placed right next to each other. To
avoid this, we can slightly modify the report. Open the Designer.
Increase the height of the MasterData1 band to about 1cm
and set both of its objects' Top properties to 38 pixels (or 1 cm).
As you probably know, the measurement units are configured
via the Designer's Options menu (ViewOptions). Then you
can again close the Designer, save project and run the program
to view the report in the preview window. Now our report looks
better.
To show the association of text with a particular image more
clearly, we could draw a line under our objects. Once again
increase the height of the MasterData1 to about 1 cm (or 38
pixels), without changing the height and position of objects.
Find in the Designer's left vertical toolbar the icon named Draw
and click on it. A menu will appear. Select the Line object item.
When you move the cursor to the page, the cursor changes its
form to a pencil with a plus sign, whose centre indicates where
the line will be drawn. Aim the crosshairs of this '+' at the left
edge of the page workspace below the Picture object. Click
the left mouse button and, while holding it down, draw a
horizontal line to the right page margin. Then release the
button. You will see a line object Line1 of type
TfrxLineView. If it's necessary, you can move this line up
or down, or change its length.
56

COMPONENTS
DEVELOPERS

Finally, you can run the program again and see how the altered
report looks in the preview window.
So, our first example is completed. Summing up, I want to
emphasize that FastReport is able to cope well with the
representation of static images, often used in reports (for
example, a company logo), as well as loadable images with text
in tabular form, provided there is no splitting of the data band.
FastReport Graphic Objects
This section about FastReport's graphic objects is theoretical, but
I suggest you open the FR Designer with a blank report and play
with dropping and modifying graphic objects directly as you read.
This will help you to under-stand what you are reading better.
One of the lower icons in the Designer's left vertical toolbar is
named Draw. When you click this icon, the menu shown in Figure
10 appears.

Figure 10

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Learning to use FastReport 4 for VCL Part 3 (continuation 4)


In the previous example, we drew a line on the master data
band. Here we will draw another line, this time directly on the
page. Select the Line Object menu item and draw a line
anywhere on the page, as you did in the previous example.
Now look in the Object Inspector. Let's consider our line's
properties. All lines available in the above menu are
TfrxLineView objects with different settings. All these
settings can be changed in the Object Inspector. The Left and
Top coordinate properties, and Width and Height
dimensions at this time require the single comment: this object's
line always unfortunately has a fixed thickness. The boolean
Diagonal property determines whether the line is diagonal.
If it is set to False, the line will be strictly vertical or strictly
horizontal, regardless of the Width and Height values.
Otherwise, these properties define the line shift from the point
[Left, Top] to the second point of the line. The point with
[Left, Top] coordinates is the starting (initial) point of the
line. Accordingly, the end point will be [Left+Width,
Top+Height].
So, let's continue the practical exercises. Set the Diagonal
property of the line to True, and then click on the toolbar's
top icon (Select Tool, it has an arrow image). After that, move your
mouse to any of the boundary points of the line.
The cursor changes to a + sign. Click the left mouse button and,
holding it, move the mouse cursor over the page's working area.
The line will rotate around one of its boundary points. If you
examine the changes in the Width and Height properties,
you will see that these properties can have negative values.
Now try to set either of these properties: ArrowStart or
ArrowEnd to True. You'll see the arrow on the
corresponding end of the line. You can configure the visual
properties of the line arrows, using the ArrowSolid,
ArrowWidth and ArrowLength properties.
The ArrowSolid property, when true, fills the arrow.
The ArrowWidth and ArrowLength properties determine
the width and length of the arrows in pixels. You can try to
manage these properties in the Object Inspector and note the
appearance of the line - perhaps, it will be useful to you later.
So, learning about the Line object is complete. You can remove
it from the page by selecting it and pressing the [Delete] key.
Now let's study the Shape objects. Similar to lines, all shape
objects are represented by the TfrxShapeView class with
different property values visible in the Object Inspector. Now
we will investigate these settings. Using the Draw toolbar button
and its menu, place a rectangle on the page and manually set its
size, say, to 7x5 cm. OK, it's time to consider this object's
properties. It might seem odd that this object has few properties.
Its rectangular shape is determined by the Shape property,
which is set to skRectangle. You can specify several other
forms by changing the value of this property in the drop-down
list in the Object Inspector. In addition to the rectangular shape,
available shapes are: skDiagonal1, skDiagonal2
(different direction diagonal lines), skDiamond ,
skEllipse , skRoundRectangle (rounded rectangle) and
skTriangle. The BrushStyle property of type
TBrushStyle determines how all of these figures are filled
(except skDiagonal1 and skDiagonal2). The Color
property determines the fill colour (don't confuse this with the
colour of the shape itself). If filling is not required, set the
BrushStyle to bsSolid, and the Color to clNone.
The Curve integer property determines the degree of
rounding of the rounded rectangle's corners (Shape =
skRoundRectangle).

The triangle shape (Shape = skTriangle) has some


peculiarities. This triangle is always isosceles, and its apex is
always directed to the Top coordinate. You can't manually
invert the triangle in the Designer. But this can be done in the
Object Inspector, by setting a negative value for the object's
Height property, and then moving the object to the desired
position. I want to say that I have not tested the behaviour of
this object on split bands, so I recommend you test out the
triangle's behaviour on a split band before using it in this
situation.
Summing up, I would say that FastReport allows you to include
only the simplest of shapes (with only basic characteristics) in a
report. If you need to place a more complex shape in a report,
you should design and save it as an image and place it in the
report using a Picture component.
The Chart
This section gives you an example of placing a chart in a report.
You will find the example project in the Example2 folder of the
downloadable archive (produced using Delphi 2010).
For this example we will use the dbdemos.mdb included with
Delphi, connecting to it using ADO technology. This database
has an EMPLOYEE table listing employees. Our aim is to print
a short report from this table together with a diagram,
constructed according to this report.
Create a new Delphi project. Place the frxReport1
component on the form and, as usual, open the Designer by
double clicking on it. Place the Report title band on the page,
and then put a text object with any text on this band. Place a
MasterData band on the page. The data source will be
defined later.
Open the Data page, as you did in the first example. Place
ADODatabase1 and ADOQuery1 components on this page.
Let's configure the DatabaseName property of
ADODatabase1. In the first box you must set the OLE DB
provider to Microsoft Jet 4.0 OLE DB Provider. Then click the
[Next] button. In the next box specify the path and file name of
the dbdemos.mdb database (in Delphi 2010 it's located by
default in the directory C:\Program Files\Common
Files\CodeGear Shared\Data), set the user name to Admin,
then check the 'Empty password' checkbox and test the database
connection by clicking the appropriate button. If successful,
press the [OK] button. Then set the
ADODatabase1.LoginPrompt property to False and
its Connected property to True. If no error messages
appear, you can continue.
Now let's configure the ADOQuery1 component. The
Database property must point to ADODatabase1, the
property CloseDataSource is set to True. Specify the
SQL property with this query text:
SELECT LastName + " " + FirstName, Salary
FROM employee
WHERE Salary >= 40000;
Go back to the Page1 tab. Double-click on the MasterData1
band and set its Dataset to ADOQuery1. In the data tree
expand the ADOQuery1 component by clicking on the [+] icon
near its name. You will see the dataset fields Expr1000 and
Salary. Drag them on the MasterData1 band, to create
two new text objects. Place them on the band as a table with two
columns and give them all-side frames. As a result, the report
should look like Figure 11.

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

57

Learning to use FastReport 4 for VCL Part 3 (continuation 5)

Figure 11: The Designer with Expr1000 and Salary fields


Since we established the connection to the database, you can look at the report data in the preview window already at this stage. If
all components have been set up correctly, the report will contain 9 records.
Now create the Footer band ReportSummary1 and set to it a height of about 10 cm. We will now construct a diagram on this
band. Find the Chart object button in Designer's left vertical toolbar and click on it. When you move the mouse cursor on the
page, you will see a square cursor. Choose its location on the band's workspace and click the left mouse button. The chart setup
window will appear (Figure 12).

Figure 12: The Chart Editor


Close this window. Manually stretch this object to be as wide as the borders of the page's workspace and about 8 cm. high. The top
of object must be located about 1 cm below the top of the footer band. Then re-open the chart setup window by double-clicking
this object. The first phase of the diagram's construction is adding the series. In our example, we need to add one series. First click
on the button with a yellow + sign at the top of the window. A window will appear (Figure 13)

Figure 13: The Chart Gallery (initial view)


58

COMPONENTS
DEVELOPERS

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Learning to use FastReport 4 for VCL Part 3 (continuation 6)


In the left panel select the Bar item. The window changes to
look like Figure 14.

Figure 14: Selecting Bar chart styles in the Gallery

Select the Normal type and press [OK]. Then


you can configure the data sources and fields
in the chart setup window, as shown in
Figure 15.
Figure 15: Setting up the chart properties
After you set the fields and click [OK], the process of creating the chart will be completed. You can see how it will look in the
preview window. Now we have to write the VCL application code. Here this code will be very easy. We are using the only command
to show the report in the main form's OnShow event handler.
procedure TForm1.FormShow (Sender: TObject);
begin
frxReport1.ShowReport (true);
end;

OK, we have considered an example of constructing a simple chart. In fact, FastReport in combination with the TChart
library allows you to add many different chart types to a report. It's almost impossible to describe all these types. You
can try them for yourself, either using Delphi's demos, or your own databases. This article has described working with
images, graphic objects and simple diagrams in FastReport. I hope that these examples will help you develop high
quality FastReport reports for your own applications.
If you write to me by e-mail at slyubez@gmail.com with your ideas or questions about Delphi I may be able to
discuss the matters you raise in a future article. See you again.
SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

59

SUMMER OFFER:

LAZARUS

the complete guide


Blaise Magazine is making a summer deal available
to all our subscribers. If you purchase the newly published book
(Lazarus the Complete Guide)
we will include with it the following bonus items at no extra
charge:
- a Windows installer CD for Lazarus version 0.9.30
- a preinstalled copy Lazarus on a free 4GB USB stick.
- 50 additional sample projects
-

Blaise Pascal Magazine Library

17 complete issues on a 4GB USB stick


850 pages of articles
very fast search facility
comprehensive index covering all issues
locate the article of interest with one click
separate code index
separate index by author
overall index for complex searches covering all articles
all code completely accessible, linked to the relevant article
- extra: additional preinstalled component suites

PAPER BACK 50,00 + postage


HARDCOVER 60,00 + postage

COMPONENTS
DEVELOPERS

Pag 87

For Android, iOS, Linux, Mac,


Win CE, Windows XP / 7

Become

BLAISE PASCAL MAGAZINE


subscriber ...

A special offer for all Delphi users:


Anyone who buys a new DELPHI product and takes out a new subscription for BLAISE
PASCAL MAGAZINE (whether for a year or more) will receive the Blaise Pascal Library on a 4GB USB
stick for free, including future free updates to include all issues to the end of 2011. The last
update will be available in January 2012 through our web service. The free USB stick has approximately 2.8 GB
unused capacity.
Existing magazine subscribers renewing their subscriptions have two options:
- You can buy the Blaise Pascal Library on a 4GB USB stick for the discounted price of 15
(the cost to non-subscribers is 50)
- You can download the Blaise Pascal Library for free as a 1.2GB ISO file for burning to a
DVD of your own. This also includes free access to future magazine issues updated
to the end of 2011. The last update will be available in January 2012 through our web service.
The Blaise Pascal Library is available to non-subscribers on a USB stick, priced at 50
The Blaise Pascal Library contains all issues both articles and code, including program
examples, totalling 17 English issues (Issue 1 up to Issue 17), with all the code ever published in
Blaise Pascal Magazine:
17 complete issues on a 4GB USB stick
850 pages of articles
very fast search facility
comprehensive index covering all issues
locate the article of interest with one click
separate code index
separate index by author
overall index for complex searches covering all articles
all code completely accessible, linked to the relevant
article
Here are the new company subscription prices:
Blaise Pasacal Magazine is now published 6 times a year (bimonthly).
The magazine has a minimum of 60 full colour pages.
Company subscription rates:
single person annual subscription by download 35
5-person annual subscription by download 150
10 -person annual subscription by download 300
50 -person annual subscription by download 1125
100 - person annual subscription by download 2250
single person annual subscription for printed copies 50 (excluding postage)
5-person annual subscription for printed copies 225 (excluding postage)
10-person annual subscription for printed copies 425 (excluding postage)
50-person annual subscription for printed copies 2250 (excluding postage)
100-person annual subscription for printed copies 4250 (excluding postage)

Creating 64-bit applications with Delphi XE2 By Jeremy North


starter

expert

DELPHI XE 2 ONLY

64-bit Windows-based computing has been around for


over a decade, starting with Itanium and Windows XP
64-bit back in 2001. It wasn't until the release of
Windows 7 in 2009 that installing the 64-bit version of
Windows was the default. Before then most large
computer retailers were still installing the 32-bit version
of the latest operating system. Today many of these
vendors no longer even offer installation of a 32-bit
operating system as an option.
With the release of Windows Server 2008 R2, Microsoft
released the first version of an operating system that only
supported 64-bit you cannot purchase a 32-bit version
of Windows 2008 R2.

Shell Extensions
If you write Windows shell extensions then you have no doubt
been waiting impatiently for a 64-bit Delphi compiler (or moved
long ago to another product that compiled for 64-bit).
Now you can use Delphi to create Windows shell extensions for
64-bit operating systems.
Working with 64-bit in the IDE
This article will just cover working with VCL 64-bit applications
using XE2. The new FireMonkey framework also supports 32bit and 64-bit compilation, as well as being able to target OS X
32-bit (Snow Leopard and above) and iOS (using Free Pascal and
XCode). Creating a new VCL application reveals the first hint of
change. If you look at the Project Manager, you will see that
there is a new node called Target Platforms under the Build
Configuration node.

64-bit Windows Time Line


2009 Windows Server 2008 R2 (first 64-bit only OS release)
2009 Windows Win7 64-bit and 32-bit
2006 Windows Vista 64-bit and 32-bit
2005 Windows XP 64-bit for em64T/amd64 chips

Figure 1

2001 Windows XP 64-bit for the Itanium chip

When creating a new VCL application the 32-bit platform is


automatically added as a platform target. To add another
platform, simply right click on the Target Platforms node and
Do I actually need 64-bit?
select the Add Platform command. This displays a dialog that
Obviously there is no single answer that applies to every
developer's applications. If you are manipulating large amounts allows you to select an additional target platform for your
of data (such as large media files) then offering a 64-bit application application. For VCL applications, 32-bit Windows and 64-bit
Windows are the only platforms available. For a FireMonkey
will most likely provide benefits over a 32-bit application.
The main reason for this is the ability to address more than 4GB application, OS X is also available.
of memory (since 32-bit Windows always sees less than that amount).
You might think it would be beneficial to throw 32GB of RAM
into your Windows 7 Home Premium system. You can certainly
do this, but you will only be able to access half of it!
This is because Windows 64-bit editions have upper limits on
their accessible memory. The different operating system
version's maximum memory limits are shown in the following
table.
Figure 2
Reference: http://en.wikipedia.org/wiki/64-bit

Edition Maximun Ram


Starter

GB

Home Basic

GB

Home Premium

16

GB

Professional

192 GB

Enterprise

192 GB

Ultimate

192 GB

It is not possible to add the same target platform more than


once to a project. Target platforms can be removed from a
project, so if your application just needs to target 64-bit systems,
right click on the 32-bit Windows entry under Target Platforms
and select the Remove Platform command.

Library Paths
The Library options page (Tools | Options | Delphi Options |
Library) has been updated to include a Selected Platform combo
box at the top of the page. This lists all valid Target Platforms
Access to gigantic memory spaces is not the only reason for
wanting your application to compile as 64-bit. Being recognised for the IDE (not framework specific). This means that each
as a first class citizen in the operating system has many benefits. platform's paths have to be maintained separately. When
32-bit applications are not able to store to and access some parts switching to the 64-bit Windows platform you will notice
of the system as 64-bit applications usually would.
instances of $(Platform) in the paths displayed. The IDE treats
This includes access to the registry and file system areas. 32-bit
this as a variable and whenever a path contains $(Platform) the
applications are also installed to their own program files folder,
appropriate platform abbreviation is substituted.
called "Program Files (x86)".
Shared resources are stored in a different folder from the
System32 folder called "SysWoW64". Yes, the System32 folder
contains proper 64-bit versions of the system files.
64

Platform Name

Abbreviation

32-bit Windows

Win32

64-bit Windows

Win64

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Creating 64-bit applications with Delphi XE2 (continuation 1)

Figure 3

Figure 4
For those with a keen eye, another minor change to the dialog is
the replacement of the "Namespace prefixes" field title with the
title "Unit scope names".
In XE2, all units have a prefix applied to them. These prefixes
are not platform specific so fall outside the scope of this article.
Project Options
As well as being able to set paths for all applications in the
Library section of the options dialog, you need to set your
project's options against a particular platform (and configuration).

As expected, the Project Options dialog has been enhanced to


display a drop down of available paths for the project's targeted
framework (VCL in this case). This means the drop down will
contain two entries right? Wrong! It will contain six (default
settings)!
This is because it contains an entry for each build configuration
and each platform as well as a default entry for each build
configuration. Confused? Well here is a picture...

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

65

Creating 64-bit applications with Delphi XE2 (continuation 2)

Figure 5
Registering Components for 32-bit and 64bit usage
To target the 64-bit platform, components must be made
available to the 64-bit platform. This can be done in two ways
and just to be sure, I've used both ways for my components.
1. The runtime package (that the design time package
requires) has the 64-bit Windows platform specified.
2. You can use the new Component Platforms Attribute.

This means that when you are modifying project-specific


options, make sure you are modifying the options for precisely
the platform and build configuration you desire (and not
inadvertently changing options for a configuration you are not
using).
Output Paths
When creating new projects with Delphi XE2 you will notice
that the Output Directory and Unit Output Directory include the
default output folder of .\$(Platform)\$(Config) ensuring that
each targeted platform's binary and .dcu files are written to a
unique location.

An example of using the second of these two options:

Working with the Form Designer in a 64-bit


application
For XE2 there is no such thing as 64-bit design time packages.
The IDE is still a 32-bit application. This means that no 64-bit
packages are loaded in the IDE. There are a couple of
scenarios where the effect of this may cause some frustration.
1.
2.

Works fine at design time


While debugging your 64-bit targeted application you are
seeing errors that don't occur at design time.
No 32-bit component available

type
[ComponentPlatformsAttribute(pidWin32 or pidWin64]
TMyComponent = class(TComponent)
end;

For those wondering about FireMonkey and OS X,


another possible value is pidOSX32
Debugging
Unless you are the world's greatest developer, you will need a
debugger and thankfully XE2 ships with a 64-bit debugger.
Remember how I mentioned the IDE is still a 32-bit application
and it can't load 64-bit packages. Well it can't host a 64-bit
debugger either!

If you want your component to be installable, you must have a


32-bit version to install. This means that it is impossible to have
a 64-bit only component to use at design time. This doesn't mean
you can't "fake" a 32-bit implementation, having basically a 32bit skeleton to represent the 64-bit component at design time.
As long as the published properties of the 32-bit skeleton are
consistent with its 64-bit counterpart, using a design-time-only
32-bit version of the component is plausible.

How do I debug my 64-bit application?


Use a remote debugger of course. Since the 64-bit debugger is a
remote debugger, some project linking options need to be set.
To verify these settings are correct for your project you should
check the Project Options dialog and select the Linking node.
Make sure the correct Target at the top of the dialog is selected
and make sure the following options are enabled:

Components
All VCL components shipped with the IDE are available in 32bit and 64-bit versions.

1. Debug Information - checked


2. Include remote debug symbols - checked

Figure 6
66

COMPONENTS
DEVELOPERS

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Creating 64-bit applications with Delphi XE2 (continuation 3)


For new projects created in XE2 these options should
be set correctly. If you're using a project from a
previous Delphi version, you will have to ensure that
the above settings are correct.
Can I debug my application if I am
using a 32-bit operating sytem?
Sure you can. What? Well you do need to be able to
access a 64-bit machine for actually running the
Platform Assistant server and application on. Platform
Assistant is the name of the remote debugger.
Adding a Remote Profile to a target
platform
To be able to debug remotely a remote profile must be
assigned to the target platform. Within a project a
remote profile can be assigned to any target platform
listed under the Target Platforms node in the project
manager.
To create a new Remote Profile for the 64-bit
Figure 7
Windows platform, right click on the 64-bit Windows This will display a dialog of previously entered remote profiles. Initially
entry in the project manager and select the Assign
this list will be empty so select the Add button to create a new remote
Remote Profile command.
profile.

Figure 8
The first page of the wizard requires you to
name the remote profile. The platform
should already be selected. Once the name
has been entered, select Next.

Figure 9
The second page requires the IP Address of the remote machine as well as a port number and password for connection. The port
number specified (64211) is the default port number used by the Platform Assistant server. If you change this value in the profile,
make sure the PAServer.config file has been updated on the remote machine. When PAServer is launched on the remote machine,
it requests a password - this is the password you should enter in the password field of the profile.
You can test the connection now to verify it all works, provided you have already installed Platform Assistant on the remote
machine (we'll step through that process shortly).

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

67

Creating 64-bit applications with Delphi XE2 (continuation 4)


The final wizard page is for C++
applications, so just leave it as-is and
select the Finish button. Once you have
added the profile, it will appear in the list
of available remote profiles. Select the
newly added profile and click OK.
This will then update the project
manager to display the remote profile
name next to the target platform it is
relevant to.

Figure 10
Figure 11
Modifying Remote Profiles
It is possible to modify remote profiles in the Tools | Options dialog. Select
the Remote Profiles node.

Figure 12
68

COMPONENTS
DEVELOPERS

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Creating 64-bit applications with Delphi XE2 (continuation 5)


Platform Assistant
The Platform Assistant has a separate installer available in the
<InstallFolder>\PAServer folder. Copy the setup_paserver.exe
file (~42MB) to your Windows 64-bit system and install it.
NOTE: The setup_paserver.zip file is the OS X version of
Platform Assistant - it won't work on Windows!
It is a simple installation to complete, just remember where you
installed it because you will need to start it!
Running Platform Assistant
To start Platform Assistant, locate the PAServer.exe file in the
install folder and run it. The Platform Assistant is a command
line application and will prompt for a password. This should be
the same password that you entered when setting up your
remote profile.
The port used should also be changed if you are using a
different port from the one originally selected. Change the
default port by modifying the PAServer.config file.
Unable to connect to Remote Host?
The first time you try to debug remotely on a 64-bit machine
you may receive a connection error, even if a test connection
succeeds. This is probably due to your firewall preventing the
debugger from starting. If so, you should allow access for the
dbkw64_16_0.exe application in your firewall software.
Code changes
In many circumstances you will have to make modifications to
your existing code in order for your applications to run
successfully as 64-bit. Some of these changes will be discovered
by the compiler when you add the new 64-bit platform target to
your application and compile it. Others will be discovered only
while running the application, so it is important to have a good
test plan that covers all areas of your application. Adding a new
platform target should mean your application gets a thorough
test under the new application.
New compiler defines
Two new compiler defines have been introduced to help with
64-bit development.
CPUX64 - Use this to differentiate 32-bit and 64-bit assembler.
WIN64 - Use this to differentiate between specific 32-bit and
64-bit API versions.
Type changes (and non-changes)
Integer - remains 32-bit (4 bytes).
NativeInt - use this for shared code when a 32-bit integer is
required on 32-bit platforms and a 64-bit integer is required on
64-bit platforms.
Extended has lost precision in 64-bit. It is now the same
size as the Double type. If you currently use Extended you
should thoroughly inspect areas of usage. Retrieving
Extended values saved in a file will also need careful
consideration. Extended has changed precision because the
64-bit compiler generates SSE and not FPU instructions.
Extended80 is a new 10-byte type to cover the fact that
Extended in 64-bit has been reduced in precision.
The Extended80 type should be used when a 10-byte type is
required under 64-bit. This new type will not be as fast as the
Extended type available in 32-bit Windows.
Pointers in 64-bit Windows are 8 bytes. In 32-bit Windows they
are 4 bytes. This is why it is important to use NativeInt
when typecasting pointers in shared platform code.

Typecasting Pointers
Where in the past you got away with typecasting pointers as
Integer or LongInt, now you should be using
NativeInt, IntPtr or LParam. They all mean the same
thing.
Loop Variables
If you need your loop to handle 64-bit values, use NativeInt
for your loop counter.
Handle References
In the past it was common to use Longint or Cardinal for
references to handles. This is something that needs to be
changed for 64-bit. So you now should use either the THandle
or HWND type.
Windows Messages
When working with messages, typecast using WParam,
LParam and LResult.
Before:
After:

Msg.LParam := LongInt(Self);
Msg.LParam := LPARAM(Self);

Storing object references in the Tag


property
Typecast the address of the object with IntPtr or
NativeInt (they are the same).
LControl.Tag := IntPtr(@LOriginal);

Replace Integer typecasts with IntPtr (or NativeInt).


Before:
LMyInteger = Integer(ListOfIntegers[0]);
After:
LMyInteger = IntPtr(ListOfIntegers[0]);

VCL changes
A large number of records and methods in the VCL have been
updated now to use the correct type. Some methods have even
been deprecated due to these changes, so make sure you inspect
your application's Hints and Warnings. There could be useful
information in there.
Conclusion
If you want your code to run on both 32-bit and 64-bit
versions of Windows, identifying the above code changes will
help to make the transition to 64-bit 'first class citizen' status
as smooth as possible. Hopefully this article gives you a good
guide to beginning your successful journey into 64-bit
development with Delphi XE2.

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

69

Now available for Lazarus

COMPONENTS
DEVELOPERS
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.

also to be found as part of kbmMW CodeGear


Edition in the form of TkbmMWDOMXML and the
high performance TkbmMemTable.

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
Delphi Starter Edition also lacks proper XML kbmMemTable.
support and the local TClientDataset, which is
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

kbmSQL Structured Query Language for your memory table


starter

expert

DELPHI 7 and above (Win32)


LAZARUS

The following samples are based on kbmSQL v. 1.01.00

You may already have used kbmMemTable in your


applications. If not, then its certainly about time to try it
out. You can download kbmMemTable CodeGear Edition
for free from www.myc4d.com after registering, but please
notice that only latest version of Delphi is supported with
kbmMemTable CodeGear Edition.
If you would like to use kbmMemTable for other versions
of Delphi, C++Builder or FPC, you need to get
kbmMemTable Standard Edition from
www.components4developers.com. Its only US $30 and
includes source and kbmSQL, which this article is about.
kbmMemTable is known for being a very feature rich and fast memory
table.
Whats a memory table?
It's a memory storage of row/field oriented data.
kbmMemTable contains lots of features for searching, filtering,
sorting and grouping the data contained in it, just like a database
like Oracle, MSSQL etc. However apart from the fact that a
kbmMemTable holds one single table, a big difference between
Oracle/MSSQL etc and kbmMemTable is that Oracle/MSSQL
supports (actually primarily uses) the SQL language for searching
and manipulating data in the tables. For years, kbmMemTable
was not able to understand proper SQL syntax, but we
introduced a free, bundled add-on for kbmMemTable Standard
Edition and kbmMemTable Professional Edition that we named
kbmSQL. kbmSQL enables support for a large subset of the
SQL language for any number of kbmMemTables you have.
Over time kbmSQL is expected to evolve to support an even
larger subset of the current SQL standard.
So how to use it then?
After having downloaded and installed kbmMemTable and
kbmSQL, you will find several components in the
kbmMemTable section.

Figure 1.
As usual you will find TkbmMemTable,
TkbmBinaryStreamFormat,
TkbmCSVStreamFormatter and
TkbmThreadDataset (which is available for backwards
compatibility and rarely used) components. And then as something
new - TkbmMemSQL. TkbmMemSQL is in it self a memory
table, that is based on TkbmMemTable, and will be the
container for any results coming from a SQL statement.
Those results can thus be further manipulated or presented in
the same way as a regular TkbmMemTable instance by using it
as a source of data for some other procedure or connect it to
data aware controls. To make TkbmMemSQL tick, we need to
give it two things to work with data and a SQL statement. The
raw data is provided for it via other datasets. Currently
kbmMemTable is supported as a data provider, but kbmSQL
has been designed with extesibility in mind, since other data
providers may be added later on.
So let's start with a sample application.
72

COMPONENTS
DEVELOPERS

Figure 2.
I have added a kbmMemTable (for the raw data), a kbmMemSQL
component for doing our SQL, and a couple of grids to show
the raw and result data, and finally a button that we will click to
execute the SQL.
Ill make it simple and just put the code we need into the
Execute SQL button.
uses
// Make sure to include this unit to have support for kbmMemTable registered in kbmSQL.
kbmSQLMemTableAPI;
procedure TForm1.Button1Click(Sender: TObject);
begin // Prepare some raw data to work on.
kbmMemTable1.Reset;
kbmMemTable1.FieldDefs.Add
('StringField1',ftString,50);
kbmMemTable1.FieldDefs.Add
('StringField2',ftString,50);
kbmMemTable1.FieldDefs.Add
('NumberField1',ftFloat);
kbmMemTable1.FieldDefs.Add
('NumberField2',ftFloat);
kbmMemTable1.CreateTable;
kbmMemTable1.Open;
kbmMemTable1.AppendRecord
(['String 1','AnotherString 1',120,10.10]);
kbmMemTable1.AppendRecord
(['String 2','AnotherString 1',15,20.10]);
kbmMemTable1.AppendRecord
(['String 3','AnotherString 1',10,10.10]);
kbmMemTable1.AppendRecord
(['String 4','AnotherString 2',33,10.10]);
kbmMemTable1.AppendRecord
(['String 5','AnotherString 2',12,10.10]);
kbmMemTable1.AppendRecord
(['String 6','AnotherString 2',99,20.10]);
kbmMemTable1.AppendRecord
(['String 7','AnotherString 3',19,10.10]);
kbmMemTable1.AppendRecord
(['String 8','AnotherString 3',143,20.10]);
kbmMemTable1.AppendRecord
(['String 9','AnotherString 3',199,10.10]);
// Register kbmMemTable1 as source for kbmMemSQL1.
// Notice that we have given kbmMemTable1 a name that identifies it in SQL (table1).
kbmMemSQL1.Tables.Clear;
kbmMemSQL1.Tables.Add('table1',kbmMemTable1);
// Prepare some simple SQL.
kbmMemSQL1.ExecSQL('select * from table1');
end;

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Structured Query Language for your memory table (Continuation 1)


By running this simple application and clicking the button, we
quickly have our first kbmSQL result returned namely the
same records that were contained in the kbmMemTable data
source. Result:

Calculations and string concatenation:


select
StringField2+ Some text as MyField,
NumberField2 / 2 as Numbers from table1

Result:

Figure 3.
Notice that kbmSQL operates with case insensitive field/table
names, and thus the field names are uppercase by default.
Lets play a little with the different syntaxes kbmSQL supports.
Change the SQL code in the ExecSQL call to try them out, one
by one. Remember to escape (duplicate) single quotes (') when
used in strings in Delphi. You can also use double quotes ()
which do not need to be escaped (duplicated) in Delphi strings.

Figure 6.
Sorting ascending:
select * from table1 order by NumberField1

Result:

I've already shown the SQL syntax for how to get data from all
fields and all records. See above.
Specific fields:
select StringField1,NumberField2
from table1
Result:

Figure 7.
Sorting descending:
select *
from table1 order by NumberField1 desc

Result:

Figure 4.
Field alias with descriptive text:
select
Figure 8.
StringField2 as MyField (My string field),
NumberField2 as Numbers from table1 Filtering:
select *
Result:

from table1 where NumberField1<30


and NumberField2>15

Result:

Figure 5.
Figure 9.
SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

73

Structured Query Language for your memory table (Continuation 2)


Statistical functions:
select count(*), min(NumberField1),
max(NumberField1),avg(NumberField1),
sum(NumberField1) from table1
Result:
Figure 14.
In addition kbmSQL supports filtering using between
and in keywords. Eg.

Figure 10.
Grouping:

select NumberField2
from table1
where NumberField1 between 10 and 20

select NumberField2,
count(NumberField2),
sum(NumberField1)
from table1 group by NumberField2

Result:

Result:

Figure 15.
Figure 11
Inserting new data using SQL:

And

insert into table1


(StringField1,StringField2,
NumberField1,NumberField2)
values (New String1,New string2,88.2,383)

select StringField1 from table1


where NumberField1 in (15,99,33)

Result:

Result (in raw data source):

Figure 16.
And the SQL LIKE operator is also supported. LIKE
matches string masks using * and ? as wildcards. * matches
any number of arbitrary characters and ? matches exactly
one arbitrary character. Eg.
select * from table1
where StringField1 like *6*

Result:

Figure 12.
Changing data using SQL:
update table1 set NumberField1=
NumberField1 + 100 where NumberField2<20

Result (in raw data source):

Figure 17.
also contains a large predefined set of functions
that can be used in expressions. To use them, you need to
add kbmSQLStdFunc to the uses clause of the application.
The functions include:
kbmSQL

COS(n), TAN(n), LOG(n), LOG2(n), TRUNC(n),


MOD(n1,n2), DIV(n1,n2), SQR(n), UPPER(s),
LOWER(s), TRIM(s), NOW, DATE(n), TIME(n),
YEAR(n), MONTH(n), DAY(n), HOURS(n),
MINUTES(n) and SECONDS(n).

Figure 13.
Deleting data using SQL:
delete from table1 where NumberField2<20

Result (in raw data source):


74

COMPONENTS
DEVELOPERS

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Structured Query Language for your memory table (Continuation 3)


Eg.
select COS(NumberField1) from table1

Result:

Figure 18.

Basically what we do in the code, is to define our function,


check that the correct arguments are available, produce the
results that are to be used as arguments (d1 and d2) to our
calculation and calculate a result.
In addition the function contains functionality to tell
kbmSQL about the resulting field width size, when kbmSQL
needs to get that information.
In the initialization section, we register the new function,
and all that's needed for that function to be available for
your SQL, is to include the unit in your main applications
uses clause.
Eg.
select COS(NumberField1), Pythagoras(10,20)

Notice that the default field name is now generated as F1. You
from table1
can of course redefine the name using the AS syntax as shown
Result:
earlier on. If you would like to use an expression function that is
currently not available in kbmSQL its very easy to extend
kbmSQL with new functionality.
Lets say we want to define a function that calculates Pythagoras'
hypotenuse value:
PYTHAGORAS(x,y) = sqr(x*x+y*y)

Then we create a new unit and add it to the project. Lets call it
myfunctions.pas.
Put the following into it:
interface
uses
DB, kbmSQLElements, kbmSQLFuncAPI,
Math, Classes, SysUtils;

Figure 19.
Obviously we could have used other field values or
expressions instead of the constant values 10 and 20,
used as arguments for Pythagoras.

implementation
procedure CheckArgs(const AArgs:TkbmSQLNodes;
const ACnt:integer);
begin
if AArgs.Count<>ACnt then
raise
Exception.Create('Invalid number of
arguments. Expected '+inttostr(ACnt));
end;

All the above examples can be mixed and matched as


required to perform complex searches and calculations on
a single table.
Currently kbmSQL does not support joins or HAVING
syntax, but that's expected to be provided later on.

function SQLPythagoras(ASender:TObject;
AOperation:TkbmSQLFunctionOperation;
AArgs:TkbmSQLNodes; var AResult:variant):boolean;
var
d1,d2:double;
begin
CheckArgs(AArgs,2);
if AOperation=foExecute
then
begin
d1:=AArgs.Node[0].Execute;
d2:=AArgs.Node[1].Execute;
AResult:=sqrt(d1*d1+d2*d2);
end
else
AResult:=AArgs.Node[0].Width;
// Let it take width after first argument for function.
Result:=true;
end;
initialization
kbmSQLFunctionRegistrations.RegisterFunction
('PYTHAGORAS',@SQLPythagoras,ftFloat);
end.

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

To receive a copy of
the printed magazine
you need to
order a subscription
from our website:
www.blaisepascal.eu
COMPONENTS
DEVELOPERS

75

High-Level Multithreading
starter

expert

By Primoz Gabrijelcic

DELPHI 7 and above (Win32)


LAZARUS

Working with threads at a low level is fine for those who


enjoy it, but most programmers don't want to do that
just as they don't want to code in assembler. They
prefer high-level languages, the VCL, and packaged
solutions.
For multithreading, the OmniThreadLibrary is such a
high-level tool. Although the initial motivation for its
design was to create an easy-to-use TThread wrapper,
this low-level TThread replacement has turned out to
be an excellent tool for writing high-level constructs
that allow any user to use parallel processing to
advantage, without having to put too much thought
into the low-level plumbing details.
This article focuses on writing multithreaded code
using the OmniThreadLibrary. As this library is
constantly being developed, the article focuses on the
last stable release, version 2.1.
In its 2.1 release the OmniThreadLibrary supports six high-level
parallelization constructs:
Async (simple background execution)
Join (execution of multiple background tasks)
Future (execution of background tasks that return results)
ForEach (a parallelized for statement)
Pipeline (a parallelized staged pipeline)
ForkJoin (implementing a parallel divide and conquer
strategy).
The implementation of these software tools uses anonymous
methods extensively, which is why they are supported only in
Delphi 2009 and subsequent Delphi versions.
To start using the OmniThreadLibrary (called OTL from here
on), download it from
http://code.google.com/p/omnithreadlibrary/
downloads/list
Unpack it to a new folder (such as c:\omnithreadlibrary).
Add this folder and its \src subfolder
(c:\omnithreadlibrary\src in this example) to Delphi's

library path or to your project's search path. Add OtlParallel


to your program's uses lists. And that's all folks! If you have any
questions after reading this article, visit
http://otl.17slon.com/

where you'll find pointers to articles about the OTL and a web
forum where you can raise your questions.
Async

The Async method allows you to create simple, one-shot


background tasks that don't require interaction with the
main thread. To create a background task (that is, a piece of
code that will execute in a background thread), call
Parallel.Aync and pass it a block of code. This can be
a parameterless method, procedure or anonymous
method. For short examples, such as those in this article,
I like to stick with anonymous methods as they need less
typing. Let's write a simple background task that just beeps
and nothing more.
Parallel.Async(
procedure
begin
MessageBeep($FFFFFFF);
end
);

76

COMPONENTS
DEVELOPERS

Yes, that's it. Parallel.Async will create a background


thread (or reuse a previously created thread that is now waiting
idly for some work) and run your code in it. If you don't
believe me, put a breakpoint on the MessageBeep call, run the
program and check the Threads window (View, Debug
windows, Threads). Running unattended background tasks
is fine, but sometimes you need additional information. For
example you may want to be informed when the task has been
completed. For such situations, Async accepts a second
parameter, Parallel.TaskConfig, which you can configure
in various ways. If you just want to know when the task has
completed, you have to write an OnTerminated handler as in
the example below:
procedure TfrmAsync.btnOnTerminatedClick
(Sender: TObject);
begin
btnOnTerminated.Enabled := false;
Parallel.Async(
procedure
begin
// executed in background thread
Sleep(500);
MessageBeep($FFFFFFFF);
end,
Parallel.TaskConfig.OnTerminated(
procedure (const task: IOmniTaskControl)
begin
// executed in main thread
btnOnTerminated.Enabled := true;
end
)
);
end;

Clicking the btnOnTerminated button disables it and executes


the background task. Method btnOnTerminatedClick then
exits immediately and your app can proceed, executing other
code. After half a second sleep, MessageBeep is executed in a
background thread. The background task then terminates.
As it finishes its termination code re-enables the previously
clicked button in the main thread. (Again, you can put a breakpoint
on the btnOnTerminated.Enabled := true to verify my
claims). If you're wondering what the const task parameter is
doing there it represents the underlying task control
interface, something that you would use when working with the
OmniThreadLibrary at a low level, and you can safely ignore
it here. You just have to remember to add this parameter to the
OnTerminated handler and to add the OtlTaskControl
unit to the uses statement.
A second example shows how you can get a section of code to
execute in the context of the main thread. In other words, once
you are running the background task, you can schedule some
code to run back in the main thread. This is very important if
you want to interact with the VCL because you must never try
to do that from a background task! The VCL is not thread-safe.
It is written on the assumption that it will always run from the
main thread, and very bad things can happen if you try to
update a form or execute other VCL tasks from a background
thread!
The code below uses task.Invoke to execute a code fragment in
the main thread (again, this fragment could be an anonymous
method, a normal method, or a classless procedure).
It is similar in its operation to the Queue method (not to
Synchronize) since it doesn't force the code to complete.

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

High-Level Multithreading (continuation 1)


Code is scheduled to be executed in the main thread at the first
opportunity. Meanwhile, the background task continues with its
own agenda.
To use Invoke, you have to pass the IOmniTask parameter to
the Async task and add the OtlTask unit to your uses list.

procedure TfrmJoin.btnAnonymousClick(Sender:
TObject);
begin
btnJoinAnon.Enabled := false; Update;
Parallel.Join(
procedure
begin
Sleep(2000);
end,
procedure begin
Sleep(3000);
end
);

procedure TfrmAsync.btnInvokeClick(Sender: TObject);


formThreadID: DWORD;
var
begin
formThreadID := GetCurrentThreadID;
Parallel.Async(
procedure (const task: IOmniTask)
var taskThreadID: DWORD;
begin

// this will execute in the context of the worker thread


taskThreadID := GetCurrentThreadID;
task.Invoke(
procedure
begin

// this will execute in the context of the main thread


frmAsync.lbLog.Items.Add(Format
('Current thread ID: %d,
task thread ID: %d, ' +
' form thread ID: %d',
[GetCurrentThreadID, taskThreadID,
formThreadID]));
end

btnJoinAnon.Enabled := true;
end;

Similarly to Async, Join accepts Parallel.TaskConfig as a second


parameter. It also supports the IOmniTask parameter which you
can use to communicate with the main thread.
Although the Join in release 2.1 is very simple, it was greatly
improved after that release. New features are described in an
article at
http://www.thedelphigeek.com/2011/07/lifeafter-21-paralleljoins-new-clothes.html.

Future
Future is a tool that enables you to start a background
);
calculation and then forget about it until you need the result of
end;
the calculation. To start a background calculation, you simply
Further information about TaskConfig can be found on my
create an IOmniFuture instance of a specific type (indicating the
type returned from the calculation).
blog: http://www.thedelphigeek.com/2011/04/
Future := Parallel.Future<type>(calculation);
configuring-background-otlparallel.html
Calculation will start in the background, and the main thread
Join
will continue with its work. When the calculation result is
Join is another very simple tool which allows you to start
needed, you simply query Future.Value. If the calculation has
multiple background tasks and wait until they have all
already completed its work, Value will be returned immediately.
completed. No result is returned at least directly, since you can If not, the main thread will block until the background
always store the result in a shared variable. If your code returns calculation is done.
a result you are better off using Future or ForkJoin.
The example below starts a background calculation that returns
The simple demonstration of Join (shown below) starts two tasks the number of prime numbers in the range 1..1,000,000. While
one sleeps for two and the other sleeps for three seconds. When the calculation is running, it uses the main thread for creative
you run this code, Parallel.Join will create two background
work outputting numbers into a listbox and sleeping. At the
threads and run RunTask1 in the first thread and run RunTask2
end, the calculation result is returned by querying future.Value.
in the second thread. It then waits for both threads to finish, and
only then will the main thread execution continue.
procedure
);
end

TfrmOTLDemoFuture.btnCalcFutureClick(Sender:
procedure TfrmJoin.btnParallelClick(Sender: TObject); TObject);
const
begin
CMaxPrimeBound = 1000000;
btnJoinMethods.Enabled := false; Update;
var
Parallel.Join([RunTask1, RunTask2]);
future
: IOmniFuture<integer>;
btnJoinMethods.Enabled := true;
i
integer;
:
end;
numPrimes: integer;
begin
procedure TfrmJoin.RunTask1;
// create the background calculation
begin
future := Parallel.Future<integer>(
Sleep(2000);
function: integer
end;
begin
Result := CountPrimesTo(CMaxPrimeBound);
procedure TfrmJoin.RunTask2;
end
begin
);
Sleep(3000);
// simulate another task
end;
for i := 1 to 10 do begin
lbLog.Items.Add(IntToStr(i));
Join ensures compatibility with single-core computers. If you
Sleep(20);
run the above code on a single-core machine (or if you limit the
lbLog.Update;
process to one core of a multicore machine) it will simply execute the
end;
tasks sequentially, without creating a thread.
// get the result
Log(Format('Num primes up to %d: %d',
Join accepts anonymous methods. The above demo could also
CMaxPrimeBound, future.Value]));
be coded as a single method executing two anonymous methods. [end
;

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

77

High-Level Multithreading (continuation 2)


If you have data in a container that supports enumeration (with
As with Join, there are two Future<T> overloads, one
one limitation the enumerator must be implemented as a class, not as
exposing the internal task parameter and another not.
TaskConfig can be provided as an optional second parameter. an interface or a record) then you can enumerate over it in parallel.
class function Future<T>(action:
TOmniFutureDelegate<T>;
taskConfig: IOmniTaskConfig = nil):
IOmniFuture<T>; overload;
class function Future<T>(action:
TOmniFutureDelegateEx<T>;
taskConfig: IOmniTaskConfig = nil):
IOmniFuture<T>; overload;

nodeList := TList.Create;
//
Parallel.ForEach<integer>(nodeList).Execute(
procedure (const elem: integer)
begin
if IsPrime(elem) then
outQueue.Add(elem);
end);

ForEach is extremely powerful and allows you to iterate over


various containers, to aggregate results, to run without blocking
the main thread and more. For a longer introduction, see my
blog post :

IOmniFuture<T> has some other useful features. You can


cancel the calculation (Cancel) and check if calculation has
been cancelled (IsCancelled). You can also check if the
calculation has already completed (IsDone and TryValue).

www.thedelphigeek.com/2010/06/omnithreadlibrar
y-20-sneak-preview-1.html

IOmniFuture<T> = interface
procedure Cancel;
function IsCancelled: boolean;
function IsDone: boolean;
function TryValue(timeout_ms: cardinal;
var value: T): boolean;
function Value: T;
end;

and the implementation trilogy articles:


www.thedelphigeek.com/2011/01/
parallel-for-implementation-1-overview.html
www.thedelphigeek.com/2011/01/parallel-forimplementation-2-input.html

Further information about Future can be found at:


www.thedelphigeek.com/2010/06/omnithreadlibrar
y-20-sneak-preview-2.html

Also, there were some changes after the 2.1 release, mostly
relating to exception handling:
http://www.thedelphigeek.com/2011/07/
life-after-21-exceptions-in.html.

ForEach
Parallel For (actually called ForEach because For would clash with the
reserved keyword for) is a construct that enumerates in a parallel
fashion over different containers. The most typical usage is
enumerating over a range of integers (just as with the classical for),
but it can also be used similarly to the for..in construct for
enumerating over Delphi (or Windows) enumerators.
The following very simple example loops over an integer range
and increments a global counter for each number that is prime.
This is another way to count the number of primes in the range
1..CHighPrimeBound.

As the code accesses a shared variable from multiple threads, it


must ensure that the threads don't mutually interfere. That's why
the shared variable (numPrimes) is not a simple integer, but a
special thread-safe object, provided by the GpStuff unit,
which is included in the standard OmniThreadLibrary
distribution.
COMPONENTS
DEVELOPERS

Pipeline
The Pipeline construct implements high-level support for
multistage processes. The assumption is that the process can be
split into stages (or sub-processes) connected by data queues.
Data flows from the (optional) input queue into the first stage,
where it is partially processed and then emitted into an
intermediate queue. The first stage then continues execution,
processing more input data and outputting more output data.
This continues until the entire input has been processed.
The intermediate queue leads into the next stage which does the
processing in a similar manner and so on and so on.
At the end, the data is output into a queue which can then be
read and processed by the program that created this multistage
process. Overall a multistage process functions as a pipeline
data goes in, and eventually data comes out.

What is important here is that no stage shares its state with


any other stage. The only interaction between stages is via
the data passed through the intermediate queues.
The quantity of data, however, doesn't have to be
constant. It is entirely possible for a stage to generate
more data (or less data) than it received at its input.
In a classical single-threaded program the execution plan
for a multistage process is very simple.

procedure TfrmForEach.btnForEachIntClick (Sender:


TObject);
var
numPrimes: TGp4AlignedInt;
begin
numPrimes.Value := 0;
Parallel
.ForEach(2, CHighPrimeBound)
.Execute(
procedure (const value: integer)
begin
if IsPrime(value) then
numPrimes.Increment;
end
);
lbLog.ItemIndex := lbLog.Items.Add(Format('%d
primes', [numPrimes.Value]));
end;

78

www.thedelphigeek.com/2011/02/parallel-forimplementation-3-output.html

In a multithreaded environment, however, we can do better than


that. Because the stages are largely independent, they can be
executed in parallel.

To receive a printed copy of the magazine


you need to go to our website to place your order

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

High-Level Multithreading (continuation 3)

Stages are implemented as anonymous procedures, procedures


or methods taking two queue parameters one for input and
one for output. Except in the first stage where the input queue
may not be defined, both queues are automatically created by
the Pipeline implementation and passed to the stage delegate.
To use the pipeline, you will have to add OtlCollections to
your uses list as it is the home of the
IOmniBlockingCollection.

TIME

STAGE 1
STAGE 2
STAGE 3

TPipelineStageDelegate = reference to procedure


(const input, output: IOmniBlockingCollection);

--STAGE N

A pipeline is created by calling the Parallel.Pipeline


function which returns an IOmniPipeline interface.
There are two overloaded versions one for general
pipeline building and another for simple pipelines that
don't require any special configuration.

The various pipeline stages are shown below. The first stage
ignores the input (which is not provided) and generates
elements internally. Each element is written to the output queue.
procedure StageGenerate(const input, output:
IOmniBlockingCollection);
var
i: integer;
begin
for i := 1 to CNumTestElements do
if not output.TryAdd(i) then Exit;
end;

class function Pipeline: IOmniPipeline; overload;


class function Pipeline(
const stages: array of TPipelineStageDelegate;
const input: IOmniBlockingCollection = nil):
IOmniPipeline; overload;

The following three stages read data from their input (by using a
for..in loop), and output modified data into their output queue.
For..in will automatically terminate when a previous stage
terminates and its input queue runs out of data (that's a feature of
the IOmniBlockingCollection enumerator).
The latter version takes two parameters an array of processing
As you can see from the code, values in input/output
stages and an optional input queue. An input queue can be
queues are not integers, but TOmniValue s (declared in the
used to provide initial data to the first stage. It is also completely
OtlCommon unit), which is an OTL version of Delphi's
valid to pass 'nil' as the input queue parameter and to run the
Variant.
first stage without any input.

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

79

High-Level Multithreading (continuation 4)


procedure StageMult2(const input, output:
IOmniBlockingCollection);
var
value: TOmniValue;
begin
for value in input do
if not output.TryAdd(2 * value.AsInteger) then
Exit;
end;

forkJoin := Parallel.ForkJoin<integer>;

and then create computations owned by this instance


max1 := forkJoin.Compute(
function: integer begin
Result :=
end);
max2 := forkJoin.Compute(
function: integer begin
Result :=
end);

procedure StageMinus3(const input, output:


IOmniBlockingCollection);
var
value: TOmniValue;
begin
for value in input do
if not output.TryAdd(value.AsInteger - 3) then
Exit;
end;

To access the computation result, simply call the computation


object's Value function.
Result := Max(max1.Value, max2.Value);

The code below shows how Fork/Join can be used to find the
maximum element in an array. At each computation level,
ParallelMaxRange receives a slice of original array.
If it is small enough, a sequential function is called to determine
the maximum element in the slice.
Otherwise, two sub-computations are created, each working on
one half of the original slice. Results from both subcomputations are aggregated by calling the Max function,
and the result is returned to the upper level.

procedure StageMod5(const input, output:


IOmniBlockingCollection);
var
value: TOmniValue;
begin
for value in input do
if not output.TryAdd(value.AsInteger mod 5)
then
Exit;
end;

function TfrmForkJoin.ParallelMax(const forkJoin:


IOmniForkJoin<integer>; left,
right: integer): integer;
var
computeLeft : IOmniCompute<integer>;
computeRight: IOmniCompute<integer>;
mid
: integer;

The last stage also reads data from its input but outputs only
one number a sum of all input values.
procedure StageSum(const input, output:
IOmniBlockingCollection);
var
sum : integer;
value: TOmniValue;
begin
sum := 0;
for value in input do
Inc(sum, value);
output.TryAdd(sum);
end;

function Compute(left, right: integer):


IOmniCompute<integer>;
begin
Result := forkJoin.Compute(
function: integer
begin
Result := ParallelMax(forkJoin, left,
right);
end
);
end;

Read more about pipelines in the OmniThreadLibrary at


www.thedelphigeek.com/2010/11/
multistage-processes-with.html

Fork/Join
ForkJoin is the most complicated high-level parallel construct in
the OTL. It is an implementation of the divide-and-conquer
technique. In short, ForkJoin allows you to execute multiple
tasks, wait for them to terminate and collect their results.
The trick here is that subtasks may spawn new subtasks and so
on ad infinitum (probably a little less than infinite, or you'll run out of
stack ). For optimum execution, ForkJoin must therefore
guarantee that the code is never executing too many background
threads (the optimal value is usually equal to the number of cores in the
system), and that those threads don't run out of work.
ForkJoin subtasks are in many way similar to Futures. They offer
slightly less functionality (there is no cancellation support) but they
are enhanced in another way when a ForkJoin subtask runs
out of work, it will start executing some other task's workload,
keeping the system busy.
A typical way to use Fork/Join is to create an

begin
if (right - left) < CSeqThreshold then
Result := SequentialMax(left, right)
else begin
mid := (left + right) div 2;
computeLeft := Compute(left, mid);
computeRight := Compute(mid + 1, right);
Result := Max(computeLeft.Value,
computeRight.Value);
end;
end;

My blog post at:


http://www.thedelphigeek.com/2011/05/divideand-conquer-in-parallel.html
contains more information about ForkJoin, and shows how
to implement a parallel QuickSort with the help of this OTL
construct.

IOmniForkJoin<T> instance

To receive a printed copy of the magazine


you need to go to our website to place your order

80

COMPONENTS
DEVELOPERS

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

productivity software building blocks

TMS Scripter Studio Pro


By Wagner Landgraf & Bruno Fierens
starter

expert

Delphi 5 and above

Flexibility and the possibility of customization is important in every application. Your application needs to be
flexible enough to fit the requirements of all your customers, even if you have a large, heterogeneous customer base.
You can make your application customizable by allowing features to be adaptable depending on the software
configuration. You also need to provide dialogs so users can configure the available options. All this takes
development time and, worse still, requires that you define in advance which features will be customizable and
which won't be. If a customer unexpectedly asks you if he can change some font colour, you can point him to your
application's font options dialog unless you never imagined any customer would ask for that and you just don't
have a font options dialog.
That's one of the purposes of a scripting system: to allow for customization. By adding scripting to your
application, you raise your application's flexibility to a new level, by letting users (or your support team) customize
the application using scripts, without having to worry about pre-preparing the application for a particular
customization. In the example above, you could have just prepared your application to run a script before the form
opens, and then the user could customize the form the way he wants including setting the colour with a simple
Font.Color := clBlue;.
The purpose of this article is to show how scripting increases your application's flexibility. Not only that our
intention is to show how quickly you can do this using TMS Scripter Studio Pro and all its built-in features that are
almost plug-and-play.
Subject: The ActionBars demo
To illustrate scripting usage we will take an existing application and customize it, since this is what happens in real life. You don't
often build an application designing it to be fully customizable. Rather, you have already built your working application and
subsequently wish to make it customizable. So we will use the ActionBars demo that comes with Delphi's sample applications.
This demo is intended to show how to use action bars, but in fact it's just a small RTF editor. What we will do is build a macro
system so users can create new macros and run them, yielding similar functionality to Microsoft Word's macro feature.

Figure 1
Preparing the GUI
Before going into scripting itself, let's just quickly modify the application's graphical user interface so that users can create and run
macros. Basically we will just add a new menu called Macros with two options: Edit Macros and Execute Macro.
The first option allows users to create and edit macro content, and the second option allows users to choose a macro to be executed.

Figure 2:
Here is a screenshot of our
modified ActionBars demo.
SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

81

TMS Scripter Studio Pro (Continuation 1)


Here is what those two menu options do:
Edit Macros: Opens a scripting IDE in which users can create new scripting projects and save them into files.
Execute Macro: Opens a file dialog for the user to choose a script project and then execute it.
Note that the GUI is very simple and it's not the purpose of this article to elaborate it to provide a more complex one. We could
obviously offer more options such as a list of recent used macros, macro shortcuts, the ability to save macros in a database, and so
on.
Initial scripting setup
To start building very simple macros, we will just use TMS Scripter Studio's basic setup. First we need to drop three components on
the form: TIDEEngine, TIDEScripter and TIDEDialog.

Figure 3
TIDEEngine is the core of scripting projects. It handles opening, saving and running projects among other things.
It has a property named Scripter, which we must point to the TIDEScripter component. This is the scripting engine, which
effectively runs the scripts and holds information about supported types. Finally, TIDEDialog is a high-level GUI dialog which
opens a ready-to-use IDE for us to edit and debug our scripts. It must be linked to the TIDEEngine component through its
Engine property. In addition to these components, we should also add some basic types to our scripting system.
For example, we want our users to be able to call the IntToStr procedure, to be able to use edits and combos in the script form,
so we will add some imported libraries to our application. Those are libraries already deployed with TMS Scripter Studio, and they
include almost all the VCL imports. We will add basic ones like Forms, and SysUtils.
To do that, you just need to add some units to the uses clause of the ActionBars project:
implementation
uses
ap_SysUtils, ap_Windows, ap_Classes, ap_Forms, ap_Dialogs, ap_StdCtrls, ap_Controls,
ap_Graphics, TypInfo;

Once we do that, we are able to run scripts. Now we just need to integrate the scripting system with our new GUI. We will add
code to our two newly created actions, acEditMacros and acExecuteMacro. Here is how (see listing ):
procedure TForm1.acEditMacrosExecute(Sender: TObject);
begin
IDEEngine1.NewProject;
IDEDialog1.Execute;
end;
procedure TForm1.acExecuteMacroExecute(Sender: TObject);
begin
if OpenDialog1.Execute then
begin
IDEEngine1.OpenProject(OpenDialog1.FileName);
IDEEngine1.RunProject;
end;
end;

It's as simple as that. In acEditMacrosExecute, we just call the TIDEEngine.NewProject method in order to clear any
macro project that is in memory, then call TIDEDialog.Execute to show the IDE to edit the macros. In the
acExecuteMacroExecute method, we let the user choose a script file, then load the project using the TIDEEngine.OpenProject
method and later run it using TIDEEngine.RunProject.

82

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

TMS Scripter Studio Pro (Continuation 2)


The Hello World Macro
So here's what happens when end-user clicks Edit Macro: the IDE is displayed with a new blank project.

Figure 4
This blank project just creates and displays an empty form. Let's just remove Unit2 from the project (this holds the empty form),
by selecting Unit2, and then choosing menu option File | Remove from project.
Then we change the source code of Unit1 to just show a Hello world message:

Figure 5
We can just use File | Save All menu option to save all the files. We can rename Unit1 as HelloWorldUnit.psc and Project1 as
HelloWorld.ssproj, and we're done, our new macro is created.
Executing the Hello World Macro
Now in our ActionBars main form we can choose the Macros | Execute Macro menu option. It will display a file dialog from
which we can choose our HelloWorld.ssproj project file, and then execute it:

Figure 6
SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

83

TMS Scripter Studio Pro (Continuation 3)

Figure 7

Figure 8
Full integration with the application
This is already a start, but we can't do many interesting things with a stand-alone scripting system if we don't integrate it into our
application. This is a key part of a good flexible system. In summary, what we will do is make parts of our application available
from scripting. Usually and ideally you would have a collection of business objects and make them available so end-users can
manipulate those objects from scripting.
In the present case, our main purpose is to get the user to interact with the text editor itself. So we will make the TRichEdit
available to the script. We will create an InitScripter method and we will call it from the FormCreate event:
procedure TForm1.FormCreate(Sender: TObject);
<snip>
begin
InitScripter; // New line added to FormCreate
<snip>
procedure TForm1.InitScripter;
begin
IDEScripter1.DefineClassByRTTI(TRichEdit, '', [mvPublic, mvPublished], true, roOverwrite);
IDEScripter1.AddComponent(RichEdit1);
end;

That's all we need! In InitScripter, we make the scripter aware of the TRichEdit class, most of its methods and properties, and
also subclasses, like TTextAttributes. The second line makes the script aware of a TRichEdit instance we have in our
application, RichEdit1,so it can be used from the macros.
Note that this code uses a new feature that is only supported in Delphi 2010 and up, as a result of the new RTTI system.
TMS Scripter Studio Pro is very powerful and flexible. You can add any class, method or property in your application manually to
the scripting system. You could add each method of TRichEdit individually, and you could add other instances (for example we
could have added the toolbar so users could manipulate the toolbar).
But this is the most interesting one, because it's very straightforward at only two lines of code!
This allows us to build much more interesting macros, with full code completion and parameter hints!
84

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

TMS Scripter Studio Pro (Continuation 4)


The CommentSelection macro
This macro illustrates how adding the RichEdit1 instance to the scripting system can make the system much more flexible. Let's
build a simple macro that comments selected text:

Figure 9: Now our users can just select a piece of text in the editor:

Figure 10: Execute and select the


CommentSelection macro:

Figure 11: And our text


becomes commented:
SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

85

TMS Scripter Studio Pro (Continuation 5)


The GotoLine macro

As a final example, we will build a GotoLine macro to illustrate how to use the Form Designer to improve flexibility
even more. We will build a macro that firstly displays a form (so the user can input the line number which he wants the
cursor to be positioned at), and then proceeds to change the cursor position to the given line number. In this macro we
will use two scripts, one for the form, and the other to display the form and execute the operation. The following
screenshots show all the files:

Figure 12

Figure 13

Figure 14
86

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

TMS Scripter Studio Pro (Continuation 6)

Figure 15: So when a user asks to


execute the GotoLine macro,
this is what happens:

Figure 16: On pressing [Ok],


the cursor will be positioned
at the line six.

Figure 17
SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

87

TMS Scripter Studio Pro (Continuation 7)


Debugging
It's important to note that when editing the macros, you can easily debug them to test your code, using watches to see what's going
on. Here is a screenshot of a debug session looking at the CommentSelection macro:

Figure 18: Debugging in Scripter Studio


Conclusion
In this article we wanted to show you how quickly an application could be customized using TMS Scripter Studio
Pro, by using the ready-to-use IDE and the new RTTI which allows you to have scripting integrated into your
application with just a few lines of code. For more detailed information, advanced topics, and testing, you can
download a trial and try it out for yourself.
For more information and a free trial download of TMS Scripter Studio Pro, visit:
http://www.tmssoftware.com/site/
scriptstudiopro.asp

About the Author


Wagner R. Landgraf
Electronics Engineer, M.Sc.
The Product Manager at TMS Software since 1998, has been working with Delphi since its version 1.
Besides developing components and frameworks for Delphi and .NET in the past years, has also worked
with ERP software and also Digital Signal Processing tools.

88

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

FPVectorial - A vector graphics library


starter

expert

LAZARUS

Pascal developers are most familiar with a kind of


image known as a raster image. In both Delphi and
Lazarus TBitmap is the principal class that
implements support for raster images. This class
represents an image stored in memory and
alongside this class are many other related classes,
such as TCanvas, TPen, TBrush and TFont for
drawing geometrical figures and text. Lazarus
additionally provides the TLazIntfImage class,
which offers fast pixel access to a bitmap. As well
as the standard raster image support provided by
Delphi's VCL and Lazarus' LCL there are many
other raster image libraries, for example
Graphics32, which works both in Delphi and
Lazarus. In a raster image, the picture is composed
from a matrix of pixels, each pixel having an
associated colour and representing the smallest dot
possible within the image. Computer monitors are
specifically designed for displaying pixel matrices,
which is why raster images are the most frequently
encountered type of computer image.

Felipe Monteiro de Cavalho

The special place which paths occupy in FPVectorial derives


from the original need for this library - to be used in software
controlling a CNC machine. In computer-controlled milling
machines it is desirable that there are no complex drawings at
all, so all elements must be converted to paths which the
machine can execute by physically moving its drill or laser focal
point. You can use software such as CorelDraw to convert
vector image elements into paths, and then use FPVectorial to
open a CorelDraw-generated PDF, and convert it to G-Code to
control the CNC milling machine.
The many vector image formats in use each support different
features, which can lead to data loss when loading and saving
vector image files. For example AutoCAD supports dimension
elements, but most other formats (which are not geared towards
engineering) do not support dimension elements. Most formats
support only one page in each file, but PDF supports multiple
pages. This article shows you how to use FPVectorial to create
new vector drawings, how to load them from a disk file, and
how to convert data between the different formats.

Creating vectorial images


FPVectorialial's simplest use is in creating vector images. To do
this simply create a new instance of the TvVectorDocument
class. The document's size can be defined in millimetres in the
TvVectorDocument.Width and TvVectorDocument.Height
properties. This information is helpful for viewer applications,
but not a requirement. You can then use any of the various
Besides the raster image there is another kind, the vector image, TvVectorDocument methods to add paths, geometrical
which is less well known, but widely used in fields such as
elements, text and other objects. To create a new path you must
commercial drawing and design, marketing, cartography, geology always begin with the StartPath() method, which must include
and all types of engineering. The basic concept behind vector
information about the path's start position. Thereafter you can
(or vectorial) images is completely different from raster images. use any of the path segment addition methods to add path
Vector images are described in terms of elements (for example
segments to this intermediate path as it is being constructed.
text, lines, polygons, etc.) rather than pixels (dots), and this
Listing 1. Creating two vectorial documents
makes it possible to modify each element of the image
program fpvwritetest;
separately. So you can change the elements' Z-order and carry
{$mode objfpc}{$H+}
out other operations which are impossible with raster images.
There are hundreds of vector image formats, but a few of them uses fpvectorial, svgvectorialwriter, fpvutils;
have evolved to become the most widely used. For example
const cFormat = vfSVG; cExtension = '.svg';
SVG is the vector image format recommended by the World
Vec: TvVectorialDocument;
var
Wide Web Consortium (W3C) for web pages. Other formats
supported by FPVectorial are: DXF (the AutoCAD format),
{$R *.res}
Encapsulated PostScript, PDF and G-code for CNC (Computer
begin
Numerical Control) machines.
Vec := TvVectorialDocument.Create;
An advantage of vector images over raster images is the
try // 10cm x 10cm
potential of resizing vector images without resolution loss or
Vec.Width := 100;
Vec.Height := 100;
pixellation. Since there is no unique mapping of the image to
screen pixels, the actual resized onscreen appearance will depend
// single_line_1.svg
on how the image is rendered, and it is possible during this
Vec.StartPath(0, 20);
rendering to scale it without getting a low-resolution result. Most
Vec.AddLineToPath(30, 30);
Vec.EndPath();
vector image formats allow real-world units to be specified in
Vec.WriteToFile('single_line_1'
the drawing of the image. (The most notable exception is the
+ cExtension, cFormat);
SVG format, which does not allow real-world unit specification).
// polyline_1.svg
The FPVectorial library was created with these unique
characteristics of vector images in mind, and currently it
supports documents which can contain paths, drawing elements,
text and raster images. Paths are the most important part of
vector images and they represent a set of connected points. The
segments between points may be straight lines or Bzier curves,
and the internal area of the path may or may not be filled.
Elements are usually geometrical elements, such as polygons,
ellipses, and so on, but there might also be a non-geometrical
element such as a size measurement drawing. Text is encoded as
UTF8, as in the Lazarus LCL.
90

COMPONENTS
DEVELOPERS

Vec.Clear;
Vec.StartPath(0, 0);
Vec.AddLineToPath(10, 10);
Vec.AddLineToPath(20, 30);
Vec.AddLineToPath(30, 20);
Vec.EndPath();
Vec.WriteToFile('polyline_1'
+ cExtension, cFormat);
finally
Vec.Free;
end;
end.

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

FPVectorial - A vectorial graphics library (continuation 1)


Each segment starts where the previous one ends, so they are
always connected (though it is possible to have invisible
segments which give the illusion of disconnection). Each
segment is a line or a curve connecting its starting and finishing
points. After adding all segments you must call the EndPath()
method which will record the path in the document. The basic
routines to add path segments are:
AddLineToPath() to add line segments
AddBezierToPath() which adds Bzier cubic curves
AddMoveToPath() which adds an empty segment (which can
also be interpreted as an invisible segment)
One way to verify if the generated drawing is correct is to save it
to a file using the TvVectorDocument. SaveToFile method, and
use a vector image viewer to inspect it. The following example
creates two documents, one with a straight line and another with
a line composed from various segments, and saves them to SVG
files. You can use a browser such as Opera or Firefox to open
SVG files, or you can use a vector image editor, such as
Inkscape, to visualize the generated SVG files. To be able to
create two documents using a single TvVectorDocument
instance we use the Clear() method. This removes all the
document's current contents, effectively giving you a new
document from the existing instance.

Figure 1: The single_line_1.svg file


rendered by Inkscape (note that
Inkscape adds a document border).

Figure 2: The polyline_1.svg


file rendered by Inkscape..
Creating curves
Now you know how to create straight lines, the next step is to
create curves, and for that you need to understand how Bzier
curves work. FPVectorialial uses a cubic Bzier curve, and it
builds the curve starting at a point P0 and ending at the final
point P3. The path to be followed is determined by two control
points, P1 and P2. Any kind of curve can be created by
combining various Bzier segments together. The equation of a
cubic Bzier curve is shown below.
B(t) = (1 t)3P0 + 3(1 t)2tP1 + 3(1 t)t2P2 + t3P3 , t [0,1]

Listing 2. Creating a Bzier curve

// bezier_1.svg
Vec.Clear;
Vec.StartPath(0, 0);
Vec.AddLineToPath(10, 10);
Vec.AddBezierToPath(10, 20, 20, 20, 20, 10);
Vec.AddLineToPath(30, 0);
Vec.EndPath();
Vec.WriteToFile('bezier_1' + cExtension, cFormat);

Figure 4: The bezier_1.svg file rendered by Inkscape


Creating text
Along with lines and curves it is also possible to add text to the
vector drawing. The text is defined as a Pascal string containing
UTF8 characters (just as the Lazarus IDE and LCL use), and by
the position where it is placed. Text will be drawn starting
upwards and to the right of the stated position, which means in
the positive directions of the X and Y coordinates. It is
important to note that these coordinates are understood in
exactly the same way as the LCL defines them. However, in
FPVectorial the axes' origin is the bottom-left corner, while in
the LCL it is the top-left corner. This means that the X axis is
identical in FPVectorial and in the LCL, but the Y axis starts at a
different place from the LCL's Y axis, and also points in the
opposite direction. In FPVectorial the Y axis grows upwards
(whereas the LCL's Y axis grows downloads). This difference is
introduced because FPVectorial uses the coordinate system
preferred for vector documents. Besides defining a text and a
position, you can also added a colour, a text size and the name
of the preferred font to render it. The way in which the font
name is handled will depend on the program being used to
render the drawing.
The following example shows how to add multiple elements to
the same drawing. First lines and curves are added as explained
above, and then text is added with the AddText() method. The
text itself is encoded using UTF8, and its correct rendering will
depend on the fonts available on the computer and on the
program being used to render it.
Listing 3.
Creating a vectorial document with paths and text

Figure 3: The Bzier cubic curve equation


The following example shows how to create a path composed
of two line segments and a Bzier curve. The previously
presented TvVectorDocument.AddLineToPath() method adds
the line segments, and the AddBezierToPath() method is used
to draw the Bzier curve. The six parameters for this method
follow this sequence: The X and then the Y coordinate of
control point 1, the X and then the Y coordinate of control
point 2, and the X and then the Y coordinates of the curve's
final point. Note that the listing presented here shows only how
to use an already created instance of the TvVectorDocument
class. (The code needed to create the instance was given above
and is not repeated in Listing 2).

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

// multi_test_1.svg
Vec.Clear;
Vec.StartPath(0, 20);
Vec.AddLineToPath(30, 30);
Vec.EndPath();
Vec.StartPath(0, 0);
Vec.AddLineToPath(100, 0);
Vec.AddLineToPath(100, 100);
Vec.AddLineToPath(0, 100);
Vec.AddLineToPath(0, 0);
Vec.EndPath();
Vec.StartPath(0, 0);
Vec.AddLineToPath(10, 10);
Vec.AddBezierToPath(10, 20, 20, 20, 20, 10);
Vec.AddLineToPath(30, 0);
Vec.EndPath();
Vec.AddText
(10, 10, 0, '10,10 Some text in english.');
Vec.AddText(20, 20, 0, '20, 20 Mwic, czesc,
Wlosku,Parabns.');
Vec.AddText(30, 30, 0, '30, 30 ');
Vec.WriteToFile('multi_test_1'
+ cExtension, cFormat);

COMPONENTS
DEVELOPERS

91

FPVectorial - A vectorial graphics library (continuation 2)

Figure 5: The multi_test_1.svg file as rendered by the Opera navigator.


Setting an element's colour and width
Besides creating paths and text, it is also possible to define an
element's colour, width and style. The width of a path to be
constructed can be defined with the
TvVectorDocument.SetPenWidth() method. This method can
only be used for newly constructed paths, so it can only be
executed between a call to StartPath() and EndPath(). A single
colour, style and width can be specified for the whole path, or
you can add segments which each specify their own style data
which overrides the main path style.
You use SetPenColor() to define a path's colour, and similarly
you use SetPenWidth() to define the width of the path.
FPVectorial utilizes the main FCL colour type, TFPColor, which
provides 2 bytes for each of the four main colour channels: red,
green, blue and alpha. There is a convenience routine in the
fpvutils unit called RGBToFPColor(). This allows for the easy
creation of a 100% opaque colour, and its parameters are
respectively the red, green and blue channels in 1-byte values
(which are used more commonly than the 2-byte values in
TFPColor).

Modifying documents
The methods shown above are adequate for adding new
elements to a document, but not for modifying existing
elements already in a document. For that you have to use other
methods. Each instance of the TvVectorDocument class
contains a list of elements already in the drawing. Everything is
considered an element: paths, texts and other entities. To modify
an element you have to obtain it with the GetEntity() routine
which takes the index of the element in this list as a parameter.
Note that the index might change if elements are removed from
the list. The index goes from zero to GetEntityCount() 1, and
with this information it is possible to iterate through all
elements in the document. There are also two very similar
methods which list only the path elements called GetPath() and
GetPathCount(). They work very similarly and can be used to
obtain just the path elements of the document. They are
provided mostly for backwards compatibility.
Listing 5.
The methods to obtain elements of a document
function GetPath(ANum: Cardinal): TPath;
function GetPathCount: Integer;
function GetEntity(ANum: Cardinal): TvEntity;
function GetEntityCount: Integer;

The example below shows how you can modify a vector


document. It reads a file called bezier_1.svg (created in the
previous example), and it will move the contents of the drawing
10mm upwards, which means that it will increase the Y
coordinate of every element by 10mm. The modified file is then
saved as bezier_1_mod.svg. The first step to accomplish this is
to use ReadFromFile() to read the file, and then use a loop to
iterate through all the file's paths and segments. The loop goes
from zero to GetPathCount() 1, and it uses GetPath() to
obtain the individual paths. The general position of a path does
not mean anything, because its elements are what really contain
the position information, so we use the
TPath.PrepareForSequencialReading method together with
TPath.Next to iterate though all a path's segments.
PrepareForSequencialReading should be called once, and then
you can call Next() until Next() returns nil, which means that
there are no more segments in the file. The segments may be of
various types, so we really have to watch out for the
particularities of each kind of segment. The commonest ones
are T2DSegment which is a line segment, and
T2DBezierSegment which is a cubic Bzier segment.

Listing 4. Creating a document with colors


// pen_test_2.svg
Vec.Clear;
Vec.StartPath(0, 20);
Vec.AddLineToPath(30, 30);
Vec.SetPenWidth(10);
Vec.SetPenColor(RGBToFPColor(255, 0, 0));
Vec.EndPath();
Vec.StartPath(0, 0);
Vec.AddLineToPath(100, 0);
Vec.AddLineToPath(100, 100);
Vec.AddLineToPath(0, 100);
Vec.AddLineToPath(0, 0);
Vec.SetPenWidth(10);
The latest version of FPVectorial includes a
Vec.SetPenColor(RGBToFPColor(0, 255, 0));
TvEntity.Translate method which allows you to move
Vec.EndPath();
Vec.StartPath(0, 0);
entities very easily, without the need to know precisely
Vec.AddLineToPath(10, 10);
how the entity was formed. Knowing how to iterate
Vec.AddBezierToPath(10, 20, 20, 20, 20, 10);
through segments of a path and how to access an entity's
Vec.AddLineToPath(30, 0);
type can be very useful. You can see the FPVectorial class
Vec.SetPenWidth(10);
Vec.SetPenColor(RGBToFPColor(0, 0, 255));
chart below, which shows more information about how
Vec.EndPath();
the different types of entities are related.
Vec.WriteToFile('pen_test_2'
+ cExtension, cFormat);

Image 4. The file pen_test_2.svg


rendered by Inkscape.
92

COMPONENTS
DEVELOPERS

Figure 5: The bezier_1_mod.svg file


rendered by Inkscape
SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

FPVectorial - A vectorial graphics library (continuation 3)


Listing 6. Modifying a vectorial document
program fpvmodifytest;
{$mode objfpc}{$H+}
uses fpvectorial, svgvectorialwriter, svgvectorialreader, fpvutils;
const cFormat = vfSVG; cExtension = '.svg';
var
Vec: TvVectorialDocument; Path: TPath; i: Integer; Segment: TPathSegment;
_2DSegment: T2DSegment; BezSegment: T2DBezierSegment;
begin
Vec := TvVectorialDocument.Create;
try // Read the file
Vec.ReadFromFile('bezier_1.svg');
// Now add 10 to the Y coordinate of all elements
for i := 0 to Vec.GetPathCount() - 1 do
begin
Path := Vec.GetPath(i);
Path.PrepareForSequentialReading();
Path.Next();
while Path.CurPoint <> nil do
begin
Segment := Path.CurPoint;
if Segment is T2DBezierSegment then
begin
BezSegment := Segment as T2DBezierSegment;
BezSegment.Y := BezSegment.Y + 10;
BezSegment.Y2 := BezSegment.Y2 + 10;
BezSegment.Y3 := BezSegment.Y3 + 10;
end
else if Segment is T2DSegment then
begin
_2DSegment := Segment as T2DSegment;
_2DSegment.Y := _2DSegment.Y + 10;
end;
Path.Next();
end;
end; // Write the changed file to disk
Vec.WriteToFile('bezier_1_mod' + cExtension, cFormat);
finally
Vec.Free;
end;
end.

Figure 6: The FPVectorial class hierarchy

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

93

FPVectorial - A vectorial graphics library (continuation 4)


Converting between vectorial formats
Another use of this library is for conversion of files from one
vector format to another, and it is very easy to do this kind of
conversion with FPVectorial. You just call the
TvVectorDocument.ReadFromFile()method to read a file and
then use WriteToFile() to save it in the new format. These two
routines have very similar parameters and both have an
overloaded version which allows you to specify the format, and
another one which omits this parameter. This means that
FPVectorial tries to auto-detect the format based on the file
extension. In the table below you can see the formats and file
extensions supported by FPVectorial.
Constant Extension Descriptionv
fPDF

.pdf

of

vfSVG

.svg

vfEncapsulated
PostScript .eps
vfDXF

.dxf

vfCorelDRAW
.cdr

vfGCodeAvisoCNC
PrototipoV5
.g

94

Listing 7. Converting between vectorial formats


procedure TformVectorialConverter.buttonConvertClick
(Sender: TObject);
var
Vec: TvVectorialDocument;
begin
Vec := TvVectorialDocument.Create;
try
Vec.ReadFromFile(editInput.FileName);
Vec.WriteToFile(editOutPut.FileName, vfPDF);
finally
Vec.Free;
end;
end;

Drawing a vector image on the screen


As demonstrated earlier, it is possible to work with vector files,
save them to a disk file, and then use an external application to
open and visualize them. But FPVectorial also allows vector
images to be drawn directly in a Delphi or Lazarus form. The
unit fpvtocanvas comes with a routine for drawing a vector
image on a TCanvas. It is very flexible and allows a document to
be drawn with a configurable zoom factor. At this point it is
important to consider which drawing strategy to use. You can
either draw the entire document to a buffer and then show a
part of it each time, or you can draw only the visible part of the
document. In the first case it will take some time to zoom the
document, because a new drawing will need to be generated at
each zoom. In the second case it will take longer to move
around the document, and this can be seen in many programs
which visualize vector documents (Inkscape, for example). The
currently provided routine in FPVectorial follows the strategy of
drawing everything, which can be time-consuming if the
document has hundreds of thousands of elements. In the future
we may add a routine to draw only part of a document. You can
see this routine's declaration below, and the parameters AMulX
and AMulY indicate the zoom level. ADestX and ADestY
indicate where the image should start to be drawn in the canvas.
When you use this routine it is important to remember that
TCanvas uses different units than FPVectorial does, so the basic
values to give a normal view (100% zoom) are 1.0 for AMulX
and -1.0 for AMulY. Similarly ADestY should receive the size of
the canvas less the desired position.

PDF is not a pure vectorial image


format, it is actually a document
format more similar to MS Word
documents which can hold a plethora
different information. PDFs can hold
rich text, vectorial images, raster
images and even miscellaneous
objects such as user interface controls
and even Videos in the latest versions.
FPVectorial will use only the vectorial
image data when reading a PDF
currently, so it should only be used
with PDFs generated from vectorial
image editors like CorelDraw or
Inkscape, and not with just any
random PDF file. The vectorial image
data in PDFs is stored as PostScript
commands. PDFs are a simple textbased format, but the text data may be
compressed.
This is the vectorial image format
recommended by W3C for usage in
the internet. It is an open format and
with a simple and well documented
specification. The main shortcomming
of SVG is that it doesn't allow the use
of real world units, it simply supports
only measures in raw numbers,
without any units and therefore the
size must be decided by the rasterizer
Listing 8. The function rawFPVectorialToCanvas
software. Some software just
hardcodes to 90 dpi for SVG, for
procedure DrawFPVectorialToCanvas(ASource:
example Inkscape, Opera Browser and
TvVectorialDocument;
fpvectorial. But some other software
{$ifdef USE_LCL_CANVAS}
uses other values.
ADest: TCanvas;{$else}ADest: TFPCustomCanvas;{$endif}
These files contain PostScript code
ADestX: Integer = 0; ADestY: Integer = 0;
inside them and can represent very
AMulX:Double = 1.0; AMulY: Double = 1.0);
complex vectorial images
Below you can see how an example application, fpvviewer, uses
One of the formats from AutoCAD.
FPVectorial to visualize vector images. This application was
The DXF is designed to be used by
AutoCAD when there is need for the
written in Lazarus and runs equally well in Windows, Linux and
use of the file by third-parties,
Mac OS X. It can be downloaded from the lazarus-ccr (Lazarus
because the main file format from
Code and Components Repository) by anonymous svn from the
AutoCAD, DWG is not documented and
directory lazarus-ccr/applications/fpvviewer. Figure 7 shows
is a commercial secret of this software
how this example application uses the
manufacturer.
This format has no open specification, DrawFPVectorialToCanvas procedure to draw a vector image to
so any implementation of read and
the screen when the user clicks on the [Visualize] button. The
write support for it relies only on
area on the screen where the image is drawn is a custom control
guessing and reverse engineering. An implemented as a descendant of TCustomControl. It has an offattempt was made to add support for
screen TBitmap used as a buffer for drawing the full image. The
this format to fpvectorial, but without
display only shows the necessary section of the image each time,
much success.
which makes for fast panning across the full image. The user can
A G-Code format for a particular
move around the image by dragging it or by using the keyboard
milling machine.

arrow keys. Figure 7 demonstrates this application running


under Mac OS X.
COMPONENTS
DEVELOPERS

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

FPVectorial - A vectorial graphics library (continuation 5)


Listing 9.
Drawing a vectorial document to the screen
procedure TfrmFPVViewer.btnVisualizeClick
(Sender: TObject);
const
FPVVIEWER_MAX_IMAGE_SIZE = 1000;
FPVVIEWER_MIN_IMAGE_SIZE = 100;
FPVVIEWER_SPACE_FOR_NEGATIVE_COORDS = 100;
var
Vec: TvVectorialDocument;
CanvasSize: TPoint;
begin
Vec := TvVectorialDocument.Create;
try
Vec.ReadFromFile(editFileName.FileName);
...
Drawer.Drawing.Width := CanvasSize.X;
Drawer.Drawing.Height := CanvasSize.Y;
Drawer.Drawing.Canvas.Brush.Color := clWhite;
Drawer.Drawing.Canvas.Brush.Style := bsSolid;
Drawer.Drawing.Canvas.FillRect(0, 0,
Drawer.Drawing.Width, Drawer.Drawing.Height);
DrawFPVectorialToCanvas(Vec, Drawer. Drawing.
Canvas,FPVVIEWER_SPACE_FOR_NEGATIVE_COORDS,
Drawer.Drawing.Height FPVVIEWER_SPACE_FOR_NEGATIVE_COORDS,
spinScale.Value, -1 * spinScale.Value);
Drawer.Invalidate;
finally
Vec.Free;
end;
end;

Figure 8: Turbo Circuits and Vectors in action


Conclusion
This article has shown you how to manipulate complex
vector images using FPVectorial. To accomplish this
without FPVectorial would be an immense amount of
work, because each supported format (pdf, eps, dxf, svg,
gcode, etc.) differs greatly from all the other formats, and
some formats have specifications which are very hard to
understand or are trade secrets. This library was written
entirely in Object Pascal so Lazarus projects can use it
without needing any other external dependency. For
example it is possible to add an AutoCAD or SVG
visualizer to an application without the user having to
install a huge and very expensive application like AutoCAD
or CorelDraw. The advantage on Linux is even greater,
because most CAD software won't run on non-Windows
or non-Mac systems. In addition, the library allows you to
convert between vector graphic formats, generate new
vector images, modify existing ones, and render vector
images into bitmaps. Of course there is still a lot of room
for improvement in FPVectorial, and this library is growing
all the time as people contribute code and money to this
project.
Links
FPVectorial internet page
http://wiki.lazarus.freepascal.org/fpvectorial

Figure 7: The sample viewer program

Wikipedia article about Bzier curves


http://en.wikipedia.org/wiki/Bzier_curve

We can also implement a simple viewer with FPVectorial, and


the image below shows Turbo Circuits and Vectors, which is a
vector image editor written in Lazarus using FPVectorial. This
application has many tools for drawing electric circuit symbols
and designs, as well as tools for drawing of any vector image. Its
source code can be downloaded from the SourceForge page:
http://sourceforge.net/projects/turbocircuit/

About the Author Monteiro de Cavalho


Felipe is an electrical engineer who graduated at the
University of So Paulo. He is one of the developers of the
Lazarus and Free Pascal projects, currently focusing on the
development of software for smartphone platforms and the
development of various libraries such as for handling
vectorial images, spreadsheets, etc. His current
employment is with Opera Software in Wrocaw - Poland as
a quality assurance engineer.

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

95

A tale of the nightingale


a story for starters

You can pass null for this parameter to stop the sound playing.
We want to use the asynchronous flag here (snd_async)
because then the call will exit immediately, allowing the
animation to proceed. You might think that a synchronous call
would mean the animation and sound were synchronised. But
here it does not mean that! Theoretically you could achieve the
same effect in another way: cut up the program time into tiny
slices and quickly alternate a bit of sound and a bit of
animation.

by Howard Page -Clark- telling the tale


Detlef Overbeek - creating the project and illustrations
Jean Pierre Hoefnagels - advices on coding
starter

expert

DELPHI 7 and above (Win32)


LAZARUS

This project is designed as an example of how you can


program very simple image animation while
simultaneously playing accompanying sound. The
technique employed is quite straightforward so you can
easily adapt it for your own purposes using different
images in a similar setup. All you
need is a bit of inspiration or access to
the goddess of creation: Thetis.
To make this project suitable for two different IDEs (Integrated
Development Environments) Lazarus and Delphi - I chose to
create two differently coded versions, both interchangeable. One
version uses a common-sense algorithm, the other uses a
compressed sort of algorithm. (Put simply, an algorithm is a
step-by-step procedure for carrying out a sequence of
calculations or instructions).

The computer chip works in this sliced fashion. But that is


very complicated and I do not believe it would be an appropriate
method for such a simple application as this. Another solution
yielding simultaneous sound and animation would be
multithreading. But again it is more complicated than we need
here. Windows offers an easy alternative with its asynchronous
PlaySound call. So why not go for this easier alternative?

The simple algorithm we started with is largely selfdocumenting, particularly since I added suitable comments
where I felt an explanation was needed. Now the fun is that the
compiler is not interested in the beauty of the code, nor in its
length. It will process whatever you have written, whether in
short or long Pascal statements.
I would advise you to try both the simpler (more extended)
We will start of course with the simplest presentation of the
version as well the shorter compressed version. You will learn
algorithm, written as a sequence of instructions so as to be
from looking at both. By the way, for lovers of multithreading:
readily understandable. For the alternative compressed
Primoz
Gabrijelicic has written a further article about High
algorithm we will organise the instructions differently and make
Level
Multithreading
in this issue (page74).
them more compact, saving the code in a separate unit. This can
lead to a noticeable reduction in the number of lines of code.
Although better in some ways it is not so readily understood by You can download both of these nightingale projects:
one unit is for Delphi the other for Lazarus. But they are
beginners (and we were all beginners once).
interchangeable without a problem. The image data files are
My personal opinion is that abbreviated code, while often very
included with the projects, so you can see exactly how the
beautiful, can be hard to understand later on, especially if you
animation works.
have not documented it well at the time of writing. The
algorithm in this project uses an array, a Windows API call
The sound and action have to be triggered by an event, so we
(sndPlaySound) and a timer object. Nothing too difficult.
have made that event the starting point. To animate an image
requires drawing it in a sequence of subtly different positions.
Drawing this sequence of images in quick succession gives the
illusion of movement. So for an animated bird I found a
photograph of a real nightingale, from which I created a series
of sketches incorporating subtle variations.

Figure 1: The Nightingale in a natural pose


The aim of the project
What we want to produce is a program that displays a moving,
singing bird where the animation and sound happen together (i.
e. simultaneously, not separately). By the way, the nightingale's sound
is taken from an original live nightingale recording. As an extra
you can listen to the story of the Chinese Nightingale (a Chineseinspired Hans Andersen tale) read by Howard Page-Clark.
As soon as you click on the bird you should hear the story.
(Maybe you could make it respond to gestures).
It is not as easy as it looks to use the Windows sound API
because the sndPlaySound() call in the MMSystem unit has two
parameters which must be understood for its correct use. The
first is a string identifying the waveform audio (.wav) file
containing the sound to play, and the second parameter is a
combination of flags that determine the sound play options.
96

COMPONENTS
DEVELOPERS

To make it easy to do the sketches I kept the bird's body in


exactly the same position and just let the bird's head and beak
change position (like some politicians). (Did you know that all birds
evolved from dinosaurs? To be precise from Velociraptors. So eat more
Kentucky Fried Chicken, they're all Dinos!) Apart from looking
beautiful this nightingale also sings splendidly. I would like to be
woken each morning with its song. (Maybe we could ask Philips to
add the sound, or add it ourselves via an iPhone, as featured in the Philips
Wake Light which comes with a slot for your iPhone)

Figure 2: The Philips Wake-up light

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

A tale of the nightingale (continuation 1)


Const FNames:array[0..49] of string = (
'..\jpg\Nightingale1.jpg',
'..\jpg\Nightingale2.jpg',
'..\jpg\Nightingale2.jpg',
'..\jpg\Nightingale2.jpg',
'..\jpg\Nightingale3.jpg',
'..\jpg\Nightingale3.jpg',
'..\jpg\Nightingale3.jpg',
'..\jpg\Nightingale4.jpg',
'..\jpg\Nightingale4.jpg',
'..\jpg\Nightingale4.jpg',
'..\jpg\Nightingale5.jpg',
'..\jpg\Nightingale5.jpg',
'..\jpg\Nightingale5.jpg',
'..\jpg\Nightingale5.jpg',
'..\jpg\Nightingale6.jpg',
'..\jpg\Nightingale6.jpg',
'..\jpg\Nightingale6.jpg',
'..\jpg\Nightingale6.jpg',
'..\jpg\Nightingale6.jpg',
'..\jpg\Nightingale7.jpg',
'..\jpg\Nightingale7.jpg',
'..\jpg\Nightingale7.jpg',
'..\jpg\Nightingale8.jpg',
'..\jpg\Nightingale8.jpg',
'..\jpg\Nightingale8.jpg',
'..\jpg\Nightingale9.jpg',
procedure TfrmNightingale.Timer1Timer(Sender: TObject);
'..\jpg\Nightingale9.jpg',
'..\jpg\Nightingale9.jpg',
procedure DoPic(const fPic: TFilename; aTag: integer);
'..\jpg\Nightingale8.jpg',
begin
'..\jpg\Nightingale8.jpg',
imgNightingale.Picture.LoadFromFile(fPic);
'..\jpg\Nightingale8.jpg',
imgNightingale.Tag:= aTag;
'..\jpg\Nightingale7.jpg',
end;
'..\jpg\Nightingale7.jpg',
'..\jpg\Nightingale7.jpg',
begin
'..\jpg\Nightingale6.jpg',
case imgNightingale.Tag of
'..\jpg\Nightingale6.jpg',
0: DoPic('..\jpg\Nightingale1.jpg', 1 );
'..\jpg\Nightingale6.jpg',
1: DoPic('..\jpg\Nightingale2.jpg', 2 );
'..\jpg\Nightingale5.jpg',
2: DoPic('..\jpg\Nightingale2.jpg', 3 );
'..\jpg\Nightingale5.jpg',
3: DoPic('..\jpg\Nightingale2.jpg', 4 );
'..\jpg\Nightingale5.jpg',
4: DoPic('..\jpg\Nightingale3.jpg', 5 );
'..\jpg\Nightingale4.jpg',
5: DoPic('..\jpg\Nightingale3.jpg', 6 );
'..\jpg\Nightingale4.jpg',
6: DoPic('..\jpg\Nightingale3.jpg', 7 );
'..\jpg\Nightingale4.jpg',
7: DoPic('..\jpg\Nightingale4.jpg', 8 );
'..\jpg\Nightingale3.jpg',
8: DoPic('..\jpg\Nightingale4.jpg', 9 );
'..\jpg\Nightingale3.jpg',
9: DoPic('..\jpg\Nightingale4.jpg', 10 );
'..\jpg\Nightingale3.jpg',
10: DoPic('..\jpg\Nightingale5.jpg', 11 );
'..\jpg\Nightingale2.jpg',
...
'..\jpg\Nightingale2.jpg',
...
'..\jpg\Nightingale2.jpg',
45: DoPic('..\jpg\Nightingale3.jpg', 46 );
'..\jpg\Nightingale2.jpg');
46: DoPic('..\jpg\Nightingale2.jpg', 47 );
47: DoPic('..\jpg\Nightingale2.jpg', 48 );
procedure TfrmNightingale.Timer1Timer
48: DoPic('..\jpg\Nightingale2.jpg', 49 );
(Sender: TObject);
begin
49: begin
if State >= length(FNames)-1
DoPic('..\jpg\Nightingale2.jpg', 0);
then State:= 0
// To go the starting position
else inc(State);
end;
imgNightingale.Picture.LoadFromFile
(FNames[State]);
end;
end;
end;

Once you have an image of the nightingale (BMP or JPG or


PNG) you need to separate the head from the body.
(I wish it were always that easy). Drawing suitable small variations
to the head and beak will make for smooth animated movement.
You will have to do quite a number of sketches the beak
slightly open and increasingly opened further; the head a bit
oblique and with some emerging blush to show the effort the
bird is making It eventually took me a whole day's work
before I was satisfied with all the sketches needed. I created
loads of action while sitting still on my chair...

Compare code above with the following compressed version


shown here:
You need to create an array of constants which enables you to
repeat the images continuously. You repeat the same code
section with each iteration, so you don't have to write out the
whole sequence repeatedly. The procedure calls in the two
versions are exactly the same.

procedure TfrmNightingale.btnStartClick(Sender: TObject);


begin
sndPlaySound('..\wav\Nightingale_song.wav',
SND_FILENAME or SND_ASYNC);
Timer1.Enabled := True;
end;

Thats what it is all about...

end.

So this is not very difficult. You might decide to create


such a tale for your own child to enjoy. Perhaps change the
Nightingale into a Warrior; or create a drawing application
with different sounds associated with its buttons; or create
a talking interface or ... whatever.
Have fun.

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

97

barnss en
DEVELOPMENT &
DATABASE
TOOLS

Barnsten B.V. - Embarcadero Technology Centre


Postbus 5010 2000 GA Haarlem Nederland
Tel.: +31 23 542 22 27 / Fax.: +31 84 755 52 60
Web: www.barnsten.com Info: info@barnsten.com

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

99

FireMonkey (continuation 1)

FireMonkey System Requirements


FireMonkey development requires Delphi XE2, C++Builder XE2,
or RAD Studio XE2 running on Windows
iOS development requires Delphi XE2 or RAD Studio XE2
FireMonkey applications run on multiple platforms with these requirements:
WindowsOS XiOSSupported virtualization products for running in a Virtual Machine (host

requires GPU)

Intel x86-compatible, Pentium 4 or later


Basic GPU Any vendor DirectX 9.0 class or better (Pixel Shader Level 2)
32-bit or 64-bit Windows
Microsoft Windows 7
Microsoft Windows Vista SP2
Microsoft Windows XP Home or Professional, SP2 or SP3
Microsoft Windows Server 2003 SP1, 2008, or 2008 R2
Mac OS X 10.6 Snow Leopard or OS X 10.7 Lion
2 GB RAM or more
All Intel Macs have a qualified GPU iOS 4.2 or higher
All iOS devices have a qualified GPU VMware Fusion 3
VMware Workstation 7
100

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Using the Advantage Database Client Engine Michal Van Canneyt


starter

expert

DELPHI 7 and above (Win32)


LAZARUS

Advantage Database server has an embedded client


engine (the Advantage Client Engine) which can be used
royalty-free for desktop applications. This article shows
how you can quickly build an application that needs an
SQL database using the Advantage Client Engine.
Introduction
The Advantage Database Server is a mature, full-blown SQL
database which runs on Intel Windows 32/64 bit and Linux
32/64 bit. It supports stored procedures using SQL
programming, external functions in native code, and it supports
checking referential integrity and enforcing other constraints.
Last but not least, it offers Full Text Search (FTS) on the
database as a native mechanism.
You may not know that an embedded version of this engine is
also available, which allows you to create SQL-enabled
applications that are easy to deploy. You simply need to copy a
few DLLs to your application's installation directory, and
everything is ready to go. This embedded engine can be used
royalty-free, provided it is used in what are essentially single-user
applications. As soon as you build multi-tier or webserver
applications on top of the engine, you need to obtain a licence.
Licensing details can be found on the website indicated below.
For multi-tier or multi-user applications it may be altogether
preferable to use the full Advantage Database Server solution.
An application that starts out as single-user may evolve to
become a complete client/server application.
If so, upgrading is as easy as distributing another connection
library and setting a different connection string in the
application. Advantage Database is a solution that scales easily.
The client engine (whether embedded or not) can be used from
several languages apart from Object Pascal. Other supported
languages include C++, .NET, Python, PHP and Java. Object
Pascal support for the Advantage Client Engine is very
complete. It comes with its own TDataset descendants, ready
for use in Delphi or Lazarus. Object Pascal can also be used to
write stored procedures or triggers for an Advantage Database.
The installer is available at
http://www.advantagedatabase.com/
(look for the Delphi TDataset component). The installer will
automatically install the components in the installed Delphi
IDE. For Lazarus users, a Lazarus package (adsl) is available
which must be compiled and installed in the IDE. (Instructions
can be found in the installed Help file).
Following a successful installation, a new Advantage tab appears
on the component palette. It contains the following
components, which will seem quite familiar to the experienced
database developer:
TAdsConnection
This component represents a connection to the database. It is
equivalent to the TADOConnection or TSQLConnection
components in Delphi or Lazarus.
TAdsTable
This TDataset descendant represents a table in the ADS
database. It is the equivalent of TADOTable or TIBTable in
Delphi. In Lazarus, it is roughly equivalent to the TDbf
component.

TAdsQuery
This TDataset descendant can be used to execute SQL
statements. It is equivalent to the TADOQuery, TIBQuery or
TSQLQuery components in Delphi and Lazarus.
TAdsStored
This component can be used to execute or retrieve data from a
stored procedure. Again, it is the equivalent of existing stored
procedure components in Delphi, tuned for use with the
Advantage Database server.
TAdsSettings
This component can be used to manipulate the local database
engine settings. This is something which can also be done in
custom code, but the component makes the process easier.
TAdsDictionary
This component encapsulates an Advantage Data Dictionary.
More is written about data dictionaries in what follows.
There are several other Advantage components as well, but
those listed above are the most important.
Creating a database
Advantage Databases have no single database file as in Firebird
or MS-SQL Server. Basically, an Advantage database is a
directory with a collection of table files. Therefore, it is not
really necessary to create a database, other than creating a
directory to contain the table files. This kind of database is
termed 'Free Tables', and is in fact very similar to a Paradox
database: namely a set of flat-file tables that are connected only
through the logic of program that uses them. For simple
systems, this may well be enough.
However, a data dictionary can be associated with a database.
The data dictionary contains metadata about the tables in the
database: it describes constraints on tables, relations between
tables, stored procedures, user access rights and many more such
attributes all the kinds of features one looks for in an RDBMS
system.
A data dictionary can be created in one of 3 ways:
1. Through a low-level API call of the Advantage Client
Engine library (AdsDDCreate).
2. Through a method of the TAdsDictionary class.
The class is marked as deprecated, but it is in fact the easiest
way to create a data dictionary in code.
3. Using the Advantage Data Architect.
This is a program which has to be downloaded and installed
separately from the ADS TDataset support. It comes
with full source, which is an invaluable source of
information on using the ADS Delphi components.
Using the Advantage Data Architect is in fact the fastest way to
create an Advantage Database. Under the 'File' or 'Connection'
menu items you will find a 'New connection Wizard'. The
wizard starts by asking whether a connection should be made to
an existing database, or whether a new database is to be created.

To receive a printed copy of the magazine


you need to go to our website to place your order

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

101

Using the Advantage Database Client Engine (continuation 1)


For most purposes, the data dictionary is the better option.
The wizard will then prompt you for the necessary parameters
such as the name of the dictionary, and the location of the
database. The dictionary file will then be saved using the name
of the dictionary and the extension .add.
If you choose the 'Free Tables' option, instead of a dictionary
file being created, a new alias is introduced which refers to the
database directory.

Creating tables
Once the data dictionary has been created you can add tables to
it. There several ways to do this:
1.
Using the Data Architect to structure the table.
2. Using the Data Architect to import existing data into
a new table.
3. Use Delphi code based on the TAdsTable component
which contains a table-creation method.
4. Using a DDL SQL statement in the Data Architect or
from a Delphi program.
The first option is certainly the easiest. A simple set of tables to
contain Blaise Pascal Magazine's issues and their contents can be
modelled in less than 5 minutes, and is shown in Figure 2.

Figure 1: Creating a new data dictionary


After choosing 'Connection to a new database', the wizard will
ask whether a database consisting of 'Free Tables' is to be
created, or whether an actual data dictionary is to be created.

A particularly nice thing about the Data Architect is that it can at will - write code (in several programming languages) to
recreate the modelled table. It can do the same with SQL,
writing SQL statements which will recreate the whole data
dictionary or a simple table. This is a useful feature also found in
the Lazarus Database Desktop, for instance.

Figure 2: Creating a new table


102

COMPONENTS
DEVELOPERS

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Using the Advantage Database Client Engine (continuation 2)


Either method can be used to insert code in an application to
create an empty Advantage Database when the application is
first installed and started on the end-user's system, so there is no
need to distribute the data dictionary or table files.
The following code, for instance, will create the 'contents' table.
It uses a TAdsTable component, called TContents:
Procedure TBlaiseModule.CreateContentsTable;
Function AddDef(AName: String;
AType: TFieldType): TFieldDef;
begin
Result:=TContents.FieldDefs.AddFieldDef;
Result.Name:=AName;
Result.DataType:=AType;
end;
begin
With TContents do
begin
TableName := 'contents';
TableType := ttAdsADT;
AdsTableOptions.AdsCollation := 'ansi';
end;
TContents.FieldDefs.Clear;
AddDef('ISSUE',ftInteger);
AddDef('STARTPAGE',ftSmallInt);
AddDef('ENDPAGE',ftSmallInt);
AddDef('AUTHOR',ftWideString).Size:=50;
AddDef('TITLE',ftWideString).Size:=100;
AddDef('ABSTRACT',ftWideMemo).Size:=1;
AddDef('Keywords',ftWideString).Size:=200;
AddDef('PageCount',ftSmallInt);
TContents.CreateTable;
end;

The QCreate component needs to be connected to a


TADSConnection instance. The SQL property (of type
TStrings) can be filled with an SQL statement that the
Advantage Server understands, and ExecSQL will execute the
query. The supported SQL syntax is documented in the
Advantage help file.
The Data Architect tool creates more than one SQL statement
per table since the table constraints (required fields, indexes and
so on) are not included in the CREATE TABLE statement, but
are created using stored procedures.
For instance, the following procedures will create an index on
the 'AUTHOR' field in the contents table and will set the
Required flag to True:
EXECUTE PROCEDURE sp_CreateIndex90(
'contents','contents.adi','AUTHOR',
'AUTHOR', '', 2, 512, ':en_US' );
EXECUTE PROCEDURE sp_ModifyFieldProperty (
'contents',
'AUTHOR', 'Field_Can_Be_Null',
'False', 'APPEND_FAIL', 'contentsfail' );

The complete list of stored procedures that can be used to


modify the data dictionary is included in the ADS help file.
Under normal circumstances, it is not necessary to know this list
as the Data Architect can be used to create the necessary
statements to recreate a table.
In addition to tables, it is also possible to create stored
procedures. Stored procedures are useful for reducing lengthy
operations' execution time by letting the server execute them.
The Advantage Data Architect lets you define stored
procedures, and also allows you to debug them, which is a very
handy feature. The following is an example of a stored
procedure that returns all records from the contents table that
have a particular word in the keywords or title field:

The code above will seem very familiar to programmers who


have used the TTable or TDbf components to create Paradox
or DBF tables. First you add every field to the TAdsTable
component's collection of FieldDefs. Once you have specified
all the needed field definitions and properties, the
CreateTable call creates both the table in the database and CREATE PROCEDURE KEYWORDARTICLES(
akeyword CHAR (50),
the data dictionary (the TAdsTable component must be
title CHAR (100) OUTPUT,
connected to a TAdsConnection instance).
author CHAR (50) OUTPUT,
The Advantage Data Architect can also write a set of SQL
issue Integer OUTPUT)
statements to recreate the tables. They can be executed using the BEGIN
DECLARE cursor1 CURSOR AS
TAdsQuery component like this:
Const
SCreateSQL = 'CREATE TABLE contents ('+
' ISSUE Integer,'+
' STARTPAGE Short,'+
' ENDPAGE Short,'+
' AUTHOR NVarChar(50),'+
' TITLE NVarChar(100),'+
' ABSTRACT NMemo,'+
' Keywords NVarChar(200),'+
' PageCount Short) IN DATABASE';

SELECT TITLE,KeyWords,Author,Issue FROM


CONTENTS;
DECLARE thelike varchar(55);
thelike=(select rtrim(ltrim(akeyword)) from
__input);
thelike='%'+thelike+'%';
OPEN cursor1;
While FETCH Cursor1 do
if (Cursor1.TITLE like thelike)
or (Cursor1.Keywords like thelike) then
Insert into __Output

begin
With QCreate do
begin
SQL.Text:=SCreateSQL;
ExecSQL;
end;
end;

values(Cursor1.title,cursor1.Author,Cursor1.is
sue);
end if;
end while;
Close Cursor1;
END;

procedure TBlaiseModule.CreateContentsSQL;

To receive a printed copy of the magazine


you need to go to our website to place your order

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

103

Using the Advantage Database Client Engine (continuation 3)


The syntax of this stored procedure is pretty self-explanatory.
The use of __input and __output to access the input and
output parameters is something you have to get used to, but
other than that, the syntax is straightforward and easy to learn.
You can then call this stored procedure like this:
EXECUTE PROCEDURE KEYWORDARTICLES('Editorial');

It returns a list of all articles containing the word 'Editorial'.


Note that three parameters for this procedure are of type
'CHAR', which necessitates some trimming prior to using the
parameter in the condition. The need for this is explained in the
'Getting data using stored procedures' section below.

Accessing Data
Now that the database has been created, accessing the data can
be done using one of three possible components:
TAdsTable This TDataset descendant can be used to
view all table data. It supports filtering and searching
using the standard TDataset properties and methods
(Filter and Locate). Two tables can easily be joined
together in a master-detail relationship using the
MasterSource and MasterFields properties, a well
known mechanism from the Delphi BDE and TDBF
components.
TAdsQuery This component can be used to run a
standard SQL SELECT statement if data from several
tables needs to be shown in a single dataset, or it can
be used to display a selection of records from a single
table.

IndexFieldNames This must be set to Issues. The


index is needed to filter the contents table on the
Issue field.
MasterSource This property must be set to DSissues
which tells the TContents component that it should
filter the records shown depending on what is in the
current record in the TIssues dataset.
MasterFields This must also be set to Issues. This field
will be used to filter the records using the value of
the matching field in the TIssues dataset. As the user
scrolls through the TIssues dataset, the TContents
dataset will automatically refresh itself.
Once this is done, an MDI Child form can be created. The unit
in which the datamodule is defined (dmBlaise) must be added
to the uses clause of the form. All that remains to be done is to
drop a couple of navigators and grid components on the form,
connect them to the datasources on the data module and the
form is ready to go. We also need to ensure that the datasets are
opened when the form is first shown:
procedure TTablesForm.FormShow(Sender:
TObject);
begin
BlaiseModule.TIssues.Open;
BlaiseModule.TContents.Open;
end;

The MDI child form is shown using a menu item in the


application's main form, and should look like Figure 3.
Figure 3: Browsing the contents of Blaise using TAdsTable
components

TAdsStoredProc This can be used to read data returned


The application is in fact ready to be used, and it's possible to
by a stored procedure (or to execute one if it does not add or edit records in both grids. To make sure that the masterreturn data).
detail relationship is respected when a new record is inserted in
the TContents dataset, a value is inserted in the Issues field:
To demonstrate this we will create a small application. It will
connect to the database that was created in the Data Architect.
The connection component (of type TAdsConnection and
named CBlaise) and the table components are all placed on a
data module (named BlaiseModule). This module will contain
the methods to create the tables indicated earlier:
Issues is a table that contains information about the
various issues of Blaise: the magazine number, the
date of publication, the issue's theme, and the number
of pages.
Contents is a table that holds information about the
articles in a single Blaise issue: title, author, keywords
etc. It is linked to the 'Issues' table using a foreign key
(an 'RI Object' in the Data Architect) through the
field 'Issue'.
For each of these tables, a TAdsTable component is dropped on
the module (the components are named TIssues and
TContents), and are linked to a set of TDatasource instances
(DSIssues and DSContents). Both are also connected to the
TAdsComponent.

The application is a MDI application, and one of the MDI


forms (TTablesForm) shows how to browse the issues using a
master-detail relationship between the two TAdsTable
components. In order to establish a master-detail relationship,
you have to set three properties of the TContents table:
104

COMPONENTS
DEVELOPERS

To receive a copy of
the printed magazine
you need to
order a subscription
from our website:
www.blaisepascal.eu

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Using the Advantage Database Client Engine (continuation 4)


procedure
TBlaiseModule.TContentsAfterInsert(DataSet:
TDataSet);
begin
If TIssues.Active then
TContentsISSUE.AsInteger:=
TIssuesIssue.AsInteger;
end;

Figure 4: Using a parameterised TAdsQuery


The 'Issue' column in the grid is hidden, so the user cannot
override the inserted value.

Parameterised queries
The same setup for browsing data can be coded using
TAdsQuery components. In fact, they are even better suited for
such situations: the TAdsQuery component supports
parameterised queries, and this mechanism can be used to create
master-detail relationships as well.
A parameterised query is one which contains a named
parameter. Here is an example:
SELECT
Issues.Date, Issues.Issue, Issues.Theme,
Contents.Title,Contents.Author,Contents.
Abstract,Contents.StartPage
FROM
Contents
Left Join Issues on
(Contents.Issue=issues.Issue)
WHERE
Keywords like :Keyword

The ':Keyword' indicates a parameter. Its value is so far


unknown, but the engine can already prepare the query, parse it,
check for syntax errors, allocate resources and calculate the
query plan. This only needs to be done once. The query might
be executed many times (each time with a different value for the
keyword parameter). Since the engine has already done the
preparatory work, it will therefore return a result faster.
In TAdsQuery, when one or
more parameters are detected in
the The ':Keyword' indicates a
parameter. Its value is so far
unknown, but the engine can
already prepare the query, parse
it, check for syntax errors,
allocate resources and calculate
the query plan. This only needs
to be done once. The query
might be executed many times
(each time with a different value
for the keyword parameter).
Since the engine has already
done the preparatory work, it
will therefore return a result
faster.
In TAdsQuery, when one or
more parameters are detected in
the SQL property, the Params
collection is filled with an item
for each named parameter. The
Params collection is used to
hold values for the parameters.
When the query is activated,
the value of the parameter is
fetched from the item in the
Params collection and supplied
to the database engine.
The above query can be used to
implement a window to search
for articles in the contents table,
based on the keyword.
Implementing this window is
very simple: we just need an edit control (EKeyword), a button
(BSearch) and a grid (GSearch). The grid is hooked up to a
TADsQuery instance (named QSearch) which is located on the
project's datamodule. The button's OnClick event handler
contains the following code:
procedure TSearchQueryForm.BSearchClick(Sender:
TObject);
Var S : String;
begin
S:='%'+EKeyword.Text+'%';
With BlaiseModule.QSearch do
begin
if not Prepared then
Prepare;
Close;
Params.ParamByName('KeyWord').AsString:=S;
Open;
end;
end;

We surround the value of the search term entered by the user


with wildcards. Then the query is prepared, closed (if it was still
open from a previous search run), supplied with the parameter
and then opened again. The result of a user entering a search
term is shown in Figure 4.

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

105

Using the Advantage Database Client Engine (continuation 5)


Now, how can this be used to establish master-detail relationships using queries? By connecting the query to another dataset
using the DataSource property, the TAdsQuery will, when
opened, fetch the parameter values (for parameters where no value
was given explicitly) from the connected dataset. At the same time,
when a user scrolls through the connected dataset, the query
component will react to this and re-open itself with new values
for all parameters. This can be demonstrated using two query
components. The first (QIssues) has an SQL statement like this:
SELECT * FROM Issues

The DataSource property of the QContents query is set to a


datasource connected to QIssues We can now program a form
in exactly the same way as was done with the two TAdsTable
components, and the result will look exactly the same. Except
this mechanism is vastly more powerful than the mechanism
with tables, since the use of parameters allows much more
freedom (and normally is also more speed efficient).

COMPONENTS
DEVELOPERS

The demonstration application shows this. However, there is a


descendant, called TAdsStoredProc, which was
created specially to deal with stored procedures, whether they
return a result or not. This descendant just needs the name of the
stored procedure it should execute or get data from. After that, it
behaves like a regular TDataset. The sample application also
demonstrates this component. A TADSStoredProc component is
dropped on the data module and named SPSearch after which it
is hooked up to the connection component. Finally, the name of
the stored procedure is set (KEYWORDARTICLES).
At this point, the Params
property of the
component will
automatically be filled with
the names of the
parameters.
Note that at the time of
writing, TAdsStoredProc
lacks support for Unicode
string parameters, which is
why the stored procedure
was declared using CHAR
typed parameters instead
of NVARCHAR.
The form which
demonstrates this is
identical to the form
which searches using a
regular query, but is of
course hooked to the
TAdsStoredProc instance,
which means there is a
slight difference when
passing the parameter
prior to opening the
dataset: As can be seen,
there is no need to enclose
it in wildcards. The form
will now look and behave exactly like the first search form.
TDataset

SELECT * FROM Contents where (Issue=:Issue)

106

We created a stored procedure earlier in this article which


essentially performs the same operation as the search query
presented above. Using the stored procedure is even more
efficient than the query, since it is already analyzed and prepared
from the moment the database is created. (There is an even more
efficient way than using a stored procedure, which is to use Full Text Search).
It is possible to use a parameterised TAdsQuery component to
execute the stored procedure and show its results. The following
SQL statement would do it:
EXECUTE PROCEDURE KEYWORDARTICLES(:KeyWord)

and the SQL property of the second query (QContents)


contains a parameter:

procedure
TSearchStoredProcForm.BSearchClick(Sender:
TObject);
begin
With BlaiseModule.SPSearch do
begin
Close;
Params.ParamByName
('aKeyWord').AsString:=EKeyword.Text;
Open;
end;
end;

Getting data using stored procedures

Conclusion
Advantage Database Server is a prime candidate for an
embedded SQL database solution because it is so easy to
deploy (two DLLs suffice for most installations). Added to
this, its excellent support for stored procedures and embedded
functions makes it very suitable for deployment with Object
Pascal applications. The free licensing scheme, easy upgrade
path to a complete client/server solution with remote
database, and last but not least its strict adherence to SQL
typing make it a preferred option compared to alternatives
such as sqlite.

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Introduction to Databases Part 5:


Configuring OLE DB Providers and ODBC Drivers By Cary Jensen
starter

expert

DELPHI 5 and above (Win32)

This is the fifth article in a series designed to introduce


you to Delphi database development.
In part 4 of this series I considered the various database
access mechanisms that Delphi supports out of the box.
Beginning with this installment, I will focus on some of
the individual data access mechanisms in greater depth.
This article takes a look at the configuration of drivers
for the most versatile data access mechanism supported
directly by Delphi, dbGo.
I begin with a quick overview, following by a discussion
of OLE DB Providers and their configuration.
I will then consider the Microsoft OLE DB Provider for
ODBC Drivers that is included with every installation of
Windows, which will give me the opportunity to show
you how you can use ODBC drivers in your Delphi
applications.
Overview of dbGo
As you learned in the preceding article in this series, dbGo is
Delphi's trademark for its data access mechanism that employs
OLE DB Providers. OLE DB Providers are part of Microsoft's
Data Access Components (MDAC), a collection of COM-based
technologies available in some form in every version of
Windows since Windows 95.
Since MDAC is natively available in the operating system, and
most database vendors provide an OLE DB Provider for their
database, dbGo offers Delphi developers with a convenient
mechanism for accessing most databases. Those database
vendors who do not supply an OLE DB Provider almost always
provide an ODBC driver. Fortunately, Microsoft provides an
OLE DB Provider that interfaces with ODBC drivers, making
MDAC the most comprehensive data access mechanism
available.
From the Delphi perspective, dbGo conforms to Delphi's
TDataSet interface. This makes it convenient for you to access
your data without having to learn an additional interface,
specifically ActiveX Data Objects, or ADO.
Making the Connection
You define how your dbGo components connect to your data
using a connection string. All four of the dbGo TDataSet
components (ADODataSet, ADOQuery, ADOTable, and
ADOStoredProc), as well as the ADOCommand component, have
a ConnectionString property. If you assign a properly configured
connection string to that property of any of these components
they can be activated and used to execute queries and/or stored
procedures on your database.

There is another benefit of using an ADOConnection. It


permits you to control whether or not the user will be prompted
for a username and password before they can successfully
connect to the database. When the
ADOConnection.LoginPrompt property is set to True
(its default), a dialog box is displayed to the user to collect a
username and password before a connection is attempted. If the
user fails to provide a valid username and password, the
connection will fail.
On the other hand, you can set LoginPrompt to false if you
provide another mechanism for supplying a username and
password. For example, you may display a custom dialog box for
retrieving the user's name and password. Alternatively, you could
hard code the username and password within your application.
Obviously, this alternative poses a significant security risk, in
that ts permits anybody who has access to the application to
access data. In addition, a hard coded password can be hacked,
unless you take specific measures to encrypt it within your
source code.
I'm not going to spend any more time considering the issues
associated with passwords and security, other than to
acknowledge that it's an important issue that you must take into
consideration. Instead, I am moving on to the issue of defining
a connection string
Using Connection Strings
If you are well versed in the configuration of ADO for your
particular OLE DB Provider, you could simply write your
connection string using any editor (such as Notepad) and assign
that text to the ConnectionString property of your
ADOConnection. What makes this task especially challenging is
that each OLE DB Provider has its own unique set of
properties. As a result, a connection string that you use to attach
to Microsoft's SQL Server is different from the one you use to
connect to IBM's DB2.
Fortunately, it is almost never necessary for you to become a
connection string expert since Windows provides you with the
Data Link Properties dialog box which simplifies the process of
creating connection strings. Furthermore, Delphi exposes this
dialog box as both a component editor for the ADOConnection
component, as well as a property editor for the
ADOConnection.ConnectionString property.

To display this dialog box, either right-click an ADOConnection


and select Edit Connection or click the ellipsis button that
appears when you select the ConnectionString property in the
In most cases, though, you will not define a connection string to Object Inspector. Delphi responds by displaying the
ConnectionString dialog box, shown in Figure 1.
individual dbGo TDataSets. You are more likely to use an
ADOConnection component, and then associate your dbGo
TDataSets (or ADOCommands) with that one connection through
their Connection properties, which are of type
TADOConnection. This allows you to configure a single
component (the ADOConnection) and share its connection
with those other components.

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

107

Configuring OLE DB Providers and ODBC Drivers (continuation 1)


I am going to continue this demonstration by
selecting the Microsoft Jet 4.0 OLE DB Provider,
which you can use to connect to Microsoft Access
database (*.mdb) files. This will be convenient, since
Delphi installs a Microsoft Access database as part
of its installation, and we will use this connection
string to connect to it.
With the Microsoft Jet 4.0 OLE DB
Provider selected, either click the Next button or
select the Connection tab. Windows responds by
displaying the Connection page of the Data Link
Properties dialog box, shown in Figure 3.

Figure 1
You use this dialog box to either enter a connection string (or
build it), or to select a data link file. Data link files are discussed
later in this article, so I will focus here on building the
connection string. To build the connection string, click the
button labeled Build. Windows responds by displaying the
Provider page of the Data Link Properties dialog box, shown in
Figure 2.
The Data Link Properties dialog box has four pages. You use the
Provider page of this dialog box to select which installed OLE
DB Provider you want to use to connect to data. Windows ships
with a rather generous collection of OLE DB Providers,
including providers for SQL Server, Oracle, and MS Access.
These can be seen in Figure 2.
If you have installed additional OLE DB Providers, those will
appear on this page as well As you can see from Figure 2, I have
two custom OLE DB Providers: One for the Advantage
Database Server and another for IBM's AS400.

Figure 3
Here you select the database to which you want to connect.
If you are using an OLE DB Provider that connects to a remote
database, you might have additional options, such as selecting
the server on which your database is installed. In this case,
you can use the ellipsis button to select the Microsoft Access
.mdb file and optionally provide a username and password
(assuming that the database is encrypted).
To select the MS Access database installed by Delphi, click the
ellipsis button () and navigate to C:\Program Files
(x86)\Common Files\CodeGear Shared\Data
(this directory might be different if you are using Delphi XE2 or later or
Delphi 2005 or earlier). Select the dbdemos.mdb file located in
that directory and select Open.
You may have to supply additional information, but not in this
case, since the dbdemos.mdb file is not encrypted.
You are now ready to test your connection.
Click the Test Connection button. If everything is ok, you will
see a dialog box similar to the following.

Figure 2
SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

109

Configuring OLE DB Providers and ODBC Drivers (continuation 1)

Figure 4
Select OK to close this dialog box, then select Next or click the
Advanced tab to continue to the Advanced page of the Data
Link Properties dialog box, shown in Figure 5.

Figure 6
Here is where you can see that OLE DB Provider properties,
and the corresponding connection string values, are specifically
associated with the OLE DB Provider you selected. If you have
selected any other OLE DB Provider from the Provider page of
the Data Link Properties dialog box the properties displayed on
the All page would be significantly different. Examples of
properties that you might encounter here include whether or not
to employ a connection pool, the connection pool size,
connection timeout, and transaction isolation level, among
others.
Again, we don't need to make any additional changes in this
case, so click OK to accept the connection string you have built
and return to the ConnectionString dialog box. The newly
constructed connection string will now appear in the
Connection String field, as shown here.

Figure 5
You use this page of the Data Link Properties dialog
box to define additional connection information,
including what locking mechanism you want to use to
connect to the database. In this case we don't need to
make any changes. Click the All tab to display the All
page of the Data Link Properties dialog box, shown in
Figure 6.

Figure 7

110

COMPONENTS
DEVELOPERS

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Configuring OLE DB Providers and ODBC Drivers (continuation 1)


Finally, select OK to close the ConnectionString dialog box. The
connection string that you have defined will now appear in the
ConnectionString property of the ADOConnection component.
If you now connect an ADOTable component to the
ADOConnection, and select the ADOTable.TableName
property in the Object Inspector, you will see the names of the
tables in the dbdemos.mdb file to which the connection string
points, as shown in Figure 8.

The purpose of a data link file is to store connection string


information outside of the application. When setting up the
connection string for an ADOConnection component, you can
select the data link file by selecting the Use Data Link File radio
button on the ConnectionString dialog box (shown in Figure 1).
You can then select from one of the data link files that reside in
the data link file directory from the provided combo box.
Once you have selected a data link file,
Delphi assigns this fully-qualified
filename to the ConnectionString
property using the following form:
FILE NAME=path\filename.udl

When you attempt to connect an


ADOConnection component to its
database when a data link file is
employed, Windows expands the
contents of the data link file to a fully
formed connection string.
Creating Data Link Files
Creating a data link file is actually very
simple, and is it demonstrated in the
following steps. These steps assume that
you are running a 32-bit version of
Windows, which will surface the 32-bit
Data Link Properties dialog box (the one
displayed by Delphi XE and earlier). If you
are running 64-bit Windows, this
technique will display the 64-bit Data Link
Properties dialog box, which will only
include the 64-bit OLE DB Providers.
Begin by opening Notepad as an
Administrator. Next, select Save.

Figure 8
It is worth noting that all of an OLE DB Provider's properties
have default values, and the values that appear in the connection
string are only for those properties where you did not select the
default property value.
Using Data Link Files
A data link file is a file with a .udl extension which contains the
information necessary to connect to a data store using an OLE
DB Provider. In most cases, this file is stored in a directory
defined in the Windows registry. Fortunately, the ADODB unit
in Delphi also surfaces a function, DataLinkDir, which returns
the fully qualified path to this directory. In a typical Windows 7
installation, this directory is c:\Program Files
(x86)\Common Files\System\OLE DB\Data Links for
32-bit applications, and c:\Program Files\Common
Files\System\OLE DB\Data Links for 64-bit
applications.

Set the Save as type dropdown to Any file


(*.*) and then proceed to save a file
named MyData.udl in the directory
returned by the DataLinkDir function.
(On my system, I actually had to create
this directory.) Once you have saved the
file, close Notepad and use the Windows
Explorer to navigate to the directory
where you saved the file. From there,
right-click the MyData.udl file and select
Open with | OLE DB Core Services (on
older Windows systems it was sufficient to simply select Open).
Windows responds by displaying the Data Link Properties
dialog box shown earlier in this article.
Configure the connection string as you would do normally.
When you finally close the Data Link Properties dialog box, the
non-default property data is written to the data link file in plain
text. For example, if you configured the data link file to use the
Microsoft Jet 4.0 OLE DB Provider (as described earlier), the
contents of the data link file will look like that shown in the
following listing:
[oledb]
; Everything after this line is an OLE DB initstring
Provider = Microsoft.Jet.OLEDB.4.0;
Data Source = C:\Program Files (x86)\Common
Files\CodeGear Shared\Data\dbdemos.mdb

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

Pag 111

Configuring OLE DB Providers and ODBC Drivers (continuation 1)


If you are running Windows 7 64-bit, you can use the following
command from the command prompt (run as Administrator) to
launch the 32-bit version of the Data Link Properties dialog
box. Due to the length of the command, it appears here on
more than one line, but it must be entered into the command
prompt as a single statement.

To do this, navigate to the C:\Windows\SysWOW64 directory.


There you will find a program named odbcad32.exe.
Run this program to display the ODBC Data Source Administrator
for 32-bit ODBC drivers, shown in Figure 10.

C:\Windows\syswow64\rundll32.exe "C:\Program Files


(x86)\Common Files\System\OLE DB\oledb32.dll",
OpenDSLFile mydata.udl

Once the data link file has been created, you can use the
ConnectionString dialog box to select it, as shown here.

Figure 9

Figure 10

One of the more important decisions you will make will be to


choose between using a user, system, or file DSN. A user DSN
can be only used by the user who is currently logged onto the
machine. A system DSN, by comparison, can be used by any
user (and this is very important if you are creating a DSN that will be
accessed from a non-user account, such as those accessed from a Windows
service or an ISAPI Web server extension). Like system DSNs, file
While ODBC is a rather old technology (it is just slightly newer DSNs can be used by any user, but the connection information
than ODAPI, the Open Database API, now referred to as the
is stored in a physical file.
Borland Database Engine for Windows), it is nonetheless a
There is more to configuring an ODBC DSN, but those topics
universal data access mechanism available in Windows. For those are beyond the scope of this article.
databases that do not provide an OLE DB Provider, they are
almost guaranteed to provide a suitable ODBC driver.
Summary
Using ODBC Drivers
ODBC is a data access mechanism based on the openSQL call
level interface (CLI). It provides a generic mechanism for
executing SQL (structured query language) statements against a
database. In order to access a particular database using ODBC,
an appropriate ODBC driver must already be installed.

Delphi's dbGo components permit you to use Microsoft's Data


Access Components, including ADO (ActiveX Data Objects), a
COM-based mechanism built into the Windows operating
system for access your data. While most database vendors
provide custom OLE DB Providers for accessing their data,
those who do not will likely provide an ODBC driver. Using the
Microsoft OLE DB Provider for ODBC drivers, you can also
access these drivers using dbGo.
In the next installment in this series, I will take a more detailed
look at accessing data using ClientDataSets.

While you can still use installed and configured ODBC drivers
from the Borland Database Engine, the preferred way to use
ODBC drivers is to use them through Microsoft's OLE DB
Provider for ODBC Drivers.
Once you have chosen the Microsoft OLE DB Provider for
ODBC Drivers on the Provider page of the Data Link
Properties dialog box (see Figure 2), you can advance to the
Connection page to select the Data Source Name (DSN)
associated with your installed and configured ODBC Driver.
Installation of an ODBC driver is performed by the software
provided by the ODBC Driver publisher. Configuration of the
DSN is something that you must do (or can be done by your
installation program, if it is ODBC aware).
If you are running 32-bit Windows, you select the Data Source
(ODBC) applet from the Administrative Tools applet on the
control panel.
If you are running 64-bit Windows, and want to use a 32-bit
ODBC driver from a 32-bit Delphi executable, things are a little
more complex. Instead of using the Data Source (ODBC)
applet from Administrative Tools (which only works with 64-bit
ODBC drivers), you must run the 32-bit version of the ODBC
Data Source Administrator.
112

COMPONENTS
DEVELOPERS

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Delphi XE2 LiveBinding


starter

expert

By Bob Swart

DELPHI XE 2

Since its very first version Delphi has offered so-called


data-aware VCL controls to provide data access. For the
FireMonkey Application Platform, Embarcadero decided
to implement a different way of data binding, not just for
a particular set of data-aware components, but for all
visible (and even invisible) components. This technique
is called LiveBinding, and you should realize that XE2
carries the first implementation of this, and there will be
enhancements in future versions of Delphi.
The good news is that LiveBinding is not limited to
FMX, but is also available for VCL controls and
applications. In fact, it will probably be easiest to start
with a VCL example to demonstrate the capabilities of
LiveBinding.

The Links group consists of a TBindLink (to link datasets to


lists for example), TBindListLink (to link a component to a
list), TBindGridLink (to link a component to a grid) and
TBindPosition (to keep track of the position).
Finally, the Lists group consists of a TBindList (binding
components to lists) and TBindGridLists (binding
components to grids).
For the first example, we can use a simple bind expression
between the TEdit and TLabel. Since I want to keep the
TLabel synchronised with the TEdit, the obvious choice
here is the TBindExprItems class.

As an example, create a Delphi XE VCL Forms Application,


and place a TEdit and a TLabel component on the form. We
can now define a LiveBinding between the TEdit and the
TLabel, updating the contents of the TLabel when the
contents of the TEdit changes.
Since the TLabel is the component that needs to be
controlled, we should start there. Select the TLabel
component and look for the LiveBinding property in the
Object Inspector. If you open the drop-down combobox for the
value of the LiveBinding property, you can select the New
LiveBinding option. Selecting that will result in a New
LiveBinding dialog where we can specify the type of
LiveBinding that we want to add to the TLabel control:

Figure 2
This choice will create a BindExprItemsLabel11 instance
for the LiveBindings property of the Label1 control. We
need to set a number of properties: the SourceComponent
should point to Edit1, and the Category should be set to
Binding Expressions already. Then, we should define the
ClearExpressions (to define what the Label will show
initially) and the FormatExpressions (to define how the
Label should mimic the Edit value).
However, before we define these two Expressions, lets first take
a look at the form, and notice that we suddenly have an extra
non-visual component: the TBindingsLists. If you
double-click on the TBindingsList component, you'll get a
list of all LiveBindings, and can select them individually to edit
the properties (including the FormatExpressions and
ClearExpressions.

Figure 1
There are eight different LiveBinding types available here,
divided into three logical groups. The Binding Expressions
group consists of a simple TBindExpression (for a
binding expression) and a TBindExprItems (for keeping
controls synchronized).
To receive a printed copy of the magazine
you need to go to our website to place your order

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

113

Delphi XE2 LiveBinding (continuation 1)

Figure 3
Let's define the FormatExpressions now. Click on the
ellipsis for the FormatExpressions property in the Object
Inspector, which will show another window where we can specify
the Control Expression (from the TLabel) as well as the Source
Expression (from the TEdit).

Figure 4
Here, we can specify property names, like the Caption of the
TLabel and the Text property of the TEdit. But we can
also stipulate that the TLabel should show the
UpperString version of the Text. Right-click in the
FormatExpressions list and Add a new item. Set the
SourceExpression to Text (from the Edit1) and the
ControlExpression to Caption (from the TLabel)

procedure TForm5.Edit1Change(Sender: TObject);


const {$J+}
Busy: Boolean = False;
begin
if not Busy then
begin
Busy := True;
try
BindingsList1.Notify(Sender, '')
finally
Busy := False
end
end
end;

The effect of this additional implementation can be seen


immediately. Any change to the TEdit's Text property
is immediately reflected in the TLabel's Caption.
While this took a very impressive number of steps, we
could just have implemented the OnChange event handler of
the TEdit as follows:
Label1.Caption := Edit1.Text;

for the same effect. I totally agree, but I just wanted to start with
a simple example, before showing some LiveBinding examples
that may not be very trivial to implement otherwise.
But before we move to other examples, let's try to extend this
example a little bit. Instead of simply using the Text as
SourceExpression, we can also add expressions here, like
"UpperCase:" + UpperCase(Text)

Note that any literal text should be placed in double-quotes, and


we can call a limited number of functions in the Expression, like
UpperCase and LowerCase.
An almost useful example of LiveBinding can be given for a
TMemo that starts with its height equal to a single line, but
which grows as soon as the number of lines increases. For this,
Figure 5
we have to set both the SourceComponent and the
This will be enough to set the Caption of the TLabel to the ControlComponent to the same TMemo, and the
Text property of the TEdit. However, if we make changes to SourceExpression to something like Lines.Count *
22, to result in a height of 22 pixels for each line in the Lines
the TEdit's Text, we should notify the LiveBinding that
property. Of course, we also need to implement the OnChange
updates may be required. For this, we can implement the
event
handler to ensure that the TMemo will actually grow while
OnChange event of the TEdit as follows:
we're
typing.
Note that a modification caused by a LiveBinding can cause
another change (I'll give an example shortly), which can lead to
an endless recursive loop, so it's safest to use a semaphore
(called Busy) to ensure that we're not notifying the LiveBindings
To receive a printed copy of the magazine
if we're already being notified of changes.
you need to go to our website to place your order
114

COMPONENTS
DEVELOPERS

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Delphi XE2 LiveBinding (continuation 2)


LiveBinding to a StringGrid
A more complex example of LiveBinding involves a StringGrid
(not a TDBGrid, but a regular TStringGrid, which is
normally hard to bind to a dataset). We should also have a
TDataSet filled with data, such as a TClientDataSet
connected to the biolife example data. We also need a
TBindScopeDB component, connected to the
TDataSource which in turn is connected to the
TClientDataSet (or any other dataset you want to bind to
the StringGrid).

We can optionally also enter a FormatColumnExpression, to


control how the column should be formatted, for example the
width of the column or the title on top of the column. For the
column position in the StringGrid, we have to specify Cells with
two arguments: the first one for the column, and the second one
for the row (this should always be 0 if we want to assign to the
header of the StringGrid of course).
We can use the Object Inspector to enter two
FormatColumnExpressions for the aforementioned examples, as
follows:

Figure 8

Figure 6
We should create a new LiveBinding for the StringGrid, and this
time select the TBindGridLink type. The
SourceComponent of the new LiveBinding should point to
the TBindScopeDB component (connected to the dataset).
Then, we can define the ColumnExpressions using the ellipsis in
the Object Inspector to show the ColumnExpressions dialog.
Individual TColumnExpression items have a
ColumnName, ColumnIndex (starting at 0),
SourceMemberName (the fieldname in the dataset), and a
FormatColumnExpressions item. The latter consists of
a SourceExpression (from the DataSet) and a
ControlExpression (from the StringGrid).

Become
BLAISE PASCAL MAGAZINE
subscriber ...

To cut a long story short, let's configure the first


ColumnExpression as follows: ColumnName = Species No,
SourceMemberName = Species No (during the typing of
this string, the IDE will try to auto-fill it as Species Name,
which comes before Species No alphabetically of course). We
also need a FormatCellExpression to specify that the
contents of the Species No field will end up in some cell of
the StringGrid. So, add a FormatCellExpression with
Control Expression = Cells[0] (the left-most cell of the line in
the StringGrid) and Source Expression = AsString. This will
ensure that for each record in the dataset, the AsString value
of the Species No field is shown in Cells[0] of the
StringGrid (where each record will get its own row in the
StringGrid).

Figure 7
SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

A special offer for all Delphi users:


Anyone who buys a new DELPHI product and takes out
a new subscription for BLAISE PASCAL MAGAZINE
(whether for a year or more) will receive the Blaise Pascal
Library on a 4GB USB stick for free, including future
free updates to include all issues to the end of 2011. The
last update will be available in January 2012 through our
web service. The free USB stick has approximately 2.8 GB unused
capacity.
Existing magazine subscribers renewing their
subscriptions have two options:
- You can buy the Blaise Pascal Library on a 4GB USB
stick for the discounted price of 15 (the cost to nonsubscribers is 50)
- You can download the Blaise Pascal Library for free
as a 1.2GB ISO file for burning to a DVD of your own.
This also includes free access to future magazine
issues updated to the end of 2011. The last update will
be available in January 2012 through our web service.
The Blaise Pascal Library is available to non-subscribers
on a USB stick, priced at 50
The Blaise Pascal Library contains all issues both
articles and code, including program examples, totalling
17 English issues (Issue 1 up to Issue 17), with all the
code ever published in Blaise Pascal Magazine:
17 complete issues on a 4GB USB stick
850 pages of articles
very fast search facility
comprehensive index covering all issues
locate the article of interest with one click
separate code index
separate index by author
overall index for complex searches covering all articles
all code completely accessible, linked to the relevant
article

COMPONENTS
DEVELOPERS

115

Delphi XE2 LiveBinding (continuation 3)


We can repeat this for the fields Category, Common_Name, Species_Name, Length_In, etc.. However, rather
than using the Object Inspector to start the item's editors each time, we can use a more direct approach now.
If we double-click on the TBindingsList component on the form, we get the BindingsList Editor,
which looks like the following illustration:

Figure 9
In order to work on the BindGridLinkStringGrid11, we can double-click on that line in the above
dialog. This will produce a dialog where we can directly edit the expressions for the Columns of the
StringGrid. Right now, only one column exists, for the Species No:

Figure 10
Right-click on the Columns node to add new columns, and then use the Object Inspector to set the ColumnName and
SourceMemberName (for example both to Category), and then use the LiveBindings Expressions dialog to select the
ColFormat, CellFormat and optionally CellParse nodes, right-click on any of them to add a new item, and edit them
using this Binding Expression Editor. For example, right-click on the ColFormat node for the new Category column, and add a
new ColFormat expression, this will produce the following display in the Binding Expression Editor:

116

COMPONENTS
DEVELOPERS

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Delphi XE2 LiveBinding (continuation 4)

Figure 11
Right-click on the Columns node to add new columns, and then use the Object Inspector to set the ColumnName and
SourceMemberName (for example both to Category), and then use the LiveBindings Expressions dialog to select the
ColFormat, CellFormat and optionally CellParse nodes, right-click on any of them to add a new item, and edit them
using this Binding Expression Editor. For example, right-click on the ColFormat node for the new Category column, and add a
new ColFormat expression, this will produce the following display in the Binding Expression Editor:

Figure 12

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

117

Delphi XE2 LiveBinding (continuation 5)

Figure 13
In a similar way we can add a new Expression item for the
CellFormat, set the Control Expression to Cells[1] and
the Source Expression to AsString. Leading to the following
total of five expressions for the two columns in the StringGrid:

Figure 15

Figure 14
When running this VCL Forms application with an active TClientDataSet, the result is very similar to a TDBGrid, only this
time we're actually seeing the data inside a regular TStringGrid.
I leave it as exercise for the reader to add the other fields and columns, which shouldn't be hard now. This will take a bit more
effort to produce using regular Delphi code (without using data-aware controls), and the usefulness of LiveBindings becomes
even more apparent if you realize that FireMonkey, the cross-platform GUI framework, does not have a concept of data-aware
controls, but only relies on LiveBindings to put any data in components, including datasets in edits or grids. In fact,
FireMonkey makes it even easier, as we'll see when we create a Mac OS X demo project

118

COMPONENTS
DEVELOPERS

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Delphi XE2 LiveBinding (continuation 6)


FireMonkey LiveBindings
In order to create a FireMonkey application for the Mac, we should first create a
FireMonkey HD Application for Delphi (which initially will be for Win32), and then
add a new Target Platform for OSX32 (see my earlier article about the steps to add a
FireMonkey OS X project and how to run and deploy it). For the LiveBindings
demo, and especially for the one that shows the biolife data in a TStringGrid
like the last VCL LiveBindings example, we need to start with a
TClientDataSet and a TDataSource, exactly like we did for the VCL
version. The biolife example data can be found in the
C:\Users\Public\Documents\RAD Studio\9.0\
Samples\Data directory as biolife.xml. However, after assigning the
FileName property of the TClientDataSet, we should open the
TClientDataSet (by setting the Active property to True), and then clear the
FileName property again. Obviously, the Windows fully qualified path will be
invalid when run on the Mac. However, with the TClientDataSet open, the data
will be stored in the form file (which is .fmx for FireMonkey by the way, where VCL
uses .dfm). Make sure to connect the TDataSource to the
TClientDataSet, in the usual way, and then place a TStringGrid on
the FireMonkey form. The display will be a bit different than usual, with the
black border, and the TStringGrid itself will also look a bit different at
design-time.

Figure 17
If you click on OK now, then two new components
will be created and placed on the FMX form:
a TBindScopeDB and a TBindingList (that
we saw before in the VCL example). Also, the
TStringGrid is filled with the live data from
the TClientDataSet:

Figure 16
The TStringGrid has an Align
property that works in FireMonkey as
well. We get a bit more choices
however:
alBottom, alCenter,
alClient, alContents,
alFit, alFitLeft,
alFitRight, alHorizontal,
alHorzCenter, alLeft,
alMostBottom, alMostLeft,
alMostRight, alMostTop,
alNone (the default), alRight,
alScale, alTop,
alVertCenter,and
alVertical.

Figure 18
For this demo, I've selected alFitLeft.
Next, we can open up the LiveBindings property of the
TStringGrid, and notice that apart from a choice New
LiveBinding (that we saw in the VCL world), there is now also a
choice Link to DB DataSource. If we pick the latter, we get
a dialog where we can select the datasource (DataSource1).

We can even open up the DataSource, to see which fields


are exposed and made available by selecting that particular
DataSource. Where did the magic come from? If you
double-click on the BindingList, you'll see a DB links
category (one that isn't available to select in the VCL world), and the
DBLinkStringGrid11 that links the grid control
StringGrid1 to the source BindScopeDB1 (which was
also autocreated):

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

119

Delphi XE2 LiveBinding (continuation 7)

Figure 19
If you double-click on the DBLinkStringGrid11, you can see all the individual Columns
and Expressions that were generated automatically (and were generated by hand in the VCL example).

Figure 20
Finally, to illustrate the fact that LiveBindings are not only more important, but also better implemented for FireMonkey
compared to the VCL, if we add a new LiveBinding in a FireMonkey application, the list of available choices is far more extensive,
and includes a new category DB Links with TBindDBEditLink, TBindDBTextLink, TBindDBListLink,
TBindDBImageLink, TBindDBMemoLink, TBindDBCheckLink, and TBindDBGridLink (the latter was used in
our recent example).

120

COMPONENTS
DEVELOPERS

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

Delphi XE2 LiveBinding (continuation 8)


Also, to show the image, I've added a TImageControl,
with Align set to alClient, and used the
LiveBinding property to create a New DB Link to the
Graphic field. BTW, running the application on the Mac OS X
would result in an error regarding a missing libmidas.dylib
image,

Figure 21

Figure 22

Obviously, the DB Links are less important for VCL


applications since we already have data-aware controls in the
VCL. In closing, the last screenshot shows the Biolife table in a
FireMonkey form running on Mac OS X. Note that I've added a
few more controls. A TBindNavigator with the
BindScope set to BindScopeDB1 and the Align property
set to alTop. The Align setting will overlap with the
TStringGrid with the Align set to alFitLeft.
We have to change the Align of the TStringGrid to alLeft
to correct that.

which means we have to add the libmidas.dylib from the


C:\Program Files\Embarcadero\RAD Studio
\9.0\binosx32 directory to the list of Deployment files:
BTW, running the application on the Mac OS X would result in
an error regarding a missing libmidas.dylib image, which means
we have to add the libmidas.dylib from the C:\Program
Files\Embarcadero\RAD Studio\9.0\binosx32
directory to the list of Deployment files:

Figure 23
Summary
In this article, I've shown what LiveBinding is, how it works (using a number of simple to more complex demos) for both VCL and
FireMonkey applications. Since the latter does not include data-aware controls, the importance of LiveBindings for
FireMonkey is enormous. There may be enhancements and changes to the implementation of LiveBindings (and some extra
documentation is also welcome), but for now it certainly is a good start.
Bob Swart (Bob@eBob42.com)
Bob Swart Training & Consultancy www.bobswart.com

SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18

COMPONENTS
DEVELOPERS

121

SUMMER OFFER:

LAZARUS

the complete guide


Blaise Magazine is making a summer deal available
to all our subscribers. If you purchase the newly published book
(Lazarus the Complete Guide)
we will include with it the following bonus items at no extra
charge:
- a Windows installer CD for Lazarus version 0.9.30
- a preinstalled copy Lazarus on a free 4GB USB stick.
- 50 additional sample projects
-

Blaise Pascal Magazine Library

17 complete issues on a 4GB USB stick


850 pages of articles
very fast search facility
comprehensive index covering all issues
locate the article of interest with one click
separate code index
separate index by author
overall index for complex searches covering all articles
all code completely accessible, linked to the relevant article
- extra: additional preinstalled component suites

PAPER BACK 50,00 + postage


HARDCOVER 60,00 + postage

COMPONENTS
DEVELOPERS

Pag 87

For Android, iOS, Linux, Mac,


Win CE, Windows XP / 7

Become

BLAISE PASCAL MAGAZINE


subscriber ...

A special offer for all Delphi users:


Anyone who buys a new DELPHI product and takes out a new subscription for BLAISE
PASCAL MAGAZINE (whether for a year or more) will receive the Blaise Pascal Library on a 4GB USB
stick for free, including future free updates to include all issues to the end of 2011. The last
update will be available in January 2012 through our web service. The free USB stick has approximately 2.8 GB
unused capacity.
Existing magazine subscribers renewing their subscriptions have two options:
- You can buy the Blaise Pascal Library on a 4GB USB stick for the discounted price of 15
(the cost to non-subscribers is 50)
- You can download the Blaise Pascal Library for free as a 1.2GB ISO file for burning to a
DVD of your own. This also includes free access to future magazine issues updated
to the end of 2011. The last update will be available in January 2012 through our web service.
The Blaise Pascal Library is available to non-subscribers on a USB stick, priced at 50
The Blaise Pascal Library contains all issues both articles and code, including program
examples, totalling 17 English issues (Issue 1 up to Issue 17), with all the code ever published in
Blaise Pascal Magazine:
17 complete issues on a 4GB USB stick
850 pages of articles
very fast search facility
comprehensive index covering all issues
locate the article of interest with one click
separate code index
separate index by author
overall index for complex searches covering all articles
all code completely accessible, linked to the relevant
article
Here are the new company subscription prices:
Blaise Pasacal Magazine is now published 6 times a year (bimonthly).
The magazine has a minimum of 60 full colour pages.
Company subscription rates:
single person annual subscription by download 35
5-person annual subscription by download 150
10 -person annual subscription by download 300
50 -person annual subscription by download 1125
100 - person annual subscription by download 2250
single person annual subscription for printed copies 50 (excluding postage)
5-person annual subscription for printed copies 225 (excluding postage)
10-person annual subscription for printed copies 425 (excluding postage)
50-person annual subscription for printed copies 2250 (excluding postage)
100-person annual subscription for printed copies 4250 (excluding postage)

David I

Marco
Cant

Delphi-Tage 2011 in Cologne (Kln)


The meanwhile seventh Delphi - Days in Cologne- Gremany took place during the weekend of 8 - 10 September 2011
in the so called MediaPark in Cologne. A large part of the visitors arrived at the Community-night (Friday) had an
incredible enjoyable dinner in the Restaurant "Frh am Dom". Impressive were the steps you needed to go up and
down in the verry old catacombs, the beer was hard needed - it was incredible warm that day. I met a lot of the very
international crowd over there. It was a very good organized and meaningfull meeting: 280 vsistors only here!

You might also like