Blaise 9UK PDF

Download as pdf or txt
Download as pdf or txt
You are on page 1of 40

BLAISE PASCAL MAGAZINE

Pascal

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


AND RELATED LANGUAGES

LEDS

LEDS

LEDS

LEDS
LEDS

LEDS

The 3D spheres generator - David Dirkse page 5


Christmas is a comming...
What is kbmMW? - By Benno Evers page 7
An explanation how to install
Delphi 2010 Feature Highlight - Debugger Visualizers - Jeremy North page 12
A fantastic alternative for its expensive competitors, and its even cheaper
Introduction to multithreading - Primo Gabrijeli page 18
Explains a lot
Writing Delphi Components III:
Compound Components and Custom Event - Marco Cant page 21
In the new Delphi versions it looks all different.
Talking Delphi - Henk Schreij page 25
Delphi talking back to you...
LCD Interfacing: Driving a HD44780 LCD in Delphi - Thiago Batista Limeira page 28
Get text on an lcd panel
Exploring the inplace editing capabilities of TAdvStringGrid By Bruno Fierens page 34
Beautiful features

December 2009
Publisher: Foundation for Supporting the Pascal Programming Language
in collaboration with the Dutch Pascal User Group (Pascal Gebruikers Groep)
Stichting Ondersteuning Programmeertaal Pascal
Cover price Europe: 10.00 / UK 10.00 / US $ 10.00

BLAISE PASCAL MAGAZINE 9


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

CONTENTS

Volume 8, ISSN 1876-0589

Articles
The 3D spheres generator
David Dirkse page 5
Christmas is a comming...
What is kbmMW?
By Benno Evers page 7
An explanation how to install
Delphi 2010 Feature Highlight - Debugger
Visualizers
Jeremy North page 12
A fantastic alternative for its expensive competitors, and its even
cheaper
Introduction to multithreading
Primo Gabrijeli page 18
Explains a lot
Writing Delphi Components III:
Compound Components and Custom Event
Marco Cant page 21
In the new Delphi versions it looks all different.
Talking Delphi
Henk Schreij page 25
Delphi talking back to you...
LCD Interfacing: Driving a
HD44780 LCD in Delphi
Thiago Batista Limeira page 28
Get text on an lcd panel
Exploring the inplace editing
capabilities of TAdvStringGrid
By Bruno Fierens page 34
Beautiful features

Columns
Foreword, page 4
Readers write... page 4
BLAISE PASCAL TROPHY: the winners page

10

Advertisers
Advantage Database Server page 3
Barnsten page 27
Components for Developers page 40
Datanamic page 6
Delphi Special classic editions upgrade pricing page 39
Fastreport for VCL page 17
Fastreport for .Net page 20
RT science page 24

Editor in chief
Detlef D. Overbeek, Netherlands
Tel.: +31 (0)30 68.76.981 / Mobile: +31 (0)6 21.23.62.68
News and Press Releases
email only to [email protected]
Authors
B Peter Bijlsma,
C Marco Cant,
D David Dirkse, Frans Doove,
G Primo Gabrijel! i! ,
N Jeremy North,
O Tim Opsteeg,
P Herman Peeren,
S Henk Schreij, Rik Smit, Bob Swart,
V Hallvard VassBotn.

Editors
Rob van den Bogert, W. (Wim) van Ingen Schenau,
M.J. (Marco) Roessen.
Corrector
A.W. (Bert) Jonker, M. L. E. J.M. (Miguel) van de Laar
Translations
M. L. E. J.M. (Miguel) van de Laar,
Kenneth Cox (Official Translator)
Copyright See the notice at the bottom of this page.
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

(prices have changed)


1: Printed version: subscription 50.-(including code, programs and printed magazine, 4 issues
per year including postage.
2: Non printed subscription 30.-(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
[email protected]
Subscriptions can start at any date. All issues published in the
calendar year of the subscription will be sent as well.
Cover price in Europe:
12.50 / UK 12.00 / US $ 18.00 plus postage.
Subscriptions are parallel to the calender
year. Subscriptions will not be prolonged without notice.
Receipt of payment will be sent by email. Invoices will be
sent with the March issue.
Subscription can be paid by sending the payment to:
ABN AMRO Bank Account no. 44 19 60 863
or by credit card: Paypal or TakeTwo
Foundation for Supporting the Pascal Programming Language
(Stichting Ondersteuning Programeertaal Pascal)
IBAN: NL82 ABNA 0441960863
BIC ABNANL2A VAT no.: 81 42 54 147
(Stichting Programmeertaal Pascal)
Subscription department
Edelstenenbaan 21 3402 XA IJsselstein, The Netherlands
Tel.: + 31 (0) 30 68.76.981/Mobile: + 31 (0) 6 21.23.62.68
[email protected]

Copyright notice
All material published in Blaise Pascal is copyright SOPP Stichting Ondersteuning Programeertaal Pascal unless otherwise noted and may not be copied,
distributed or republished without written permission. Authors agree that code associated with their articles will be made available to subscribers after publication by
placing it on the website of the PGG for download, and that articles and code will be placed on distributable data storage media. Use of program listings by subscribers
for research and study purposes is allowed, but not for commercial purposes. Commercial use of program listings and code is prohibited without the written
permission of the author.

Page 2 / 2156

COMPONENTS
DEVELOPERS

December 2009 BLAISE PASCAL MAGAZINE 9

Foreword
It is already a white Christmas over here.
Lots of snow and children's enthusiastic voices.
Love that.
We have seen another year of Embarcadero
working hard to fulfill its roadmap. Yet there is no
version of Delphi for the beginners.
So because of that, we have made the choice to
bring out a special CD for Lazarus and to support
it (Windows, Linux (ready to launch) and Mac, so
beginners can start at low costs. Demo programs
etc. are on the CD's.
A little Christmas present from us:
To have fun at the web without problems,
we have created a special Internet CD.
This CD starts up without needing your Operating
System (there is a small version of Linux on it),
only the hardware. You can give that CD to any
person, young or old and even children, because
they can do nothing wrong. It can't store any data
because of it being on a CD. Useful for People
who are scared by using your system, or not
experienced with Internet.
Your PC or notebook is not harmed in any way,
no viruses or other unwanted nonsense.
Yet, if you want to store any data from the website
you still can do that to your USBstick.
To get the CD Free Internet, all you have to do
is download the ISO and burn it, and if you want
it in a beautiful case - to have a nice present - you
can order it at our website.
I hope you will enjoy this. Let us hear about your
experiences.
We have some news for the next year:
we will publish a special issue about Databases
and database programming.

It will be published in the second half of the next


year (2010). Special Issue means: not included in
your subscription.
At the end of January 2010 we will ask you if you
want to continue your subscription,
we never do that without asking.
Next year we will do some articles about UML
and Design patterns and Delphi. Michael Rozlog
will do some of the articles. We would like you to
respond to our articles and will publish your
reactions under Readers write.
There is a first reaction at the end of the page.
One of the questions was could you please
enlarge the font size. We did that.
The result is the change of lay out in this issue.
Hope you like it.
The coding is kept small to be able to keep the
length of a line of code on one row, so its easyer
to read.
Would you like some more information about
Delphi Prism? Let us know.
Send us your questions and requests, we will
respond to that.
Let us her from you...
Merry Christmas...
Detlef D. Overbeek
Editor in Chief

Readers write...
Hi,
Just wanted to comment on your editor input as well as Jeremy
North's article "Using Free Pascal and Lazarus to create
application for OSX" in the latest issue of Blaise. Very much
appreciate Mr. North's helpful overview and I hope to see more of
these types of articles in Blaise!
Yes, Mac OSX does many, many things better than windows.
However:
After having made several sample applications using apple's
Xcode and interface builder I certainly share Mr. North's
implicitly stated frustration with these tools. Any Delphi
developer who attempts to play around with xcode will be baffled
by the almost ridicules manner in which things are done.
Producing a "hello world" will take at least twice as many mouse
clicks through several different dialogs and applications. What
xcode does expect from its users is that they are very conscious
about what they are doing, but its far from RAD as we have
become accustomed to in Delphi.

Page 4 / 2158

COMPONENTS
DEVELOPERS

The Embarcadero/CodeGear roadmap regarding multiple OS's is


a vital component towards the sustainability of the business as it
will support customer needs.
Moving away from Microsoft Windows dependency and embracing
a marketing approach that now works from the outside-in is
certainly a viable means to achieve this. Now is the time.
Apart from the VCL RAD type IDE, the Delphi for Mac OSX must
support all of the OSX frameworks for it to be truly useful. Other
projects such as QT do this to some extent. Delphi can do it better.
Regards,
B.J. Rao
intricad.com

December 2009 BLAISE PASCAL MAGAZINE 9

The 3D spheres generator By David Dirkse


starter

expert

DELPHI 2010 / Win32

Introduction

This article explains the construction of Christmas (tree)


decorations. It describes how 3D-spheres are drawn.
Surprisingly, no difficult math is required: only the
Pythagoras lemma and linear functions are needed. Some
controls, small arrows, are added to adjust light position
and colors. The article also describes how these arrows
are programmed and how the sliding is accomplished.
Color selection is done by 7 speedbuttons, which are
placed in an array and are created at runtime.

Observe point P(x,y). rx and ry are the horizontal- and


vertical distances to the center M.
rx := x centerX
ry := y - centerY

Using the Pythagoras lemma, P is not outside the circle if :


MP = sqrt(rx*rx + ry*ry) <= radius.

Variables Xposition and Yposition are the positions of the


X- and Y-slides and also the coordinates on the sphere
where light intensity is maximal.
To simplify calculations, the coordinates of the center M are
(temporarily) supposed to be (0,0).
Relative to M, focusX and focusY are the coordinates for
the maximum intensity:
focusX := Xposition centerX
focusY := Yposition centerY

We calculate the color of pixel (rx, ry) .


If (px,py) is the distance to the focuspoint then:
px := rx focusX
py := ry focusY

Distance d to to focuspoint is:


d := sqrt(px*px + py*py);

Figure 1:

The colors on the sphere change from the point of highest


intensity to the borders. This suggests the 3 dimensional
effect. It is unnatural to change the colors from,say, blue to
red. Not the color itself changes but the intensity does.
Therefore, at first, we only define the intensity, which
ranges from 0 (dark) to 255 (maximum). Next , a colorcode
defines the colors that participate. See figure 2.
colorcode bit -0- enables red, bit -1- enbles green and bit -2enables blue. At design time, the colorcode is stored in the
tag property of the corresponding speed button.

The intensity changes from focuspoint to border, measured


along the line though M.
Variables CCint and BCint hold the color intensity (0..255)
of focuspoint and border. A good 3D effect can be obtained
by applying a simple linear realition, surprisingly enough.
Per pixellength the intensity changes by the value (max
intensity - min.intensity)/(focus - border) so:
colorstep := (CCint - BCint-1)/(radius +
sqrt(focusX*focusX+focusY*focusY));

This sets the intensity for point P to :


col := CCint - trunc(d * colorStep);

The real color for pixel P becomes:


color := colorcode2color(col,colorcode,true);

The switch "true" indicates that the red and blue color fields
have to be traded, due to the pf32 bit format.
The scanline[..] property is used twice to get the pointer to
pixel[0,0] and to get the increment value to the pixel below
in the bitmap. Avoiding the scanline and pixel[...] statements
accellerates painting considerably. Pixels are written directly
in memory.

Figure 2:
Drawing the sphere
This is done in bitmap Smap. (S - Sphere). All pixels of the
bitmap are addressed left to right, top to bottom. For each
pixel (x,y) a check is made to find it's position: inside or
outside the circle. See fig.3

Figure 4:
The slides
The slides are implemented by paintboxes in which the
arrows are painted. Xbox controls the horizontal focus
position, Ybox the vertical position. CCbox controls the
center-intensity, BCbox the border intensity.
Figure 3:

December 2009 BLAISE PASCAL MAGAZINE 9

For a slide, following conditions may be noticed:


type TSlidestatus = (stIdle,stOnSpot,stLocked);
var slidestatus : Tslidestatus

COMPONENTS
DEVELOPERS

Page 5 / 2159

The 3D spheres generator (continuation)


indicates that no action takes place.
The mousepointer is not over an arrow;
slidestatus = stOnSpot indicates that the mousepointer is
over an arrow.
slidestatus = stLocked indicates "onSpot"and mouseDown.
Pressing mouseDown when slidestatus = stOnSpot sets
slidestatus to stLocked. This enables mousemoves to
change the arrow position. Slidestatus is reset to stIdle after
a mouseUp event. The same method is used for all arrows.
For the mouseDown and mouseMove events, methods are
separate for each paintbox. In the source code we notice the
variable Offset. Offset is not strictly necessary here because
the arrow is very small. Fig. 5 below demonstrates the need
in case of a larger object.
slidestatus = stIdle

A rectangle can be shifted horizontally. X is the mouse


pointer at a mouseDown event. By calculating:
Offset := X position and correcting X at later
mouseMove events: X := X Offset we simulate thar the
mousebutton was pressed exactly at the center (position) of
the object. X then is the position of the object.

Page 6 / 2160

COMPONENTS
DEVELOPERS

Drawing the arrows.


Please refer to figure 6. for drawing a horizontal
arrow.

The starting point of the drawing is also the position of the


arrow. Pen movements for X- and Y-directions are stored
separately in an array[1..7]. See procedure
paintRightArrow for details. Notice, that the old arrow is
first erased before the new arrow is painted.
Of course, a class could be made to program the arrows and
their movement. It would certainly be more elegant.
However, in that case it is obvious to add several options
which would make the code less convenient to read. Also,
real time saving is only obtained after component
registration.
This needs some time for testing.
So, I happily leave that exercise to my readers.

December 2009 BLAISE PASCAL MAGAZINE 9

What is kbmMW? By Benno Evers


Installing kbmMW CodeGear Edition
starter

expert

DELPHI 2010 / Win32

KbmMW is a very flexible and extensible middleware


framework fully written in Delphi. The framework is
using a service based architecture. The service based
architecture does require a bit of a different view if you
are used to the classic setup using datamodules talking
directly to a database. The big advantage of learning to
use services however is that you get well defined and
tested chunks of reusable business code. The advantage of
kbmMW is that these services do not need to be at one
server, they can be distributed.

features, both for database caching (saving connections and


database access) as for caching client results. By smart use
of these cache settings, a lot of performance can be gained.It
is even possible creating a classic client server setup using
kbmMW by combining the client and the application server
in one exe file. Due to the smart cache the performance in
most cases will be better than when directly accessing the
database. I recommend having a look at the feature matrixes
of kbmMW. They are categorized on features and give a
good impression about the different versions of kbmMW.
There are a number of feature matrixes available at
http://www.components4programmers.com/products/kbmmw/feat

KbmMW Codegear edition is provided


as a free binary distribution, compiled for a specific Delphi
version. Both Pro and Enterprise versions are commercial
licenses and come with full delphi source code.

urematrix/index.htm

The framework is designed in a very flexible architecture so


the developer is able to extend the framework if he wishes.
KbmMW is available in 3 versions
a free Codegear edition for Delphi 2007
and Delphi 2009
a Pro Version
an Enterprise version.

All versions can be used in the same service oriented


architecture, but differ in the feature set they provide.
Among these features are the supported databases. The free
edition is limited to a number of popular databases only.
The commercial versions support more databases (30+).
Another big advantage of both commercial versions are the
cross database adapters. Using the cross adapters it is very
easy for one applicationserver to support numerous
different databases. Only changes are the database specific
things like SQL dialects. Another important difference
between versions are the transports. Transports are what is
used for connections between clients and the application
server(s). The free version has only local transport, TCP/IP
transport (using Indy 10) and Isapi transport.
Transports
Part of the power of the kbmMW framework is in these
transports. Indy can be used as a transport layer but also
Synapse. The basic setup is using a request / response setup.
This means a client will place a call to the server and wait
for a response. Both the free and the commercial versions
support this.
Commercial versions
The commercial versions of kbmMW also support
transports like compressed
binary (speed), AJAX, HTTP and AMF3 (adobe flex).
That means an adobe flex application can communicate
with a kbmMW application server in it's native stream
format.
Another very powerful feature only available
in the enterprise edition is messaging. Messaging is an
asynchronous communication mechanism between the
client and the application server. The advantage is the
client can send a request to the server and continue
with other tasks. The server will deliver the response
when it is ready in an asynchronous queue at the client.
This messaging can be used peer to peer, but also in a
broadcast way. The advantage of this is that with a very
low CPU load a lot of nodes (clients) can be notified
using a broadcast.
KbmMW as a framework also has very powerful caching

December 2009 BLAISE PASCAL MAGAZINE 9

KbmMW CodeGear edition


In this article I will be using CodeGear edition 3.20 Beta
and Delphi 2009 Pro. A copy of kbmMW CodeGear edition
can be acquired by registering at www.turbomiddleware.com.
After registration you can request a license for kbmMW
codegear edition. The request will be validated and after
some time a zip can be downloaded after logging in to
www.turbomiddleware.com.
The features provided in the current CodeGear edition are:
Database support:

MT
BDE
ADOX
IBX5
DBX

kbmMemTable (Components4Developers)
Borland Database Engine (CodeGear)
ADO Express (CodeGear)
IB Express v5 (CodeGear)
DB Express (CodeGear)

Transport support:
Local transport
Indy 10 TCP/IP Request/Response transport
ISAPI transport
Depending on the chosen version of kbmMW CG edition,
IDE support is installed for Delphi2007 or Delphi2009.
Installing kbmMW CodeGear edition
After downloading the CodeGear edition for your specific
IDE, unzip it. Inside this zip file there is an installer
<kbmMW_CG_Setup> that automates most of the tasks.
Just follow the steps of the setup wizard. By default the
software is installed in c:\Program Files\kbmMWCG
When prompted to select the components, don't change
anything because all components are needed.

COMPONENTS
DEVELOPERS

Page 7 / 2161

What is kbmMW? (continuation 1)


After finishing the setup wizard, all components and demo's
are installed in the choosen directory, by default c:\Program
Files\kbmMWCG.
Before we can use kbmMWCG edition in Delphi, we need
to take some further steps. The installer has installed 4
packages that can best be moved to the standard BPL
directory of delphi (Package output directory).
This path can be found by choosing Tools-Options inside
delphi menu. A Dialog is shown.

4 Files need to be moved from C:\Program


this BPL directory. The files are:

kbmMemDes<delphi
kbmMemRun<delphi
kbmMWDes<delphi
kbmMWRun<delphi

Files\kbmMWCG

to

version>CG.bpl
version>CG.bpl
version>CG.bpl
version>CG.bpl

will be D2007 for the 2007 version and


D2009 for the 2009 version of kbmMWCG edition. Do not
change any of the configuration files inside the
kbmMWCG directory.
The codegear
edition is a binary
distribution, so you get
a default onfiguration.
<delphi version>

Figur 2:
When clicking the button right from the editbox a dialog is
opened showing the BPL path

Page 8 / 2162

COMPONENTS
DEVELOPERS

The last step we need to take before we can use the


kbmMW CodeGear edition is to add the directory
containing our DCU files to the Library and Browsing path.
This can be done using the dialog we get using ToolsOptions in the menu. Choose delphi options and then
Library Win32 (see IMG2). Add the path the installer used
to install kbmMWCG (default C:\Program
Files\kbmMWCG) to both the Library search path and the
browser search path.c After these steps we are ready to start
using kbmMWCG edition.

December 2009 BLAISE PASCAL MAGAZINE 9

What is kbmMW? (continuation 2)


Testing the kbmMWCG install
The setup of kbmMWCG comes with a
number of demo's. These demo's can be found
in the kbmMWCG\Demo\Basic directory.
We can start by opening the BDE Server
project, that can be found in C:\Program
Files\kbmMWCG\Demo\Basic\BDEServer. Open the
srvDemo project.
It is possible Delphi will complain about
converting D2007 files. Just choose OK if this
happens.
Now choose Shift+F9 to build the server. If
you do not get any errors, kbmMWCG edition
was installed correctly. Running the project
will show you the server.

The inventory service is a standard kbmMW


service, that can be used to provide
information about available services at this
applicationserver, including the service
version.
Clicking Requesting Inventory 100 times
will show you the available services of this
application server. See how the counter on the
server will increase with every request.

Using the supplied demo's


The easiest way to start learning about the kbmMWCG
framework is to use the demo's. BDEserver together with
the supplied client demo will be the most intuitive
combination to start.
The BDEserver project we created when
testing our install always needs to be
running. So please start srvdemo.exe and
click the Listen button.

The clidemo is a good project to have a first


view in the use of kbmMW. Have a look at
the code behind the different buttons. Both
the RPC (calling a function at the server) and
the way to use dataservices are shown in this
demo. KbmMW does have a learning curve,
you need to invest some time to get to know
it. But once you take this effort you will see
it's power and potential.
To help you in learning there is a helpfile in the kbmMWCG
directory. Also there are a lot of tutorials in the kbmMW
university. I recommend reading at least the following
documents to start understanding the concept of kbmMW.

http://www.components4programmers.com/products/kbmmw/featurematrix/index.htm
http://www.components4programmers.com/products/kbmmw/university/index.htm

Usefull reading material

(Its easy to click on the connection if you use the download


version of this issue)

http://www.components4programmers.com/downloads/kbmmw/documentation/Creation_of_customized_services.pdf
http://www.components4programmers.com/downloads/kbmmw/documentation/Using_kbmMW_as_a_query_server.pdf
http://www.components4programmers.com/downloads/kbmmw/documentation/Resolving_250.pdf

Generic information on service oriented applications availabe at the opengroup.


http://www.opengroup.org/

Now in Delphi open the cliDemo project that can be found in


C:\Program Files\kbmMWCG\Demo\Basic\client.
Again when delphi asks to convert D2007 files click OK.
Compile and run clidemo. In clidemo choose the tab
Standard services like the inventory service and click
connect. Make sure the srvdemo gets focus too and click
button request abstract for inventory and check what
happened with the counter in the middle of the srvdemo. It
should go from 0 to 1 indicating a client request just came in.
After clicking this inventory service your clientscreen should
look like this.

December 2009 BLAISE PASCAL MAGAZINE 9

About the author:


Benno Evers
is an independent developer with a background in electronics and
embedded software. Software running on windows is developed using
Delphi, starting with Delphi 1. Since 2003 kbmMW has been a big part of
most applications requiring data-access. The database platform used
is often Firebird, using kbmMW and UIB. Remarks and questions can be
emailed to [email protected]

COMPONENTS
DEVELOPERS

Page 9 / 2163

BLAISE PASCAL TROPHY / DELPHI CONTEST

The winners

Winner of the first Price:


Peter Bijlsma for his article:
Fast Graphic Deformation by using scanlines
Price:
CodeGear RAD Studio 2010 (Professional license),
Sometimes it's necessary to manipulate picture representations, e.g. to correct the
perspective. Programs like Photoshop have that functionality: buildings who are
leaning towards each other can be set straight in no time. How can we accomplish
such a deliberate deformation? We know, either by own experience or by reading
articles or books about programming, that routines manipulating images on pixellevel
are very slow. Older Pascallers (like me) will remember the Putpixel procedure and
our, sometimes desperate, attempts to learn enough machine language to make our
routines faster by using Inline code. Nowadays we do not have only faster machines,
but also the programming possibilities developed. Delphi gave us a powerful property
to bitmaps: Scanlines. This article will describe the use of scanlines in connection with
a program called Deform!.

Winners of the second Prices:


David Dirkse for his articles:
The X-Bitmans class and Freehand Drawing
Price: Delphi 2010 (Professional license)

Siegfried Zuhr for his article:


Lazarus ready for use under Linux
Price: Delphi 2010 (Professional license)

Winner of the third Price:


Thiago Batista Limeira for his article:
Controlling the Parallel port
Price: Advantage Database Server (5 users)
The parallel port is a very commonly known port, it was widely used to
connect a printer to the PC, if you look at the back of your computer, for
those who don't have newer computers, there will be a port with 25 pins
and a small printer symbol. This port is known as LPT port or printer port.
We can program this port for device control and/or data transfer, in projects
of robotics, electronics and even LCD interfacing. In this article, I'll show
the basics of parallel port and some programming principles using the
LPTPort Component.
Page 10 / 2164

COMPONENTS
DEVELOPERS

December 2009 BLAISE PASCAL MAGAZINE 9

Essential Pascal (course edition)


Marco Cant's
book on Pascal and object-oriented Pascal

NEW!
Just published!
Specially designed
for self-study

Consisting of:
* translation of Marco Cant's book
Essential Pascal
* Lazarus for Windows on CD-ROM
* Delphi RAD Studio on CD-ROM (30-day trial
version)
* Sample projects
* Alphabetical dividers for notes
* Empty sample project folders for ring binder
Follow-up subscriptions available at 25 per year
(plus shipping fee)
Consisting of:
1. Articles on electronic applications for Delphi
2. Articles on the Lazarus IDE
3. Articles on the Delphi 2010 IDE
4. Exercises and code examples
Full subscription price in combination with
purchase of book with course and DVDs: 55.00
Orders may be placed with
our website store at
http://www.blaisepascal.eu/index.php?actie=./subscribers/subscription_mainpage or

[email protected]

December 2009 BLAISE PASCAL MAGAZINE 9

COMPONENTS
DEVELOPERS

Page 11 / 2165

Delphi 2010 Feature Highlight - Debugger Visualizers


starter

expert

DELPHI 2010 / Win32

By Jeremy North

This article describes the new Debugger Visualizers


feature in Delphi 2010 as well as including the details
required to help create your own custom visualizer.
What are Debugger Visualizers
Debugger Visualizers allow the data shown by the debugger
for a particular type to be represented in a different format.
For example the TDateTime type when viewed in the Watch
List or Evaluator Tooltip hint appears as a floating point
value. In most cases this is not helpful when debugging
code. This is where Debugger Visualizers help, by
displaying TDateTime (also works for TDate and TTime
types) in an easy to read format.
So instead of seeing a value of '40105.900806' you
see '19/10/2009 9:37:09 PM'.
Types of Debugger Visualizers
There are two different types of debugger visualizers. The
most basic is the Value-Replacer. This visualizer just
replaces the string seen in the particular debug instance for
the expression. A limitation of the Value-Replacer visualizer
is that only one can be registered at a time.
A more complex visualizer is the External-Viewer
visualizer. External-Viewer visualizers allow the user to
invoke an external window to display more information or
enhanced GUI for the selected type. There is no limit to the
number of External-Viewer visualizers that can be
registered for a type.
Included Visualizers
Delphi 2010 ships with two Debugger Visualizers available
for immediate use (they are already active by default).
The TDateTime visualizer displays the formatted value
inplace. This means the date and time are displayed where
the float value would have been. There are no additional
actions required to invoke the visualizer. The TDateTime
visualizer is an example of a Value-Replacer debugger
visualizer. The TStrings visualizer is an example of an
External-Viewer visualizer. For this visualizer the viewer
displays a dockable window that displays the contents of
the TStrings variable.

Figure 2: Setting the breakpoint


Clicking on the glyph with the drop down arrow displays a
menu that lists all of the visualizers associated with the type
of variable. That is correct; multiple External-Viewer
Debugger Visualizers can be registered for the same type.
Where can Debugger Visualizers be used?
Visualizers are available throughout the IDE. They work in
the following debugger related windows.
* Watch List
* Local Variables
* Debug Inspector
* Evaluate / Modify
* Evaluator Tooltips
While you can enable/disable a visualizer in the Watches
properties dialog, it is also possible to disable a visualizer in
the Options dialog. Navigate to the Debugger Options |
Visualizers option page to view a list of installed
visualizers. This dialog is shown later on in this article once
you have installed the sample visualizer. You may want to
disable a registered Value-Replacer visualizer in favour of a
different Value-Replacer visualizer for the same type, since
only one Value-Replacer visualizer can be active for a given
type.
Disabling visualizers
If you want to see the default textual representation, there
are three ways to disable a visualizer for a specific type.
1. Edit the Watch and uncheck the 'Use visualizer' check
box.

Invoking External-Viewer visualizers


Visualizers that have an external viewer are displayed
slightly different in the IDE to show that the variable can be
viewed with an external Visualizer.
The following examples show the user interface for how an
external visualizer is identified for Watch List and
Evaluator Tooltip items.
Figure1: The watch list

Figure 3: Bug creeping up the watch properties


1. Disable the visualizer in the
Tools | Options | Debugger Options |
Visualizers list of available visualizers.
2. Typecast the Watch name. In the example used above
that would be Double(FDateTime). NOTE: This will
not always work.
Page 12 / 2166

COMPONENTS
DEVELOPERS

December 2009 BLAISE PASCAL MAGAZINE 9

Debugger Visualizers (continuation 1)


Creating your own visualizers
Hopefully you now have an idea of what a debugger
visualizers and how they can improve your debugging
experience.
Now we'll create both types of Debugger Visualizers to
work with the TColor type. For those that have never
created an IDE Expert before, we'll start with some required
knowledge.
Writing IDE Experts - Overview
There are two deployment options for IDE Experts,
packages or DLL's. Both have their advantages. Packages
are instant on; you can install it within the IDE you are
using to do some testing.
DLL's require you to either restart you IDE (and updating
the registry) or running another instance of the IDE to
debug the expert.
For this article our Debugger Visualizers will be located in a
package.
NOTE: Be aware that when using packages the names of
the units must be unique within all packages loaded by the
IDE.
Project Group setup
Run Delphi 2010 and create a new package project.
Save the project in a new folder with a unique name. I've
named my package ColorVisualizer2010.
NOTE: I don't use the package Suffix or Prefix options
(which are called Shared object name in Delphi 2010).
The first visualizer we will create is the Value-Replacer.
Add a new unit to the package project and save it as
CVAddin.pas.
Add a new frame to the package project and save it as
CVViewerFrame.pas.
Right click on the Requires node in the project manager for
the package and select the Add Reference... command from
the menu.
In the Package Name text edit type in designide and click
OK. This is the package that gives us access to the
IDE Open Tools API functionality. By requiring this package
we can now refer to the various interfaces of the Open Tools
API.
Add a new project to the project group.
This time add a VCL Forms Application, this is the
application we'll debug in order to test our
Debugger Visualizer.
Save the project as VisualizerTestProj.
Save the unit in the VisualizerTestProj as MainForm.
Save the project group as ColorVisualizer2010Group.
The screen shot below shows how your project manager
should look.

December 2009 BLAISE PASCAL MAGAZINE 9

Figure 4: The project manager View


Registering the Debugger Visualizer
The Open Tools API uses interfaces heavily. Almost all
functionality requires you to register a class that implements
specific interfaces.
For any visualizer to work it must be registered with the
IDE. The way to register your notifier is to call the
RegisterDebugVisualizer method on the
IOTADebuggerServices interface. This method takes a
single interface parameter of type IOTADebuggerVisualizer.
This means that we must create a class that implements this
interface. You want this class to descend from
TInterfacedObject. Since we are implementing the
IOTADebuggerVisualizer interface we also need to
implement the methods defined on that interface.
TColorVisualizer = class(TInterfacedObject, IOTADebuggerVisualizer)
public
procedure GetSupportedType(Index: Integer;
var TypeName: string; var AllDescendents: Boolean);
function GetSupportedTypeCount: Integer;
function GetVisualizerDescription: string;
function GetVisualizerIdentifier: string;
function GetVisualizerName: string;
end;

The class needs to return the results for each of these


methods.
GetVisualizerName Used to show the name of the visualizer
in the options dialog.
GetVisualizerDescription Description for the visualizer
that appears in the options dialog.
GetVisualizerIdentifier This should be a unique identifier
for the visualizer.
I recommend prefixing it with your company name.
GetSupportedTypeCount Return the number of types your
visualizer will handle.
GetSupportedType This method is called internally the
number of times that the GetSupportedType Count method
returns.

COMPONENTS
DEVELOPERS

Page 13 / 2167

Debugger Visualizers (continuation 2)

The index is the iteration of the count. Return the name of


the type you want to handle in the TypeName parameter.
For our example this will be TColor for Delphi.
NOTE: The AllDescendents parameter is ignored in the
Delphi 2010 release of the visualizers functionality.
So you need to register descendants separately by returning
a GetSupportedTypeCount that includes all descendent classes
you want to handle.

Implementing the Visualizer Interfaces


With two types of visualizers you'd be correct in
thinking there are two different interfaces that can be
implemented.

While we have created a class that implements the


IOTADebuggerVisualizer class, registering it will make the
visualizer show in the list of available visualizers. But it
isn't accessible since we need to implement at least one
more interface first.
Currently our class is not a visualizer of any use as it needs
to implement either the
IOTADebuggerVisualizerValueReplacer or
IOTADebuggerVisualizerExternalViewer interface.

IOTADebuggerVisualizerValueReplacer =
interface(IOTADebuggerVisualizer)
['{6BBFB765-E76F-449D-B059-A794FA06F917}']
function GetReplacementValue(const Expression, TypeName,
EvalResult: string): string;
end;

Registering the Visualizer


Before implementing the final interface, let's register the
visualizer.
When the IDE loads a package it scans each of the units
within the package looking for a Register (the name is case
sensitive) procedure. If it finds one, it calls it and this is
how most packages should register itself into the IDE.
The following code handles the registration of the
visualizer.
var
_Color: IOTADebuggerVisualizer;
procedure Register;
var
LServices: IOTADebuggerServices;
begin
if Supports(BorlandIDEServices, IOTADebuggerServices,
LServices) then
begin
_Color := TColorVisualizer.Create;
LServices.RegisterDebugVisualizer(_Color);
end;
end;
procedure RemoveVisualizer;
var
LServices: IOTADebuggerServices;
begin
if Supports(BorlandIDEServices, IOTADebuggerServices,
LServices) then
begin
LServices.UnregisterDebugVisualizer(_Color);
_Color := nil;
end;
end;
initialization
finalization
RemoveVisualizer;

When writing code that will run within the IDE, it is


recommended you be defensive with your method
implementations.
Remember one simple bug can bring the IDE crashing
down, and you don't want to do that.
Instead of using Supports on the BorlandIDEServices
global variable (which is declared in the ToolsAPI unit) we
could have used an as cast and gotten the same result.
Using Supports is safer though since it won't raise an
exception if the BorlandIDEServices variable does not
implement the IOTADebuggerServices interface.
Page 14 / 2168

COMPONENTS
DEVELOPERS

Value-Replacer
To create a Value-Replacer visualizer, implement the
IOTADebuggerVisualizerValueReplacer interface.

Add the interface to the class and add the


GetReplacementValue method to the class definition. Your
class should now look like the following:
TColorVisualizer = class(TInterfacedObject,
IOTADebuggerVisualizer,
IOTADebuggerVisualizerValueReplacer)
public
function GetReplacementValue(const Expression: string;
const TypeName:string;const EvalResult:string): string;
procedure GetSupportedType(Index: Integer;
var TypeName: string;
var AllDescendents: Boolean);
function GetSupportedTypeCount: Integer;
function GetVisualizerDescription: string;
function GetVisualizerIdentifier: string;
function GetVisualizerName: string;
end;

For our sample visualizer the implementation of the


GetReplacementValue is:
function TColorVisualizer.GetReplacementValue(const
Expression, TypeName, EvalResult: string): string;
begin
Result := ColorToString(StrToInt(EvalResult));
end;

is the name of the variable.


is the name of the type that the expression
parameter is.
EvalResult is the default textual representation of the
evaluated result of the Expression.
Expression
TypeName

The ColorToString method is declared in the Graphics.pas


unit. It returns a string representation of some default VCL
and Windows colors, such as clNavy and clBtnFace.
This is an improvement when compared to the default
integer based representation that a color normally is.
If the color isn't predefined then the result is a formatted as
hex.
External-Viewer
To create an External-Viewer visualizer, implement the
IOTADebuggerVisualizerExternalViewer.
IOTADebuggerVisualizerExternalViewer =
interface(IOTADebuggerVisualizer)
function GetMenuText: string;
function Show(const Expression, TypeName,
EvalResult: string;
SuggestedLeft, SuggestedTop: Integer):
IOTADebuggerVisualizerExternalViewerUpdater;
end;

December 2009 BLAISE PASCAL MAGAZINE 9

Debugger Visualizers (continuation 3)


The first thing that should stand out from the interface
definition is that it returns a new interface.
Yes, external viewer visualizers are a little more complex
than their Value-Replacer compatriots.
The methods that need to be implemented on the
IOTADebuggerVisualizerExternalViewer are:
GetMenuText Remember that External-Viewer visualizers
are invoked from a popup menu. This method should
return the caption of the menu item.
Show This is the method that the IDE calls when the user
selects your visualizers menu item.
The Expression, TypeName and EvalResult parameters are the
same as when the GetReplacementValue method is called on
the IOTADebuggerVisualizerValueReplacer interface.
The SuggestedLeft and SuggestedTop parameters are the
recommended screen location for the viewer to use.
Your show method should create the window that displays
the expression and then return an
IOTADebuggerVisualizerExternalViewerUpdater interface
reference. This means you must create a class that
implements this
IOTADebuggerVisualizerExternalViewerUpdater interface,
which for this example will be the frame used for the user
interface.
IOTADebuggerVisualizerExternalViewerUpdater = interface
procedure CloseVisualizer;
procedure MarkUnavailable(Reason:
TOTAVisualizerUnavailableReason);
procedure RefreshVisualizer(const Expression,
TypeName, EvalResult: string);
procedure SetClosedCallback(ClosedProc:
TOTAVisualizerClosedProcedure);
end;

When this method is called you should


close your visualizers window. This means that the thread
that was responsible for invoking the visualizer has been
destroyed.
MarkUnavailable This method is called when the visualizer
is unavailable. Currently the reason can be either Out of
scope or Process not accessible.
RefreshVisualizer This method is called whenever the
visualizer needs to be updated. This method will be called
in response to the user using the Evaluate/Modify dialog
to modify the expression your visualizer is displaying.
If the visualizer is not visible, it doesn't display anything.
SetClosedCallback Call the method which is passed as the
ClosedProc parameter when your visualizer window is
closed so that the IDE stops sending the RefreshVisualizer
message. This means you need to save the passed
parameter for later use.
CloseVisualizer

The following is the Show method implementation. This


code calls a class method on the frame used to display the
visualizer details to the user. I've purposely hidden the
method implementation from this article since it goes
beyond the scope (and space) for the article. All you really
need to know is that the frame designed for the visualizer is
parented on a dockable IDE form.

December 2009 BLAISE PASCAL MAGAZINE 9

function TColorVisualizer.Show(const Expression: string;


const TypeName: string; const EvalResult: string;
SuggestedLeft: Integer; SuggestedTop: Integer):
IOTADebuggerVisualizerExternalViewerUpdater;
begin
Result := TfmCVViewer.CreateAndShow(Expression,
TypeName, EvalResult, SuggestedLeft, SuggestedTop);
end;

Calling methods on expressions


When creating an External-Viewer visualizer one thing you
may want to do is call a method on the expression the
visualizer displaying. For example, you might have a TTable
visualizer and want to display the current contents of the
active record as well as displaying the total record count for
the table. There is a specific technique for doing this and it
has not been covered in this article. It will be discussed in
the next edition.
Supporting C++
I will preface this section with the following comment - I
am not a C++ developer.
To support the C++ personality with this visualizer we
need to make two changes. The first is the change the
supported count to return 2 and the second is to provide the
fully qualified type name as the TypeName parameter in the
GetSupportedType method. This has been done in the
source code that is included with the article. Thank you to
the two C++ developers that tested and provided feedback
on the visualizer in the C++ personality.
Creating the visualizer user interface
The frame that was originally added to the project is what
will be displayed to the user. The user interface is very
basic with just a panel, popup menu and action list on it.
The actual frames color is what is used to show the
expressions value as a color. The panel is used to display
the name of the color. The action list contains actions for
copying the Expression and Evaluated Result to the
clipboard and the popup menu contains those actions.
Continuation

About the author of this article:


Jeremy North
began programming with Delphi in 1997 while at
college in the USA, and since his return to Australia
he has kept pace with emerging windows
technologies. More recently he has concentrated his
focus on writing components and IDE experts, as
well as creating tools to help other developers work
more efficiently.
Some of these components and experts are available
to the public on his JED Software website:

http://www.jed-software.com
Some are free, while others incur a small charge. So
lets go visit him at his site!

COMPONENTS
DEVELOPERS

Page 15 / 2169

Debugger Visualizers (continuation 4)

Figure 6: To install the visualizer, right click on the package name in the project manager and select the Install command.
Double clicking on the frame copies the Evaluated Result to
the clipboard, and double clicking on the panel copies the
Expression name to the clipboard.
The screen shot below shows the frame at design time.

Figure 5: The Frame


Installing the visualizer
To install the visualizer, right click on the package name in
the project manager and select the Install command.
A dialog will display confirming the status of the install
attempt (success or failure).
Once installed, you can also see your visualizer in the
Debugger Options | Visualizers section of the
Tools | Options dialog.
The finished visualizer in action
The final thing to show is the visualizer in use within the
IDE.
Running the provided test project the application has a
single form with three buttons on it.
Page 16 / 2170

COMPONENTS
DEVELOPERS

The screen shot below shows the test projects user interface.
The TColor property being changed is the forms Color
property. This means that the background color of the form
will change when clicking on one of the provided buttons.

Figure 7: The test form


Clicking on the first button and activating the visualizer by
selecting the Show Color command from the menu shows
the visualizer with a clBtnFace background and the
ColorToString representation of the value as clBtnFace.

Figure 8: After selecting the Show Color command from


the popup menu, the visualiser's viewer is displayed

December 2009 BLAISE PASCAL MAGAZINE 9

Debugger Visualizers (continuation 5)

Figure 9: Stepping over the color assignment to navy line


then changes the visualizer to display a navy background
with clNavy text.

Figure 10: Also note in the screen shot above where the
Value-Replacer part of the visualizer is also shown.
If you select a custom color after selecting the Select a
color... button in the test project the hex format of the
expression is displayed (as shown below).

Figure 11:
The hex format
Future Enhancements
* Allow custom painting added to the Value-Replacer
visualizer type. This should then allow a Value-Replacer
version of the TColor visualizer to just paint the colour
next to the formatted TColor value, instead of requiring a
popup window to show this information.
* It would be nice to be able to include a screen capture
associated with a visualizer that was displayed in the
registered visualizers list in the options dialog.
* Project specific enabling and disabling of visualizers.
Conclusion
I have no doubt Debugger Visualizers will improve your
debugging efficiency. This will certainly be the case when
you need to drill into and call methods on the expression
being visualized.
The source code and test project is available to subscribers.
Remember to catch the follow up article on how to call
methods on expressions in the next edition. There will also
be details of how to use the IDE wizard I'm creating to
easily create the base code for your own visualizers.

December 2009 BLAISE PASCAL MAGAZINE 9

Page 17 / 2171

Page 5 / 2159

Introduction to multithreading
starter

expert

DELPHI 2... 2010 / Win32

For the last fifty years, we programmers had it easy.


We could write slow, messy, suboptimal code and when a
customer complained we would just say:
"What? You're still using that old hardware? Throw that
junk away, get a new computer and program will fly!"
With some luck new hardware would solve the problem
and if not we could pretend to fix the problem until new
generation of computers came out.
In other words - Moore's law worked in our favor.
This situation changed radically
in the last year. New processors are not significantly faster
than the old ones and unless something will drastically
change in CPU design and production, that will stay so.
Instead of packing more speed, manufacturers are now
putting multiple processor units (or cores as they are
usually called) inside one CPU.
In a way that gives our customers faster computers, but
only if they are using multiple programs at once. Our
traditionally written programs that can use only one
processor unit at any moment won't profit from multiple
cores.
Even worse, we cannot say "buy a new computer" anymore.
Maybe it will have a new, better processor with more cores
than the previous one but our program, our precious
program that can utilize only one core, will not run any
faster.
As we can all see, this Is No Good.
We have to do something to make our programs faster on
multi-core processors. The only way to do that is to make
the program do more than one thing at the same time and
the simplest and most effective way to do it is to use
multithreading or using the ability of the operating system
to execute multiple threads simultaneously.
(A note to experienced readers: There's more to threads,
threading and multithreading than I will tell. If you want to
get a full story, check the Wikipedia,
en.wikipedia.org/wiki/Thread_(computer_science).
I'll intentionally limit myself to the Windows and Delphi.)
A word of warning
- this will be a long journey.
Today I'll only scrap the surface and give you an overview
of the topic. In next installments we'll start working on
multithreaded programs using not only Delphi's native way
but also using 3rd party extensions, components and
libraries. We'll describe and use messaging, locking, shared
data, lock-free structures, and more. Stay tuned!
A process and a thread work together.
As a programmer you probably know, at least instinctively,
what is a process. In operating system terminology, a
process is a rough equivalent of an application - when the
user starts an application, operating system creates and
starts new process. Process contains (or better, owns)
application code, but also all resources that this code uses memory, file handles, device handles, sockets, windows etc.

Page 18 / 2172

By Primo Gabrijeli

COMPONENTS
DEVELOPERS

When the program is executing, the system must also


keep track of the current execution address, state of the
CPU registers and state of the program's stack.
This information, however, is not part of the process, but
belongs to a thread.
Even a simplest program uses one thread, which describes
the program's execution. In other words, process
encapsulates program's static data while thread encapsulates
the dynamic part.
During the program's lifetime, the thread describes its line
of execution - if we know the state of the thread at every
moment, we can fully reconstruct the execution in all
details.
All operating systems
support one thread per process (obviously) but some go
further and support multiple threads in one process.
Actually, most modern operating systems support
multithreading (as this approach is called), the difference is
just in details (and for those, see the Wikipedia topic I
mentioned earlier).
With multithreading, operating system manages multiple
execution paths through the same code and those paths may
execute at the same time (and then again, they may not but more on that later).
An important fact is that processes are
heavy.
It takes a long time (at least at the operating system level
where everything is measured in microseconds) to create
and load a new process. In contrast to that, threads are light.
New thread can be created almost immediately - all the
operating system has to do is to allocate some memory for
the stack and set up some control structures used by the
kernel.
Another important point about processes is that they are
isolated. Operating system does its best to separate one
process from another so that buggy ((or malicious) code in
one process cannot crash another process (or read private
data from it). If you're old enough to remember Windows 3
where this was not the case you can surely appreciate the
stability this isolation is bringing to the user. In contrast to
that, multiple threads inside a process share all process
resources - memory, file handles and so on. Because of that,
threading is inherently fragile - it is very simple to bring
down one thread with a bug in another.
Multitasking and multithreading
In the beginning, operating systems were single-tasking. In
other words, only one task (i.e. process) could be executing
at the same time and only when it completed the job (when
the task terminated), new task can be scheduled (started).
That was good for hardware because operating system
could be simple and unobtrusive and programs could
execute at full speed. Programmers and users, on the other
hand, hated that. To run a program (or even to compile a
program) you had to put it into the queue and wait for a
long time. After which the program would start and
immediately crash, in most cases, and you'll have to start
looking for the bug - on the paper as there was no
interactive debugging. Ah, the good times .

December 2009 BLAISE PASCAL MAGAZINE 9

Introduction to multithreading (continuation 1)


As soon as the hardware was fast enough, multitasking was
invented. Most computers still had only one processor
(or better, they have one or more processor motherboards
which acted together as one single-core CPU would today)
but through the operating system magic it looked like this
processor is executing multiple programs at the same time.
Each program was give a small amount of time to do its job
after which it was paused and another program took its
place. After some indeterminate time (depending on the
system load, number of higher priority tasks etc)
the program could execute again and operating system
would run it from the position in which it was paused, again
only for the small amount of time.
In technical terms, processor registers were loaded from
some operating system storage immediately before the
program was given its time to run and were stored back to
this storage when program was paused. (And we all know
that processor registers also include the location of current
instruction in code, or Instruction Pointer, yes?)
Two very different approaches
to multitasking are in use. In cooperative multitasking, the
process itself tells the operating system when it is ready to
be paused. This simplifies the operating system but gives a
badly written program an opportunity to bring down whole
computer.
Remember Windows 3? That was cooperative multitasking
at its worst.
Better approach is pre-emptive multitasking where each
process is given its allotted time (typically about 55
milliseconds on a PC) and is then pre-empted; that is,
hardware timer fires and takes control from the process and
gives it back to the operating system which can then
schedule next process. This approach is used in Windows
95, NT and all their successors.
That way, multitasking system can appear to execute
multiple processes at once event if it has only one processor
core. Things go even better if there are multiple cores inside
the computer as multiple processes can really execute at the
same time then.
The same goes for threads.
Single-tasking systems were limited to one thread per
process by default. Some multitasking were single-threaded
(i.e. they could only execute one thread per process) but all
modern Windows are multithreaded - they can execute
multiple threads inside one process. Everything I said about
multitasking applies to threads too. Actually, it is the
threads that are scheduled, not processes.
Just for the curiosity - even on single-tasking operating
system (for example, on DOS) it was possible to write a
program that pretended to do multiple things at once. The
programmer had to break down processing into very small
parts and then mix those parts. Program would execute
small part of job A, then part of job B, then job C and
would return to the next part of job A. Complicated, errorprone, but definitely possible.

December 2009 BLAISE PASCAL MAGAZINE 9

When to use multithreading


I've already hinted that multithreading is not simple.
Still, multithreading must be good or operating systems
would not support it. When should you use multithreading,
then?
In the beginning I've given one part to that answer - when
the program is slow and we want to speed it up.
If that's the case we must somehow split the slow part into
pieces that can be executed at the same time (which may be
very hard to do) and then put each such piece into one
thread.
If we are very clever and if the problem allows that, we can
even do that dynamically and create as many threads are
there are processing units.
Another good reason to
implement more than one thread
in a program is to make it more responsive.
In general, we want to move lengthy tasks away from the
thread that is serving the graphical interface (GUI) into
threads that are not interacting with the user
(i.e. background threads).
A good candidate for such background processing are long
database queries, lengthy imports and exports, long CPUintensive calculations, file processing and more.
Multithreading simplifys code
Sometimes, multithreading will actually simplify the code.
For example, if you are working with an interface that has
simple synchronous API (start the operation and wait for
its result) and complicated asynchronous API (start the
operation and you'll somehow be notified when it is
completed) as are file handling APIs, sockets etc, it is often
simpler to put a code that uses synchronous API into a
separate thread than to use asynchronous API in the main
program.
If you are using some 3rd party library that only offers you
a synchronous API you'll have no choice but to put it into a
separate thread.
A good multithreading example are servers that can serve
multiple clients.
A server usually takes a request from the client and then,
after some potentially lengthy processing, returns a result.
If the server is single-threaded, the code must be quite
convoluted to support multiple simultaneous clients.
It is much simpler to start multiple threads, each to serve
one client.
Problems and solutions
Remember when I said that multithreading is not simple?
Well, I don't know how to tell you gently, but I lied.
Multithreading is hard. Very hard.
For example, splitting task into multiple threads can make
the execution slower instead of faster.
There are not many problems that can be nicely parallelized
and in most cases we must pass some data from one thread
to another. If there's too much communication between
threads it can use more CPU than the actual, data
processing code.
COMPONENTS
DEVELOPERS

Page 19 / 2173

Introduction to multithreading
(continuation 1)
Then there's a problem of data sharing. When threads share
data, we must be very careful to keep this data in a
consistent state.
For example, if two threads are updating shared data, it may
end in a mixed state where half the data was written by the
first thread and another half by the second.
This problem, race condition as it's called, is usually solved
by some kind of synchronization.
We use some kind of locking (critical sections, mutexes,
spinlocks, semaphores) to make sure that only one thread at
a time can update the data.
However, that brings us another problem or two.
Firstly, synchronization makes the code slower.
If two threads try to enter such locked code, only one will
succeed and another will be temporarily suspended and our
clever, multithreaded program will again use only one CPU
core.
Secondly, synchronization can cause
deadlocks
This is a state where two (or more) threads forever wait on
each other.
For example, thread A is waiting on a resource locked by
thread B and thread B is waiting on a resource locked by
thread A.
Not good. Deadlocks can be very tricky; easy to introduce
into the code and hard to find.
There's a way around synchronization problems too.
You can avoid data sharing and use messaging systems to
pass data around or you can use well-tested lock-free
structures for data sharing.
That doesn't solve the problem of livelocks though.
In livelock state, two (or more) threads are waiting on some
resource that will never be freed because the other thread is
using it, but they do that dynamically - they're not waiting
for some synchronization object to become released.
The code is executing and threads are alive, they can just
not enter a state where all conditions will be satisfied at
once.
The most famous (theoretical) example of resource
protection is Dijkstra's Dining philosophers problem,
well described at
en.wikipedia.org/wiki/Dining_philosophers.
Until next time it is your task to read about it and at least
pretend to understand
, so that we can continue our
journey.
Primoz Gabrijelcic

has been programming in Pascal since Turbo Pascal 3


for CP/M. He's the author of several open source Delphi
components, available at gp.17slon.com. He also
writes the Delphi Geek blog (thedelphigeek.com)

Page 20 / 2174

COMPONENTS
DEVELOPERS

December 2009 BLAISE PASCAL MAGAZINE 9

Writing Delphi Components III:


Compound Components and Custom Event By Marco Cant
starter

expert

DELPHI 6 ... 2010 / Win32

This article is the third of a series introducing a very


relevant features of Delphi's architecture, namely writing
your own custom components. The first article of this
introductory series provided an overview of the Delphi
Component Wizard and showed a first very simple
component, the second showed how to add custom
properties and event, and inherit from existing controls,
the third is focused on more advanced issues like building
a graphical compound component and defining events
with custom signatures.
A Compound Component:
The Traffic Light (or Semaphore)
Now well build a component that bundles three TCntLed
components (covered in the previous article) and a Timer
component together. This is sometimes referred to as a
compound component. The TCntSemaphore example
component has a number of features that will interest the
component developer. Since the code is quite complex,
well examine it in small chunks.
In programming, a semaphore is an object that synchronizes
the behavior of several segments of a program. For
instance, multi-threaded Win32 programs can use
semaphores to coordinate the actions of different threads.
In this example, our TCntSemaphore class has nothing to
do with operating-system semaphores, but instead is an
example of a traffic light component, built to demonstrate
how you can encapsulate other components within a
component, and then make those components work
together.
Specifically, well display three TCntLed
components (red, yellow, and green), allow no
more than one of them to be on at any given
time, and then provide an alternate mode where
the red TCntLed flashes at an interval specified
in a property. Here is an example of the component
with the green light on:
Choosing a Base Class
Here is the first part of the TCntSemaphore class declaration
for the component:
TCntSemaphore = class (TCustomControl)

Why did we derive TCntSemaphore from TCustomControl?


When we began researching this component, we first tried
embedding another graphical control within the
TCntSemaphore class.
However, embedding a graphical control within another
graphical control is rather complex since you have to
manipulate the parent property in peculiar ways.
Deriving TCntSemaphore from TWinControl is a bit better,
because it provides the proper framework for parenting other
components directly. The TWinControl class owns a window,
which can directly host the graphical Led components.
However, TCustomControl is an even better base class than
TWinControl, because it provides painting capabilities similar
to the TGraphicControl (such as a Paint method you can
override).
In contrast, TWinControl provides poorer painting support.

December 2009 BLAISE PASCAL MAGAZINE 9

Creating the Embedded Components


First we need to declare the three Led components and
build them as we create the semaphore component (in its
constructor):
type
TCntSemaphore = class (TCustomControl)
private
// the three traffic lights
fGreenL, fYellowL, fRedL: TCntLed;
...
constructor TCntSemaphore.Create (Owner: TComponent);
begin
inherited Create (Owner);
// create the LEDs and set their color
fGreenL := TCntLed.Create (self);
fGreenL.Parent := self;
fGreenL.Color := clLime; // light green
fYellowL := TCntLed.Create (self);
fYellowL.Parent := self;
fYellowL.Color := clYellow;
fRedL := TCntLed.Create (self);
fRedL.Parent := self;
fRedL.Color := clRed;
...

Next, we need to declare the SemState property. We avoided


using the name Color for this property because it might be
confusing to the components users (the property usually
has a different role), and also because we want to consider
the off and pulse states.
As with the Status property of the Led component, weve
based the SemState property on an enumeration:
type
TSemState = (scRed, scGreen, scYellow, scOff, scPulse);

Here are the additions to the class declaration, including the


SemState property with its read, write, and default
specifiers:
private
fSemState: TSemState; // status
protected
procedure SetSemState (Value: TSemState);
published
property SemState: TSemState
read fSemState write SetSemState default scOff;

The SetSemState method is more complex than most propertysetting methods, in that it calls other private methods of this
class (TurnOff, StartPulse, and StopPulse). In fact, besides
assigning the new Value for the property, we need to start or
stop the Timer (if the SemState property changes to or from
scPulse), and change the status of the three embedded Led
components.
procedure TCntSemaphore.SetSemState (Value: TSemState);
begin
if Value <> fSemState then
begin
TurnOff;
if fSemState = scPulse then
StopPulse;
case Value of
scRed: fRedL.Status := lsOn;
scGreen: fGreenL.Status := lsOn;
scYellow: fYellowL.Status := lsOn;
scPulse: StartPulse;
// scOff: nothing to do
end;
fSemState := Value;
end;
end;

COMPONENTS
DEVELOPERS

Page 21 / 2175

Writing Delphi Components III (continuation 1)


The TurnOff procedure, which we call at the beginning of
the SetSemState method and at the end of the constructor,
sets the Status property of all Led components to lsOff:
procedure TCntSemaphore.TurnOff;
begin
:= lsOff;
fRedL.Status
fGreenL.Status := lsOff;
fYellowL.Status := lsOff;
end;

The other two methods called by SetSemState are


StartPulse and StopPulse, which dynamically create and
destroy the Timer that we use to make the red Led flash:
procedure TCntSemaphore.StartPulse;
begin
fTimer := TTimer.Create (self);
fTimer.Interval := fInterval;
fTimer.OnTimer := TimerOnTimer;
fTimer.Enabled := True;
end;
procedure TCntSemaphore.StopPulse;
begin
fTimer.Enabled := False;
fTimer.Free;
fTimer := nil;
end;

We also call the StopPulse method in the destructor, in case


the light is flashing:
destructor TCntSemaphore.Destroy;
begin
if fSemState = scPulse then
StopPulse;
inherited Destroy;
end;
The effect of the Timer, and the reason we need it, is to
turn the red Led on and off:
procedure TCntSemaphore.TimerOnTimer (Sender: TObject);
begin
if fRedL.Status = lsOn then
fRedL.Status := lsOff
else
fRedL.Status := lsOn;
end;

(You might want to change this behavior to turn on and off


the yellow light, if you live in a country where yellow is the
pulsing light of a traffic signal.)
We added a Timer component reference to the class
declaration, as well as one more property, Interval, which
we use to set the Timer interval. Here are the new field,
property, and method declarations, including the last few
methods described:
type
TCntSemaphore = class (TCustomControl)
private
...
fTimer: TTimer; // timer for pulse
fInterval: Integer; // timer interval
procedure TimerOnTimer (Sender: TObject);
procedure TurnOff;
procedure StartPulse;
procedure StopPulse;
public
destructor Destroy; override;
published
property Interval: Integer
read fInterval write SetInterval default 500;

Notice that we dont create the Timer in the constructor,


but only when we need it (when the SemState is scPulse).
If we had chosen to create the Timer in the constructor, we
could have used its Interval property and not declared an
Interval property for the TCntSemaphore class. Since the
Timer doesnt exist for the life of this component,
Page 22 / 2176

COMPONENTS
DEVELOPERS

well need to set the internal field, and then copy the value
to the embedded component:
procedure TCntSemaphore.SetInterval
(Value: Integer);
begin
if Value <> fInterval then
begin
fInterval := Value;
if Assigned (fTimer) then
fTimer.Interval := fInterval;
end;
end;

Overriding the SetBounds Method


Our program also has to deal with changes to the size of the
TCntSemaphore component. For this component, we
basically have three Led components in a column.
Accordingly, we need to specify dimensions for the
component, or at least its paint area. A user can actually
change the Width and Height properties of the component
independently, either by using the Object Inspector or by
dragging the components borders.
Redefining these properties to resize the three Led
components (adjust the Height or Width property for each
as the enclosing component changes) would require some
work. In fact, it would create many problems, since the
property values are actually interrelated.
However, when we examined the VCL source code (and the
help file) we discovered that setting any of the TControls
positional properties (Left, Top, Height, and Width) always
results in a call to the SetBounds method.
Since this is a virtual method, we can simply override it to
customize the sizing of the component and the components
it contains.
Here are the final additions to the class declaration
(including the Paint method, which we discuss in the next
section):
public
procedure Paint; override;
procedure SetBounds (ALeft, ATop,
AWidth, AHeight : Integer); override;

SetBounds defines a minimum size for the component,


computes the actual size of the TCntSemaphore image
(which doesnt take up the complete surface of the
components), and sets the size and position of each Led
accordingly:
procedure TCntSemaphore.SetBounds ( ALeft, ATop, AWidth,
AHeight : Integer);
var
LedSize: Integer;
begin
// set a minimum size
if AWidth < 20 then
AWidth := 20;
if AHeight < 60 then
AHeight := 60;
inherited SetBounds (ALeft, ATop, AWidth, AHeight);
// compute the actual size of the semaphore image
if AWidth * 3 > AHeight then
LedSize := AHeight div 3
else
LedSize := AWidth;
// set the LED position and size
LedSize := LedSize - 2;
(1, 1, LedSize, LedSize);
fRedL.SetBounds
fYellowL.SetBounds(1, LedSize + 3, LedSize, LedSize);
fGreenL.SetBounds (1, LedSize * 2 + 5,LedSize,LedSize);
end;

December 2009 BLAISE PASCAL MAGAZINE 9

Writing Delphi Components III (continuation 2)


Painting the Semaphore
Here is the Paint method, which merely delegates the work
to the Led components (this is not evident from the source
code, because Delphi automatically calls the Paint methods
of the three sub-components):
procedure TCntSemaphore.Paint;
var
LedSize: Integer;
begin
// compute the actual size
// of the semaphore image
if Width * 3 > Height then
LedSize := Height div 3
else
LedSize := Width;
// draw the background
Canvas.Brush.Color := clBlack;
Canvas.FillRect (Rect (0, 0,
LedSize, LedSize * 3));
end;

The effect of this Paint method is visible also at design


time, as you place the component on a form:

type
TCntSemaphore = class (TCustomControl)
...
private
fGreenClick, fRedClick, fYellowClick:
TLightClickEvent;
// LED click response methods
procedure GreenLedClick (Sender: TObject);
procedure RedLedClick (Sender: TObject);
procedure YellowLedClick (Sender: TObject);
published
// custom events
property GreenClick: TLightClickEvent
read fGreenClick write fGreenClick;
property RedClick: TLightClickEvent
read fRedClick write fRedClick;
property YellowClick: TLightClickEvent
read fYellowClick write fYellowClick;

As you can see from the code above, there is no technical


difference between an event and a property. You define both
using the property keyword, the IDE saves both to the DFM
file, and both properties and events require storage and read
and write specifications. The fact that events and properties
show up in different pages of the Object Inspector is a
result of their data type.
Now lets examine the code for the GreenLedClick method.
Basically, if weve assigned a method to the corresponding
event property, we call that method. Whats unusual is that
we must provide an initial value for the parameter that well
pass by reference (the Status variable, which becomes the
Active parameter when you call the method), and then we
have to check the final value of the parameter, which might
have been changed by the handler for this event:

Defining Custom Events


Finally, we want to examine the TCntSemaphore
components custom events. Instead of simply redeclaring
(sometimes called surfacing) standard events, as we did in
previous components, we want to define new events.
Specifically, we want to create events for clicks on any of
the Led components.
As it turns out, this is not only a custom event, it also has a
custom event type (that is, a custom method pointer type):
TLightClickEvent.
Here is the definition of the new data type, marked by the
keywords of object to indicate that we are defining a
method pointer type instead of a procedural type:
type
TLightClickEvent = procedure (
Sender: TObject; var Active: Boolean) of object;

Notice that in addition to the typical Sender parameter,


weve defined a second parameter thats a Boolean value
passed by reference. Well use this parameter to allow the
event handler to pass information back to the component
(based on some condition determined by the program that
uses the component, as well see in an example shortly).
To support our custom events, weve added three new
TLightClickEvent fields, three methods we are going to use
to intercept the Led components events, and three new
properties for the actual events:

December 2009 BLAISE PASCAL MAGAZINE 9

procedure TCntSemaphore.GreenLedClick (Sender: TObject);


var
Status: Boolean;
begin
if Assigned (fGreenClick) then
begin
Status := (fGreenL.Status = lsOn);
fGreenClick (self, Status);
if Status then
SemState := scGreen;
end;
end;

As we mentioned earlier, the Active property allows an


event handler to return any change of value to the
corresponding methods because we used a reference
parameter.
The rationale behind this approach is that when a user
clicks on one of the components Led, the program will
notify the component to turn that LED on.
I wont support the opposite operation, turning off a Led
when the user clicks on it, because that would put the
TCntSemaphore component in an undefined state.
Remember, this is not an event defined for one of the Led
components, but an event of the TCntSemaphore
component, which acts as a single entity.
In fact, the code above changes the Status of the traffic
light, and not that of an embedded Led component.
By the way, defining event properties using reference
parameters isnt very common in Delphi, but there are
several examples in the VCL. The two most common are
the OnCloseQuery and OnClose events of the form. As you
have seen, this approach is rather simple to implement, and
it makes the component more powerful for the
programmers using it. The big advantage is that it requires
less code to implement this specific behavior.
COMPONENTS
DEVELOPERS

Page 23 / 2177

Writing Delphi Components III (continuation 2)


Another Temporarily Conclusion
There are a few more features we need to implement to
finish the development of this component. In its SetBounds
method we could have added code to limit the size of the
component to that of the actual image, by modifying the
parameters in the inherited SetBounds call by adding the
line:
inherited SetBounds (ALeft, ATop, LedSize, LedSize * 3);

However, this call doesnt do what you might expect, as Ill


explain in the next article, which will cover (among other
topics) components state and the Loaded method.

Page 24 / 2178

COMPONENTS
DEVELOPERS

December 2009 BLAISE PASCAL MAGAZINE 9

Talking Delphi By Henk Schreij


starter

expert

DELPHI 3 ... 2010 / Win32

Sometimes it's handy to have your program be able to read


a message out loud. This gets attention, and it can be quite
useful in certain situations. With the advent of Vista, the
standard voice that comes with the system (Anna) is easy
to understand, although the XP voice (Sam) is also
possible to understand despite its nasal twang. As can be
seen from this article, it's very easy to give your computer
a voice. However, if you want a language other than
English, it will have an English accent. This is because
only English is installed as standard, despite the fact that
Windows can in principle speak messages in another
languages, as can be seen in some other countries such as
Japan. Maybe we can have our own accent in a few years.
Early or late binding
Speech is handled by the Speech API (SAPI) in Windows.
Starting with version 5.1, which is installed as standard with
XP, it includes a type library with the most important
interfaces. As a result, this SAPI is easy to use with
automation via early or late binding.
Early binding means that you import the type library, which
enables you to use the Component palette to place a voice
component on your screen.
After this, you can easily program it by means of Properties,
Events, or (lest we forget) Code Insight or Code
Completion (with dropdown menus after the point, etc).
The advantage of late binding is that you do not have to
import a type library. The disadvantage is that you have to
write more code and you cannot use Code Completion.
The entry threshold is thus higher with this approach, since
you don't know the function names, constants, and so on.
For a nice example of speech using early binding, have look
at movie 31 on the Codegear Guru website
(http://www.codegearguru.com/video/031/speech.html).
In this article I present an example of late binding.

Example of reading text out loud


If you want to use late binding, you need to know the names
of the functions and so on. Fortunately, Microsoft publishes
this information on its MSDN website in the form of a tree
view. (http://msdn.microsoft.com/en/library)
However, there are so many items that you are bound to get
lost, so I show the most important ones for this article in
Figure 1.
The code for late binding is essentially the same in all cases.
Add ComObj to 'uses', declare a Variant as a placeholder
under 'private', and use CreateOleObject to add the Variant
to the OnCreate section of the form. A memo and a button
for specifying the message to be spoken have also been
added to the following code:
unit Unit1;
interface
uses Windows, Messages, SysUtils, Variants, Classes,
Graphics, Controls, Forms, Dialogs, StdCtrls, ComObj;
type
TForm1 = class(TForm)
Button1: TButton;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
SpVoice: Variant;
public
{Public declarations }
end;
var Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
SpVoice:= CreateOleObject('SAPI.SpVoice'); //uses ComObj
end;
procedure TForm1.Button1Click(Sender: TObject);
SpVoice.Speak(Memo1.Text, 1); //reads the text in Memo out loud
end;
end.

If you go to 'Application Level Interfaces' in the MSDN tree


structure and select 'Text to Speech Interfaces', under
'Overview' you will see that the 'SpVoice' interface has a
'Speak' method for reading a message or a text file out loud.
The Speak method has two parameters with the following
definitions (the third one can be ignored for this application):
pwcs

A PChar string (possibly containing XML markup)


to be synthesized. If dwFlags is SPF_IS_FILENAME,
this should point to a PChar path to a file.
dwFlag Flags used to control the process. The flag values
are contained in the SPEAKFLAGS enumeration.

In the example above, the value '1' is specified as the second


parameter (flag). This value was taken from enumerated list on
the 'SpeakFlags' page (see Figure 2).

Figure 1: The most important MSDN items for speech

December 2009 BLAISE PASCAL MAGAZINE 9

COMPONENTS
DEVELOPERS

Page 25 / 2179

Talking Delphi (continuation 1)


This stops speaking (purge) without reading out loud a new
text ( a blank string in BEFORESPEAK).
It is also possible to read the content of a file on disk out
loud by using parameter value '4' (IS_FILENAME). It's a
good idea to do this asynchronously so you can stop the
process if you accidentally choose a long file.
To do this, specify a parameter value of '1 or 4'.
Alternatively, you can simply specify a value of 5, since
bitwise OR combinations are allowed. Note here that 5 can
only be generated as the sum of 1 and 4.
To enable file selection, place an OpenDialog on your
screen with a filter that limits the choice to .txt files. The
resulting code is:
procedure TForm1.Button3Click(Sender: TObject); //Read out loud
begin
OpenDialog1.Filter:= 'Text (*.txt)|*.txt';
if OpenDialog1.Execute then
SpVoice.Speak(OpenDialog1.FileName, 5);
end;

Figure 2. MDSN parameter list for speech


Bit-type enumerations (summary lists) start with '0' and are
hexadecimal numbers, such as can be seen from the
following table:

Incidentally, SpVoice.Speak also accepts .wav files (such as


which it simply plays back.
You can also specify an Internet address, such as
http://www.nldelphi.nl, but this is not relevant in the present
context.
C:\Window\Media\Chimes.wav),

The most interesting flag value is '8', which allows you to


add XML code to the text to be read out loud.
Set a parameter value of '9' (1 or 8) in Button1Click
instead of '1', or a value of '13' in Button3Click.
This enables you to make all sorts of adjustments to speech
output.

On the MSDN site, the effect of each flag is described


separately below the parameter list.
With the DEFAULT flag, the message is spoken
synchronously and purging (forced termination of the spoken
message) is not possible.
This is why the value '1' (asynchronous speech) was chosen
for the example above, since it allows us to use a 'Stop' button
as shown in Figure 3.

Reading text out loud with XML


As you can see from Figure 1, you will find an XML text to
speech (TTS) tutorial in the 'white papers' section of the
MSDN tree structure.
If you add 'IS_XML' (or 8) to your parameter, you can
use XML to adjust a large number of settings for the spoken
text. A few simple examples are described below.
Use the following XML code to insert a pause in the spoken
message (such as 1 second in this example):
The brown fox<silence msec="1000" />jumps over the dog.

The specified value is placed between double quotation


marks after an equal sign. Here there is a single tag with a
slash at the end.
Paired tags are is also possible, such as when a word is
spelled out letter by letter:
Figure 3. Example program: spoken text with Stop option

Delphi <spell>Delphi</spell>.

The following code is assigned to the 'Stop' button:


procedure TForm1.Button2Click(Sender: TObject); //Stop
begin
SpVoice.Speak('', 2);
end;

Page 26 / 2180

COMPONENTS
DEVELOPERS

Here the computer first speaks the word 'Delphi' and then
spells it letter by letter.
There are also tags that can be used either singly or in pairs,
such as the 'Volume' tag. The parameter value for normal
volume is level = "100". In the first of the following
examples the word 'fox' is spoken softly, while in the
second example all of the text after 'fox' is spoken softly:

December 2009 BLAISE PASCAL MAGAZINE 9

Talking Delphi (continuation 2)


The brown <volume level="50">fox</volume> jumps over the
dog.
The brown fox <volume level="50" /> jumps over the dog.

You can also adjust the speech rate over a range of


-10 to +10 ('0' is normal rate). To significantly slow
down the speech rate for spelling out the word in the
previous example, set the rate to -5:
Delphi <rate speed="-5"><spell>Delphi</spell></rate>.

A less important option, but nevertheless perhaps


interesting, is that you can change the pitch of the voice.
Here again the adjustment range is -10 to +10, where
'0' is normal pitch.
This isn't especially noticeable unless you use the minimum
or maximum setting, such as in the following example:
The brown fox <pitch middle="-10> jumps over the dog
</pitch>.

The remaining difficulty is that the speech engine generates


only English pronunciation. If you want to control the
pronunciation yourself, SAPI allows you to specify the
pronunciation using individual letters. For example:
<pron sym="D eh l f iy"/>

This way you will hear 'Delphi' with the authentic Greek
pronunciation, instead of the frightful 'Delfaai' of American
English. You can even use this to mimic a different
language. For example, you can use the following
pronunciations to count from one to four in Dutch:
<pron sym ="ey n"/>.
<pron sym ="t w ey"/>.
<pron sym ="d r iy"/>. <pron sym ="v iy r"/>.

As this is nearly impossible to read, even if you are a native


Dutch speaker, you can also include the original word
(which will not be spoken):
<pron
<pron
<pron
<pron

sym
sym
sym
sym

aa
ae
ah
ao
aw
ax
ay
b
ch
d

father
cat
cut
dog
foul
ago
bite
big
chin
dig

dh
eh
er
ey
f
g
h
ih
iy
jh

then
pet
fur
ate
fork
gut
help
fill
feel
joy

k
l
m
n
ng
ow
oy
p
r
s

cut
lid
mat
no
sing
go
toy
put
red
sit

sh
t
th
uh
uw
v
w
y
z
zh

she
talk
thin
book
too
vat
with
yard
zap
pleasure

You are limited to these letters, although it is also possible


to use other punctuation marks.
Conclusion
It is very easy to have your computer read messages out
loud by using late binding. You can get around the
drawback that you don't know the functions and constants
by looking them up on Microsoft's MSDN website. For
languages other than English, you can mimic the normal
pronunciation of the language by specifying it with
individual letters.
One useful application is to have your computer read
instructions out loud. However, people whose native
language isn't English will have to wait until Microsoft adds
a separate voice for each language.
About the author
Henk Schreij:
has graduated as a technical designer.
He works for his own company, as a programmer
of 'custom made software' for several different
companies. Henk has written articles for Blaise
since Delphi 1, totalling more than 100 now,
mainly aimed at starting programmers.
These articles usually have something of
interest, too, for those with programming
experience.

="ey n" >een</pron>.


="t w ey">twee</pron>.
="d r iy">drie</pron>.
="v iy r">vier</pron>.

You can find the full list of letters in the tree structure on
the MSDN site in the 'American English Phenomea
Table' under the 'Miscellanea' heading:

Buy
Delphi
now

barnsten

DEVELOPER & COLLABORATION TOOLS

Embarcadero Partner for Belgium, Netherlands and Luxembourg


December 2009 BLAISE PASCAL MAGAZINE 9

COMPONENTS
DEVELOPERS

Page 27 / 2181

LCD Interfacing: Driving a HD44780 LCD in Delphi


by Thiago Batista Limeira
starter

expert

DELPHI 3 ... 2010 / Win32

LCDs are widely used in electronic applications, to


demonstrate the state of many devices, and also, are
extensively used in CaseModding.
There are a lot of projects showing how to interface a
LCD to display computer's information such as the CPU's
speed and temperature, the system time, Winamp's play
list, etc.
In addiction there are specialized programs for controlling
LCDs like LCD Smartie, which is opensource and was
totally developed in Delphi!
In this article, we are going to develop a program for
interfacing a 16x2 LCD, with it we'll be able to initialize,
write text, send commands, etc.
This will be done by using the parallel port with the help
of TLPTPort component, which in my opinion offers the
simplest and easiest access to the parallel port's registers.

status. By setting the RS pin high, character data can be


transferred to and from the module.
Pin 5 is the Read/Write (R/W) pin.
This pin is pulled low in order to write commands or
character data to the module, or pulled high to read
character data or status information from its registers.
Pin 6 is the Enable (E) pin.
This input is used to initiate the actual transfer of commands
or character data between the module and the data lines.
When writing to the display, data is transferred only on the
high to low transition of this signal. However, when reading
from the display, data will become available shortly after the
low to high transition and remain available until the signal
falls low again.
Pins 7 to 14 are the eight data bus pins (D0 to D7).
Data can be transferred to and from the display, either as a
single 8-bit byte or as two 4-bit nibbles. In the latter case,
only the upper four data lines (D4 to D7) are used. This
article will cover the 8-bit mode. See Figure 2.

Figure 1. Driving LCD modules ,one of the most used


practices in case modding
Note: For more details about the parallel port's
architecture and control in Delphi, I recommend reading my
article Controlling the Parallel Port, which presents much
detailed information in Blaise Pascal Magazine
#6.
Important details about LCDs
Before we start interfacing the LCD, we need first
understand some very important details about LCDs that
will be extremely necessary in order to interface them.
Among this issues we need to define which driver based
LCD we intend to interface, the LCD pins, schematic,
commands, etc.
We are going to use an HD44780 alphanumeric LCD (or
compatible), this type of module can be easily found. The
display controller has the task of controlling the liquid
crystal matrix, even though this module doesn't offer the
same advantages of the most modern LCDs used in cell
phones, notebooks, and many other equipments, its
simplicity will be an advantage when it comes to hardware
assembly.
The LCDs have a standard pins (those who doesn't offer
backlight option) with 14 pins whereas those who have
backlight 16 pins, these 2 pins are responsible for activating
the backlight. Table 1 shows the HD44780 LCD pins and
its functions.
Pins 1 and 2 (Vss and Vdd)
are the power supply pins. The Vdd should be connected to
the positive supply and Vss to the 0V supply or ground.
Pin 3 is a control pin, Vee,
which is used to alter the contrast of the display. Ideally,
this pin should be connected to a variable voltage supply.
Pin 4 is Register Select (RS) pin,
the first of the three command control inputs. When this pin
is low, data bytes transferred to the display are treated as
command, and data bytes read from the display indicate its
Page 28 / 2182

COMPONENTS
DEVELOPERS

Tabel 1. HD44780 LCD pins.

Figure 2: Schema for our example.

December 2009 BLAISE PASCAL MAGAZINE 9

LCD Interfacing (vervolg 1)


Caution!
The parallel port is directed connected to your PC's mother
board, therefore we recommend the subscribers caution
when connecting electronic circuits to this port, one
electrical discharge can damage your computer. We take no
responsibility for any kind of damage.
Creating the THD44780 Class
Now that we know the basics about LCD, we can start
coding our LCD control application. Initialize your Delphi,
create a new application and save it as LCD.dpr, save
the unit as uMain.pas, put a TLPTPort and a
XPManifest components onto the form, which will be
renamed as frmMain. Initialliy we need to create a LCD
driver class to control the LCD module more easily.
Create a new unit and save it as uHD44780.pas, in it
we are going to create the THD44780 class, see
Listing 1.
type
THD44780 = class
private
public
Columns : integer;
: integer;
Lines
LPTPort : TLPTPort;
procedure InitializeLCD;
procedure ClearDisplay;
procedure PosCursor(X, Y : integer);
procedure Cursor_Display(CursorOn, DisplayOn,
BlinkCursor : Boolean);
procedure ShiftCursor_Message(ShiftText,
ToRight : Boolean);
procedure WriteText(Text : string);
procedure SendCommand(Command : byte);
procedure SendData(Data : byte);
procedure WritePort(Address : word; Value : byte);
constructor Create(LPT: TLPTPort);
destructor Destroy; override;
end;

Listing 1: THD44780 class definition.

With this class we can encapsulate all control


functionalities HD44780 module, in the next pages we'll be
discussing this class in details. Observe that our class has
some public variables below:
public
Columns : integer;
Lines
: integer;
LPTPort : TLPTPort;

constructor THD44780.Create(LPT: TLPTPort);


begin
LPTPort := LPT;
Columns := 16;
Lines := 2;
WritePort($378, 0);
WritePort($37A, 0);
end;
destructor THD44780.Destroy;
begin
LPTPort.Free;
inherited Destroy;
end;

Listing 2: THD44780 Create and Destroy methods.

As we can see this the constructor receives a LPT


parameter, which is a pointer to a TLPTPort component that
is responsible for controlling the parallel port as mentioned
before. After, we set the Columns and Lines variables.
The WritePort can be seen in the last two lines of the
constructor, it is just a routine for setting the port's address
and value to be sent. The WritePort definition can be seen
below in Listing 3.
procedure THD44780.WritePort(Address : word; Value :
byte);
begin
LPTPort.SelectPort(Address);
LPTPort.Out(Value);
end;

Listing 3. WritePort method.

With it we initialize the port address and value with zero.


And in the destructor we free the LPTPort component
from the memory.
Powering the LCD
When powered up, the display should show a series of
dark squares, only on the superior part of the display (see
Figure 3).
These character cells are actually in their off state, so the
contrast control should be adjusted until the squares are
just visible.
The display module resets itself to an initial state when
power is applied, which curiously has the display blanked
off, so that even if characters are entered, they can't be
seen.
It is therefore necessary to issue a command at this point,
to switch the display on see Table 2 for a full list of
commands.

The Columns and Lines variables are used to define de


number of columns and lines o four LCD, in this example
we have a 16x2 LCD (16 Columns and 2 Lines). The
LPTPort variable is responsible for accessing the parallel
port.
Declare the THD44780 class and hold Ctrl+Shift+C, for Class
Completion.
Below we have Listing 2, with the Create and Destroy
methods of the THD44780 class.

Figure 3. Powering up the LCD module.

December 2009 BLAISE PASCAL MAGAZINE 9

COMPONENTS
DEVELOPERS

Page 29 / 2183

LCD Interfacing (vervolg 2)


procedure THD44780.SendCommand(Command : Byte);
begin
{Register Select Low and Enable High}
{Obs: pin 1 (Strobe) is inverted in the port's hardware}
WritePort($37A,0); // sends 00000000 to the control register
sleep(1); // 1 millisecond
WritePort($378,Command); // sends Command to the data register
sleep(1); // 1 millisecond
{Register Select Low and Enable Low}
WritePort($37A,1); // sends 00000001 to the control register
if (Command = LCD_CLEAR) or (Command = LCD_HOME) then
sleep(2) // 2 milliseconds
else
sleep(1); //1 milliseconds
end;

Table 2 Shows a full list of commands

Listing 5. THD44780 SendCommand methode.

In addition, the THD44780 class uses series of constants in


order to help its configuration and initialization. For the full
list of constants see Listing 4.

The LCD control pins are being controlled by the parallel


port control register, therefore we initially write a value of 0
in the port's address $37A. This way the Enable pin will
have a logic level value of 5V and the Register Select pin
0V. So the LCD Will interpret the signal we're about to send
as a command. We need to wait the LCD to process for 1
millisecond, send the command we want to the port's data
register at $378, wait again for the LCD to process the
command sent, and finally putting Enable in low logic level
value (0V). There can be differences in time delay
depending on the command sent (1 or 2 milliseconds).

const
// Command:
LCD_CLEAR
LCD_HOME

= $01;
= $02;

Description:
// Clear Display
// Put cursor in the first line
// and first column

Binary Value:
00000001
00000010

LCD_ONOFF
LCD_ONOFF_CP
LCD_ONOFF_C
LCD_ONOFF_D

=
=
=
=

// Display Control:
// Blink Cursor or not
// Cursor On or not
// Display On or not

00001000
00000001
00000010
00000100

LCD_SHIFT
LCD_SHIFT_D
LCD_SHIFT_M

= $10;
= $04;
= $08;

// Cursor Shift:
// Shift right or left
// Shift message

00010000
00000100
00001000

LCD_FUNCTION
LCD_FUNCTION_M
LCD_FUNCTION_L
LCD_FUNCTION_B

=
=
=
=

// Operation Mode:
// Use 5x10 or 5x7 Matrix
// 2 or more lines or 1 line
// 8 bits or 4 bits

00100000
00000100
00001000
00010000

LCD_DDRAM

= $80;

// DDRAM Address

10000000

$08;
$01;
$02;
$04;

$20;
$04;
$08;
$10;

Delay between commands


A very important detail is that for every command sent to
the LCD, there must be a minimum delay we have to wait
in order to the LCD to process new commands or data,
usually this time varies from microseconds to milliseconds.
In our case we dont need much time accuracy therefore the
sleep function can be used without concerns, this function
interrupts the application execution for a specified time.
Sending Commands to the LCD
Now we need to program the basic routines for sending
commands and data. And learn in details how to drive the
LCD module. For sending commands we need to follow
some steps:
1. Select Command Register
(Register Select = 0) and
Enable High
(Enable = 0, inverted hardware);
2. Wait for LCD to process;
3. Send Command (write operation);
4. Wait for LCD to process;
5. Send Enable Low
(Enable = 1, inverted hardware);
6. Wait for LCD to process;
Now that we have learned how to send commands, we can
program the SendCommand method described in Listing 5.
Page 30 / 2184

COMPONENTS
DEVELOPERS

Note
The Parallel Port Control register (base address + 2) was
intended as a write only port. When a printer is attached to
the Parallel Port, four "controls" are used. These are
Strobe, Auto Linefeed, Initialize and Select Printer, all of
which are inverted except Initialize.
Sending data to the LCD
To send data to the LCD, we need to put the Register Select
pin low to select the LCD's data register. Below, we have
the needed steps:
7. Select Data register (Register Select = 1) and
Enable High
(Enable = 0, inverted hardware);
8. Wait for LCD to process;
9. Send data (write operation);
10. Wait for LCD to process;
11. Send Enable Low
(Enable = 1, inverted hardware);
12. Wait for LCD to process;
With this steps we can implement our SendData method, see
Listing 6.
procedure THD44780.SendData(Data : Byte);
{Register Select High and Enable High}
begin
WritePort($37A,4); // sends 00000100 to the control register
sleep(1); // 1 millisecond
WritePort($378,Data); // sends data to the data register
sleep(1); // 1 millisecond
{Register Select High and Enable Low}
WritePort($37A,5); // sends 00000101 to the control register
sleep(1); // 1 millisecond
end;

Listing 6. THD44780 SendData Methode.

In this method, which is basically the same as the


SendCommand, the only difference is that the Register
Select pin is high in the parallel port's control register
($37A).

December 2009 BLAISE PASCAL MAGAZINE 9

LCD Interfacing (vervolg 3)


Initializing the LCD
Finally with the SendCommand method, we can initialize
the display using the InitializeLCD method described in
Listing 7).
procedure THD44780.InitializeLCD;
begin
SendCommand(LCD_FUNCTION
or LCD_FUNCTION_B
or LCD_FUNCTION_L);
sleep(15); // 15 milliseconds
end;
Listing 7. LCD Initialization method.

In the InitializeLCD method we send


LCD_FUNCTION (sets the operation mode),
LCD_FUNCTION_B (sets the 8 bit interface) and
LCD_FUNCTION_L (sets 2 lines mode),
after sending these commands we wait 15 milliseconds for
the LCD to process the initialization.
LCD Control Parameters
After initializing the display, we need to set some control
parameters of the display: cursor on, display on and blink
cursor. In the Cursor_ Display, we have 3 boolean
parameters that we use to set the display state, see Listing 8.
procedure THD44780.Display_Cursor(CursorOn, DisplayOn,
BlinkCursor : Boolean);
var Temp : byte;
begin
Temp := LCD_ONOFF;
if CursorOn
then Temp := Temp or LCD_ONOFF_C;
if DisplayOn
then Temp := Temp or LCD_ONOFF_D;
if BlinkCursor then Temp := Temp or LCD_ONOFF_CP;
SendCommand(Temp);
end;

Listing 8. Methode voor het zetten van de display


control parameters.

In this method, the variable Temp is the byte mask we use


for the display control (using the LCD_ONOFF constant),
using the bit operator or. After that we send the Temp value
as a command using SendCommand method.
Clearing the LCD
To clear the display, just send the LCD_CLEAR constant
using the SendCommand method, as shown below:
procedure THD44780.ClearDisplay;
begin
SendCommand(LCD_CLEAR);
end;

Positioning the cursor


It is possible to control the cursor positioning using the
LCD_DDRAM constant, which allows Access to the LCD
data position address, see Listing 9.
procedure THD44780.PosCursor(X, Y : Integer);
var offset : Integer;
begin
offset := 0;
if X > Columns then
begin
X := 1;
inc(Y);
end;
if Y > Lines then Y := 1;
if Y = 1 then offset := 0;
if Y = 2 then offset := 64;
SendCommand(LCD_DDRAM + offset + (X-1));
end;

Listing 9: Method for cursor positioning.

December 2009 BLAISE PASCAL MAGAZINE 9

Note that by using the X and Y parameters we


\can point the cursor to a new position, if X is
greater than the number of columns, we place X at the
first position of the next line. And case Y is greater than
the number of lines we place Y in the first line.
With the offset byte variable we can set the cursor position
using the LCD_DDRAM constant.
Shifting cursor and/or text
The HD44780 is very versatible, we can shift cursor, text
or both at the same time using the LCD_SHIFT Constant.
See Listing 10 for the ShiftCursor_Message method.
procedure THD44780.ShiftCursor_Message(ShiftText,
ToRight : Boolean);
var
Temp : byte;
begin
Temp := LCD_SHIFT;
if ShiftText then Temp := Temp or LCD_SHIFT_M;
if ToRight then Temp := Temp or LCD_SHIFT_D;
SendCommand(Temp);
end;

Listing 10. Methode voor het verschuiven van de cursor en/of tekst.

In this method we have 2 boolean parameters, the first


ShiftText, if True makes both the cursor and text shift and
the second ToRight if True, makes the display shifting to the
right direction otherwise makes the shifting to the left
direction. Notice that we add the constants value to the
Temp variable, after that we send this value as a command
to the LCD using SendCommand method.
Writing characters to the LCD
In order to send characters to the LCD we need to make use
of the WriteText method listed below in Listing 11.
procedure THD44780.WriteText(Text : string);
var
i : byte;
begin
for i := 1 to Length(Text) do SendData(Ord(Text[i]));
end;

Listing 11: Method for writing characters to the display

Note
that in order to send the Text parameter to the LCD we use
one for looping to convert each letter to its ASCII value
using the Ord function. Every time the LCD receives a
character, it writes it and moves 1 position to the right, the
cursor marks the place where the next character is going to
occupy.
Back to the application example
With the finished class we can go back to the example
application construction, now declare the uHD44780 in the
interface section. Do not forget to declare a THD44780
variable, just like below:
LCD : THD44780;
With this variable we can access all the functionality we
have already implemented in the THD44780 class, and we
will be able to interface the display without any concerns.
Now double click the frmMain and in its OnCreate event
type the code below:
LPTPort.Port[$378];
LCD := THD44780.Create(LPTPort);
COMPONENTS
DEVELOPERS

Page 31 / 2185

LCD Interfacing (vervolg 4)


In this code we associate the frmMain's LPTPort
component with our THD44780 instance.
The LPTPort address is set to $378.
Do not forget to free the LCD variable from memory in the
frmMain Destroy event, with the following command:
LCD.Free;

Writing Messages to the LCD


To write messages in the LCD from our application, place a
TEdit component in the form and alter its Name property to
edtMessage, the Edit text will be send to the LCD by the
btnWriteClick, see the code below:
LCD.WriteText(edtMessage.Text);

Opening the LPTPort


Now we need a TButton and TComboBox components to
get the computer's LPT Ports list. Place the Button in the
form with its Name property altered to btnOpen and
change the ComboBox Name property to cmbxLPT.
Double click the btnOpen and type the following code in
the OnClick event:
LCD.LPTPort.Open;

Changing the cursor


Put 3 checkboxes on the form, these checkboxes will alter
the cursor behavior; change its Name properties to
chckbxCursorOn, chckbxDisplayOn and
chckbxBlinkCursor.
Whenever the CheckBoxes Checked property is changed we
update the cursor state. Double click the chckbxCursorOn
and type the code listed in Listing 13. Point the events of the
other two CheckBoxes to the chckbxCursorOnClick event.

Enumerating the LPTPorts


By using the LPTPort's Open function, the component loads
all necessary DLLs to access the port, and the LPTPort
listing becomes available. On the cmbxLPT DropDown
event type the following code:
LCD.LPTPort.EnumPorts(cmbxLPT.Items);
This command lists all computer's LPT Ports and places
this list in the cmbxLPT Items property.
Initializing the LCD
For the LCD initialization, place 3 Buttons in the form with
the Name properties changed to btnInitialize, btnWrite
e btnClear. In the btnInitialize OnClick event, type the
Listing 12 code.
procedure TfrmMain.btnInitializeClick(Sender: TObject);
begin
LCD.InitializeLCD;
LCD.ClearDisplay;
sleep(1);
LCD.WriteText('Blaise Pascal');
LCD.PosCursor(1,2);
LCD.WriteText('Magazine Rocks!');
LCD.Cursor_Display(True,True,True);
end;

Listing 12. btnInitializeClick.

Clicking the btnInitialize will make a call to the


InitializeLCD method, clear the display and generate a
delay of 1 millisecond. After that with the WriteText
method we write Blaise Pascal, at the first line.
The PosCursor places the cursor at the second line where
we write Magazine Rocks!, with the Cursor_Display
method we activate all cursor control parameters, see below
the LCD in action, after the btnInitialize click (Figure 4).

procedure TfrmMain.chckbxCursorOn(Sender: TObject);


begin
LCD.Cursor_Display(chckbxCursorOn.Checked,
chckbxDisplayOn.Checked,
chckbxBlinkCursor.Checked);
end;
Listing 13: Cursor state code.

Moving the cursor and/or the message


To finalize our example put 4 SpeedButtons in the form and
its Name properties to spdbtnCursorLeft,
spdbtnCursorRight, spdbtnTextLeft,
spdbtnTextRight, these buttons will be used for shifting
the cursor and message (to right or left) using the
ShiftCursor_Message method. Listing 14 shows the code
for the SpeedButtons.
procedure TfrmMain.spdbtnCursorLeftClick(Sender: TObject);
begin
LCD.ShiftCursor_Message(False, False);
end;
procedure TfrmMain.spdbtnCursorRightClick(Sender: TObject);
begin
LCD.ShiftCursor_Message(False, True);
end;
procedure TfrmMain.spdbtnTextLeftClick(Sender: TObject);
begin
LCD.ShiftCursor_Message(True, False);
end;
procedure TfrmMain.spdbtnTextRightClick(Sender: TObject);
begin
LCD.ShiftCursor_Message(True, True);
end;

Listing 14. Code for shifting the cursor or text (SpeedButtons).

We have altered the ShiftCursor_Message method


parameters to shift text and cursor in both directions.
Figure 5 shows a suggested GUI for the example
application development.

Figure 5. Suggested
GUI for the
application example
Figure 4: Display after the initialization.
Page 32 / 2186

COMPONENTS
DEVELOPERS

December 2009 BLAISE PASCAL MAGAZINE 9

Conclusion
With our THD44780 LCD control class, interfacing
displays becomes very easy, which is a great alternative to
hobbyists and casemodders, besides recycling old printer
cables we can also give our computers a new design using
our favorite programming language. See you next time and
stay tuned to the Blaise Pascal Magazine.
Links
LCD Interfacing
http://www.8051projects.net/lcd-interfacing/

HD44780

About the Author


Thiago Batista Limeira
Is a Computer Engineer who specializes in Delphi
programming. Since 2004, he has been
programming data communication software in
Delphi/C++ Builder, developing custom vcl
components and developing electronic projects
with microchip's Pic microcontroller. Currently, his
development tools are Delphi and C++ Builder.
Thiago is located in So Paulo, Brazil.
Contact Thiago at: [email protected].

http://pic.rocklizard.org/ReferenceDocs/HD44780HowTo.pdf

BLAISE PASCAL
ARCHIVE 2009
* 2 years complete issues on DVD
* More than 360 pages (in PDF format)
* Very fast search with multiple terms
* All issues indexed
* Find the article you want with just one click
* Full code for all articles included
Full version of Lazarus for Windows on DVD
Trial version of Delphi RAD Studio on DVD
All 35 Code Rage video tutorials on DVD - more than 3
three full days of self study material on
Delphi features and functions
Orders may be placed with
our website store at
http://www.blaisepascal.eu/index.php?actie=./subscribers/subscription_mainpage or

[email protected]
Price 20,00 including postage

December 2009 BLAISE PASCAL MAGAZINE 9

COMPONENTS
DEVELOPERS

Page 33 / 2187

Exploring the rich & versatile inplace By Bruno Fierens


editing capabilities of TAdvStringGrid
starter

expert

DELPHI 3 ... 2010 / Win32

As soon as you want to use a grid for more than just


presenting information, the availability of strong inplace
editing capabilities is crucial for creating user-friendly
applications.
In TAdvStringGrid, not only a very wide range of built-in
inplace editors is available but there is also an extensive
and fine grained control over when inplace editors appear
and how they interact with the various key events in the
grid. TAdvStringGrid was designed to enable most
scenarios users want with just setting a few properties
rather than writing a lot of code.
In this article, we start with a brief introduction of the
basic inplace editors. Then a more detailed look is given to
inplace editing and navigation. Next, more complex builtin inplace editors are handled and finally, there is a
section on using any TWinControl descendent control as
inplace editor in the grid.
Basic editing capabilities in the grid
First of all, editing in the grid is enabled by setting
goEditing to true in grid.Options. In code, this can be done
with:
grid.Options := grid.Options + [goEditing];

This enables editing for all cells in the grid except fixed
cells. In many cases it is desirable that editing is enabled
only in some specific cells or columns.
A cell can be set as readonly by using:
grid.ReadOnly[column,row]: boolean

and setting this property to true. An alternative is by doing


this dynamically via the event OnCanEdit.
In this code snippet, editing is only possible in columns 2
and 4:
procedure TForm1.AdvStringGrid1CanEditCell(Sender:
TObject; ARow, ACol: Integer;
var CanEdit: Boolean);
begin
CanEdit := ACol in [2,4]
end;

Editing and navigation in the grid


Often it is desirable to make it convenient and fast to fully
operate the editing in the grid with as few keypresses as
possible. Therefore it is often convenient to automatically
start the inplace editor for the next sequential cell when
pressing the return key. To enable this capability, set
grid.Navigations.AdvanceOnEnter := true.

Typically, the next sequential cell is the cell right from the
current cell, ie. editing sequence is from left to right.
In some cases, it can be required that the editing sequence
is from top cell to bottom cell. The direction can be choosen
with the property grid.Navigation.AdvanceDirection.
Similary, it can be convenient that the inplace editor is also
immediately shown when the user moves to a next cell with
the arrow keys. To have this behaviour, set
grid.Navigation.AlwaysEdit to true. When you want to allow
that the user uses the TAB key to move to the next cell, set
goTabs to true in grid.Options. Normally, the TAB key
moves focus between controls. With goTabs set to true,
TAB key moves focus in cells of the grid and by default,
when the last cell (bottom right cell) is reached, the TAB
key moves focus back to the first cell. If you want that the
focus moves to the next control on the form when TAB is
pressed on the last cell, set
grid.Navigation.TabToNextAtEnd = true.

Another interesting feature is called CursorWalkEditor.


When grid.Navigation.CursorWalkEditor is set to
true, the left & right arrow keys will move the focus to the
previous or next cell when either the LEFT Key is pressed
when the caret is at position 0 in the editor or when the
RIGHT key is pressed when the caret is after the last
character.

Typically, editing is started by:


1) a mouse click on the focused cell
2) pressing F2
3) typing any character

Built-in regular editors and using their


properties
The default editor is the type edNormal. It is set with
grid.DefaultEditor. This is the default editor type that is
used for all cells in the grid. The edNormal inplace editor
type is similar to a regular TEdit control.
Any character is allowed, there is no size limitation and
multiline text can be entered by using CTRL-ENTER to
start a new line.

A few settings that allow control over this default behaviour


are:

Fine-tuning the basic edNormal editor:


- When multi-line text should not be allowed, set

grid.MouseActions.DirectEdit: boolean;

grid.Navigation.AllowCtrlEnter = false

When true, the inplace editor is shown after the first


mouseclick

- When the length of the entered text should be limited, set


grid.MaxEditLength to a value larger than zero.
When MaxEditLength is set to zero, it is ignored.

grid.MouseActions.EditOnDblClickOnly: boolean;

When true, the inplace editor is only shown after a double


click
grid.MouseActions.CaretPositioning: boolean;

When false, the inplace editor starts with all text selected
and the caret after the last character. When true, the caret is
positioned at the location where the mouse was clicked to
start the inplace editing.
grid.MouseActions.EditSelectAll: boolean;

When false, the caret is positioned after the last character


but no text is selected, allowing to immediately type any
characters without overwriting the selection.
Page 34 / 2188

COMPONENTS
DEVELOPERS

Variations of this basic editor type are:


edNumeric: allow numbers only with a sign
edPositiveNumeric: allow positive numbers only
edFloat: allow a floating point number, ie. number, decimal
separator and thousand separator
edUpperCase: added characters are forced to upper-case.
edMixedCase: added characters are forced to mixed-case, ie.
auto capitalizing the first letter of words.
edLowerCase: added characters are forced to lower-case.
edValidChars: allow only the characters that are in the set
grid.ValidChars: set of char;
edEditBtn: edit control with embedded button.

December 2009 BLAISE PASCAL MAGAZINE 9

Exploring the TAdvStringGrid (continuation 1)


A click on this embedded button triggers the OnEllipsClick
event.
edNumericEditBtn: numeric edit control with embedded button
edFloatEditBtn: float edit control wih embedded button

grid.InvalidEntryTitle: string;
grid.InvalidEntryText : string;
grid.InvalidEntryIcon : integer;

This sample code snippet selects different editor types for


different columns.
procedure TForm1.AdvStringGrid1GetEditorType(Sender:
TObject; ACol, ARow: Integer;
var AEditor: TEditorType);
begin
AdvStringGrid1.MaxEditLength := 255;
case ACol of
1:
begin
AEditor := edNumeric;
AdvStringGrid1.MaxEditLength := 4;
end;
2:
begin
AEditor := edMixedCase;
end;
3:
begin
AEditor := edValidChars;
AdvStringGrid1.ValidChars := 'ABCDEF0123456789';
AdvStringGrid1.MaxEditLength := 8;
end;
end;
end;

In this code snippet, the editor in the first column accepts


only a numeric value with maximum 4 digits. In the second
column, a mixed case editor is specified and in the last
column an 8 digit hexadecimal value only is allowed.
Validation after editing
While the grid has several inplace editor types that
automatically restrict entry thereby disallowing users to
enter
unwanted or incorrect data, this is not always possible.
Therefore, in many cases, validation is required when the
user is about to stop editing. In TAdvStringGrid, the event
OnCellValidate is triggered with as parameters the
coordinates of the cell being edited, the new value that is
about to be entered and a parameter to indicate this
value is valid or not. When this Valid parameter is set to
false, inplace editing is not stopped, forcing the user
to enter a valid value. As the Value parameter is also a
variable parameter, it can also be used for auto correcting
purposes. In this sample code snippet, the user should enter
a numeric value between 3 and 6 digits and when valid,
the value is auto corrected to have a dollar sign suffix. In
addition, a balloon is used to inform what exactly is
incorrect. The grid has public properties:

Title of the balloon


Text of the balloon
Icon of the balloon

procedure TForm1.AdvStringGrid1CellValidate(Sender:
TObject; ACol, ARow: Integer; var Value: string;
var Valid: Boolean);
var
len: integer;
begin
len := Length(Value);
Valid := (len >= 3) and (len <= 6);
if Valid then
Value := Value + '$'
else
begin
AdvStringGrid1.InvalidEntryTitle :=
'Incorrect number';
AdvStringGrid1.InvalidEntryText :=
'Please enter a number with 3 to 6 digits';
AdvStringGrid1.InvalidEntryIcon := ieError;
end;
end;
procedure TForm1.AdvStringGrid1GetEditorType(Sender:
TObject; ACol, ARow: Integer;
var AEditor: TEditorType);
begin
AEditor := edNumeric;
end;

Combobox and spin editors


Another type of inplace editors are comboboxes and spin
editors. The types defined are:
edComboEdit: editable combobox (csDropDown style)
edComboList: non editable combobox
(csDropDownList style)
edSpinEdit: numeric spin editor
edFloatSpinEdit: floating point spin editor
For the comboboxes, values can be accessed with
grid.ComboBox.Items or also with methods
grid.ClearComboString,
grid.AddComboString,
grid.AddComboStringObject.

The editor type is also set from the OnGetEditorType event


and the values for the combobox can be set from the event
OnGetEditorProp. The value of the combobox can also be
preset with grid.SetComboSelection(ItemIndex) or
grid.SetComboSelectionString(string).
To make it clear how this works, this sample shows the use
of two different comboboxes and two diferent spin
editors:

Figur 1:

December 2009 BLAISE PASCAL MAGAZINE 9

COMPONENTS
DEVELOPERS

Page 35 / 2189

Exploring the TAdvStringGrid (continuation 2)


procedure TForm1.AdvStringGrid1GetEditorProp(Sender:
TObject; ACol, ARow: Integer; AEditLink: TEditLink);
var
i: integer;
begin
case ACol of
1:
begin
AdvStringGrid1.ClearComboString;
for i := 0 to 10 do
AdvStringGrid1.AddComboStringObject(IntToStr(i),
TObject(i));
end;
2:
begin
AdvStringGrid1.ClearComboString;
AdvStringGrid1.AddComboString('BMW');
AdvStringGrid1.AddComboString('Audi');
AdvStringGrid1.AddComboString('Porsche');
AdvStringGrid1.AddComboString('Ferrari');
AdvStringGrid1.AddComboString('Mercedes');
// preset the selection to Mercedes
AdvStringGrid1.SetComboSelection(4);
end;
:
3
begin
AdvStringGrid1.SpinEdit.MinValue := 0;
AdvStringGrid1.SpinEdit.MaxValue := 100;
AdvStringGrid1.SpinEdit.Increment := 1;
end;
4:
begin
AdvStringGrid1.SpinEdit.MinFloatValue := -1.5;
AdvStringGrid1.SpinEdit.MaxFloatValue := +1.5;
AdvStringGrid1.SpinEdit.IncrementFloat := 0.01;
end;
end;
end;

Date & time inplace editors


For editing time, date or combined time and date values in
the grid, different editors
are available:
Windows datepicker control with dropdown
calendar
edTimeEdit : Windows timepicker control
edDateEditUpDown: Windows datepicker with spin editor
edDateSpinEdit: VCL date spin editor
edTimeSpinEdit: VCL time spin editor
edDateTimeEdit: combined date and time editor
edDateEdit :

The edDateEdit, edTimeEdit inplace editor can also be


directly accessed via grid.DateTimePicker
to further fine-tune properties such as formatting of
date/time display, appearance of the calendar etc...
To demonstrate this, the code below shows how the format
of the date can be controlled for the date picker
and weeknumbers are turned on on the calendar while the
default display of today's date is disabled:
procedure TForm2.AdvStringGrid1GetEditorType(Sender:
TObject; ACol, ARow: Integer;
var AEditor: TEditorType);
begin
AEditor := edDateEdit;
AdvStringGrid1.DateTimePicker.Weeknumbers := true;
AdvStringGrid1.DateTimePicker.ShowToday := false;
AdvStringGrid1.DateTimePicker.Format := 'ddd dd MMM yyyy';
end;

procedure TForm1.AdvStringGrid1GetEditorType(Sender:
TObject; ACol,
ARow: Integer; var AEditor: TEditorType);
begin
case ACol of
1: AEditor := edComboEdit;
2: AEditor := edComboList;
3: AEditor := edSpinEdit;
4: AEditor := edFloatSpinEdit;
end;
end;

Notice that by default, the combobox or spin editor only


appears when the editing starts. It can be desirable to have
the combobox or spin editor always visible so that the user
is aware that these cells do not have a regular editor. This
can be enabled by setting:
grid.ControlLook.DropDownAlwaysVisible := true
grid.ControlLook.SpinButtonsAlwaysVisible := true

Figur 3:
Dropdown editors
For an even more rich user experience, TAdvStringGrid v5
introduces a new set of inplace editors for choosing colors,
images, time, edit numbers via a calculator, pick values
from a combobox with detail notes per item or pick values
from a dropdown grid. This set of inplace editors shares a
common structure.
The dropdown has a header and footer. Both header and
footer can contain HTML formatted informational text
about the editor and can feature buttons as well. The
settings for the dropdown control header and footer are
exposed via grid.ControlLook.DropDownHeader and
grid.ControlLook.DropDownFooter.
Note that the dropdown header and footer are optional and
can be turned off by setting the respective Visible property
to false. When the SizeGrid property is set to true on the
footer, the dropdown can be resized by dragging from the
bottom-right corner.

Figur 2:

Page 36 / 2190

COMPONENTS
DEVELOPERS

December 2009 BLAISE PASCAL MAGAZINE 9

Exploring the TAdvStringGrid (continuation 3)


Using the time picker, memo, trackbar and calculator
dropdown is
straightforward. Just like with all other edit controls, use
the OnGetEditorType event and set the editor
to the correct editor type. For the color picker and image
picker, some more detailed interaction with
the grid is available. By default, the color picker will set
the cell color to the color choosen and will
trigger the event OnColorSelected. If we have added a
shape in the cell though, it is just the color of the
shape that the color picker will set. To demonstrate this,
add following code:
procedure TForm2.AdvStringGrid1GetEditorType(Sender:
TObject; ACol, ARow: Integer;
var AEditor: TEditorType);
begin
// set this editor just for cell 1,1
if (Acol = 1) and (ARow = 1) then
begin
AEditor := edColorPickerDropDown;
// select the colorcube as color selector
AdvStringGrid1.ColorPickerDropDown.
ColorSelectionStyle := csColorCube;
end;
end;
procedure TForm2.FormCreate(Sender: TObject);
begin
AdvStringGrid1.Options := AdvStringGrid1.Options +
[goEditing];
AdvStringGrid1.AddShape(1,1,csRectangle, clWhite,
clBlack, haBeforeText, vaCenter);
end;

Similar to a color picker, an image picker dropdown can also


be used to edit an imagelist
image set in a cell. By default, it will just trigger the
OnImageSelected event when editing
is done, but when a cell has an imagelist image, it will also
automatically update this
image. Again, with very little code this can be achieved.
Drop an ImageList on the form
and assign it to grid.GridImages and add the code:
procedure TForm2.AdvStringGrid1GetEditorType(Sender:
TObject; ACol, ARow: Integer; var AEditor:
TEditorType);
begin
if (Acol = 1) and (ARow = 1) then
begin
AEditor := edImagePickerDropDown;
// will automatically load all images from the imagelist in the image picker
AdvStringGrid1.ImagePickerDropDown.
AddImagesFromImageList;
// forces the imagepicker to display images in 2 columns
AdvStringGrid1.ImagePickerDropDown.Columns := 2;
end;
end;
procedure TForm2.FormCreate(Sender: TObject);
begin
AdvStringGrid1.Options :=
AdvStringGrid1.Options + [goEditing];
AdvStringGrid1.AddDataImage(1,1,0,haBeforeText,
vaCenter);
end;

The detail picker dropdown can be considered as a


combobox with an optional extra image per item and
additional notes text for each item. Its use is straightforward
and becomes clear with following code:
procedure TForm2.AdvStringGrid1GetEditorType
(Sender: TObject; ACol, ARow: Integer;
var AEditor: TEditorType);
begin
AEditor := edDetailDropDown;
end;

December 2009 BLAISE PASCAL MAGAZINE 9

procedure TForm2.FormCreate(Sender: TObject);


var
di: TDetailItem;
begin
AdvStringGrid1.DetailPickerDropDown.Images :=
ImageList1;
AdvStringGrid1.DetailPickerDropDown.ItemHeight :=
40;
AdvStringGrid1.DetailPickerDropDown.Items.Clear;
di := AdvStringGrid1.DetailPickerDropDown.Items.Add;
di.ImageIndex := 0;
di.Caption := 'Delphi';
di.Notes :=
'Most productive IDE for Win32 development';
di := AdvStringGrid1.DetailPickerDropDown.Items.Add;
di.ImageIndex := 1;
di.Caption := 'Delphi Prism';
di.Notes := 'Take your Pascal skills to .NET';
di := AdvStringGrid1.DetailPickerDropDown.Items.Add;
di.ImageIndex := 2;
:= 'Delphi PHP';
di.Caption
:= 'RAD development for PHP';
di.Notes
end;

Finally it is possible to have a grid as inplace editor.


The value that will be displayed in the cell is the value
from the column in the grid on the selected row that is set
as lookup column with property
GridDropdown.LookupColumn. To set the properties for each
column in the grid, the grid.Columns collection
is available.
Via this column of type TDropDownColumn, it can be
defined whether a column contains text or an imagelist
image. The items in the grid can be added via grid.Items
which is a collection of TDropDownItem objects.
How everything falls into place is made clear with the
sample code to initialize a dropdown grid:
var
dc: TDropDownColumn;
di: TDropDownItem;
begin
AdvStringGrid1.GridDropDown.Images := ImageList1;
dc := AdvStringGrid1.GridDropDown.Columns.Add;
dc.Header := '';
dc.ColumnType := ctImage;
dc.Width := 30;
dc := AdvStringGrid1.GridDropDown.Columns.Add;
dc.Header := 'Brand';
dc.ColumnType := ctText;
dc := AdvStringGrid1.GridDropDown.Columns.Add;
dc.Header := 'Type';
dc.ColumnType := ctText;
di := AdvStringGrid1.GridDropDown.Items.Add;
di.ImageIndex := 0;
di.Text.Add('');
di.Text.Add('BMW');
di.Text.Add('7 series');
di := AdvStringGrid1.GridDropDown.Items.Add;
di.ImageIndex := 1;
di.Text.Add('');
di.Text.Add('Mercedes');
di.Text.Add('S class');
di := AdvStringGrid1.GridDropDown.Items.Add;
di.ImageIndex := 2;
di.Text.Add('');
di.Text.Add('Porsche');
di.Text.Add('911');
di := AdvStringGrid1.GridDropDown.Items.Add;
di.ImageIndex := 3;
di.Text.Add('');
di.Text.Add('Audi');
di.Text.Add('A8');
AdvStringGrid1.GridDropDown.LookupColumn := 1;
end;

COMPONENTS
DEVELOPERS

Page 37 / 2191

Exploring the TAdvStringGrid (continuation 4)


Using custom editors
Finally, if the wide range of built-in editors is not
sufficient for your needs, the grid offers the
capability to use any TWinControl descendent
control as inplace editor for the grid. There are
basically two ways to do this.
First way is to create a class descending from
TEditLink that implements the interface
between your edit control and the grid.
Implementing this class allows fine grained control
how the editor should interact with the grid.
Describing this in detail deserves an article on
itself.
You can find more information at
The second way
is a lot faster. Drop a TFormControlEditLink
on the form and the edit control you want to use as inplace
editor. Assign the control to TFormControlEditLink.Control.
Implement the grid's OnGetEditorType event as:

http://www.tmssoftware.com/site/asg24.asp

Biography Bruno Fierens,


founder of TMS software
He started doing several small projects in the mideighties
procedure TForm1.AdvStringGrid1GetEditorType(Sender:
in GWBasic and soon after discovered Turbo Pascal v3.0
TObject; ACol, ARow: Integer;
var AEditor: TEditorType);
and got hooked to its fast compilation, clean language
begin
and
case ACol of
procedural coding techniques. Bruno followed the Turbo
1:
Pascal releases and learned object oriented
begin
programming when it was added to the Pascal language
AEditor := edCustom;
AdvStringGrid1.EditLink := FormControlEditLink1;
by Borland. With Turbo Pascal for Windows and
end;
Resource Workshop,
end;
he could do his first steps in Windows programming for
end;
several products for the local market. In 1995 Delphi 1
revolutionized all that. The power of reusability that
From now, starting editing in the grid will show the control
Delphi brought through its component framework
as inplace editor and leaving focus hides this inplace
quickly led to creating our own components for in-house
editor. Only thing left is to implement two events for
projects and as it looked interesting, Bruno decided to
TFormControlEditLink that will transfer the value of the
publish some of
control to the grid and vice versa. In this example, this is
these components on some portal websites. It didn't
achieved with:
take long before people all around the world started to
contact Bruno for requesting new features, asking for
procedure TForm1.FormControlEditLink1GetEditorValue
(Sender: TObject; Grid: TAdvStringGrid; var AValue: string); new components and reporting issues. This enhousiasm
of the Delphi community for his components motivated
begin
AValue := PlannerDatePicker1.Text;
Bruno to focus more and more on component creation.
end;
This way, TMS software became Borland Technology
Partner in 1998 and the team grew to 4 persons in the
procedure TForm1.FormControlEditLink1SetEditorValue
main office in Belgium and developers in Brazil,
(Sender: TObject; Grid: TAdvStringGrid; AValue: string);
Uruguay, India, Pakistan
begin
PlannerDatePicker1.Text := AValue;
doing specific component development. TMS software is
end;
now overlooking a huge portfolio of Delphi components
and looks forward to strengthen this product offering in
the future. With Delphi 2010, Embarcadero now offers a
very rich and powerful environment for creating fast
and solid
Windows applications using the latest technologies in
Windows 7 such as touch. Bruno said he will watch the
announced cross-platform development tools from
Embarcadero closely and TMS software is hopeful this
will bring exciting new opportunities for Embarcadero,
Delphi and our components. We live indeed again in
very interesting times for passionate Delphi developers.

Page 38 / 2192

COMPONENTS
DEVELOPERS

December 2009 BLAISE PASCAL MAGAZINE 9

Special classic editions


upgrade pricing
for
Delphi and
C++Builder
1 through 2005 users has been
extended through
December 31, 2009
If you
purchase or upgrade
to any edition of

Delphi,
C++Builder,
or RAD Studio 2010,
you get
TMS Smooth Controls for free.
Purchase or upgrade to

Delphi, C++Builder,
or RAD Studio 2010 Enterprise
or Architect edition
and you also get Delphi for PHP absolutely free.

December 2009 BLAISE PASCAL MAGAZINE 9

Page 39 / 2193

COMPONENTS
DEVELOPERS

You might also like