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;
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;
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);
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
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:
File | Save Project As...
to save this project within the desired default directory,
e.g. as C:\MyProjects\Unit1.pas and C:\MyProjects\Project1.dpr
My Directory
twice)
"C:\Program Files\Borland\Delphi4\Bin\delphi32.exe" C:\MyProjects\Project1.dpr
type
TExposedWinControl = class (TWinControl);
if Sender is TWinControl then
TExposedWinControl(Sender).Caption := 'New Caption';
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;
{*********************************************************************** 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;
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;
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.