Athena

IDE/RTL/VCL/ObjectPascal Tips

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.


IDE Tips

Introduction

The Delphi IDE started life in 1995 as a well integrated environment for doing whatever was needed in order to design, write, compile and debug your application. However, it was always 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 one or two portions of editor functionality to everyone.

Click here to download the files that accompany this paper.

The Tool Bars

As you probably already know, the Delphi 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. In Delphi 2 and later, you can access these from the Properties item on the editor's context menu, or using the menu bar (see Table 1).

Table 1: 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 and 4

Tools | Environment Options..., Display, Keystroke mapping

Delphi 5

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

Table 2: The available keystroke mappings

Keystroke mapping

Description

Default

Follows CUA guidelines for many keystrokes

Classic

Uses the same keystrokes as the old Turbo Pascal/C editor

BRIEF

Emulates the BRIEF editor

Epsilon

Emulates the Epsilon editor

Visual Studio

Emulates the Visual Studio editor

New IDE Classic

Uses key bindings defined in a supplied demo project

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.

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 Delphi editor features are not classed in the Code Insight group, but are worthy of mention.

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.

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.

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.

Editor bookmarks

In any given Delphi 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 3 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 3: Bookmark operations

Keystroke mapping

Toggle bookmark

Go to bookmark

Default or Visual Studio

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

The prime disadvantage of Delphi's 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 4 shows the keys required for other keystroke mappings.

Table 4: Keyboard macro operations

Keystroke mapping

Start recording macro

Stop recording macro

Playback macro

Default

Shift+Ctrl+R

Shift+Ctrl+R

Shift+Ctrl+P

Classic

Shift+Ctrl+R

Shift+Ctrl+R

Shift+Ctrl+P

BRIEF

F7

F7

F8

Visual Studio

Ctrl+R

Ctrl+R

Ctrl+P

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

Table 5: Case-changing keystrokes

Keystroke mapping

Upper case

Lower case

Toggle case

Default and Classic

Ctrl+K+N

Ctrl+K+O

Ctrl+O+U

BRIEF

 

 

Ctrl+O+O

Visual Studio

Ctrl+K+F

Ctrl+K+E

 

Epsilon

Alt+U or Esc+U

Ctrl+I

 

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 6) will do the trick.

Table 6: Block indent and outdent keystrokes

Keystroke mapping

Block Indent

Block Outdent

Default and Visual Studio

Ctrl+Shift+I

Ctrl+Shift+U

Default, Classic and Visual Studio

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 7 shows how to find these).

Table 7: How to access general editor options

Delphi 1

Options | Environment..., Editor options

Delphi 2

Tools | Options..., Editor

Delphi 3 and 4

Tools | Environment Options..., Editor

Delphi 5

Tools | Editor Options..., General

Incremental search

Most Delphi 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 8 shows the keystrokes that start an incremental search.

Table 8: Incremental search

Keystroke mapping

Incremental search

Search again

Default

Ctrl+E

F3 or Ctrl+L

Classic

Shift+Ctrl+S

Ctrl+L

BRIEF

Ctrl+S

Shift+F5

Epsilon

Ctrl+S

 

Visual Studio

Ctrl+I

F3 or 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 Delphi into taking the word at the input cursor position in the editor and entering it as the default search term (though 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 7 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 7 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 9 shows the available keystrokes. Clearly, BRIEF and Epsilon do not offer as many of the combinations as the other keystroke mappings.

Table 9: Miscellaneous block operations

Keystroke mapping

Print marked block

Read file as block

Write block to file

Default, Classic and Visual Studio

Ctrl+K+P

Ctrl+K+R

Ctrl+K+W

BRIEF

Alt+P

Alt+R

 

Epsilon

 

 

Ctrl+X+W

Selecting different-shaped blocks of text

Whilst most Delphi 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.

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

Table 10: Marking different types of block

Keystroke mapping

Non-inclusive

Inclusive

Line

Column

Default, Classic and Visual Studio

Ctrl+O+K

Ctrl+O+I

Ctrl+O+L

Ctrl+O+C

BRIEF

Alt+A

Alt+M

Alt+L

Alt+C

Selecting multiple lines of text

Novice Delphi users 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, rather than manually extending the selection along the whole length of the line, simply press Shift+¯ . 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 sorting out the broken indentation.

Editor drag and drop

All versions of Delphi 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. 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, 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, 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. 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 eventually select the form.

Lasso operations on container components

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

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 there is a suggestion that the next version may have an option to globally disable IDE docking.

Undocumented Delphi

Easter Eggs

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

Registry Entries

In this session, we are going to look at functional things that are undocumented in Delphi. We will start with registry entries that can be useful.

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. 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, the features will be enabled. To disable them, change the values to 0.

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.

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.

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

Object Inspector property value colour

This one works in all values of Delphi. 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 Delphi, 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: Changing the property value colour in 32-bit Delphi

[Software\Borland\Delphi\5.0\Globals]
;Delphi 2.0 and later
Type=S
PropValueColor=clRed

IDE tooltip colour

Delphi 1, 2 and 3 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

[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 both Delphi 4 and 5.

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. 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 Delphi 5 (and later) has 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 Delphi 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.

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\5.0\Editor]
;Delphi 2.0 and later
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 which were introduced in Delphi 3), you can direct the IDE into locating the file where they are stored elsewhere.

Component Templates are stored in a file called Delphi32.DCT (Delphi 3 and 4) or Delphi.DCT (Delphi 5 and later) which by default is in Delphi's BIN directory. 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.

Listing 14: Specifying a new location for component templates

[Software\Borland\Delphi\5.0\Component Templates]
;Delphi 3.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 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 files listed in Table 11 copied into it from Delphi's main BIN directory, if they exist. Delphi will then continue to use this directory for those files. You can also get Delphi to store Component Templates in your personal settings directory with the previous registry entry described above.

Table 11: Files for the personal settings directory

DefProj.Cfg

Default project options for the command-line compiler

DefProj.Dof

Default project options for the IDE

Delphi32.Dci

The text file used to store Code Templates

Delphi32.Dmt

The file used to store menu templates

Delphi32.Dsk

The default project desktop file

OH.Exe

The OpenHelp executable

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 unit describe this registry entry when explaining the PrivateDirectory property. The online help for the PrivateDirectory property also describes the setting.

IDE Command-line switches

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

Table 12: 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. 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. Attach to running process. This command-line is used to make Delphi 4 a JIT debugger on Windows 95/98/NT.

The Object Inspector

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 Delphi's Application object. If you refer to Application.MainForm, you get Delphi's main form.

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 17 shows a simple unit, IDETweak.Pas that can be added to a fresh package and installed into the IDE. 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 17: 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 Application.Title) 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 expression:

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

can be rewritten as:

Y := X

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

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

can be rewritten as:

Y := not X

In both these cases, X need not be a Boolean variable, but can be any Boolean expression. For example, Listing 18 can be rewritten as Listing 19.

Listing 18: 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 19: 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

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 20 as it is shown in Listing 21

Listing 20: 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 21: 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 Conditionals

Sometimes, the Boolean expression that must be evaluated for a conditional to proceed is cumbersome, sue 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.

Listing 22 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 23.

Listing 22: 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 23: 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 24.

Listing 24: 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 25

Listing 25: 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 26 shows another (slightly) more involved example that can be rewritten using a set, as shown in Listing 27.

Listing 26: 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 27: 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.

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

Hack classes for private members

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.

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 28 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 28: 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 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 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 29 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 30 instead.

Listing 29: 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 30: 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 Delphi programming, over those that govern whether the compiler will successfully compile your code. Here are some of the few rules that some Delphi 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 comes with full source code to the Run-Time Library and to practically all of the Visual Component Library. Since the RTL and VCL are all written in Delphi (with occasional smatterings of assembler), this collection of source files makes for an excellent educational resource.

As you develop confidence with Delphi, you should spend more and more of your time browsing these source files, found below Delphi's Source subdirectory. They can help you gain an understanding as to how the VCL 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 and my DCon 2000 pre-conference tutorial paper, Deep Sea Fishing in Delphi (VCL secrets and the practical use of the Win32 API), 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.

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.

Click here to download the files that accompany this paper.