Anda di halaman 1dari 40









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



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

Volume 8, ISSN 1876-0589

Editor in chief Detlef D. Overbeek, Netherlands Tel.: +31 (0)30 68.76.981 / Mobile: +31 (0)6 News and Press Releases email only to 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.

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.


(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 or by written order, or by sending an email to 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

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

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

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



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

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

Page 4 / 2158



The 3D spheres generator By David Dirkse

starter Introduction expert
DELPHI 2010 / Win32

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: For a slide, following conditions may be noticed:
type TSlidestatus = (stIdle,stOnSpot,stLocked); var slidestatus : Tslidestatus



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

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.

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



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

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 urematrix/index.htm

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. 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 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 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)

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

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.



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 version>CG.bpl version>CG.bpl version>CG.bpl version>CG.bpl Files\kbmMWCG


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

Page 8 / 2162



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

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.

Usefull reading material

(Its easy to click on the connection if you use the download version of this issue)

Generic information on service oriented applications availabe at the opengroup.

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.

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



Page 9 / 2163


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


Essential Pascal (course edition) Marco Cant's

book on Pascal and object-oriented Pascal

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 or



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

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


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



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. 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. 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;

Implementing the Visualizer Interfaces With two types of visualizers you'd be correct in thinking there are two different interfaces that can be implemented. Value-Replacer To create a Value-Replacer visualizer, implement the IOTADebuggerVisualizerValueReplacer interface.
IOTADebuggerVisualizerValueReplacer = interface(IOTADebuggerVisualizer) ['{6BBFB765-E76F-449D-B059-A794FA06F917}'] function GetReplacementValue(const Expression, TypeName, EvalResult: string): string; end;

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;

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


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; 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.

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.

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.

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:
Some are free, while others incur a small charge. So lets go visit him at his site!



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

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


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.


Page 17 / 2171

Page 5 / 2159

Introduction to multithreading
starter expert
DELPHI 2... 2010 / Win32

By Primo Gabrijeli

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, 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.

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 (or process from another so that buggy ( 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 .

Page 18 / 2172



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


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 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 He also writes the Delphi Geek blog (

Page 20 / 2174



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)

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;

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.

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;



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;

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;

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;

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;

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;

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;

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


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; 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;

The effect of this Paint method is visible also at design time, as you place the component on a form:

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:
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;

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;

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.

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:


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



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 ( 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. ( 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



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, but this is not relevant in the present context.

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

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.

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 The following code is assigned to the 'Stop' button:
procedure TForm1.Button2Click(Sender: TObject); //Stop begin SpVoice.Speak('', 2); end; Delphi <spell>Delphi</spell>.

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:

Page 26 / 2180



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

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

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

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.

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 ="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




Embarcadero Partner for Belgium, Netherlands and Luxembourg

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

Tabel 1. HD44780 LCD pins.

Figure 2: Schema for our example.


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; 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.

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;

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.



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 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.
const // Command: LCD_CLEAR LCD_HOME Description: // Clear Display // Put cursor in the first line // and first column // Display Control: // Blink Cursor or not // Cursor On or not // Display On or not // Cursor Shift: // Shift right or left // Shift message // Operation Mode: // Use 5x10 or 5x7 Matrix // 2 or more lines or 1 line // 8 bits or 4 bits // DDRAM Address Binary Value: 00000001 00000010

Listing 5. THD44780 SendCommand methode.

= $01; = $02;


= = = =

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

00001000 00000001 00000010 00000100 00010000 00000100 00001000 00100000 00000100 00001000 00010000 10000000

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). 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;

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

= $80;

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

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).


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.

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;

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 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 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;

Listing 11: Method for writing characters to the display

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;

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);

Listing 9: Method for cursor positioning.


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; 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; 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;

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); 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.
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 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).

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


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


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:


* 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 or Price 20,00 including postage



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];

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.

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;

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. 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. Fine-tuning the basic edNormal editor: - When multi-line text should not be allowed, set
grid.Navigation.AllowCtrlEnter = false

Typically, editing is started by: 1) a mouse click on the focused cell 2) pressing F2 3) typing any character A few settings that allow control over this default behaviour are:
grid.MouseActions.DirectEdit: boolean;

When true, the inplace editor is shown after the first mouseclick
grid.MouseActions.EditOnDblClickOnly: boolean;

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

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


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 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;

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

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;

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:

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:



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.

Figur 2:

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.

Page 36 / 2190


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; 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;

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;



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:

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



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.


Page 39 / 2193