This page is still  

General

Avoid concatenation of strings, because it's rather slow!

This is how you should *NOT* implement string functions:

{********************************************* 
 replace char ch1 by ch2 
 this function is slow because concatenation 
 of strings ("Result := Result + ch2") is slow 
*********************************************} 
function ReplaceCharSlow (const s: string; 
  ch1: char; ch2: char): string; 
var i: integer; 
begin 
  Result := ''''; 
  for i := 1 to length (s) do 
    if s [i] = ch1 then 
      Result := Result + ch2 
    else 
      Result := Result + s [i]; 
end; 
You can avoid string concatenation as well as the else branch as follows:
{********************************************* 
 replace char ch1 by ch2, about 160 times
 faster (tested by calling the function 
 10,000 times with a 1,000-byte string)
*********************************************} 
function ReplaceCharFast (const s: string; 
  ch1: char; ch2: char): string; 
var i: integer; 
begin 
  Result := s; 
  for i := 1 to length (Result) do 
    if Result [i] = ch1 then 
      Result [i] := ch2; 
end; 

The SysUtils function AnsiUpperCase is rather slow because it uses a Windows API call (CharUpperBuff).

To speed-up this, simply use an array initially filled with ANSI upper characters and write your own function ConvertToANSIUpper (it is about 3.5 times faster, tested by converting a 1,000-byte-string 10,000 times):

var ANSIUpper: array [char] of char; 

{******************************************************** 
 fill table with ANSI upper characters because 
 execution of ANSIUppercase is very slow 
*********************************************************} 
procedure FillANSIUpper; 
var ch: char; 
begin 
  for ch := Low (char) to High (char) do 
    ANSIUpper [ch] := AnsiUpperCase (ch) [1]; 
end; 

{******************************************************** 
 convert string to ANSI uppercase 
 about 3.5 times faster than 
 ANSIUppercase function 
********************************************************} 
function ConvertToANSIUpper (const s: string): string; 
var i: integer; 
begin 
  Result := s; 
  for i := 1 to length (Result) do 
    Result [i] := ANSIUpper [Result [i]]; 
end; 

initialization 
  FillANSIUpper; 

Files

Get the short file name (8 + 3) from a Win32 long file name
function ShortFileName (const FileName: string): string;
var aTmp: array[0..255] of char;
begin
  if not FileExists (FileName) then
    Result := ''
  else if GetShortPathName (PChar (FileName), aTmp, Sizeof (aTmp) - 1) = 0
  then
    Result:= FileName
  else
    Result:= StrPas (aTmp);
end;

Get the long file name from a shortened (8 + 3) file name

function LongFileName (ShortName: string): string;
var SR: TSearchRec;
begin
  Result := '';
    if (pos ('\\', ShortName) + pos ('*', ShortName) +
    pos ('?', ShortName) <> 0) or not FileExists (ShortName)
  then
    { ignore NetBIOS name, joker chars and invalid file names }
    Exit;
  while FindFirst (ShortName, faAnyFile, SR) = 0 do begin
    { next part as prefix }
    Result := '\' + SR.Name + Result;
    SysUtils.FindClose (SR);  { the SysUtils, not the WinProcs procedure! }
    { directory up (cut before '\') }
    ShortName := ExtractFileDir (ShortName);
    if length (ShortName) <= 2 then
      Break;  { ShortName contains drive letter followed by ':' }
  end;
  Result := ExtractFileDrive (ShortName) + Result;
end;

Create all directories needed for a new path at once

Suppose you want to create a new path C:\APPS\SALES\LOCAL, but the directory C:\APPS is not yet existing. Using the procedure ForceDirectories in unit FileCtrl, you can create the whole path at once.
Example:

var
  Dir: string;
  (...)
  Dir := 'C:\APPS\SALES\LOCAL';
  ForceDirectories(Dir);
  if DirectoryExists(Dir) then
    Label1.Caption := Dir + ' successfully created.'

Dialog for selecting directory

Use the function SelectDirectory in unit FileCtrl. Look in the online help for more information.
Example:

var
  Dir: string;
  (...)
  Dir := 'C:\Windows';
  if SelectDirectory(Dir, [sdAllowCreate, sdPerformCreate, sdPrompt], 0) then
    Label1.Caption := Dir;

Help Files

Display the help contents in 32 bit applications
Place in the OnClick event of the menu item the following code:
Application.HelpCommand (HELP_FINDER, 0);

Implement your own "What's this" button 
Place in the OnClick event of the button the following code:
PostMessage (Handle, WM_SYSCOMMAND, SC_CONTEXTHELP, 0);


IDE

Keystroke recording and playing back Note: This only works with default keymapping. To find out what keymapping you are using, click Environment | Options in the Tools menu. In the Editor Tab, you will see your key mapping in the Editor Speedsetting Combobox.

Code markers


Mark a rectangular block

First method

Second method

What does the blue dots at the left mean?
After compiling a project, a small blue dot in the left hand gutter is indicating a "living" line, i.e. a line not removed by the optimizer. Otherwise, the line is "dead", i.e. removed (go to this line will not work in debugger). This feature is not available in DELPHI 1/2.


Problems with debugger caused by wrong terminated lines
During debugging a unit containing dozens of utility procedures and functions, I recently noticed a strange effect. When tracing into the code of some (not all!) procedures in this unit, the debugger did not display the actually executed line (there was always a difference of 11 lines). This happened using both DELPHI 2 and 4. When I activated the CPU window, I additionally noticed some comment lines in the disassembly pane. Finally using a hex editor, it turned out that there were exactly 11 lines terminated only by $0A instead of $0D$0A (CR/LF). Reason: I downloaded a file containing C code from the web and pasted some lines from this file into my unit. Obviously, it was a UNIX file.
Download project demonstrating this effect (3 KB)
Download application to convert UNIX files to Windows format (executable only, 159 KB)
Download source code for application to convert UNIX files to Windows format (4 KB)


Setting the default project directory
Right click on the Windows shortcut icon used to start Delphi. Select properties from the menu, and change the default working directory the program starts in (e.g. C:\MyProjects). Unfortunately, this doesn't work under Windows 98. Workaround for Windows 98:


OOP

Access protected class properties

Using TList
The TList class is used to hold (pointers to) objects inherited from TObject. The number of those objects can be defined at runtime. In our example, we declare in step 1 a class for the items you want to add to the list. Our items consist of a string and an integer property:

{ type for items that will be added to a TList instance }
type
  TMyListItem = class (TObject)
    s: string;
    i: integer;
  end;
Suppose, on Form1 are placed ListBox1, SpinEdit1, and Button1. SpinEdit1 determines the number of TMyListItem items added to the list when Button1 is clicked. Then, the list is filled with items which are displayed in ListBox1. Implement the following OnClick event for Button1:
procedure TForm1.Button1Click(Sender: TObject);
var List: TList;
    j: integer;
begin
  { create the List }
  List := TList.Create;
  for j := 1 to SpinEdit1.Value do begin
    { add new items to the list without using an item instance, see note 1 below }
    List.Add (TMyListItem.Create);
    with TMyListItem (List.Items [j - 1]) do begin
      { use type-casting to set values for previously added item
        (normally, you should use methods for setting values) }
      s := 's' + IntToStr (j);
      i := j;
    end;
  end;
  { fill listbox with items }
  ListBox1.Items.Clear;
  for j := 0 to List.Count - 1 do
    ListBox1.Items.Add (TMyListItem (List.Items [j]).s + ', i = ' +
      IntToStr (TMyListItem (List.Items [j]).i));
  { free each item, see note 2 below }
  for j := 0 to List.Count - 1 do
    { use type-casting to free each item }
    TMyListItem (List.Items [j]).Free;
  { free the list }
  List.Free;
end;

Notes:
  1. No variables are declared to hold TMyListItem instances, because the number of items is known only at runtime.
  2. Because TList is not aware of the items' class type, the Free method of TList can't remove the objects from memory. We have to free each item properly using the appropriate type-casting before we free the list itself.

VCL

Provide a horizontal scrollbar for a list box
{***********************************************************************
 Listbox with horizontal scrollbar
 MaxWidth < 0 : adapt automatically to largest item
 MaxWidth >= 0: use value of MaxWidth for width of scrollable area
 ***********************************************************************}
procedure HorScrollBar (ListBox: TListBox; MaxWidth: integer);
var i, w: integer;
begin
  if MaxWidth >= 0 then
    SendMessage(ListBox.Handle, LB_SETHORIZONTALEXTENT, MaxWidth, 0)
  else begin
    { get largest item }
    for i := 0 to ListBox.Items.Count - 1 do with ListBox do begin
      w := Canvas.TextWidth (Items [i]);
      if w > MaxWidth then
        MaxWidth := w;
     end;
    SendMessage(ListBox.Handle, LB_SETHORIZONTALEXTENT,
      MaxWidth + GetSystemMetrics (SM_CXFRAME), 0);
  end;
end;

Center a form on screen at runtime

procedure CenterForm (AForm: TForm);
var ALeft, ATop: integer;
begin
  ALeft := (Screen.Width - AForm.Width) div 2;
  ATop  := (Screen.Height - AForm.Height) div 2;
  { prevents form being twice repainted! }
  AForm.SetBounds (ALeft, ATop, AForm.Width, AForm.Height);
end;

Center a form over active form at runtime

procedure CenterFormOverActive (AForm: TForm);
var ALeft, ATop: integer;
begin
  ALeft := Screen.ActiveForm.Left +
   (Screen.ActiveForm.Width div 2) - (AForm.Width div 2);
  ATop := Screen.ActiveForm.Top +
   (Screen.ActiveForm.Height div 2) - (AForm.Height div 2);
  { prevent form from being outside screen }
  if ALeft < 0 then
    ALeft := Screen.ActiveForm.Left;
  if ATop < 0 then
    ATop := Screen.ActiveForm.Top;
  if (ALeft + AForm.Width > Screen.Width) or
    (ATop + AForm.Height > Screen.Height)
  then
    CenterForm (AForm)
  else
    { prevents form being twice repainted! }
    AForm.SetBounds (ALeft, ATop, AForm.Width, AForm.Height);
end;

Prevent labels from being cut
When switching between small fonts and large fonts, the AutoSize property of TLabel components does not work properly. As work-around, call the following procedure within the form's OnCreate event:

procedure LabelsAutoSizeCorrect (Form: TForm);
var i: integer;
    Component: TComponent;
begin
  for i := 0 to Form.ComponentCount - 1 do begin
    Component := Form.Components [i];
    if (Component is TLabel) and TLabel (Component).AutoSize then begin
      TLabel (Component).AutoSize := false;
      TLabel (Component).AutoSize := true;
    end;
  end;
end;

Determine if mouse cursor is over a component at runtime

{***********************************************************************
 is mouse cursor over Control?
 ***********************************************************************}
function IsMouseOver (Control: TControl): boolean;
var p: TPoint;
begin
  { get absolute coordinates }
  if GetCursorPos (p) then begin
    { convert to relative coordinates of component }
    p := Control.ScreenToClient (p);
    { coordinates within Control? }
    Result := (p.x >= 0) and (p.x <= Control.Width) and
      (p.y >= 0) and (p.y <= Control.Height);
  end
  else
    { this should never happen, but who knows... }
    Result := false;
end;

Win32 API

Using EnumWindows

The following example lists the captions of all visible windows in a listbox:

{ list of all windows' handles }
var WindowList: TList;

{**********************************************************************
 callback function for EnumChildWindows called for each window
 **********************************************************************}
function GetWindow (Handle: HWND; LParam: longint): bool; stdcall;
begin
  Result := true;
  { add each handle to the list }
  WindowList.Add (Pointer(Handle));
end;

{**********************************************************************
 look for all visible windows
 **********************************************************************}
procedure TForm1.Button1Click(Sender: TObject);
var i: integer;
    Hnd: HWND;
    Buffer: array [0..255] of char;

begin

  try
    { initialize list }
    WindowList := TList.Create;
    { get all windows }
    EnumWindows (@GetWindow, 0);

    ListBox1.Items.Clear;

    for i := 0 to WindowList.Count - 1 do begin
      { handle of current list item }
      Hnd := HWND (WindowList [i]);
      if IsWindowVisible (Hnd) then begin
        GetWindowText (Hnd, Buffer, SizeOf (Buffer) - 1);
        if Buffer [0] <> #0 then
          { caption is not empty }
          ListBox1.Items.Add (StrPas (Buffer));
      end;
    end; { for i }

  finally
    WindowList.Free;
  end;

end;

Disabling screen saver temporarily
If you want to turn off the screen saver while your program is running, you don't have to disable the Windows screen saver at all. Just define the following method to handle the appropriate Windows message:

procedure TForm1.AppMessage (var Msg: TMsg; var Handled: boolean);
begin
  if (Msg.Message = WM_SYSCOMMAND) and (Msg.wParam = SC_SCREENSAVE) then
    Handled := true;
end;
On the form's OnCreate event, assign
Application.OnMessage := AppMessage;

Closing another application
To close e.g. Notepad, you simply send the message WM_CLOSE to the application's main window:

procedure TForm1.Button1Click(Sender: TObject);
var Hnd: THandle;
begin
  Hnd := FindWindow (PChar ('Notepad'), nil);
  if Hnd > 0 then
    SendMessage (Hnd, WM_CLOSE, 0, 0);
end;

Database

Not enough virtual memory While exporting a large database table (over 500,000 records) into a CSV file using a TQuery with a SQL statement like select * from table_orders I noticed an error after about 50,000 records ("not enough virtual memory, change settings in control panel"). I knew already that the result set received from the database server is cached by the BDE, but how to disable this feature? The solution: set the property TQuery.UniDirectional to True.

The online help says: Traditionally SQL cursors are unidirectional. They can travel only forward through a dataset. The BDE, however, permits bidirectional travel by caching records. If an application does not need bidirectional access to records in a result set, set UniDirectional to True. When UniDirectional is True, an application requires less memory and performance is improved.