
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.