Академический Документы
Профессиональный Документы
Культура Документы
Pascal
ALL ABOUT DELPHI AND DELPHI PRISM(.Net) , LAZARUS & PASCAL AND RELATED LANGUAGES
LEDS
LEDS
LEDS
LEDS LEDS
LEDS
The 3D spheres generator - David Dirkse page 5 Christmas is a comming... What is kbmMW? - By Benno Evers page 7 An explanation how to install Delphi 2010 Feature Highlight - Debugger Visualizers - Jeremy North page 12 A fantastic alternative for its expensive competitors, and its even cheaper Introduction to multithreading - Primo Gabrijeli page 18 Explains a lot Writing Delphi Components III: Compound Components and Custom Event - Marco Cant page 21 In the new Delphi versions it looks all different. Talking Delphi - Henk Schreij page 25 Delphi talking back to you... LCD Interfacing: Driving a HD44780 LCD in Delphi - Thiago Batista Limeira page 28 Get text on an lcd panel Exploring the inplace editing capabilities of TAdvStringGrid By Bruno Fierens page 34 Beautiful features
December 2009
Publisher: Foundation for Supporting the Pascal Programming Language in collaboration with the Dutch Pascal User Group (Pascal Gebruikers Groep) Stichting Ondersteuning Programmeertaal Pascal Cover price Europe: 10.00 / UK 10.00 / US $ 10.00
CONTENTS
Articles
The 3D spheres generator David Dirkse page 5 Christmas is a comming... What is kbmMW? By Benno Evers page 7 An explanation how to install Delphi 2010 Feature Highlight - Debugger Visualizers Jeremy North page 12 A fantastic alternative for its expensive competitors, and its even cheaper Introduction to multithreading Primo Gabrijeli page 18 Explains a lot Writing Delphi Components III: Compound Components and Custom Event Marco Cant page 21 In the new Delphi versions it looks all different. Talking Delphi Henk Schreij page 25 Delphi talking back to you... LCD Interfacing: Driving a HD44780 LCD in Delphi Thiago Batista Limeira page 28 Get text on an lcd panel Exploring the inplace editing capabilities of TAdvStringGrid By Bruno Fierens page 34 Beautiful features
Editors
Rob van den Bogert, W. (Wim) van Ingen Schenau, M.J. (Marco) Roessen. Corrector A.W. (Bert) Jonker, M. L. E. J.M. (Miguel) van de Laar Translations M. L. E. J.M. (Miguel) van de Laar, Kenneth Cox (Official Translator) Copyright See the notice at the bottom of this page. Trademarks All trademarks used are acknowledged as the property of their respective owners. Caveat Whilst we endeavour to ensure that what is published in the magazine is correct, we cannot accept responsibility for any errors or omissions. If you notice something which may be incorrect, please contact the Editor and we will publish a correction where relevant.
Subscriptions
(prices have changed) 1: Printed version: subscription 50.-(including code, programs and printed magazine, 4 issues per year including postage. 2: Non printed subscription 30.-(including code, programs and download magazine) Subscriptions can be taken out online at www.blaisepascal.eu or by written order, or by sending an email to office@blaisepascal.eu Subscriptions can start at any date. All issues published in the calendar year of the subscription will be sent as well. Cover price in Europe: 12.50 / UK 12.00 / US $ 18.00 plus postage. Subscriptions are parallel to the calender year. Subscriptions will not be prolonged without notice. Receipt of payment will be sent by email. Invoices will be sent with the March issue. Subscription can be paid by sending the payment to: ABN AMRO Bank Account no. 44 19 60 863 or by credit card: Paypal or TakeTwo Foundation for Supporting the Pascal Programming Language (Stichting Ondersteuning Programeertaal Pascal) IBAN: NL82 ABNA 0441960863 BIC ABNANL2A VAT no.: 81 42 54 147 (Stichting Programmeertaal Pascal) Subscription department Edelstenenbaan 21 3402 XA IJsselstein, The Netherlands Tel.: + 31 (0) 30 68.76.981/Mobile: + 31 (0) 6 21.23.62.68 office@blaisepascal.eu
Columns
Foreword, page 4 Readers write... page 4 BLAISE PASCAL TROPHY: the winners page 10
Advertisers
Advantage Database Server page 3 Barnsten page 27 Components for Developers page 40 Datanamic page 6 Delphi Special classic editions upgrade pricing page 39 Fastreport for VCL page 17 Fastreport for .Net page 20 RT science page 24
Copyright notice All material published in Blaise Pascal is copyright SOPP Stichting Ondersteuning Programeertaal Pascal unless otherwise noted and may not be copied, distributed or republished without written permission. Authors agree that code associated with their articles will be made available to subscribers after publication by placing it on the website of the PGG for download, and that articles and code will be placed on distributable data storage media. Use of program listings by subscribers for research and study purposes is allowed, but not for commercial purposes. Commercial use of program listings and code is prohibited without the written permission of the author.
Page 2 / 2156
COMPONENTS
DEVELOPERS
Foreword
It is already a white Christmas over here. Lots of snow and children's enthusiastic voices. Love that. We have seen another year of Embarcadero working hard to fulfill its roadmap. Yet there is no version of Delphi for the beginners. So because of that, we have made the choice to bring out a special CD for Lazarus and to support it (Windows, Linux (ready to launch) and Mac, so beginners can start at low costs. Demo programs etc. are on the CD's. A little Christmas present from us: To have fun at the web without problems, we have created a special Internet CD. This CD starts up without needing your Operating System (there is a small version of Linux on it), only the hardware. You can give that CD to any person, young or old and even children, because they can do nothing wrong. It can't store any data because of it being on a CD. Useful for People who are scared by using your system, or not experienced with Internet. Your PC or notebook is not harmed in any way, no viruses or other unwanted nonsense. Yet, if you want to store any data from the website you still can do that to your USBstick. To get the CD Free Internet, all you have to do is download the ISO and burn it, and if you want it in a beautiful case - to have a nice present - you can order it at our website. I hope you will enjoy this. Let us hear about your experiences. We have some news for the next year: we will publish a special issue about Databases and database programming. 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 intricad.com
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
COMPONENTS
DEVELOPERS
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
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
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));
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
COMPONENTS
DEVELOPERS
Page 5 / 2159
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
COMPONENTS
DEVELOPERS
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
http://www.components4programmers.com/products/kbmmw/feat 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 www.turbomiddleware.com. After registration you can request a license for kbmMW codegear edition. The request will be validated and after some time a zip can be downloaded after logging in to www.turbomiddleware.com. The features provided in the current CodeGear edition are: Database support:
MT BDE ADOX IBX5 DBX kbmMemTable (Components4Developers) Borland Database Engine (CodeGear) ADO Express (CodeGear) IB Express v5 (CodeGear) DB Express (CodeGear)
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.
COMPONENTS
DEVELOPERS
Page 7 / 2161
to
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
COMPONENTS
DEVELOPERS
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.
http://www.components4programmers.com/products/kbmmw/featurematrix/index.htm http://www.components4programmers.com/products/kbmmw/university/index.htm
(Its easy to click on the connection if you use the download version of this issue)
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 Benno@EversCT.nl
COMPONENTS
DEVELOPERS
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
COMPONENTS
DEVELOPERS
NEW!
Just published! Specially designed for self-study
Consisting of: * translation of Marco Cant's book Essential Pascal * Lazarus for Windows on CD-ROM * Delphi RAD Studio on CD-ROM (30-day trial version) * Sample projects * Alphabetical dividers for notes * Empty sample project folders for ring binder Follow-up subscriptions available at 25 per year (plus shipping fee) Consisting of: 1. Articles on electronic applications for Delphi 2. Articles on the Lazarus IDE 3. Articles on the Delphi 2010 IDE 4. Exercises and code examples Full subscription price in combination with purchase of book with course and DVDs: 55.00
Orders may be placed with our website store at
http://www.blaisepascal.eu/index.php?actie=./subscribers/subscription_mainpage or
office@blaisepascal.eu
COMPONENTS
DEVELOPERS
Page 11 / 2165
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
COMPONENTS
DEVELOPERS
The class needs to return the results for each of these methods. GetVisualizerName Used to show the name of the visualizer in the options dialog. GetVisualizerDescription Description for the visualizer that appears in the options dialog. GetVisualizerIdentifier This should be a unique identifier for the visualizer. I recommend prefixing it with your company name. GetSupportedTypeCount Return the number of types your visualizer will handle. GetSupportedType This method is called internally the number of times that the GetSupportedType Count method returns.
COMPONENTS
DEVELOPERS
Page 13 / 2167
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;
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
COMPONENTS
DEVELOPERS
Calling methods on expressions When creating an External-Viewer visualizer one thing you may want to do is call a method on the expression the visualizer displaying. For example, you might have a TTable visualizer and want to display the current contents of the active record as well as displaying the total record count for the table. There is a specific technique for doing this and it has not been covered in this article. It will be discussed in the next edition. Supporting C++ I will preface this section with the following comment - I am not a C++ developer. To support the C++ personality with this visualizer we need to make two changes. The first is the change the supported count to return 2 and the second is to provide the fully qualified type name as the TypeName parameter in the GetSupportedType method. This has been done in the source code that is included with the article. Thank you to the two C++ developers that tested and provided feedback on the visualizer in the C++ personality. Creating the visualizer user interface The frame that was originally added to the project is what will be displayed to the user. The user interface is very basic with just a panel, popup menu and action list on it. The actual frames color is what is used to show the expressions value as a color. The panel is used to display the name of the color. The action list contains actions for copying the Expression and Evaluated Result to the clipboard and the popup menu contains those actions.
Continuation
When this method is called you should close your visualizers window. This means that the thread that was responsible for invoking the visualizer has been destroyed. MarkUnavailable This method is called when the visualizer is unavailable. Currently the reason can be either Out of scope or Process not accessible. RefreshVisualizer This method is called whenever the visualizer needs to be updated. This method will be called in response to the user using the Evaluate/Modify dialog to modify the expression your visualizer is displaying. If the visualizer is not visible, it doesn't display anything. SetClosedCallback Call the method which is passed as the ClosedProc parameter when your visualizer window is closed so that the IDE stops sending the RefreshVisualizer message. This means you need to save the passed parameter for later use.
CloseVisualizer
The following is the Show method implementation. This code calls a class method on the frame used to display the visualizer details to the user. I've purposely hidden the method implementation from this article since it goes beyond the scope (and space) for the article. All you really need to know is that the frame designed for the visualizer is parented on a dockable IDE form.
About the author of this article: Jeremy North began programming with Delphi in 1997 while at college in the USA, and since his return to Australia he has kept pace with emerging windows technologies. More recently he has concentrated his focus on writing components and IDE experts, as well as creating tools to help other developers work more efficiently. Some of these components and experts are available to the public on his JED Software website:
http://www.jed-software.com
Some are free, while others incur a small charge. So lets go visit him at his site!
COMPONENTS
DEVELOPERS
Page 15 / 2169
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
COMPONENTS
DEVELOPERS
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
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, en.wikipedia.org/wiki/Thread_(computer_science). I'll intentionally limit myself to the Windows and Delphi.) A word of warning - this will be a long journey. Today I'll only scrap the surface and give you an overview of the topic. In next installments we'll start working on multithreaded programs using not only Delphi's native way but also using 3rd party extensions, components and libraries. We'll describe and use messaging, locking, shared data, lock-free structures, and more. Stay tuned! A process and a thread work together. As a programmer you probably know, at least instinctively, what is a process. In operating system terminology, a process is a rough equivalent of an application - when the user starts an application, operating system creates and starts new process. Process contains (or better, owns) application code, but also all resources that this code uses memory, file handles, device handles, sockets, windows etc.
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
COMPONENTS
DEVELOPERS
Page 19 / 2173
has been programming in Pascal since Turbo Pascal 3 for CP/M. He's the author of several open source Delphi components, available at gp.17slon.com. He also writes the Delphi Geek blog (thedelphigeek.com)
Page 20 / 2174
COMPONENTS
DEVELOPERS
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;
COMPONENTS
DEVELOPERS
Page 21 / 2175
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
COMPONENTS
DEVELOPERS
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.
COMPONENTS
DEVELOPERS
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
However, this call doesnt do what you might expect, as Ill explain in the next article, which will cover (among other topics) components state and the Loaded method.
Page 24 / 2178
COMPONENTS
DEVELOPERS
Sometimes it's handy to have your program be able to read a message out loud. This gets attention, and it can be quite useful in certain situations. With the advent of Vista, the standard voice that comes with the system (Anna) is easy to understand, although the XP voice (Sam) is also possible to understand despite its nasal twang. As can be seen from this article, it's very easy to give your computer a voice. However, if you want a language other than English, it will have an English accent. This is because only English is installed as standard, despite the fact that Windows can in principle speak messages in another languages, as can be seen in some other countries such as Japan. Maybe we can have our own accent in a few years. Early or late binding Speech is handled by the Speech API (SAPI) in Windows. Starting with version 5.1, which is installed as standard with XP, it includes a type library with the most important interfaces. As a result, this SAPI is easy to use with automation via early or late binding. Early binding means that you import the type library, which enables you to use the Component palette to place a voice component on your screen. After this, you can easily program it by means of Properties, Events, or (lest we forget) Code Insight or Code Completion (with dropdown menus after the point, etc). The advantage of late binding is that you do not have to import a type library. The disadvantage is that you have to write more code and you cannot use Code Completion. The entry threshold is thus higher with this approach, since you don't know the function names, constants, and so on. For a nice example of speech using early binding, have look at movie 31 on the Codegear Guru website (http://www.codegearguru.com/video/031/speech.html). In this article I present an example of late binding.
Example of reading text out loud If you want to use late binding, you need to know the names of the functions and so on. Fortunately, Microsoft publishes this information on its MSDN website in the form of a tree view. (http://msdn.microsoft.com/en/library) However, there are so many items that you are bound to get lost, so I show the most important ones for this article in Figure 1. The code for late binding is essentially the same in all cases. Add ComObj to 'uses', declare a Variant as a placeholder under 'private', and use CreateOleObject to add the Variant to the OnCreate section of the form. A memo and a button for specifying the message to be spoken have also been added to the following code:
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ComObj; type TForm1 = class(TForm) Button1: TButton; Memo1: TMemo; procedure Button1Click(Sender: TObject); procedure FormCreate(Sender: TObject); private SpVoice: Variant; public {Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.FormCreate(Sender: TObject); begin SpVoice:= CreateOleObject('SAPI.SpVoice'); //uses ComObj end; procedure TForm1.Button1Click(Sender: TObject); SpVoice.Speak(Memo1.Text, 1); //reads the text in Memo out loud end; end.
If you go to 'Application Level Interfaces' in the MSDN tree structure and select 'Text to Speech Interfaces', under 'Overview' you will see that the 'SpVoice' interface has a 'Speak' method for reading a message or a text file out loud. The Speak method has two parameters with the following definitions (the third one can be ignored for this application):
pwcs A PChar string (possibly containing XML markup) to be synthesized. If dwFlags is SPF_IS_FILENAME, this should point to a PChar path to a file. dwFlag Flags used to control the process. The flag values are contained in the SPEAKFLAGS enumeration.
In the example above, the value '1' is specified as the second parameter (flag). This value was taken from enumerated list on the 'SpeakFlags' page (see Figure 2).
COMPONENTS
DEVELOPERS
Page 25 / 2179
Figure 2. MDSN parameter list for speech Bit-type enumerations (summary lists) start with '0' and are hexadecimal numbers, such as can be seen from the following table:
Incidentally, SpVoice.Speak also accepts .wav files (such as which it simply plays back. You can also specify an Internet address, such as http://www.nldelphi.nl, but this is not relevant in the present context.
C:\Window\Media\Chimes.wav),
The most interesting flag value is '8', which allows you to add XML code to the text to be read out loud. Set a parameter value of '9' (1 or 8) in Button1Click instead of '1', or a value of '13' in Button3Click. This enables you to make all sorts of adjustments to speech output. 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
COMPONENTS
DEVELOPERS
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
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:
barnsten
COMPONENTS
DEVELOPERS
Page 27 / 2181
LCDs are widely used in electronic applications, to demonstrate the state of many devices, and also, are extensively used in CaseModding. There are a lot of projects showing how to interface a LCD to display computer's information such as the CPU's speed and temperature, the system time, Winamp's play list, etc. In addiction there are specialized programs for controlling LCDs like LCD Smartie, which is opensource and was totally developed in Delphi! In this article, we are going to develop a program for interfacing a 16x2 LCD, with it we'll be able to initialize, write text, send commands, etc. This will be done by using the parallel port with the help of TLPTPort component, which in my opinion offers the simplest and easiest access to the parallel port's registers.
status. By setting the RS pin high, character data can be transferred to and from the module. Pin 5 is the Read/Write (R/W) pin. This pin is pulled low in order to write commands or character data to the module, or pulled high to read character data or status information from its registers. Pin 6 is the Enable (E) pin. This input is used to initiate the actual transfer of commands or character data between the module and the data lines. When writing to the display, data is transferred only on the high to low transition of this signal. However, when reading from the display, data will become available shortly after the low to high transition and remain available until the signal falls low again. Pins 7 to 14 are the eight data bus pins (D0 to D7). Data can be transferred to and from the display, either as a single 8-bit byte or as two 4-bit nibbles. In the latter case, only the upper four data lines (D4 to D7) are used. This article will cover the 8-bit mode. See Figure 2.
Figure 1. Driving LCD modules ,one of the most used practices in case modding Note: For more details about the parallel port's architecture and control in Delphi, I recommend reading my article Controlling the Parallel Port, which presents much detailed information in Blaise Pascal Magazine #6. Important details about LCDs Before we start interfacing the LCD, we need first understand some very important details about LCDs that will be extremely necessary in order to interface them. Among this issues we need to define which driver based LCD we intend to interface, the LCD pins, schematic, commands, etc. We are going to use an HD44780 alphanumeric LCD (or compatible), this type of module can be easily found. The display controller has the task of controlling the liquid crystal matrix, even though this module doesn't offer the same advantages of the most modern LCDs used in cell phones, notebooks, and many other equipments, its simplicity will be an advantage when it comes to hardware assembly. The LCDs have a standard pins (those who doesn't offer backlight option) with 14 pins whereas those who have backlight 16 pins, these 2 pins are responsible for activating the backlight. Table 1 shows the HD44780 LCD pins and its functions. Pins 1 and 2 (Vss and Vdd) are the power supply pins. The Vdd should be connected to the positive supply and Vss to the 0V supply or ground. Pin 3 is a control pin, Vee, which is used to alter the contrast of the display. Ideally, this pin should be connected to a variable voltage supply. Pin 4 is Register Select (RS) pin, the first of the three command control inputs. When this pin is low, data bytes transferred to the display are treated as command, and data bytes read from the display indicate its Page 28 / 2182
COMPONENTS
DEVELOPERS
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;
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.
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.
COMPONENTS
DEVELOPERS
Page 29 / 2183
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
= $01; = $02;
LCD_ONOFF LCD_ONOFF_CP LCD_ONOFF_C LCD_ONOFF_D LCD_SHIFT LCD_SHIFT_D LCD_SHIFT_M LCD_FUNCTION LCD_FUNCTION_M LCD_FUNCTION_L LCD_FUNCTION_B LCD_DDRAM
= = = =
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;
= $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
COMPONENTS
DEVELOPERS
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).
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;
In this method, the variable Temp is the byte mask we use for the display control (using the LCD_ONOFF constant), using the bit operator or. After that we send the Temp value as a command using SendCommand method. Clearing the LCD To clear the display, just send the LCD_CLEAR constant using the SendCommand method, as shown below:
procedure THD44780.ClearDisplay; begin SendCommand(LCD_CLEAR); end;
Positioning the cursor It is possible to control the cursor positioning using the LCD_DDRAM constant, which allows Access to the LCD data position address, see Listing 9.
procedure THD44780.PosCursor(X, Y : Integer); var offset : Integer; begin offset := 0; if X > Columns then begin X := 1; inc(Y); end; if Y > Lines then Y := 1; if Y = 1 then offset := 0; if Y = 2 then offset := 64; SendCommand(LCD_DDRAM + offset + (X-1)); end;
Note that in order to send the Text parameter to the LCD we use one for looping to convert each letter to its ASCII value using the Ord function. Every time the LCD receives a character, it writes it and moves 1 position to the right, the cursor marks the place where the next character is going to occupy. Back to the application example With the finished class we can go back to the example application construction, now declare the uHD44780 in the interface section. Do not forget to declare a THD44780 variable, just like below: LCD : THD44780; With this variable we can access all the functionality we have already implemented in the THD44780 class, and we will be able to interface the display without any concerns. Now double click the frmMain and in its OnCreate event type the code below: LPTPort.Port[$378]; LCD := THD44780.Create(LPTPort);
COMPONENTS
DEVELOPERS
Page 31 / 2185
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;
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).
We have altered the ShiftCursor_Message method parameters to shift text and cursor in both directions. Figure 5 shows a suggested GUI for the example application development.
Figure 5. Suggested GUI for the application example Figure 4: Display after the initialization. Page 32 / 2186
COMPONENTS
DEVELOPERS
Conclusion With our THD44780 LCD control class, interfacing displays becomes very easy, which is a great alternative to hobbyists and casemodders, besides recycling old printer cables we can also give our computers a new design using our favorite programming language. See you next time and stay tuned to the Blaise Pascal Magazine.
Links LCD Interfacing
http://www.8051projects.net/lcd-interfacing/
HD44780
http://pic.rocklizard.org/ReferenceDocs/HD44780HowTo.pdf
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: tblimeira@gmail.com.
COMPONENTS
DEVELOPERS
Page 33 / 2187
Exploring the rich & versatile inplace By Bruno Fierens editing capabilities of TAdvStringGrid
starter expert
DELPHI 3 ... 2010 / Win32
As soon as you want to use a grid for more than just presenting information, the availability of strong inplace editing capabilities is crucial for creating user-friendly applications. In TAdvStringGrid, not only a very wide range of built-in inplace editors is available but there is also an extensive and fine grained control over when inplace editors appear and how they interact with the various key events in the grid. TAdvStringGrid was designed to enable most scenarios users want with just setting a few properties rather than writing a lot of code. In this article, we start with a brief introduction of the basic inplace editors. Then a more detailed look is given to inplace editing and navigation. Next, more complex builtin inplace editors are handled and finally, there is a section on using any TWinControl descendent control as inplace editor in the grid. Basic editing capabilities in the grid First of all, editing in the grid is enabled by setting goEditing to true in grid.Options. In code, this can be done with:
grid.Options := grid.Options + [goEditing];
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
COMPONENTS
DEVELOPERS
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:
COMPONENTS
DEVELOPERS
Page 35 / 2189
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.
COMPONENTS
DEVELOPERS
Page 36 / 2190
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;
COMPONENTS
DEVELOPERS
Page 37 / 2191
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
COMPONENTS
DEVELOPERS
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
Page 39 / 2193
COMPONENTS
DEVELOPERS