Pascal Newsletter #53
The full source code examples of this issue are available for download.
![]() |
![]() |
Pascal Newsletter #53 - 23-JANUARY-2005 Contents 1. A Few Words From the Editors 2. Code Profiling with Non-Breaking Breakpoints 3. Introducing YAPI 4. How to Set a Component's Default Event Handler 5. Forums / Mailing Lists 6. Delphi on the Net - Components, Libraries and Utilities - Freeware - Borland Product Updates - Articles, Tips and Tricks - Tutorials and Training - News - Other / Misc Sites ________________________________________________________________________ 1. A Few Words From the Editors Happy New Year! And, welcome to the first issue of the Pascal Newsletter for 2005. We'd like to thank the authors who contributed articles: Jim McKeeth, Owen Mooney and Peter Johnson. We're pleased to award them the prizes for this issue: * Peter Johnson - 'How to Set a Component's Default Event Handler' InstallAWARE 3.0 Enterprise Edition - by MimarSinan Int. ($559.95) InstallAWARE 3.0 for Windows Installer - by MimarSinan International Develop setups for Windows Installer without any knowledge of MSI! InstallAWARE automatically converts a conditionally flowing script into a logo certifiable, ICE-compliant MSI database at build time. The IDE features a visual UI which generates your setup script for you automatically and you can fully customize the script behaviour. Special limited offer: 30% off all editions, Enterprise only $559.95! http://www.installaware.com/landingea.html * Owen Mooney - 'Introducing YAPI' KnowedgeBASE Vortex 2.9 - by Delphinium Software ($49.35) KnowledgeBASE Vortex features a highly searchable and expandable information tree viewed in outline or audited within a built-in wordprocessor. Includes a database for stored references. http://www.download.com/KnowledgeBase-Vortex/3000-2064-10342084.html * Jim McKeeth - 'Code Profiling with Non-Breaking Breakpoints' YAPI Professional - by Owen Mooney ($95) Offers the easiest and yet powerful printing from Delphi. It provides: WYSIWYG setup, print preview, Non database, Database, text, Grids, tabs, bitmaps, TCanvas operation, precise positioning or free flow positioning - all using simple "writeln" statements. http://free.hostdepartment.com/o/owenmooney/ Special thanks to Delphinium Software for donating KnowledgeBASE Vortex and Owen Mooney for donating YAPI Professional as ongoing prizes for future issues. In the next issue we have three prizes up for grabs: InstallAWARE 3.0 Professional Edition, KnowedgeBASE Vortex and YAPI Professional. Issue #54 will be published in March so if you've got an idea for an article, get writing! We're also proud to continue to offer readers a special discount on InstallAWARE 3 for Windows Installer, thanks to the nice people at MimarSinan International. For a limited time, readers can get 30% off by following this link: http://www.installaware.com/landingea.html Now, on with the code... Regards, Dave Murray and Ernesto De Spirito pascal-newsletter-owner@yahoogroups.com ________________________________________________________________________ Help & Manual 3.50 by EC Software - Shareware ($ 299) - Help & Manual is a WYSIWYG help authoring tool that will aid you in creating standard WinHelp files (.HLP), Adobe PDF files, HTML pages and the new HTML HELP (.CHM) files introduced in Windows 98, as well as other file formats and printed documentation, everything from a single source. This is a must- have for any software developer. http://www.helpandmanual.com/hmpage.htm ________________________________________________________________________ 2. Code Profiling with Non-Breaking Breakpoints By Jim McKeeth <jim at mckeeth dot org> Abstract: Sometimes it is nice to know how long a specific piece of code takes to execute. The Borland IDE actually provides a means to accomplish this using non-breaking breakpoints and the event log. This article originally appeared on the Borland Developer Network (see References) and included several images. These images can be found in the zip archive accompanying this issue. Either there is a point in your program when it is running much slower then you expected, or you are debating which way would be faster to do something. Either way, you need to profile the performance of some code. Back in the old days we would add code to our programs to output the start and stop time to a log for specific processes, then we would remove it or comment it out until we needed it again. Nowadays we use code profilers that time the execution of every line of our code and provide us with detailed reports and graphs. We could spend weeks examining all the data available. Code profilers are nice, but sometimes we don't have one convenient or we really don't want to mess with one to test a couple lines of code. Maybe inserting all the additional debug information for a profiler to work changes the behaviour of our program too much. Instead of going back to adding all that code to log start and stop times we can instead use non-breaking break points. The major advantage of this technique over a traditional code profiler is when you have a fairly complex program running and you want to profile the code now without restarting it and launching the profiler. You can target this profiling so specifically that it doesn't have the impact on the overall behaviour of your program that other profilers do. In instances were you have a few select routines you want to profile you will find this technique much quicker and easier to deploy. This article is written for Delphi 7, but the techniques should work with little or no changes in other versions of Delphi as well as C++ Builder and Kylix. The attached code is specific for Windows, but I am sure a resourceful Kylix programmer would know which Linux API's to use instead. Before I show you how to use non-breaking breakpoints to profile your code I am going to cover some basics about non-breaking breakpoints. If you are already comfortable with the advanced features of breakpoints then you can skip to the section on Profiling. What are Non-Breaking Breakpoints? ---------------------------------- I know you are probably thinking that a non-breaking break point sounds like an oxymoron. As we will see it really is not. Cary Jensen offers a great article titled 'Using Non-Breaking Breakpoints in Delphi' available on the Borland Developer Network (see References). Not to worry if you haven't read that one. I'll cover everything you need to know here. The Free Online Dictionary of Computing defines a breakpoint as follows: Breakpoint A point in a program that, when reached, triggers some special behaviour useful to the process of debugging; generally, breakpoints are used to either pause program execution, and/or dump the values of some or all of the program variables. Breakpoints may be part of the program itself; or they may be set by the programmer as part of an interactive session with a debugging tool for scrutinizing the program's execution. So breakpoints only generally pause program execution. Maybe a better name would be non-pausing breakpoints. Regardless of what you call them, we are using breakpoints that still trigger special behaviour, but don't actually pause execution. So how do you obtain one of these funny breakpoints? It really is very simple. 1. Create a breakpoint however you usually do (I usually use F5) 2. Bring up the Source Breakpoint Properties dialog (I usually right click the breakpoint and choose Breakpoint Properties...) 3. Click the Advanced >> button. 4. Uncheck the Break check box. 5. Click the OK button. Fig 1. Basic Breakpoint Properties The Source Breakpoint Properties dialog before the Advanced button is clicked shows the default configuration for a breakpoint. Notice it specifies the filename and the line number that the breakpoint is on. These are greyed out from this view because we are editing an existing breakpoint's properties. If we were creating or editing the breakpoint from the Breakpoint List ([CTRL]+[ALT]+[B] for the list, [CTRL]+[A] and [CTRL]+[E] to create a breakpoint) then these fields would not be greyed out and we could specify them. Fig 2. Advanced Breakpoint Properties The Breakpoint Properties dialog after the Advanced button is clicked includes a lot of very powerful features. For now we are just interested in the Break checkbox. Uncheck it and click OK. Your breakpoint still looks the same. It is not disabled (as indicated by a green line and a grey stop symbol), but if you run your program now it won't pause on that line. If, when you run your program the line changes colour to a dark green, then your breakpoint is on a non-executing line. Choose one of the lines with a blue dot next to it and repeat the process. It's ok, I'll wait. If you did it right then your program will run without pausing on that line. Right now this isn't very useful, but at least you know how to make a non-breaking breakpoint. Logging with a Breakpoint ------------------------- Now that we have our non-breaking breakpoint we want it to write to a log to know when the line of code it marks is executed. Go back to the Breakpoint Properties dialog. Notice it goes straight to the Advanced view since you have a change in the Actions section. We are going to make another change. We are going to add text to the Log message: combobox. When profiling my code I usually have a logging breakpoint at the start and end of my block of code to be profiled. I usually have those breakpoint log "Begin" and "End", respectively. Now if you run your program after making this change you will not notice any obvious difference. The message was logged to the Event Log, and the Event Log can be accessed from View -> Debug Windows -> Event Log, or by pressing [Ctrl]+[Alt]+[V]. I had three breakpoints set in my program, with the first and last ones logging a message. This is what my Event Log looks like: Fig 3. Event Log The lines in red are the ones resulting from a break point. You will notice three "Source Breakpoint" lines and two "Breakpoint Message" lines. Now while all the other lines are interesting they are a little distracting so lets hide those for now. Simply [Right-Click] in the log window and choose Properties. That will display the Debugger Event Log Properties. Fig 4. Debugger Event Log Properties In the Messages groupbox you can uncheck any checkbox for a message you do not want to see. Simply uncheck all of them including the Breakpoint messages checkbox. The breakpoint messages checkbox is a little deceiving. It actually removes the "Source Breakpoint" lines, leaving the Breakpoint Message lines. You can also change the colours and other information if you like. I find the log is easier to read for profiling if you clear the Display process info with event checkbox as well. If you use your Event Log for other purposes as well you will want to remember to change these settings back when you are done. Now when I run my program my Event Log looks like this. Hopefully yours is similar. Fig 5. Concise Event Log I imagine you will agree that this is much more concise. TIP: Instead of using Begin and End you may find other messages more useful. Some suggestions: name of a function being called, line number, object being accessed, name of query being run, loop definition, etc. Profiling --------- Now that we know how to enter logs into our event log we can use this feature to profile our code performance. While showing that a block of code is entered and exited is useful, it is not exactly profiling. What we need is a measurement of time to show how long it takes to execute some code. This can be accomplished using the Eval Expression feature of the breakpoint. Go back to the Source Breakpoint Properties dialog. This time put GetTickCount in the Eval Expression combobox. GetTickCount is a Win32 API call that returns a cardinal containing the number of milliseconds since the computer was last booted. For Delphi programming this function is found in the Windows unit, so make sure it is in your uses clause. While the number returned is in milliseconds, the resolution isn't actually in milliseconds. If you have two calls that are just a couple milliseconds apart then they may still return the same number. This resolution can be found by calling the GetSystemTimeAdjustment API. Also, since a cardinal (or DWORD) is used the, the value will loop around to 0 every 49.7 days. Not that you will probably need to worry about running on a MS Windows system that has been up that long, but it is something to keep in mind. In my program I added GetTickCount to all three breakpoints, leaving the Begin and End on the first two. Running my program again generates a much more useful Event Log. Fig 6. Event Log With GetTickCount The Salmon or Pink coloured lines show the results of the expression evaluation. Notice there are two Breakpoint Message lines, and three Breakpoint Expression lines. If we put a message on the second breakpoint, such as "middle" then we would have an even number of each. Otherwise we have no way to associate the GetTickCount result with a specific place in the code. If we re-enable the Breakpoint messages to be displayed in the Debugger Event Log Properties dialog then we would have the line number as well. I personally prefer using meaningful messages (maybe including the line number), but you can use whatever best suits your needs. As we can see for this data it took 1.5 seconds to get from the first break point to the second, and about 1 second to get to the third. Looks like I have some optimizing to do. If you are curious where I arrived at those values it was simply a matter of subtracting each number from the one after it and dividing by 1000, rounding to the nearest half second. When using the Eval Expression feature of the breakpoints the expression it evaluates must be referenced globally and can only be a function that is included in your compiled code. Since Delphi has an optimizing linker it removed functions that have no chance of being executed by the program. GetTickCount is one of those functions that always has a chance of getting called and is included if we call it specifically or not, so it is always in there. If we use another function, one we write, or another library or API call, we need to make sure it is linked in. Also, when a method call into an object that change a field value those changes may not actually carry over after the evaluation completes. Advanced Profiling ------------------ What if you don't want to do subtraction in your head? Or maybe you really need 1 millisecond resolution in your profiling. This involves adding some code to your program, but there is a trick we can use to ensure that the code is actually not compiled in when your program is built without debug information. Instead of using GetTickCount we can use the high-performance timer routines also provided in the Windows API. These routines are not as straightforward to use, so instead of putting in complex Eval Expressions I suggest building some functions to make things easier. These functions can also subtract the values between calls. The QueryPerformanceCounter and QueryPerformanceFrequency API calls access the high-performance timer available on most systems. I am not aware of a situation when these would not be available. For more information on these API calls consult the MSDN or the Windows SDK document included with Delphi. For now just be aware that is uses Int64's instead of Cardinals, and is accurate beyond 1 millisecond. In the zip archive accompanying this issue you will find my library to use these API calls. It is very simple to use; there are three functions: ElapsedMS, ResetElapsed and UpTimeMS. Add the unit JimProfiling.pas to your uses clause and you can use these functions in your Eval Expressions. ElapsedMS Displays how many milliseconds have elapsed since the last call to ElapsedMS. ResetElapsed Restart the elapsed counter. UpTimeMS Displays how many milliseconds have elapsed since the program started. After running your program you have really useful, and highly accurate, output in your Event Log. Fig 7. Event Log Using Profiling Unit Notice that the second call to ElapsedMS has a lower value than the first. This is because it is the time since the first call. The UpTimeMS call is actually greater then the sum of the two calls to ElapsedMS because it includes the time before the call to ResetElapsed as well as the small amount of time after the second ElapsedMS call. TIP: If you need a line to put a breakpoint on for a call to ResetElapsed or some other function then just add a line of Sleep(0); While this does have a slight impact on your program it should be very minor. The exception is if you are dealing with multiple threads since it will give another thread a time slice when you call sleep. All of the code in this unit in enclosed inside {$IFOPT D+} directives so that when you compile your application without debug information it will be left out, you don't need to remember to remove it from your uses clause. Since the scope of the Eval Expression is global you can actually just put the unit in any one uses clause in your application and still be able to use it in all other units throughout your application. Final Tips ---------- If you want to keep your profiling setup between programming sessions be sure to use the Autosave Project Desktop option. This will save the location of all your windows as well as any Breakpoints you may have. You can enable it from the Tool -> Environment Options screen, on the Preferences tab under the Autosave Options header. If you don't want your desktop changes to always be saved you can turn this option on, save the project once, and then turn it off. That will make a snapshot of things that will not change when future changes are made. These settings are stored in the [project].dsk file, which is plain text (INI format). You can edit it directly if you like. Remember if you edit this file, and you have the IDE open and the auto save enabled, then it may overwrite your changes. Also, they are tracked by line number, so if you insert or delete a line above a line with a Breakpoint then the it may be moved onto the wrong line. TIP: If you specify a condition then the logging will only be carried out when that condition is true. The Breakpoint List screen is another valuable resource. I mentioned it earlier as another way to create Breakpoints. You can get to the screen using the [CTRL]+[ALT]+[B] keyboard shortcut, or from the View -> Debug Windows -> Breakpoints menu item. Fig 8. Breakpoint List Notice the Action column shows the messages and expressions we are logging. You could have various groups of profiling breakpoints, each in a different group. Then from the context menu you can enable or disable an entire group of breakpoints. You can also use the Enable / Disable Group feature of another breakpoint to turn these groups on or off as needed. TIP: Editing (Right click or use [CTRL]+[E]) breakpoints from this screen allows to you move them to other lines or files. This is especially useful if you have complex actions setup and don't want to recreate them. You can also set multiple breakpoints on a single line (use the Keep Existing Breakpoint option). Conclusion ---------- This technique for profiling your code using only the standard Delphi IDE and non-breaking break points provides you with highly targeted, 1 ms accurate execution profiling times. While this doesn't completely replace the need for a code profiling tool in your programmers toolbox, it does provide you with another powerful alternative to create faster programs in less time using the powerful IDE you already own. References ---------- 'Using Non-Breaking Breakpoints in Delphi' by Cary Jensen http://bdn.borland.com/article/0,1410,31263,00.html 'Code Profiling with Non-Breaking Breakpoints' by Jim McKeeth http://bdn.borland.com/article/0,1410,31905,00.html 'Non-Breaking Breakpoint Profiling Library 1.0' by Jim McKeeth http://codecentral.borland.com/codecentral/ccWeb.exe/listing?id=21280 ________________________________________________________________________ Vote for the Pascal Newsletter in The Borland Top 100! http://top100borland.com/in.php?who=20 ________________________________________________________________________ 3. Introducing YAPI By Owen Mooney <owenm at scottech dot net> http://free.hostdepartment.com/o/owenmooney/ Yapi - now at version 2. I'm 'senior' programmer now. I cut my teeth on Fortran, did my Thesis in Algol-60 (anyone remember that? - before Pascal) and, in the early 90's, learned to hate the Windows API using Borland C++ and OWL ('Orrible Windows Library). Then I discovered Delphi and that's what I've used ever since. The one thing Delphi has always lacked is a decent printing tool. QReports were useless for general printing. We purchased a third party product by Nevrona. With trembling and enthusiastic fingers I opened the manual and started to read. Oh dear! The tool never got used. It sat on the shelf for about 4 years till I binned it last year. In the end, all of my printing got done with the TPrinter canvas. I've heard that many others Delphi programmers have done the same. I haven't used the new tools with D2005 yet, so I can't comment on them. I probably won't try them because I don't need to. After a few years of frustration I built my own printing tools and - if you will excuse a moment of self-congratulation - they are the best! Design - The Programming ------------------------ I wanted the new tools to be easy to use, to follow the most important Delphi principle - simplicity. After some years of struggling with C++ for Windows I came across Delphi and said "Ah - Yes! That's how it should be." I wanted my printing tools to evoke the same reaction. They shouldn't be a just a little simpler than other printing tools but a LOT simpler - they should be stunning! I started from first principles. In the old days we programmed reports something like: writeln(printer,' My Big Report'); // Centred writeln(printer); // Empty line writeln(printer,'The first line'); for i := 0 to count - 1 do begin writeln(printer,'The data for line ',i,' ',Data[i]); end; With each writeln the dot matrix printer would burst into life and zip across the page, adding to the noise pollution in the office. While the technology may seem very crude by today's standards, programming this sort of thing was easy. That's because of a very important reason: A very complex report could contain hundreds of writes and writelns, but the general flow of the code followed the general flow of the report. This correspondence made programming easier to both write and follow. You could look at a report and imagine how it would be coded, and you could look at the code and imagine the report. This methodology falls to pieces with modern printers and the requirement to provide a print preview. Tools like QReports and others, tend to have a structure that either executes SQL statements, or calls user written event code. The flow of execution is controlled by THEIR code, not yours. All you get is some events. If you have a complex report you get a 'complexity blow-out' as your code tries to remember what happens when. I don't know about you lot, but I gave up. Yapi provides the same logic flow as the old writeln. In Yapi, the equivalent code to the older code above is: Paper.clear; Header.writeln('My Big Report'); // Centered, but no counting spaces Header.writeln; Body.writeln('The first line'); for i:=0 to count-1 do begin Body.write('The data for line '); Body.writeattab(inttostr(i),1); Body.writeattab(inttostr(Data[i]),2); end; Paper.preview; This is all the code required to print the equivalent to the old dot matrix printout, but of course with margins, nice fonts, headers and footers, automatic pagination and a very cool print preview screen. Design - The Formatting ----------------------- Well of course people expect WYSIWYG control over page layout, fonts, tabs, and other formatting. I'm not so daft as to provide any thing else. Yapi provides several components for this as follows: The Paper Component: This provides WYSIWYG set-up of all the report structure. Margins and tabs are set-up as well as paper orientation. Typically the dimensions are in mm but inches are also provided. The paper component shows all of these details at design time, but is invisible in the running program. The Text Component: The text components are used to set up the fonts (including colour) for reports. These are used for the bulk of the printing. The Tab Component: These setup tabs. They can be dragged into position, or have their position set (in mm or inches). Dynamic tabulation in code is also provided. These three components are the basic set. Trying It Out ------------- Lets get to the nitty gritty. How do you get something printed, and how easy is to get started? Well the absolute simplest steps are as follows: 1. Drag a Yapi Paper component on to your form 2. Drag a Yapi Text component on to the paper. 3. Add a button to the form. In its onclick event add 3 lines of code: YapiPaper1.clear; YapiText1.writeln('Hello World'); YapiPaper1.preview; That's it. Run the program, press the button and you have a print preview screen, and another button in the preview screen gives you your printout. The documents have a 5-minute tutorial, which does more or less this. If you can't do that in 5 minutes you may have a promising career as a basket weaver. More Components --------------- The three components above (available as a free set) allow almost any text based printout generation. Of course people want more. Other components are: * A header and footer component (one component does both) * A grid component for doing reports like a stringgrid or a dbgrid * An image component for doing bit maps (the pictures can be determined at design time or run time) * A paintbox component * A db report component The header and footer component does (of course) page numbers. Producing a grid was a really interesting design problem. How does one program a grid without contradicting the rather nice design philosophy I already had? The answer came after some beers (actually that is a fanciful insert to relieve the boredom of an otherwise dry and technical dissertation). The grid-text component is just like an ordinary text component, but it draws a box around itself. Put two side by side, and their adjoining edges merge to make one line. Put some underneath others, and the tops and bottom merge to a single grid line. Put rows and columns together and 'hey presto' - a grid! All of this makes grids just as easy as ordinary tabbed text. The image component was a reasonably obvious inclusion and works well, but there remained the one last printing challenge - how would Yapi deal with arbitrary graphics and text? The answer came as the yapi-paintbox. This operates with ALL of the existing TCanvas methods. At this point some gnome like programmer will comment: 'Hey Noddy! If you program yapiPaintbox as a Canvas Object, why not just use the TPrinter canvas?' The answer is simple: 'Because, Big Ears! the yapipaintbox also provides for a print preview.' A db report was included with V2. This is really just for those programmers who believe you should be able to drag and drop a whole program and complain bitterly at each and every line of code they have to write. This component will produce a report from a TQuery or a TTable with just one statement: yapidbreport.open I have provided db report as a separate object file. This prevents Delphi's database code being linked into non-database programs. Implementation -------------- Well - it wasn't easy! With each write or writeln, Yapi creates a token to represent the printed item. It stores and then uses these tokens to render the relevant page on the print preview, or onto the TPrinter canvas. Sounds easy doesn't it? Each token is an object. Inheritance was used to generalise the code as much as possible. The object hierarchy for the relevant tokens is: TyapiObject - Abstract class handles much of the processing TyapiTextObject - can be either a text or a grid text TyapiGraphicObject - can be either an image or a paintbox Each use of write or writeln generates a new token to represent the new item in the report. The initial coding had these all stored in an array in the paper component, but I had a complexity blowout. Time to rip up the code and start again. I added a new class for each line and the complexity came under control. In this second version each write or writeln adds a new token in the current line object. In addition, each writeln completes the current line object and starts a new one. The lines are stored as a dynamic array within the paper object. The components themselves have been created in fairly standard manner. The paper component inherits from TCustomPanel and operates as a line container as well as a tool to configure the report. The tab, paintbox and image inherit from the TGraphicControl component. Both the text and grid-text inherit from a common abstract base class which in turn inherits from TCustomLabel. I was very grateful for Borland's provision of the VCL source. I crawled through quite a lot of this to see how things were done. The Print Preview ----------------- The Print Preview is a standard Delphi form. It renders the printout onto its own TPaintbox. The print preview does what you would expect. Selecting page number, zooming, printer selection, page range selection - all are provided. In addition it has a couple of cute tricks. The first is the ability to control widows and orphans. A unique feature of the Yapi preview is the ability to change tabs and margins at run time, in the preview screen. That pesky line at the bottom can be eliminated just by pulling the bottom page margin up. The line disappears and automatically appears on the next page. Another trick of the preview screen is saving to a file. There are two options: If you save to a file with a .csv extension, that's what you get. The report tabs are used to separate the csv file columns. If you save to a .bmp file you get the current page as a bitmap. I chose bmp output format as this is supported by the basic VCL. This means you won't have linking problems. If you want a pdf file then I suggest you get a pdf printer driver. Conclusion ---------- Just because Yapi is very easy to use don't mistake it for a crude tool. There are lots of subtle tricks going on the background to make your life easier (e.g. word wrapping, wrapping in a grid box, handling of memo.text to mention a few). Have a look at some of the sample reports (e.g. FishFacts.pdf) on the web page. All samples are PDFs using a PDF printer driver. The basic Yapi is available free. It has the basics but is still a very useful tool. See: http://free.hostdepartment.com/o/owenmooney/ B.T.W. Yapi stands for 'Yet Another Printer Interface.' ________________________________________________________________________ InstallAWARE 3.0 for Windows Installer by MimarSinan International Special time limited offer: 30% off Enterprise Edition, only $559.95! InstallAWARE is a next generation setup authoring tool with unique features such as partial web deployment, Flash and HTML progress billboards, ten striking setup themes, fully customizable installation dialogs, and a setup script that automatically gets converted to a Windows Installer file. >> http://www.installaware.com/landingea.html << ________________________________________________________________________ 4. How to Set a Component's Default Event Handler By Peter Johnson, Copyright (c) 2004 <delphidabbler at tiscali dot co dot uk> http://www.delphidabbler.com/ Why This Article? ----------------- When you double click most Delphi components at design time the IDE automatically creates an empty event handler for the default event. Sometimes you need a different event to be used as the default. The purpose of this article is to explain how to do this. (There is some information on this topic in the Delphi 7 help file, but the example provided there is incorrect). Let's make this a little more concrete by considering these three components: type TCompA = class(TComponent) private fOnFoo: TNotifyEvent; fOnBar: TNotifyEvent; fOnClick: TNotifyEvent; fOnChange: TNotifyEvent; fOnChanged: TNotifyEvent; fOnCreate: TNotifyEvent; published property OnFoo: TNotifyEvent read fOnFoo write fOnFoo; property OnBar: TNotifyEvent read fOnBar write fOnBar; property OnChange: TNotifyEvent read fOnChange write fOnChange; property OnChanged: TNotifyEvent read fOnChanged write fOnChanged; property OnClick: TNotifyEvent read fOnClick write fOnClick; property OnCreate: TNotifyEvent read fOnCreate write fOnCreate; end; TCompB = class(TComponent) private fOnFoo: TNotifyEvent; fOnBar: TNotifyEvent; fOnChange: TNotifyEvent; published property OnFoo: TNotifyEvent read fOnFoo write fOnFoo; property OnBar: TNotifyEvent read fOnBar write fOnBar; property OnChange: TNotifyEvent read fOnChange write fOnChange; end; TCompC = class(TComponent) private fOnFoo: TNotifyEvent; fOnBar: TNotifyEvent; published property OnFoo: TNotifyEvent read fOnFoo write fOnFoo; property OnBar: TNotifyEvent read fOnBar write fOnBar; end; Double clicking the components in the Delphi IDE creates the following event handlers: * TCompA: OnCreate * TCompB: OnChange * TCompC: OnBar Suppose we want the OnFoo event to be created in all cases? We'll find out how to do this in the course of this article. Some Background --------------- Before we can solve our problem we need to examine how Delphi 7 decides which event to use as the default. (Things are a subtly different in Delphi 4 - as we'll see later). When a component is double clicked, Delphi calls the Edit method of the registered component editor. The default component editor is TDefaultEditor and it is this editor that is responsible for creating the event handler. Let's examine how TDefaultEditor.Edit decides on the the default event. Through a round about process, the Edit method ultimately calls the TDefaultEditor.EditProperty method once for each of the component's properties. Rather than pass a reference to the property itself to EditProperty, it is a reference to the associated property editor that is actually passed. EditProperty checks the name of each event property and records the property editor of the preferred event. These events, in order of preference, are: * OnCreate * OnChange * OnChanged * OnClick * the first event alphabetically This means that if OnCreate is present it is used as the default. If it is not present then OnChange is used, and so on. If none of the listed events is present then the event that is first alphabetically is used. Overriding the Default Behaviour -------------------------------- From the discussion above it is clear that if we are to specify the default event ourselves we need to change the way that TDefaultEditor.EditProperty works. Luckily for us this method is virtual, so we can subclass TDefaultEditor and override EditProperty. The new class can be declared very simply as follows: type TMyCompEditor = class(TDefaultEditor) protected procedure EditProperty(const PropertyEditor: IProperty; var Continue: Boolean); override; end; The implementation is equally straightforward: procedure TMyCompEditor.EditProperty(const PropertyEditor: IProperty; var Continue: Boolean); begin // only call inherited method if required event name if CompareText(PropertyEditor.GetName, 'OnFoo') = 0 then inherited; end; Let's look at how this works. Recall that, in the base class, TDefaultEditor.EditProperty was called once for each property in the component. The method chose the default event from all those it was passed. Now, if TDefaultEditor.EditProperty was only called once then the default event must be the one whose property editor was passed in that single call. Our overridden method works by ensuring that the inherited method is only called once - for the event we want to be the default. In our descendant class, it is TMyCompEditor.EditProperty that is called for each property in the component. The method simply checks for a property editor whose property has the required name ('OnFoo') and passes that property editor - and only that property editor - to the inherited method. And violá - OnFoo is the default event! A Reusable Solution ------------------- In the code we developed in the previous section we hard-wired the name of the default event. Our next job is to generalize the solution to make the code easier to re-use. Since the classes are distinguished only by the name of the default event they select, we will develop a base class that has an abstract method that descendants override to return the name of the required event. Here is the declaration of the base class: type TCompEditorBase = class(TDefaultEditor) protected function DefaultEventName: string; virtual; abstract; { override to return name of default event } procedure EditProperty(const PropertyEditor: IProperty; var Continue: Boolean); override; { records property editor of default event } end; The implementation should come as no surprise - we simply replace the hard-wired name in the earlier class with a call to the abstract method: procedure TCompEditorBase.EditProperty( const PropertyEditor: IProperty; var Continue: Boolean); begin if CompareText(PropertyEditor.GetName, DefaultEventName) = 0 then inherited; end; We can now implement a component editor for a specific default event - our old friend OnFoo once more - simply by overriding the abstract DefaultEventName method as follows: type TCompEditor = class(TCompEditorBase) protected function DefaultEventName: string; override; end; ... function TCompEditor.DefaultEventName: string; begin Result := 'OnFoo'; end; Tidying Up ---------- To get these examples to compile we need to use the Classes, SysUtils, DesignIntf and DesignEditors units. Note that the last two units can only be used when integrating into the IDE - they can't be used in stand-alone applications. We also need to register the component editor in our unit's Register procedure as follows: procedure Register; begin RegisterComponentEditor(TCompA, TCompEditor); // etc ... end; Delphi 4 Differences -------------------- In Delphi 4 TDefaultEditor.EditProperty has a different signature. It is defined as: procedure EditProperty(PropertyEditor: TPropertyEditor; var Continue, FreeEditor: Boolean); We can deal with this using conditional compilation. Assuming the DELPHI6ANDUP symbol is defined when we are using Delphi 6 and above, we can implement TCompEditorBase.EditProperty as follows: procedure TCompEditorBase.EditProperty( {$IFDEF DELPHI6ANDUP} const PropertyEditor: IProperty; var Continue: Boolean {$ELSE} PropertyEditor: TPropertyEditor; var Continue, FreeEditor: Boolean {$ENDIF} ); begin if CompareText(PropertyEditor.GetName, DefaultEventName) = 0 then inherited; end; Additionally, we need to replace the DesignIntf and DesignEditors units with DsgnIntf: uses Classes, SysUtils, {$IFDEF DELPHI6ANDUP} DesignIntf, DesignEditors; {$ELSE} DsgnIntf; {$ENDIF} Summary ------- In this article we have reviewed how to change the event handler that is opened in the IDE when a component is double clicked. We first examined how Delphi decides which events to use for this purpose and then developed a sub class of TDefaultEditor that can explicitly specify the default event. We then went on to generalize the solution by developing an abstract base class for component editors that change the default event. The main discussion closed with a review of the units required to compile the new classes and looked at how to register the component editor. Finally we discussed the changes that need to be made to compile the code under Delphi 4. Demo code --------- If you would like to experiment with this code, the zip archive accompanying this issue contains demo code that implements the example components and component editors discussed in this article. __________________ Peter Johnson is a hobbyist programmer living in West Wales (UK) who maintains the DelphiDabbler website (http://www.delphidabbler.com/) where his articles and freeware Delphi applications & components are published. The code for this article is also available at: http://www.delphidabbler.com/download.php?file=article-17-demo.zip ________________________________________________________________________ Vote for the Pascal Newsletter in The Delphi Top 200! http://top200.jazarsoft.com/delphi/rank.php3?id=latium ________________________________________________________________________ 5. Forums / Mailing Lists To join any of our forums, the best way is to subscribe from the web, since then you'll be able to access the website features (message archive, files section, etc). A Yahoo! ID is required for that, and you can get yours free by registering as a Yahoo! user, but you can also subscribe by email (you'll only have email access). * Delphi-En: A unique forum for intermediate-level Delphi programmers. A large group with a helpful community of members. If you are a beginner please stay as a listener and learn from others' questions and answers. Home Page: http://groups.yahoo.com/group/delphi-en/ Subscription: http://groups.yahoo.com/group/delphi-en/join delphi-en-subscribe@yahoogroups.com * Delphi-All: A Delphi group open to programmers of all levels. Home Page: http://groups.yahoo.com/group/delphi-all/ Subscription: http://groups.yahoo.com/group/delphi-all/join delphi-all-subscribe@yahoogroups.com * Kylix: Kylix programming. Home Page: http://groups.yahoo.com/group/KylixGroup/ Subscription: http://groups.yahoo.com/group/KylixGroup/join KylixGroup-subscribe@yahoogroups.com * Components: This is a forum for searching / recommending software components (VCL and CLX components, ActiveX objects, DLL libraries, .Net, etc.), as well as utilities, tutorials, information, etc. Home Page: http://groups.yahoo.com/group/components/ Subscription: http://groups.yahoo.com/group/components/join components-subscribe@yahoogroups.com * Software Developers: A place for subjects related to software development and the industry. For developers to share their experiences as an academic, professional or hobbyist. It is not a programming forum, messages here are supposed to be more general or language independent. Home Page: http://groups.yahoo.com/group/software-developers/ Subscription: http://groups.yahoo.com/group/software-developers/join software-developers-subscribe@yahoogroups.com ________________________________________________________________________ KnowedgeBASE Vortex 2.9 by Delphinium Software ($49.35 US) KnowledgeBASE Vortex features a highly searchable and expandable information tree viewed in outline or audited within a built-in wordprocessor. Includes a database for stored references. http://www.download.com/KnowledgeBase-Vortex/3000-2064-10342084.html ________________________________________________________________________ 6. Delphi on the Net By Dave Murray <irongut at vodafone dot net> Components, Libraries and Utilities =================================== Freeware -------- * Clearer v1.7 - by Mauro Venturini (with source) History is a great new feature of Delphi 2005. There is only a little drawback: after project completion getting rid of all the history files is a bit annoying. Clear makes this more easy. It also takes care of .NET local storage accumulation due to version changes. http://www.torry.net/tools/developers/other/Clearer1.6Clearer.zip * Delphi Scintilla Interface Components v0.16 - Jan Pettersen (w source) Use the Scintilla Project Syntax Highlighting edit control with Delphi Define your own languages based on the lexers in the SciLexer.dll (Scintilla Project), with the styles and keywords needed directly from the Delphi designer. Tested with Delphi 7. http://delphisci.sourceforge.net/ * Graphics32 v1.7 - by Graphics32 Team (open source, MPL) Graphics32 is a library designed for fast 32-bit graphics handling on Delphi and Kylix. Optimized for 32-bit pixel formats, it provides fast operations with pixels and graphic primitives, and in most cases Graphics32 outperforms the standard TCanvas classes. It is almost a hundred times faster in per-pixel access and about 2-5 times faster in drawing lines. http://graphics32.org/ * PUses v2.2 - by Mauro Venturini (with source) PUses implements orderly reformatting of uses clauses, including: rewriting the unit/namespace names one per line and sorting them; optionally adding the prefix unit (generated using PrefiIt!) names. Supports Delphi 2005 only. http://www.torry.net/tools/other/ide/PUses2.0PUses.zip * TTrimmingLabel v1.0 - by Mauro Venturini (with source) TTrimmingLabel is a Windows Forms label that can trim the Text string to adapt it to the available Width. The trimming is controlled by the Trimming property (of type System.Windows.Forms.StringTrimming). Supports Delphi 2005 only. http://www.torry.net/vcl/labels/enhancedlabels/TrimmingLabel.zip * Undo/Redo for TeeChart Pro VCL v1.02 - by Ivo Ungermann (with source) Component for limited or unlimited undo / redo function with TChart (part of TeeChart Pro VCL package from Steema Software). Automatically traced TChart events: Zoom, Unzoom, Scroll. Other chart changes traced with a bit of code. Tested with TeeChart Pro 7 VCL, Delphi 5 - 7. http://www.torry.net/vcl/charts/charts/TChartUndoRedo.zip * sTile v2.7 - by harmware A graphics program to create seamless 'tiles' which can be used as background images for web pages and applications. It has many filters and effects and is a fun little program to play with. By nature, it produces somewhat psychedelic images, but they can be toned down or muted for actual use. Samples on the web site include a brick wall. http://www.harmware.com/stile.htm Borland Product Updates ----------------------- * Public Beta: Delphi 8 Update 3 This patch is designed to correct issues with .NET versioning for Delphi 8. This is a dcu breaking change. Version calculation for dcuils and dcpils has been updated to depend on symbol names rather than values of metadata tokens imported from .NET assemblies. This fixes errors that users have been experiencing with updating their service packs for NET 1.1 and will prevent any further issues. http://community.borland.com/article/0,1410,32873,00.html * Delphi 2005 Update 1 Now Available This update makes Delphi 2005 rock solid! http://community.borland.com/article/0,1410,32875,00.html Articles, Tips and Tricks ========================= * BDNradio: Database Features of Delphi 2005 with Ramesh Theivendran and Joerg Weingarten Read the chat log and listen to the replay of the live interview with Ramesh and Joerg on Delphi 2005 database features. http://community.borland.com/article/0,1410,32925,00.html * BDNradio: VCL and VCL for .NET in Delphi 2005 with Seppy Bloom and Danny Thorpe Listen to the replay and read the chat room log of the live interview with Danny and Seppy. http://community.borland.com/article/0,1410,32924,00.html * BDNradio: Unit Testing in Delphi 2005 with Mark Edington Listen to the replay and read the chat room log of Mark Edington's live chat on unit testing support in Delphi 2005. http://community.borland.com/article/0,1410,32920,00.html * BDNradio: .NET data remoting in Delphi 2005 with Ramesh Theivendran Read the chat log and listen to the live chat with Ramesh on the easy, flexible, and powerful way to develop .NET data remoting applications in Delphi 2005. http://community.borland.com/article/0,1410,32917,00.html * BDNradio: Debugging in Delphi 2005 with Chris Hesik Listen to the replay and read the chat room log of Chris Hesik's interview on debugging in Delphi 2005. http://community.borland.com/article/0,1410,32923,00.html * BDNradio: Web Development in Delphi 2005 with Jim Tierney and Steve Trefethen Read the chat log and listen to the replay of this live chat with Jim and Steve on Delphi 2005's web development support. http://community.borland.com/article/0,1410,32881,00.html * Recursions in Delphi - by Zarko Gajic Recursion is a very simple, yet useful and powerful programmer's tool. Subroutines can, and frequently do, call other subroutines, a subroutine that activates/calls itself is called recursive. Recursion is a general method of solving problems by reducing them to simpler problems of a similar type. Many programmers often avoid this type of subroutine because it can be confusing and complicated. This article is going to make recursion in Object Pascal simple ... I hope. http://delphi.about.com/od/objectpascalide/l/aa120799a.htm * Graphical Combos - by Zarko Gajic Creating owner drawn Combo Boxes in Delphi. See how to code a graphical drop-down list; examples include a combo box of colours and a true-type font picker. http://delphi.about.com/od/vclusing/l/aa101700a.htm Tutorials and Training ====================== * An Introduction to COM Programming with Delphi (6/6) - by Curtis Socha Type Library: pros and cons, 5 steps to a Type Library, a look into the TLB Abyss. http://delphi.about.com/library/weekly/aa122804a.htm * Resource Files Made Easy - by Zarko Gajic Part 1 of a series of articles, explains how Delphi uses standard Windows-format resource files: icons, bitmaps and cursors. http://delphi.about.com/od/objectpascalide/l/aa113099a.htm * Inside the EXE - by Zarko Gajic Part 2 of a series of articles, this article will show you how to store (and use) sound files, video clips, animations and any kind of binary files in a Delphi executable. http://delphi.about.com/od/objectpascalide/l/aa021301a.htm * Web Site inside a Delphi EXE - by Zarko Gajic Part 3 of a series of articles, shows how HTML and associated files (pictures) can easily be included within a Delphi application. http://delphi.about.com/od/objectpascalide/l/aa113099a.htm * Creating and Using a Resource Only DLL with Delphi - by Zarko Gajic Fourth article in the series about storing more than just executable code inside a Delphi application. This part shows how to create a dynamic link library containing only resources. http://delphi.about.com/od/objectpascalide/l/aa113099a.htm * Localizing Delphi Applications using StringTable Resources - Z. Gajic Part 5 in a series of articles, while resource files enable storing more than just program code in an EXE file, by including stringtable resources to an application a Delphi developer can easily build multilanguage applications. Learn how. http://delphi.about.com/library/weekly/aa011805a.htm News ==== * Borland Going After Services Revenue Services are destined to play a greater part in Borland's future as the company assists customers around Application Lifecycle Management. http://www.cbronline.com/article_news.asp? guid=B1DA308B-4561-4A9B-83EE-F4D4EFAA07E8 * Borland Purchases Brains to Drive SDO Borland has made its first corporate purchase in two years, acquiring software process consulting specialist TeraQuest Metrics Inc. Borland plans to integrate TeraQuest's expertise, encapsulated in templates for activities such as change management, project planning and requirements gathering, into its ALM tools, processes and services. http://www.cbronline.com/article_news.asp? guid=C5F6EE17-B508-432B-987B-F274EC223A20 * Borland Schedules Q4 and Year End 2004 Conference Call and Webcast Borland today announced that its fourth quarter and year end 2004 teleconference and simultaneous Webcast is scheduled to begin at 2:30 p.m. Pacific Time, on Tuesday 1st February 2005. http://www.tmcnet.com/usubmit/2005/Jan/1106470.htm * Firebird Database Readies SMP Release Firebird developers are due to do an alpha release of version 2.0 of the open source database later this month and have completed work on SMP support, which is due to be released in late spring. http://news.zdnet.co.uk/software/developer/0,39020387,39183292,00.htm Other / Misc Sites ================== * New BDN Feature: Community Calendar At the BorCon 2004 closing session, one of the new BDN applications launched was a community calendar, code named EventCentral. Any BDN member can use EventCentral to post events of interest to Borland customers anywhere around the world. Events must be approved by a sysop to be publicly visible. http://community.borland.com/article/0,1410,32936,00.html * CodeFez Blogs Blogs by Charlie Calvert, Julian Bucknall, Lino Tadros, Nick Hodges and Steve Teixeira. Topics include software development, Delphi and other Borland tools. http://www.codefez.com/Default.aspx?tabid=79&newsType=NewsListing * PGD DogFight Game Programming Competition DelphiGamer.com recently relauched and will soon be known as "Pascal Game Development" or PGD for short. To coincide with the relaunch, a themed game programming competition has been announced. http://www.pgd.netstarweb.com/viewtopic.php?t=1747 ________________________________________________________________________ Irongut's Delphi Pages Dedicated to programming with Borland Delphi and Kylix. We have articles on programming, Borland and Delphi news, source code and components to use in your applications and more. >> http://www.paranoia.clara.net/ << ________________________________________________________________________ YOU CAN HELP US We need your help to keep this newsletter going and growing. You can help by referring the newsletter to your colleagues: http://www.latiumsoftware.com/en/pascal/delphi-newsletter.php Or you can help by voting for us in some or all of these rankings to give more visibility to our web site and thus increase the number of subscriptions to this newsletter: http://news.optimax.com/delphi/links/links.exe/click?id=70C517ECAE6E http://www.programmingpages.com/?r=latiumsoftwarecomenpascal http://top100borland.com/in.php?who=20 http://top200.jazarsoft.com/delphi/rank.php3?id=latium It's just a few seconds for you that REALLY mean a lot to us. Don't forget we also need articles and there are prizes available for contributions. All articles will be considered but we are particularly interested in articles about Kylix because there is so little available online to help Kylix developers. Send articles to: <pascal-newsletter-owner@yahoogroups.com> We are also looking for shareware authors who would like to offer their components or applications as prizes for articles in the newsletter. In return you will be promoted in the newsletter and on the Latium Software and Irongut's Delphi Pages websites. For more info contact: Dave Murray <irongut at vodafone dot net> ________________________________________________________________________ If you haven't received the full source code examples for this issue, you can get them from http://www.latiumsoftware.com/download/p0053.zip ________________________________________________________________________ This newsletter is provided "AS IS" without warranty of any kind. Its use implies the acceptance of our licensing terms and disclaimer of warranty you can read at http://www.latiumsoftware.com/en/legal.php where you will also find a note about legal trademarks. Articles are copyright of their respective authors and they are reproduced here with their permission. You can redistribute this newsletter as long as you do it in full (including copyright notices), without changes, and gratis. ________________________________________________________________________ Main page: http://www.latiumsoftware.com/en/pascal/delphi-newsletter.php Group Home: http://groups.yahoo.com/group/pascal-newsletter/ Subscribe: pascal-newsletter-subscribe@yahoogroups.com Unsubscribe: pascal-newsletter-unsubscribe@yahoogroups.com Problems with your subscription? pascal-newsletter-owner@yahoogroups.com ________________________________________________________________________ Latium Software: http://www.latiumsoftware.com/en/index.php Irongut's Delphi Pages: http://www.paranoia.clara.net/ Copyright 2005 by Ernesto De Spirito + Dave Murray. All rights reserved. ________________________________________________________________________ |
The full source code examples of this issue are available for download.
![]() |
Errors? Omissions? Comments? Please contact us!






