Athena

Delphi and C++Builder Tips and Techniques

Brian Long (www.blong.com)

If you find this article useful then please consider making a donation. It will be appreciated however big or small it might be and will encourage Brian to continue researching and writing about interesting subjects in the future.


Introduction

This paper aims to provide a useful variety of tips and techniques for using Borland’s Delphi, C++Builder and Kylix products. Some of these tips will be specific to some products and will by necessity exclude the others. However many of them are applicable to all products.

Since various parts of the paper make reference to the version of a product that something was introduced, it is useful to know the order of release of the different versions of each of these products. If something was introduced in any given product, it is typically present in all other products released after that point.

It is typically the case that new functionality is added to Delphi first, and it then migrates to C++Builder, but this is certainly not always the case. For example, C++Builder 5 was the first product to support COM+ and also the first product whose COM object wizard allows you to choose an existing registered interface for the COM object to implement.

Borland’s ObjectPascal and C++ RAD tools have been released as detailed in Table 1.

Table 1: Release points of Delphi, C++Builder and Kylix

Product Version Released
Delphi 1.00 February 1995
Delphi 1.01 April 1995
Delphi 1.02 August 1995
Delphi 2.00 February 1996
Delphi 2.01 June 1996
C++Builder 1.00 February 1997
Delphi 3.00 April 1997
Delphi 3.01 August 1997
Delphi 3.02 December 1997
C++Builder 3.00 February 1998
Delphi 4.00 June 1998
Delphi 4.01 October 1998
C++Builder 4.00 January 1999
Delphi 4.02 February 1999
C++Builder 4.01 June 1999
Delphi 5.00 August 1999
Delphi 5.01 January 2000
C++Builder 5.00 February 2000
C++Builder 5.01 August 2000
Kylix 1.00 February 2001
Delphi 6.00 May 2001
Delphi 6.01 October 2001
Kylix 2.00 November 2001
C++Builder 6.00 February 2002
Delphi 6.02 February 2002

You can download the files that accompany this paper by clicking here.

IDE Tips

The Delphi and C++Builder IDEs started life in 1995 with the release of Delphi 1 as well integrated environments for doing whatever was needed in order to design, write, compile and debug your application. However, they have always been replete with useful functionality. This functionality has been added to with each successive release. The question is, how much of the available facilities do you use?

It is not expected that all these topics will be new to everyone, but hopefully, the list provided here will introduce a few portions of useful functionality to everyone.

The IDE Tool Bars

As you probably already know, the IDE toolbars are customisable. You can choose Customize... from the tool bars’ context menu and the dialog produced allows you to add tool buttons representing most of the menu items. Some of the tool buttons in Delphi 4 and later have drop down lists available by clicking on the down arrow just to the right of the tool button.

The File | Open tool button, present by default on the Standard tool bar, has a drop down list that duplicates the File | Reopen menu, allowing you to quickly reopen any recent project or form. The Run | Run tool button, on the Debug tool bar by default, has a drop down list that allows you to switch the active project in a project group. The list shows all the relevant target executables that can be built with the active one displayed as bold.

One of the optional buttons that you can add to any toolbar is one that represents the View | Window List... menu item. Delphi 5 changed this tool button to also have a drop down list containing all the window captions that would normally appear in the dialog if you pressed the button.

The Editor

To find all the shortcuts available in the editor, and also in the rest of the IDE, lookup the shortcuts in the help file.

The shortcuts typically vary among the available keystroke mappings that can be selected. Keystroke mappings are selected from the editor options. From Delphi 2 onwards, you can access these from the Properties item on the editor’s context menu, or using the menu bar (see Table 2).

Table 2: How to access keystroke mappings from the menu

Delphi 1 Options | Environment..., Editor display, Keystroke mapping
Delphi 2 Tools | Options..., Display, Keystroke mapping
Delphi 3, 4 Tools | Environment Options..., Display, Keystroke mapping
Delphi 5, 6 Tools | Editor Options..., Key Mappings, Key mapping modules

The different keystroke mappings are designed to emulate popular editors and the Windows CUA keystroke set (see Table 3).

Table 3: The available keystroke mappings

Keystroke mapping Description
Default Follows CUA guidelines for many keystrokes
Classic or IDE Classic Uses the same keystrokes as the old Turbo Pascal/C editor
BRIEF Emulates the BRIEF editor
Epsilon Emulates the Epsilon editor
Visual Studio emulation Emulates the Visual Studio editor. Added in Delphi 5, though not present in Kylix.
New IDE Classic Uses key bindings defined in a supplied demo package
Visual Basic emulation Emulates the Visual Basic editor. Added in Delphi 6
New IDE Emacs Emulates Emacs using key bindings defined in a supplied demo package. Added in Kylix 1

Note that many of these Epsilon keystroke shortcuts appear not to work. This has been reported.

Also note that, whilst BRIEF is used as a word, it is in fact an acronym for Basic Reconfigurable Interactive Editing Facility. It used to be marketed by a company called UnderWare before being bought by Borland some years ago.

Code Insight

Code Insight was added to Delphi 3, and enhanced a little in Delphi 4. The original functionality included Code Completion, Code Parameters, Code templates and Tooltip expression evaluation. Delphi 4 introduced Tooltip symbol insight.

Code Completion is normally automatically invoked when you enter an object reference, or a record variable followed by a dot. A popup listbox is shown with all the valid entities (as far as the compiler can see) based upon an analysis of the context and available type information. The list may also contain invalid entries followed by ellipsis characters. These entries are typically objects that the compiler feels may have properties or methods that might be valid entries.

You can invoke Code Completion at any time with Ctrl+Space. You can also sort the Code Completion list either by scope (the default) or by name, by right-clicking on the Code Completion listbox and choosing a menu item.

Note that Delphi 6 adds a new Code Completion feature, but the installation program sets it up incorrectly so it doesn't work. Code Completion should allow you to choose identifiers in units not used by the current unit. If you choose such an item, the defining unit is added to the implementation section uses clause. You can find more information about this broken feature in an article on the subject, which can be found on the Borland Developer Network site. A utility application that correctly sets up this Code Completion feature (and also lets you customise the Code Completion colour scheme) is supplied with this paper and is called CodeCompletionSettings.dpr.

Code Parameters is a tooltip-like window that appears automatically when you enter an open parenthesis (the beginning of a parameter list), and shows you the formal argument declarations. If you want to explicitly invoke it, you can press Shift+Ctrl+Space.

Code Templates allow you to automatically enter regularly used code snippets by choosing them from a menu. The menu is invoked with Ctrl+J. To produce a much shorter menu, type the first letter(s) of the template name and then press Ctrl+J.

Tooltip expression evaluation occurs when the debugger has control over an application. Pausing the mouse over either a term in the editor, or a highlighted expression will evaluate the expression and display it in a tooltip.

Tooltip symbol insight works the same as tooltip expression evaluation, but when the debugger is not active. Pausing the mouse over an identifier will show you where that identifier was declared (which file and line number) as well as what type of identifier it is (procedure, variable, constant, etc.).

These next editor features are not classed in the Code Insight group, but are worthy of mention. They were introduced in Delphi 4.

Having seen where an identifier is declared using tooltip symbol insight, you can be taken to its declaration using the Code browser. Hold down the Ctrl key and move the mouse over an identifier. It will turn into a hyperlink, and the mouse cursor will turn into a pointing hand. Clicking on the hyperlink will take you to the identifier’s declaration. You get the same effect by choosing Find declaration from the editor’s context menu.

Once you start using the Code browser, the tool buttons at the top right of the editor become active. Much like in a Web browser, they allow you to navigate backwards and forwards through the Code browser links you have followed. They have keystroke equivalents of Alt+← and Alt+→ in Kylix 1 and later products.

In Delphi and Kylix, if the input cursor is positioned on the declaration of a global routine (in the interface section of a unit) or a method declaration in a class, pressing either Shift+Ctrl+↓ or Shift+Ctrl+↑ will position you on the implementation. The reverse is true as well. Pressing either keystroke when in the implementation will take you to the declaration, if one exists. This is called module navigation.

In Delphi and Kylix, class completion allows you to enter the declaration of a method in a class definition, press Shift+Ctrl+C and have the implementation manufactured. The reverse is also true. If you implement a method and press Shift+Ctrl+C, the declaration will be added to the class. Assuming the relevant option is enabled in the Explorer page of the environment options dialog, you can also get Class Completion to finish incomplete property declarations, setting up a property writer routine as it goes.

In C++Builder, the ClassExplorer performs a similar function to class completion. The ClassExplorer is the C++Builder version of the Code Explorer found in Delphi and Kylix, and is usually found docked on the left side of the editor; it was introduced in C++Builder 4. Right click on the ClassExplorer and choose New Field..., New Property... or New Method... and the resultant dialogs take the details and then add the required code.

Editor bookmarks

In any given IDE session, each file in the editor can have up to ten bookmarks dropped on it. These allow you to navigate around your source files to pre-determined destination points very easily.

In Delphi 4 and later, you can use the editor’s context menu to toggle any bookmark (alternately turn it on and off) and go to any bookmark. The editor also supports toggling and going to bookmarks with keystrokes.

Table 4 gives a summary of the available keystrokes to either toggle or go to a bookmark. In all cases, the symbol n represents any number from 0 to 9.

Table 4: Bookmark operations

Keystroke mapping Toggle bookmark Go to bookmark
Default, Visual Studio and Visual Basic Ctrl+K+n or Shift+Ctrl+n Ctrl+Q+n or Ctrl+n
Classic Ctrl+K+n Ctrl+Q+n
BRIEF emulation Alt+n Alt+J+n
Epsilon emulation Epsilon works differently Epsilon works differently

Note that the original Epsilon editor worked differently with respect to bookmarks. Consequently, you can use any of these keystrokes to drop the bookmark at the current cursor position: Ctrl+@, Alt+@, Esc+@, Ctrl+2 or Alt+2. Also, the Epsilon editor emulation supports Ctrl+X, Ctrl+X to toggle between the bookmark and the current position.

The prime disadvantage of the IDE editor bookmarks is that they are not saved along with your project’s desktop file. Consequently, if you close a project that has a number of bookmarks dropped in various source files then re-open that project, you will find all your bookmarks will be lost.

Keyboard macros

When making repetitive modifications to many lines in a source file, use a keyboard macro instead of doing it by hand all the time. Press Shift+Ctrl+R to initiate recording a macro (the editor status bar verifies that keystrokes are being recorded). Then do all the keystrokes that you want repeated (possibly including the use of a cursor key to take you to another line). Press Shift+Ctrl+R again to stop recording.

When you want to play the recorded keystrokes back, use Shift+Ctrl+P. Note that you can only record keystrokes that go to the editor. In other words, if your macro involves searching for some text, you cannot include the invocation of the search dialog in the macro. Instead, invoke it beforehand. Then, in the macro, use the keystroke for Search | Search Again (F3 or Ctrl+L, depending on the keystroke mapping).

Shift+Ctrl+R and Shift+Ctrl+P are valid in the Default and Classic keystroke mappings. Table 5 shows the keys required for other keystroke mappings.

Table 5: Keyboard macro operations

Keystroke mapping Start recording macro Stop recording macro Playback macro
Default, Classic Visual Studio and Visual Basic

Shift+Ctrl+R

Shift+Ctrl+R

Shift+Ctrl+P

BRIEF F7

F7

F8
Epsilon Ctrl+X, ( Ctrl+X, ) Ctrl+X, e or Ctrl+X, E

Case switching

There are some places where you need to turn a block of text into upper case or lower case. More frequently, though, you will find you need to invert the case of a block of text (for example when you accidentally leave Caps Lock on). The editor can cater for those cases in varying degrees with the different keystroke mappings, as shown in Table 6.

Table 6: Case-changing keystrokes

Keystroke mapping Upper case Lower case Toggle case
Default, Classic, Visual Studio and Visual Basic

Ctrl+K+N

Ctrl+K+O

Ctrl+O+U

BRIEF    

Ctrl+O+O

Epsilon Alt+U or Esc+U Ctrl+I  
New IDE Emacs Ctrl+X, Ctrl+U Ctrl+X, Ctrl+L  

Block indent and outdent

When you decide that a number of statements need to become part of another statement (a compound statement of some description) you will typically wish to indent each line. Sometimes, the opposite is also true, where you will wish to outdent a number of lines. Rather than doing this individually for each line, you can select a block and an appropriate keystroke (see Table 7) will do the trick.

Table 7: Block indent and outdent keystrokes

Keystroke mapping Block Indent Block Outdent
Default and Visual Studio

Ctrl+Shift+I

Ctrl+Shift+U

Default, Classic, Visual Studio and Visual Basic Ctrl+K+I

Ctrl+K+U

BRIEF Tab Shift+Tab
Epsilon Ctrl+X, Ctrl+I or Ctrl+X, Tab  

The number of spaces inserted in the indenting (or removed in the outdent operation) is governed by the Block Indent: option on the general editor options page (Table 8 shows how to find these).

Table 8: How to access general editor options

Delphi 1 Options | Environment..., Editor options
Delphi 2 Tools | Options..., Editor
Delphi 3, 4 Tools | Environment Options..., Editor
Delphi 5, 6 Tools | Editor Options..., General

Incremental search

Most IDE users use the search dialog when looking for some text. Sometimes an incremental search will be quicker as it involves simply one keystroke, and typing the search expression (though not necessarily all of it).

Once you invoke the incremental search, each character of the search expression that you enter causes the editor to search for the first occurrence of the string that it has so far been given. Table 9 shows the keystrokes that start an incremental search.

Table 9: Incremental search

Keystroke mapping Search forwards Search backwards Search again
Default Ctrl+E   F3 or Ctrl+L
Classic Shift+Ctrl+S   Ctrl+L
BRIEF Ctrl+S   Shift+F5
Epsilon Ctrl+S Ctrl+R  
Visual Studio and Visual Basic Ctrl+I   F3 or Ctrl+L
New IDE Emacs Ctrl+S Ctrl+R Ctrl+L

Whilst on the subject of searching, it is worth noting that there is another timesaver available. When you do invoke the search dialog (Search | Find...) you can force the editor into taking the word at the input cursor position and entering it as the default search term (although it is highlighted so you can type straight over it). This is done by ensuring that the Find text at cursor checkbox is checked on the general editor options dialog page (see Table 8 for details of how to get there).

Highlighting the current word

Assuming that the Double click line checkbox is not checked on the general editor options dialog page (see Table 8 for details of how to get there), then double-clicking any word with the mouse will highlight it. However, if you are banging away on the keyboard, it might be useful to know how to get the same effect without stretching across for the mouse.

In the Default, Classic and Visual Studio mappings, Ctrl+K+T does the trick.

Miscellaneous block operations

Other things the editor allows you to do with a block include printing it and writing it out to a text file. You can also choose a text file to insert at the current cursor position. If there is a block of text highlighted, the file will replace that block of text. Table 10 shows the available keystrokes. Clearly, BRIEF and Epsilon do not offer as many of the combinations as the other keystroke mappings.

Table 10: Miscellaneous block operations

Keystroke mapping Print marked block Read file as block Write block to file
Default, Classic, Visual Studio and Visual Basic

Ctrl+K+P

Ctrl+K+R

Ctrl+K+W

BRIEF

Alt+P

Alt+R

 
Epsilon    

Ctrl+X+W

New IDE Emacs  

Ctrl+X, I

 

Selecting different-shaped blocks of text

Whilst most Delphi and C++Builder users are familiar with selecting normal blocks of text. For example a multi-line block will start at some character on a given line and continues down to some other line, not including the character the cursor is before, and extending to the end of each line in-between. This is called a non-inclusive block.

Another type of block is called an inclusive block, where the marked block includes the character that the cursor is before.

The third type is a line block, where entire lines are highlighted, regardless of where the cursor started or stopped.

The last type of supported block is a column block. This type of block does not extend to the end of each marked line. Instead, whilst it can extend across a variable number of rows, it also extends across a variable number of columns, giving a rectangular marked block.

You can select a columnar block by doing whatever operation you normally use for a non-inclusive block whilst the Alt key is held down. So, if you normally use the cursor keys with the Shift key down, holding down Alt+Shift will mark a columnar block. If you use the mouse, then holding down the Alt key whilst doing it will also mark a columnar block (Microsoft Word uses this approach as well).

You can also instruct the editor to mark any one of these types of block instead of non-inclusive with an appropriate keystroke (see Table 11).

Table 11: Marking different types of block

Keystroke mapping Non-inclusive Inclusive Line Column
Default, Classic Ctrl+O+K Ctrl+O+I Ctrl+O+L Ctrl+O+C
BRIEF Alt+A Alt+M Alt+L Alt+C

When marking a block in BRIEF mode, you do not need to use the Shift key, regardless of the state of the Persistent blocks checkbox on the general editor options dialog page (see Table 8).

Selecting multiple lines of text

Novice users of Borland IDEs often make more work than necessary for themselves when copying text around. A common way to improve your copying is to consider the carriage return character at the end of the editor line. If you want to copy a whole line of text, firstly put your cursor at the beginning of the line. Then simply press Shift+¯ , rather than manually extending the selection along the whole length of the line. This will mark the whole line, along with its carriage return character.

If you copy this block and paste it elsewhere, you won’t need to fix the lines by pressing Enter and then sort out the broken indentation.

Editor drag and drop

All versions of the Windows IDE editor support have files dragged into the editor from Windows Explorer (or the File Manager under Windows 3.1x).

Delphi 3 and later also supports dragging text within itself (in the same way as Microsoft Word does). If you mark a block of text and drag it to another location in the editor, the text will be moved there. If you hold the Ctrl key whilst dragging the text around, it will be copied instead.

In Delphi 5 and later, the editor also supports dragging to other IDE windows. You can create a new watch expression whilst debugging by dragging an expression from the editor to the watch list window. You can also drag an expression to a debug inspector (which will inspect the contents of the dragged expression), or the stack and dump panes of the CPU window (which will position that pane to the address of the expression).

When debugging an application, a simple drag operation will do the job, but if the debugger is not active, you must hold down the Alt key whilst dragging to get results.

The Form Designer

There is not much to say on the subject of the Form Designer apart from a couple of under-used keystrokes.

Selecting the form

If you have components on your form that use the Align property, there is a good chance you will not be able to see any of the client area of your form.

Note that the client area is the area you can drop components on, and take charge of drawing using standard VCL event handlers. The non-client area includes the form borders and caption bar.

In order to select the form, you probably use the Object Inspector’s instance list (the drop down list of component instances), either with the mouse, or by pressing Ctrl+¯.

Whilst that works well, you can also use the Esc key on the Form Designer. Esc selects the parent of the currently selected component. This means that no matter how many parent/child relationships you have set up on the form, pressing Esc sufficient times will always select the form eventually.

Another option is to select any component by clicking it, then Shift+click the same component.

Moving/sizing components

You can change the size/position of components by using the Object Inspector to alter the Left, Top, Height and Width properties. The Form Designer also supports various ways of changing components' size/position. You can do it by mouse or keyboard and you can move/resize components by one grid unit (8 pixels by default) or 1 pixel.

Operation By mouse By keyboard
Move component by one grid unit Click and drag component Ctrl+Shift+cursor keys
Move component by one pixel Alt+click and drag component Ctrl+cursor keys
Resize component by one grid unit Click and drag grab handle not known
Resize component by one pixel Alt+click and drag grab handle Shift+cursor keys

Lasso operations on container components

Many IDE users know that you can lasso a group of components sitting on the form. This involves clicking on the background of the form and dragging a rectangle around the target set of components.

However, lassoing components on a container component, such as a panel or group box, tends to fox many people. Clicking on the container component and dragging tends to move the container component, rather than invoke the lasso operation. To get the desired effect, you need to press Ctrl and then perform the lasso operation as normal.

Miscellaneous

If you are in a window or dialog with several tabs available, pressing Tab will move to the next one, whilst pressing Shift+Tab will move to the previous one. This is true in dialogs, the Object Inspector and the code editor.

Note though, that in Kylix, the X window manager will typically swallow these keystrokes making them ineffective.

Overcoming Docking Problems

The drag and dock support added to the VCL in Delphi 4 was great for rip-off toolbars and the like. However, the IDE can be very irritating when you are merely trying to move some of its windows around, and they keep insisting on docking everywhere.

To ensure no docking occurs, hold down Ctrl whilst dragging IDE windows around. The same will be true if you implement docking in your own applications.

Due to popular demand, Delphi 6 adds an option in the environment options dialog to globally disable IDE docking. This option directly controls a new property of the Application object, AutoDragDocking.

Undocumented Delphi and C++Builder

Easter Eggs

Like many commercial pieces of software, Delphi and C++Builder always have Easter Eggs hidden away, which list all the members of the various teams at Borland. If you are interested in Easter Eggs, you can find a collection of Borland-related ones on my web site, at http://www.blong.com. Follow the link to Undocumented Stuff.

Registry Entries

We are now going to look at functional things that are undocumented in the IDEs. We will start with registry entries that can be useful. Note that, where relevant, these registry entries can be emulated in Kylix’s ~/.borland/delphi60rc configuration file (laid out like Delphi 1’s INI file). Registry keys turn into INI file sections and values turn into section entries.

Automatic Component Palette Operations

There are a couple of undocumented registry entries that affect the Component Palette in Delphi 4 and later. One allows a page of the Component Palette to be selected by simply moving the mouse over the tab (you do not have to click it).

The other one allows hidden components on a Component Palette page to be easily scrolled into view by moving the mouse over either the left or right palette scroller. Note that this is not to do with the scrollers that scroll the tabs into view, but the ones that appear on the Component Palette itself when there are more components than can be displayed.

To enable these undocumented features, launch a copy of RegEdit.Exe, and navigate down through this path off the HKEY_CURRENT_USER root key: Software\Borland\Delphi\5.0, substituting the appropriate product version. If there is a key called Extras, open it, otherwise you will need to create it with Edit | New | Key.

In the Extras key you need to create two string values with Edit | New | String Value. These should be called AutoPaletteSelect and AutoPaletteScroll respectively, and should both be set to a value of 1. The next time you start your copy of Delphi or C++Builder, the features will be enabled. To disable them, change the values to 0.

Note that Kylix respects AutoPaletteSelect but ignores AutoPaletteScroll.

As an alternative to using the Registry Editor application, you could compile and run the helper application shown in Listing 1 which uses an .INI file (with the same name as the application, in the same directory) containing information about registry entries to change.

Note that C++Builder users can compile this project from the command line using the DCC32.exe Delphi command-line compiler.

Listing 1: A program to set registry settings with


program RegTweak;

uses
  Registry,
  IniFiles,
  SysUtils,
  Forms,
  Dialogs,
  Controls,
  Classes;

type
  TDataType = (dtString, dtInteger, dtBool);

var
  Reg: TRegistry;
  Ini: TIniFile;
  IniName, DataTypeStr, Entry: String;
  DataType: TDataType;
  Sections, Entries: TStrings;
  Loop1, Loop2: Integer;

begin
  IniName := Application.ExeName;
  IniName := Copy(IniName, 1, Length(IniName) - 3) + 'INI';
  Ini := TIniFile.Create(IniName);
  Sections := TStringList.Create;
  Entries := TStringList.Create;
  Reg := TRegIniFile.Create;
  try
    if MessageDlg('Update registry with INI file settings?',
      mtConfirmation, [mbOK, mbCancel], 0) = mrOk then
    begin
      Ini.ReadSections(Sections);
      for Loop1 := 0 to Sections.Count - 1 do
      begin
        Entries.Clear;
        Ini.ReadSectionValues(Sections[Loop1], Entries);
        //Identify target registry entry type
        DataTypeStr := Entries.Values['Type'];
        DataType := dtString;
        if DataTypeStr <> '' then
          case UpCase(DataTypeStr[1]) of
            'I': DataType := dtInteger;
            'B': DataType := dtBool;
            'S': DataType := dtString;
          end;
        //Open the key
        Reg.OpenKey(Sections[Loop1], True);
        try
          //Set each entry
          for Loop2 := 0 to Entries.Count - 1 do
          begin
            Entry := Entries.Names[Loop2];
            //Skip the data type entry
            if UpperCase(Entry) <> 'TYPE' then
              case DataType of
                dtString: Reg.WriteString(Entry, Entries.Values[Entry]);
                dtInteger: Reg.WriteInteger(Entry,
                  StrToInt(Entries.Values[Entry]));
                dtBool: Reg.WriteBool(Entry,
                  UpperCase(Entries.Values[Entry]) = 'TRUE');
              end
          end
        finally
          Reg.CloseKey
        end
      end
    end
  finally
    Sections.Free;
    Entries.Free;
    Reg.Free;
    Ini.Free
  end
end.

A suitable RegTweak.Ini file for the RegTweak application can be seen in Listing 2. The registry path below HKEY_CURRENT_USER is specified as a section heading. The type of all the entries in the section is indicated by the Type entry (this can be S for string, B for Boolean or I for Integer). The rest of the section contains the entries that should be added to the registry.

This idea of showing a section from this .INI file will also be used for all the other undocumented registry entries.

Note that registry entries can also be ably described with .REG files, however their layout is more difficult to read than this .INI file.

Listing 2: An .INI file that will work with Listing 1


[Software\Borland\Delphi\5.0\Extras]
;Delphi 4.0 and later
Type=S
AutoPaletteSelect=1
AutoPaletteScroll=1
WYSIWYG font name in the Object Inspector

Delphi 5 updated the Object Inspector so that it can give visual feedback on certain properties (such as Color, Cursor and ImageIndex). One visual property that does not give immediate feedback, however is the Font property’s Name sub-property (each font name is shown in a fixed font).

This is because Windows installations have a tendency to include many, many fonts. As a consequence, any WYSIWYG view of all the available fonts will mean that all fonts would be loaded into, potentially taking quite some time (and resources).

But, if you want to see how it looks, you can enable WYSIWYG font name display by adding the section in Listing 3 to the INI file from Listing 2. Alternatively, you could just add the key entry line from Listing 3 into the section in Listing 2 if you prefer.

Listing 3: Making WYSIWYG Font properties


[Software\Borland\Delphi\5.0\Extras]
;Delphi 5.0 and later
Type=S
FontNamePropertyDisplayFontNames=1

This entry is ignored by Kylix.

Object Inspector property value colour

This one works in all versions of the IDE. As you may recall, the Object Inspector shows property names in black, and values in blue. If you want the property values to be displayed in another colour, you can do so.

In Delphi 1 you must edit the Delphi.Ini file in the Windows directory. The setting goes in the Globals section, which many not exist. The entry is called PropValueColor and its value is a colour value. This can be any constant that would work as a value in a Delphi program, so both $0000FF and clRed would be acceptable (see Listing 4).

Listing 4: Changing the property value colour in Delphi 1


[Globals]
PropValueColor=clRed

In 32-bit versions of the IDE up to Delphi 5, you need to add this entry to the Globals registry key. The RegTweak program (Listing 1) can do this with a new section in its .INI file as shown in Listing 5.

Listing 5: Changing the property value colour in 32-bit Delphi


[Software\Borland\Delphi\5.0\Globals]
;Delphi 2.0, 3.0, 4.0 and 5.0
Type=S
PropValueColor=clRed
IDE tooltip colour

Delphi 1, 2 and 3 and C++Builder 1 all allow you to change the colour of the IDE tooltip. Whilst it defaults to that dull yellow colour ($80FFFF in Delphi 1 or clInfoBk in 32-bit Delphi) you can change it with another entry in the Globals section. Listing 6 shows the change to make to the Delphi.Ini file and Listing 7 shows what to add to RegTweak.Ini.

Listing 6: Changing the IDE tooltip colour in Delphi 1


[Globals]
HintColor=clAqua

Listing 7: Changing the IDE tooltip colour in Delphi 2 and 3 and C++Builder 1


[Software\Borland\Delphi\3.0\Globals]
;Delphi 2.0 and 3.0
Type=S
HintColor=clAqua
Code Insight errors

The message view (where compiler errors are displayed) normally shows errors only when you ask for an explicit compilation. However, every time the Code Parameters or Code Completion parts of Code Insight (from Delphi 3 onwards) kick in, they do background compilation to get the information they require to display.

If you have an error further up the source file you are in, or maybe in another source file, Code Insight will not do anything, as it will not have compiled enough information. To be made aware when these things happen, set the registry entry as described in the RegTweak.Ini file section in Listing 8.

Listing 8: Enabling the display of Code Insight compilation errors


[Software\Borland\Delphi\5.0\Compiling]
;Delphi 3.0 and later
Type=S
ShowCodeInsiteErrors=1
No Debug Window Shortcuts

In Delphi 4 and later, the debug window options available under the View | Debug Windows all have shortcuts involving Ctrl+Alt, e.g. Ctrl+Alt+W for View | Debug Windows | Watches.

Many Windows users have desktop shortcuts set up, which will default to also using Ctrl+Alt shortcuts. You can therefore easily get ambiguity. For example, you may set up Microsoft Word to launch through Ctrl+Alt+W. In Delphi, you might press Ctrl+Alt+W for the watch window, but you would instead get Word popping up onscreen.

Additionally certain international characters are inserted using Ctrl+Alt shortcuts, e.g. Ctrl+Alt+E, Ctrl+Alt+I and Ctrl+Alt+O give é, í and ó respectively. Removing these shortcuts from the offset will avoid you getting erroneous applications launched instead of debug windows displayed.

The RegTweak.Ini section is shown in Listing 9. However, strictly speaking this setting is not undocumented, as it features in the README.TXT file of Delphi 4 and later.

Listing 9: Disabling the Ctrl+Alt+letter shortcuts for the debug menu items


[Software\Borland\Delphi\5.0\Editor\Options]
;Delphi 4.0 and later
Type=S
NoCtrlAltKeys=1
CPU window

A CPU window (with full machine disassembly and register views) was formally introduced in Delphi 4, but it existed in Delphi 2 and 3 as well. However, the CPU window in Delphi 2 was very primitive, consisting solely of a disassembly view.

To make the View | CPU Window visible in Delphi 2 or 3, use the RegTweak.Ini section shown in Listing 10.

Listing 10: Enabling the CPU window in Delphi 2 or 3


[Software\Borland\Delphi\3.0\Debugging]
;Delphi 2.0 and 3.0
Type=S
EnableCPU=1
Attach to Process Menu

Whilst C++Builder 4 and later and also Delphi 5 and later have a documented menu item for attaching to a running process (which frankly works best under Windows NT), Delphi 4 has the same menu item available, but only after setting an undocumented registry entry.

With the entry (as described in Listing 11) enabled, a Run | Attach to Process... menu item will be visible the next time you start Delphi 4.

Listing 11: Enabling the Attach to Process menu item in Delphi 4


[Software\Borland\Delphi\4.0\Debugging]
;Delphi 4.0 only
Type=S
Enable Attach Menu=1
Editor default height/width

When the IDE starts a new project, it chooses a default editor width and height (unless a default desktop has been saved). If you want to specify a different default height and width for the editor, you can do so either by setting up some kind of saved desktop (either a default project desktop, or a global desktop in Delphi 5 or later) or by setting up a pair of registry entries in products earlier than Delphi 4.

As usual, a suitable section from RegTweak.Ini can be found in Listing 13, but a section from Delphi 1’s Delphi.Ini is also shown in Listing 12.

Listing 12: Setting a new default editor height and width for Delphi 1


[Editor]
DefaultHeight=614
DefaultWidth=805

Listing 13: Setting a new default editor height and width for 32-bit Delphi


[Software\Borland\Delphi\3.0\Editor]
;Delphi 2.0 and 3.0
Type=S
DefaultHeight=614
DefaultWidth=805

Component Template directory

If you are a big user of Component Templates (those reusable collections of components with custom properties and event handlers that were introduced in Delphi 3), you can direct the IDE into locating the file where they are stored elsewhere.

Component Templates are all stored in one file, whose name depends on the product and version you are using. Delphi 3 and 4 use Delphi32.DCT, but Delphi 5 and later use Delphi.DCT. C++Builder 3 uses BCB.DCT, but C++Builder 4 and later use C++Builder.DCT. Kylix uses delphi.dct.

By default, these files are in the corresponding product’s BIN directory, apart from Kylix, which stores it in ~/.borland. If you wanted to share one of these files among several developers, you might want to locate the file on a network drive. Listing 14 shows a RegTweak.Ini section that will do it in Delphi 4 and later (Delphi 3 and C++Builder 3 endeavoured to support this feature but it was badly implemented and did not work).

Listing 14: Specifying a new location for component templates


[Software\Borland\Delphi\5.0\Component Templates]
;Delphi 4.0 and later
Type=S
CCLibDir=C:\Shared
Personal settings directory

The final setting in this section is the personal settings directory. This setting is intended for use when Delphi or C++Builder is installed on a network and there is more than one person using it, or on a single machine with several people logging in and using it.

Under normal circumstances, each person that used Delphi would update the single set of files in Delphi’s BIN directory. In order for each person’s preferences to be maintained, they can create a personal settings directory under the main Delphi directory.

Then the appropriate registry (or INI file) entry can be made to point towards this directory (see Listing 15 and Listing 16).

Listing 15: Specifying a personal settings directory in Delphi 1


[Globals]
PrivateDir=c:\Delphi\User1

Listing 16: Setting a personal settings directory in 32-bit Delphi


[Software\Borland\Delphi\5.0\Globals]
;Delphi 2.0 and later
Type=S
PrivateDir=C:\Delphi5.0\User1

Each personal settings directory should have the appropriate files from Table 12 copied into it from the relevant product’s BIN directory, if they exist (many of them won’t). The files will then be used from your private directory. You can also get Component Templates stored in this directory with the previous registry entry described above.

Note that Kylix also supports this setting in its configuration file, much like Delphi 1. However, there is almost no point using it, since Kylix makes a personal directory for storing your settings in anyway (~/.borland).

Table 12: Files for the personal settings directory

File Product Purpose
defproj.opt Delphi 1 only Default project options for the IDE
delphi.dmt Delphi 1 only The file used to store menu templates
delphi.dsk Delphi 1 only The default project desktop file
delphi.hdx Delphi 1 and 2 only The MultiHelp index file
defproj.dof Delphi 2 and later Default project options for the IDE
delphi32.dmt Delphi 2 and later The file used to store menu templates
delphi32.dsk Delphi 2 and later The default project desktop file
delphi32.dci Delphi 3 and later The text file used to store Code Templates
defproj.cfg Delphi 4 and later Default project options for the command-line compiler
oh.exe Delphi/C++Builder 4 and later The OpenHelp executable
oh.ini Delphi/C++Builder 4 and later The OpenHelp settings file
bcb.dmt C++Builder 1 and later The file used to store menu templates
bcb.dsk C++Builder 1 and later The default project desktop file
default.bpr C++Builder 1 and later Default project file
ilink32.dll C++Builder 1 and later Incremental linker
openhelp.exe C++Builder 3 only The OpenHelp executable
openhelp.ini C++Builder 3 only The OpenHelp settings file
bcb.dci C++Builder 3 and later The text file used to store Code Templates
bcb.bcf C++Builder 5 and later Editor formatting configuration file
convrtrs.txt C++Builder 5 and later XML converter configuration
default.bmk C++Builder 5 and later Default application makefile
deflib.bmk C++Builder 5 and later Default library makefile
ibm-1252.cnv C++Builder 5 and later XML converter
defproj.conf Kylix 1 and later Default project options for the command-line compiler
defproj.kof Kylix 1 and later Default project options for the IDE
delphi.dct Kylix 1 and later The Component Templates file
delphi.desk Kylix 1 and later The default project desktop file
delphi60dci Kylix 1 and later The text file used to store Code Templates
delphi60dmt Kylix 1 and later The file used to store menu templates
delphi60dro Kylix 1 and later The Object Repository settings
*.dst Delphi/C++Builder 5 and later, Kylix 1 and later Global desktop files

Note that Code Templates were introduced in Delphi 3 and are invoked by Ctrl+J They are edited on the Code Insight page of the environment options dialog in Delphi 3 and 4, or of the editor options dialog in Delphi 5 and later. They allow you to insert common snippets of code straight into the editor from a popup list. More information on Code Insight can be found earlier.

Also note that menu templates are available from the Menu Designer’s context menu. You can save common menu layouts and retrieve them when designing other menus.

This particular registry entry is not strictly undocumented, as it is mentioned in an Open Tools API source file. The comments preceding the TPropertyEditor class in the DsgnIntf.pas unit describe this registry entry when explaining the PrivateDirectory property. The online help for the PrivateDirectory property also describes the setting.

IDE internal command lines

You can ask C++Builder 5 and later to show you all the command-line options it uses when compiling each file and linking each project. Listing 18 shows a RegTweak.Ini section that does the job.

Listing 17: Asking C++Builder to show options used for compiling/linking


[Software\Borland\C++Builder\5.0\Compiling]
;C++Builder 5.0 and later
Type=S
ShowCommandLine=1

IDE Command-line switches

The IDEs support a number of command-line switches. The release of Delphi 5 and C++Builder 5 documented a number of them, and added many new ones. For those people with earlier versions, Table 13 shows a list of what is available. Note that these command-line switches are case-insensitive and can be prefixed with either or /.

Table 13: Undocumented command-line switches

ns Delphi 2 and later. No splash screen. This suppresses display of the splash screen during IDE startup.
hm Delphi 2 and later. Heap Monitor. Displays information in the IDE title bar regarding the amount of memory allocated using the memory manager. Displays the number of blocks and bytes allocated (visible in Figure 1). Information gets updated when the IDE is idle.
hv Delphi 3 and later. Heap Verify. Performs validation of memory allocated using the memory manager. Displays error information in the IDE title bar if errors are found in the heap.
attach Delphi 4 and later. Attach to running process. This command-line is used to make Delphi 4 a JIT debugger on Windows 95/98/NT.

Note that the heap verification is performed through a call to the RTL routine GetHeapStatus. The error codes are described in the RTL include file GETMEM.INC.

Also note that you can set up the IDE as a JIT debugger in the registry under \HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\AeDebug by setting the Debugger value to C:\Delphi 4.0\Delphi32.Exe /Attach:%ld, specifying the appropriate path and version of the IDE. Later versions of the IDE check this value on startup, and offer to set it for you, unless you have previously told them not to.

The Object Inspector

Following Linked Components

When an object reference property is connected to some component (such as the ActiveControl property connected to an edit control) you can have the Object Inspector inspect the connected component by holding down the Ctrl key and double-clicking the property value.

Toggling Displayed Categories

A feature introduced in Delphi 5 was that of property categories. The Object Inspector can be told to show or hide any category you choose by selecting sub-items beneath View on its context menu.

Selecting a category will toggle it from being displayed to hidden and back again. There are also options for viewing all categories, no categories or toggling the state of all categories.

The undocumented aspect of the Object Inspector is that you can hold the Ctrl key down whilst selecting any category. This causes that single category to be displayed, whilst hiding all the rest of them.

Enhancing The IDE From Within

If you find some aspect of the IDE that you want to change, it might be possible to do it with less effort than you anticipated.

A unit contained in a design-time package loaded by the IDE has direct access to the IDE’s internals. Because of the way packages work, if your package is loaded into the IDE, when you refer to Application, you get the IDE’s Application object. If you refer to the Application object’s MainForm property, you get the IDE’s main form (the window with the menus, toolbars and component palette).

Consequently, if you know the name of an object in the IDE, you can locate it with prudent use of FindComponent, and change its properties.

As an example, here are a few simple things that we might choose to do:

Listing 18 shows a simple Pascal unit, IDETweak.Pas that can be added to a fresh package and installed into either the Delphi IDE or the C++Builder. The initialisation section of the unit sets up all the changes in the list above, and the finalisation section restores things to how they were.

When the IDE loads the package, the initialisation section of the unit will be executed. When the package is unloaded, the finalisation section executes to tidy things up.

Listing 18: Customising the IDE from within


unit IDETweakU;

interface

implementation

uses
  Forms, Graphics, Controls, ComCtrls;

var
  OldHintHidePause: Integer;
  OldHintColor: TColor;
  OldTitle: String;

var
  OI: TCustomForm;
  OldOICaption: TCaption;
const
  OIName = 'PropertyInspector';

var
  OITabCtl: TTabControl;
  OldOIPropCap, OldOIEventCap: String;
const
  OITabCtlName = 'TabControl';

initialization
  //Make tooltips last for 8 seconds
  OldHintHidePause := Application.HintHidePause;
  Application.HintHidePause := 8000;

  //Change tooltip to "pleasant colour"
  OldHintColor := Application.HintColor;
  Application.HintColor := clLime;

  //Change task bar button/IDE caption
  OldTitle := Application.Title;
  Application.Title := 'Brian''s ' + OldTitle + ' IDE';

  //Locate Object Inspector
  OI := Application.MainForm.FindComponent(OIName) as TCustomForm;
  if Assigned(OI) then
  begin
    //Change caption
    OldOICaption := OI.Caption;
    OI.Caption := 'Published Property Inspector';
    //Locate Object Inspector's tab control
    OITabCtl := OI.FindComponent(OITabCtlName) as TTabControl;
    if Assigned(OITabCtl) then
    begin
      //Change tab captions
      OldOIPropCap := OITabCtl.Tabs[0];
      OITabCtl.Tabs[0] := 'Data Properties';
      OldOIEventCap := OITabCtl.Tabs[1];
      OITabCtl.Tabs[1] := 'Code Properties';
    end
  end

finalization
  //Reset hint hide pause
  Application.HintHidePause := OldHintHidePause;

  //Reset hint colour
  Application.HintColor := OldHintColor;

  //Reset task bar button/IDE caption
  Application.Title := OldTitle;

  if Assigned(OI) then
  begin
    //Reset Object Inspector caption
    OI.Caption := OldOICaption;
    if Assigned(OITabCtl) then
    begin
      //Reset Object Inspector tab captions
      OITabCtl.Tabs[0] := OldOIPropCap;
      OITabCtl.Tabs[1] := OldOIEventCap;
    end
  end
end.

The first few statements are quite straightforward. A new value is given to the HintHidePause, HintColor and Title properties of the Application object. Figure 1 shows some of the effect these statements, with a different task bar button, a different IDE caption (which, in the case of the IDE, is based on the Title property of the Application object) and a lime green tooltip.

Figure 1: A customised IDE

After these statements, the code moves on to locate the Object Inspector. The code relies upon knowing that the Object Inspector form is called PropertyInspector and is owned by the IDE’s main form.

This information was gleaned from a VCL application analysis tool that accompanies this paper in the ObjectBrowser directory. Just add the AddInIDEU.pas unit into a package and install it. This will add a new Add-In menu onto the menu bar that allows you to explore all the objects in the IDE.

Having used this information, as well as more information regarding the components on the Object Inspector, the code changes the Object Inspector’s caption, and also the captions of its two tabs (see Figure 2). All of these steps are undone in the unit’s finalisation section.

Figure 2: A customised Object Inspector

Whilst these IDE tweaks are quite minor, you should be able to clearly see that by writing code in design-time package units, you can hook into the IDE at almost any level, and change things as you like.

Language Tips

Introduction

Sometimes, when you are trying to implement some logic, what you write just doesn’t seem tidy enough. This section shows a few alternatives to some common, cumbersome expression types.

Logical assignments

The ObjectPascal expression:


if X then
  Y := True
else
  Y := False

can be rewritten as:


Y := X

In C++, the expression:


if (X)
  Y = true;
else
  Y = false;

can be rewritten as:


Y = X;

Of course, the inverse is also true. The Pascal expression:


if X then
  Y := False
else
  Y := True

can be rewritten as:


Y := not X

Similarly, the C++ expression:


if (X)
  Y = false;
else
  Y = true;

can be rewritten as:


Y = !X;

In both these cases, X need not be a Boolean variable, but can be any Boolean expression. For example, Listing 19 can be rewritten as Listing 20. The same holds true in C++ as well.

Listing 19: Enabling a button using a conditional statement


procedure TForm1.edtTextEntryChange(Sender: TObject);
begin
  if Length( Trim( ( Sender as TCustomEdit ).Text ) ) = 0 then
    btnAddTextToList.Enabled := False
  else
    btnAddTextToList.Enabled := True
end;

Listing 20: Enabling a button using a logical assignment


procedure TForm1.edtTextEntryChange(Sender: TObject);
begin
  btnAddTextToList.Enabled := Length( Trim( ( Sender as TCustomEdit ).Text ) ) <> 0
end;

Logical Array Indices in ObjectPascal

Arrays in ObjectPascal need not be indexed by numbers. In fact, an array variable can be indexed with any subrange type you like. When you define an array as:


var
  MyArray[ 1..10 ] of Byte;

the index expression 1..10 is a subrange. A subrange can be made up from any ordinal type, such as bytes, characters or enumerated type values. Additionally, False..True is a valid subrange. In fact, the Boolean type is defined in terms of the subrange False..True. Consequently, you can insert the Boolean type between the square brackets when defining a type, as in:


var
  MyArray[ Boolean ] of String;

Knowing this, and also knowing about how to set up typed constants, allows us to rewrite code like Listing 21 as it is shown in Listing 22

Listing 21: Writing some text based on a condition


procedure TForm1.UpdateUI;
begin
  if Length( Trim( edtTextEntry.Text ) ) = 0 then
  begin
    btnAddTextToList.Enabled := False;
    Bar.SimpleText := 'No text to add';
  end
  else
  begin
    btnAddTextToList.Enabled := True;
    Bar.SimpleText := 'You can add the text';
  end
end;

Listing 22: Extracting text from an array with logical indices


procedure TForm1.UpdateUI;
const
  Desc: array[ Boolean ] of String = ( 'No text to add', 'You can add the text' );
begin
  btnAddTextToList.Enabled := Length( Trim( edtTextEntry.Text ) ) <> 0;
  Bar.SimpleText := Desc[ btnAddTextToList.Enabled ];
end;

Sets and Cases for Cumbersome ObjectPascal Conditionals

Sometimes, the Boolean expression that must be evaluated for a conditional to proceed is cumbersome, due to the number of possibilities you are testing for.

If you are checking one variable against a number of different ordinal values, you can simplify things with sets or case statements. If the values you are checking for have ordinalities less than 256 you can use a set, otherwise you can use a case statement.

Note that the Ord function returns the ordinality of any ordinal type member. It is the numeric machine representation of that ordinal value. For example the ordinality of the character 'A' is 65.

Listing 23 is an example of an OnKeyDown event handler that could do with some simplification. Since all those virtual key code constants have values less than 256, we can use sets to change it to Listing 24.

Listing 23: A cumbersome conditional statement


procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  if (Key = vk_Up) or (Key = vk_Down) or
     (Key = vk_Left) or (Key = vk_Right) then
  begin
    Bar.SimpleText := 'Cursor key';
    Key := 0;
  end
  else
  if (Key = vk_Prior) or (Key = vk_Next) then
  begin
    Bar.SimpleText := 'Page movement key';
    Key := 0;
  end
  else
    Bar.SimpleText := 'Miscellaneous key'
end;

Listing 24: Using sets to simplify a cumbersome conditional statement


procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  if Key in [ vk_Up, vk_Down, vk_Left, vk_Right ] then
  begin
    Bar.SimpleText := 'Cursor key';
    Key := 0;
  end
  else
  if Key in [ vk_Prior, vk_Next ] then
  begin
    Bar.SimpleText := 'Page movement key';
    Key := 0;
  end
  else
    Bar.SimpleText := 'Miscellaneous key'
end;

The first four virtual key codes are defined with consecutive values in the Windows unit:


const
  VK_LEFT = 37;
  VK_UP = 38;
  VK_RIGHT = 39;
  VK_DOWN = 40;

Consequently, it could again be re-written as Listing 25.

Listing 25: Using subranges to help simplify sets


procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  if Key in [ vk_Left..vk_Down ] then
  begin
    Bar.SimpleText := 'Cursor key';
    Key := 0;
  end
  else
  if Key in [ vk_Prior, vk_Next ] then
  begin
    Bar.SimpleText := 'Page movement key';
    Key := 0;
  end
  else
    Bar.SimpleText := 'Miscellaneous key'
end;

As an alternative, we could turn the whole conditional expression into a case statement. If any of the constants had values larger than 255, this would have been a necessity anyway. An equivalent piece of logic would look like Listing 26

Listing 26: Using a case statement to simplify a conditional statement


procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  case Key of
   vk_Left..vk_Down:
    begin
      Bar.SimpleText := 'Cursor key';
      Key := 0;
    end;
    vk_Prior, vk_Next:
    begin
      Bar.SimpleText := 'Page movement key';
      Key := 0;
    end
    else
      Bar.SimpleText := 'Miscellaneous key'
  end
end;

Listing 27 shows another (slightly) more involved example that can be rewritten using a set, as shown in Listing 28.

Listing 27: Another cumbersome conditional statement


procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);
begin
  if ((Key > 'a') and (Key < 'z')) or
     ((Key > 'A') and (Key < 'Z')) or
     ((Key > '0') and (Key < '9')) then
  begin
    Bar.SimpleText := 'Alphanumeric key';
    Key := #0
  end
end;

Listing 28: Another set simplifying a conditional statement


procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);
begin
  if Key in [ 'a'..'z', 'A'..'Z', '0'..'9' ] then
  begin
    Bar.SimpleText := 'Alphanumeric key';
    Key := #0
  end
end;

It could also be rewritten using a case statement, as before.

ObjectPascal Access classes for protected members

Sometimes, you need access to the protected members of an object that is defined in some unit somewhere, but you will only be allowed access to the public and published members, as per the rules of the language.

However, if you examine the definition of the protected section, you will see that anything in the same unit as a class can access the protected members (similar to friend classes in C++).

Consequently, you can define a new class that inherits from the class in question, but adds nothing to it. This class can be used to typecast the object whose protected members you seek, and so the compiler will then give you access to them.

You can find many definitions of these shallow access classes or hack classes spread across the VCL source, including the Forms, VCLCOM, AxCtrls, ComCtrls, DBCtrls, and ExtCtrls units . In Delphi 3 or later, you could choose Search | Find in Files... (or use a GREP-like tool in earlier versions) and search through the VCL source for the string: Access = class(T.

For example, in the 32-bit DBCtrls unit, there is an access class defined as follows:


type
  TWinControlAccess = class( TWinControl );

This is then used by the TPaintControl class to call the protected CreateParams method of its owner (a TWinControl):


TWinControlAccess( FOwner ).CreateParams( Params );

A variation on this theme is to define a new class with the same name as your target class at the top of the unit’s type section. This can be done by fully qualifying the reference to the original class and removes the need for the typecast. As far as the compiler is concerned, you have access to the protected members of all objects defined in terms of that class.


type
  TWinControl = class( Controls.TWinControl );

This is called an interposer class, discussed by Stephen Posey in Issue 33 of The Delphi Magazine, May 1998.

Hacking for private members in ObjectPascal

Getting access to the protected section is one thing, but what about the private section? Well, it seems that you can also accomplish this, although in a much less resilient fashion than might be desired.

It relies on you knowing how many data fields precede your target private data field, and how many bytes they take up. You add this to the instance size of the ancestor class, and that gives the offset from the start of the object’s instance data at which the field lies. This technique is used by a method of a class to access inherited private data by Steve Teixeira in Issue 42 of The Delphi Magazine, February 1999.

Assuming you have the source for the original class, this is just tedious, but of course you need to re-check everything as you move from product version to product version.

Listing 29 shows a function that takes three parameters. The first is an object reference for the object whose private data field you want. The second is a class reference to the class that defines the private data field (which might be one of your object’s ancestor types). The last parameter is the byte offset that the private data field resides at, in terms of the class that defines it.

Listing 29: A function that will access private members


function GetPrivateField(Obj: TObject; Cls: TClass; DataOffset: Cardinal): Pointer;
var
  ParentInstanceSize: Cardinal;
begin
  //Get parent instance data size and add on the VMT pointer
  ParentInstanceSize := Cls.ClassParent.InstanceSize + 4;
  Result := Pointer( Cardinal( Obj ) + ParentInstanceSize + DataOffset )
end;

As a massively academic example, consider the ActiveControl property of a form. This property surfaces the private FActiveControl object reference data field, defined to be of type TWinControl. If we forget about the property being available, we can concentrate on trying to read this private data field directly.

TCustomForm defines the FActiveControl data field as the first field (offset 0). To access this field, you can use this call.


var
  Ctl: TWinControl;
...
  Ctl := TWinControl( GetPrivateField( Self, TCustomForm, 0 )^ );

Characters in ObjectPascal strings

This is just a small point, but may save the odd key press here and there. In order to embed arbitrary characters in strings, it is typical to see code that looks like this:


ShowMessage('An error occurred:' + #13 + #13 + Msg)

Whilst technically, there is nothing wrong with that statement, it can be written a tad shorter by removing the concatenations:


ShowMessage('An error occurred:'#13#13 + Msg)

Character constants can be embedded into strings by closing the string with an apostrophe, then immediately following it with the appropriate character constant.

Abbreviated ObjectPascal component creation

When dynamically creating components, you typically declare an object reference that will be used to hold the object’s address. This can then be used to access the methods and properties of the object.

Sometimes, though, the object reference can be eliminated if the object is destroyed immediately after use, and is not referred to (by the object reference variable) from anywhere else.

For example, the code in Listing 30 is a form’s OnCreate event handler that reads a query string from the Windows registry. To alleviate the object reference variable (solely to shorten the code, really), you could possibly use Listing 31 instead.

Listing 30: Using a TRegistry component


const
  RegPath = 'Software\ACME\QueryApp';
  RegEntry = 'Last Query';

procedure TForm1.FormCreate(Sender: TObject);
var
  Reg: TRegistry;
  NewSQL: String;
begin
  Reg := TRegistry.Create;
  try
    Reg.RootKey := HKEY_CURRENT_USER;
    Reg.OpenKey( RegPath, False );
    NewSQL := Reg.ReadString( RegEntry );
    if NewSQL <> '' then
      Query1.SQL.Text := NewSQL
  finally
    Reg.Free
  end
end;

Listing 31: A TRegistry component with no object reference variable


procedure TForm1.FormCreate(Sender: TObject);
var
  NewSQL: String;
begin
  with TRegistry.Create do
    try
      RootKey := HKEY_CURRENT_USER;
      OpenKey( RegPath, False );
      NewSQL := ReadString( RegEntry );
      if NewSQL <> '' then
        Query1.SQL.Text := NewSQL
    finally
      Free
    end
end;

The idea is to construct the object, then enter its scope rather than assign its address to an object reference variable. Whilst in its scope, you can access all its properties and methods directly.

RTL/VCL Tips

Guidelines

There are not many hard and fast rules with programming Delphi, Kylix and C++Builder, over those that govern whether the compiler will successfully compile your code. Here are some of the few rules that some developers abide by:

Use The Help

Whenever you need to accomplish something and you are looking for some useful routines in the RTL or VCL, use the help system. Many people are put off it by the scare stories of it being full of errors and lacking in generally helpful information. Don’t be. Whatever it may be, it is certainly a rich source of information.

Generate a full text search index with the Find tab. Be consistent in checking the See Also links on any help page you look at. Use the Browse Sequence buttons (the ones with << and >> captions), when they are present. All these things will open up avenues to things you have not encountered before.

Use The Source, Luke

This is a phrase placed in print by Danny Thorpe in his Delphi Component Design book (out of print) that is quite popular among accomplished Delphi developers. Of course, it is a play on a famous line from a famous film, but the idea is to emphasise the fact that Delphi is a 3rd Generation Language, with all the power, flexibility and open nature of most 3GLs.

Part of this open nature is the fact that Delphi, C++Builder and Kylix come with full source code to the Run-Time Library and to practically the entire Component Library. This collection of source files makes for an excellent educational resource.

As you develop confidence with your chosen product, you should spend more and more of your time browsing these source files, found in the product’s Source subdirectory tree. They can help you gain an understanding as to how the VCL/CLX is organised, how professional components are written and also to the mass of available variables, constants and routines that have not quite made it into the help.

You can find an investigation into a variety of under-used things found in the source in some of my other papers. My DCon ’99 paper, VCL Sourcery, can be found at http://www.blong.com/Conferences/DCon99/VCLSourcery/VCLSourcery.htm. My DCon 2000 pre-conference tutorial paper, Deep Sea Fishing, can be found at http://www.blong.com/Conferences/DCon2000/DeepSeaFishing/Fishing.htm. Depending on time left at this point in the presentation, some of the more immediately useful points from these papers may be briefly mentioned.

Summary

This paper has looked at the Delphi, C++Builder and Kylix IDEs and highlighted various features (both documented and undocumented) which slip past many developers. Hopefully, everyone who reads this paper will learn something new about their preferred development tool.

About Brian Long

Brian Long used to work at Borland UK, performing a number of duties including Technical Support on all the programming tools. Since leaving in 1995, Brian has spent the intervening years as a trainer, trouble-shooter and mentor focusing on the use of the C#, Delphi and C++ languages, and of the Win32 and .NET platforms. In his spare time Brian actively researches and employs strategies for the convenient identification, isolation and removal of malware. If you need training in these areas or need solutions to problems you have with them, please get in touch or visit Brian's Web site.

Brian authored a Borland Pascal problem-solving book in 1994 and occasionally acts as a Technical Editor for Wiley (previously Sybex); he was the Technical Editor for Mastering Delphi 7 and Mastering Delphi 2005 and also contributed a chapter to Delphi for .NET Developer Guide. Brian is a regular columnist in The Delphi Magazine and has had numerous articles published in Developer's Review, Computing, Delphi Developer's Journal and EXE Magazine. He was nominated for the Spirit of Delphi award in 2000.