Delphi Tips
Delphi Tips
html#AppName
martinstoeckli.ch
(https://w w w .martinstoeckli.ch/delphi/delphi.html)
(https://w w w .martinstoeckli.ch/delphi/delphi_de.html)
Php tips
Delphi tips
(https://w w w .martinstoeckli.ch
/php/php.html)
Of ten programmers stumble across the same problems, sometimes depending on the
programming environment. To prevent myself f rom doing it more than once w ith the
Delphi tips
same problem, I decided to start this page w ith some code snippets and share it w ith
(https://w w w .martinstoeckli.ch
other peoples w ith the same interests. The f unctions are all f ree and tested and most
/delphi
of them are w ritten by myself . You can use them in your ow n applications, but this
/delphi.html)
shouldn't prevent you f rom understanding w hat the code does ;-).
If you should have problems, questions or suggestions about the f unctions below , or if
CSharp
you simply f ind them usef ul, don't hesitate to send me an email to
tips
[email protected] (mailto:[email protected]?subject=Homepage Delphi) .
(https://w w w .martinstoeckli.ch
/csharp
Overview
/csharp.html)
Application f unctions
Filename of the application (exe/dll)
Version of the application
Starting an external application
Starting console application and redirect stdoutput
Links to email and w ebsite
Command line parameters
Including external resources like a cursor
File & path f unctions
Of ten used f ile f unctions
Get long f ilename f rom a short DOS f ilename
Get the size of a f ile (more than 2 GB)
Directory brow se dialog
Moving f iles and directories to the recyclebin
Searching a directory
How to copy f ilenames f rom/to the clipboard
String f unctions
Of ten used string f unctions
Working w ith PChar
Using text resources of Window s
ColorToHtml / HtmlToColor
Classes and objects
Writing events
Index properties
Using w indow s messages
Delphi VCL
Coloring a StringGrid
1 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
Application functions
sFileName := Application.ExeName;
// or
sFileName := ParamStr(0);
Perhaps you are w orking on a DLL and are interested in the f ilename of the DLL rather
than the f ilename of the application, then you can use this f unction:
uses SysUtils;
2 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
/// <summary>
/// This function reads the file resource of "FileName" and returns
/// the version number as formatted text.</summary>
/// <example>
/// Sto_GetFmtFileVersion() = '4.13.128.0'
/// Sto_GetFmtFileVersion('', '%.2d-%.2d-%.2d') = '04-13-128'
/// </example>
/// <remarks>If "Fmt" is invalid, the function may raise an
/// EConvertError exception.</remarks>
/// <param name="FileName">Full path to exe or dll. If an empty
/// string is passed, the function uses the filename of the
/// running exe or dll.</param>
/// <param name="Fmt">Format string, you can use at most four integer
/// values.</param>
/// <returns>Formatted version number of file, '' if no version
/// resource found.</returns>
function Sto_GetFmtFileVersion(const FileName: String = '';
const Fmt: String = '%d.%d.%d.%d'): String;
var
sFileName: String;
iBufferSize: DWORD;
iDummy: DWORD;
pBuffer: Pointer;
pFileInfo: Pointer;
iVer: array[1..4] of Word;
begin
// set default value
Result := '';
// get filename of exe/dll if no filename is specified
sFileName := FileName;
if (sFileName = '') then
begin
// prepare buffer for path and terminating #0
SetLength(sFileName, MAX_PATH + 1);
SetLength(sFileName,
GetModuleFileName(hInstance, PChar(sFileName), MAX_PATH + 1));
end;
// get size of version info (0 if no version info exists)
iBufferSize := GetFileVersionInfoSize(PChar(sFileName), iDummy);
if (iBufferSize > 0) then
begin
GetMem(pBuffer, iBufferSize);
try
// get fixed file info (language independent)
GetFileVersionInfo(PChar(sFileName), 0, iBufferSize, pBuffer);
VerQueryValue(pBuffer, '\', pFileInfo, iDummy);
// read version blocks
iVer[1] := HiWord(PVSFixedFileInfo(pFileInfo)^.dwFileVersionMS);
iVer[2] := LoWord(PVSFixedFileInfo(pFileInfo)^.dwFileVersionMS);
3 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
iVer[3] := HiWord(PVSFixedFileInfo(pFileInfo)^.dwFileVersionLS);
iVer[4] := LoWord(PVSFixedFileInfo(pFileInfo)^.dwFileVersionLS);
finally
FreeMem(pBuffer);
end;
// format result string
Result := Format(Fmt, [iVer[1], iVer[2], iVer[3], iVer[4]]);
end;
end;
Of ten it is better to pass nil (instead of 'open') as the operation keyw ord, Window s w ill
then use the def ault keyw ord, or 'open' if the def ault keyw ord is not def ined. With nil it
w orks like a doubleclick in the explorer.
uses ShellApi;
Sometimes you need more control over the starting process, perhaps you w ant to
w ait until the process has f inished or you need the exit code of the application.
4 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
/// <summary>
/// Executes an external program or opens a document with its
/// standard application.</summary>
/// <param name="FileName">Full path of application or document.</param>
/// <param name="Parameters">Command line arguments.</param>
/// <param name="ExitCode">Exitcode of application (only avaiable
/// if Wait > 0).</param>
/// <param name="Wait">[milliseconds] Maximum of time to wait,
/// until application has finished. After reaching this timeout,
/// the application will be terminated and False is returned as
/// result. 0 = don't wait on application, return immediately.</param>
/// <param name="Hide">If True, application runs invisible in the
/// background.</param>
/// <returns>True if application could be started successfully, False
/// if app could not be started or timeout was reached.</returns>
function Sto_ShellExecute(const FileName, Parameters: String;
var ExitCode: DWORD; const Wait: DWORD = 0;
const Hide: Boolean = False): Boolean;
var
myInfo: SHELLEXECUTEINFO;
iWaitRes: DWORD;
begin
// prepare SHELLEXECUTEINFO structure
ZeroMemory(@myInfo, SizeOf(SHELLEXECUTEINFO));
myInfo.cbSize := SizeOf(SHELLEXECUTEINFO);
myInfo.fMask := SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_NO_UI;
myInfo.lpFile := PChar(FileName);
myInfo.lpParameters := PChar(Parameters);
if Hide then
myInfo.nShow := SW_HIDE
else
myInfo.nShow := SW_SHOWNORMAL;
// start file
ExitCode := 0;
Result := ShellExecuteEx(@myInfo);
// if process could be started
if Result then
begin
// wait on process ?
if (Wait > 0) then
begin
iWaitRes := WaitForSingleObject(myInfo.hProcess, Wait);
// timeout reached ?
if (iWaitRes = WAIT_TIMEOUT) then
begin
Result := False;
TerminateProcess(myInfo.hProcess, 0);
end;
// get the exitcode
5 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
GetExitCodeProcess(myInfo.hProcess, ExitCode);
end;
// close handle, because SEE_MASK_NOCLOSEPROCESS was set
CloseHandle(myInfo.hProcess);
end;
end;
To w ait until the application has been started (instead of f inished), replace
"WaitForSingleObject" w ith "WaitForInputIdle".
For a f ull example, of how to start the "cmd.exe" and using input pipe as w ell as
output pipes, you can dow nload this small example project StoRedirectedExecute.zip
(StoRedirectedExecute.zip).
6 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
type
TStoReadPipeThread = class(TThread)
protected
FPipe: THandle;
FContent: TStringStream;
function Get_Content: String;
procedure Execute; override;
public
constructor Create(const Pipe: THandle);
destructor Destroy; override;
property Content: String read Get_Content;
end;
destructor TStoReadPipeThread.Destroy;
begin
FContent.Free;
inherited Destroy;
end;
procedure TStoReadPipeThread.Execute;
const
BLOCK_SIZE = 4096;
var
iBytesRead: DWORD;
myBuffer: array[0..BLOCK_SIZE-1] of Byte;
begin
repeat
// try to read from pipe
if ReadFile(FPipe, myBuffer, BLOCK_SIZE, iBytesRead, nil) then
FContent.Write(myBuffer, iBytesRead);
// a process may write less than BLOCK_SIZE, even if not at the end
// of the output, so checking for < BLOCK_SIZE would block the pipe.
until (iBytesRead = 0);
end;
/// <summary>
/// Runs a console application and captures the stdoutput and
7 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
/// stderror.</summary>
/// <param name="CmdLine">The commandline contains the full path to
/// the executable and the necessary parameters.
/// <param name="Output">Receives the console stdoutput.</param>
/// <param name="Error">Receives the console stderror.</param>
/// <param name="Wait">[milliseconds] Maximum of time to wait,
/// until application has finished. After reaching this timeout,
/// the application will be terminated and False is returned as
/// result.</param>
/// <returns>True if process could be started and did not reach the
/// timeout.</returns>
function Sto_RedirectedExecute(CmdLine: String;
var Output, Error: String; const Wait: DWORD = 3600000): Boolean;
var
mySecurityAttributes: SECURITY_ATTRIBUTES;
myStartupInfo: STARTUPINFO;
myProcessInfo: PROCESS_INFORMATION;
hPipeOutputRead, hPipeOutputWrite: THandle;
hPipeErrorRead, hPipeErrorWrite: THandle;
myReadOutputThread: TStoReadPipeThread;
myReadErrorThread: TStoReadPipeThread;
iWaitRes: Integer;
begin
// prepare security structure
ZeroMemory(@mySecurityAttributes, SizeOf(SECURITY_ATTRIBUTES));
mySecurityAttributes.nLength := SizeOf(SECURITY_ATTRIBUTES);
mySecurityAttributes.bInheritHandle := TRUE;
// create pipes to get stdoutput and stderror
CreatePipe(hPipeOutputRead, hPipeOutputWrite, @mySecurityAttributes, 0);
CreatePipe(hPipeErrorRead, hPipeErrorWrite, @mySecurityAttributes, 0);
8 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
Each "ReadFile" operation on a pipe w ill block, until there is either data to read, or the
handle of the pipe is closed (no more data). An application w ho sends data to the pipe
w ill f ill up the buff er and block until someone reads the data at the other end of the
pipe. This means, if w e read more than one pipe, each pipe can block the others, so
f or a saf e implementation w e have to use threads to read the pipes.
9 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
10 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
end;
end;
////////////////////////////////////////////////////////////////
// in this example, the application was started with 3
// parameters ('Good', 'evening' and 'Delphi').
procedure Sto_CommandLineDemo;
var
iParamCount: Integer;
sApplication: String;
sParam1, sParam2, sParam3: String;
begin
iParamCount := ParamCount; // iParamCount = 3
sApplication := ParamStr(0); // sApplication = filename of application
sParam1 := ParamStr(1); // sParam1 = 'Good'
sParam2 := ParamStr(2); // sParam2 = 'evening'
sParam3 := ParamStr(3); // sParam3 = 'Delphi'
end;
If you need the complete command line (e.g. f or a special parsing), then you can use
the global variable "CmdLine" or the Window s API f unction "GetCommandLine" and get
all parameters in one string.
Fortunately there is an other method w hich can include resources f rom external f iles
to your exe at compiletime. This example show s, how to include a cursor
(sto_cursor.cur) to your project, in the example all f iles are placed in the Delphi project
directory.
First you need a cursor f ile (sto_cursor.cur), copy it to your Delphi project
directory.
In the next step, create a resource script f ile. It's a simple text f ile w ith the
extension (*.rc), name it (sto_cursor.rc).
11 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
Now add the resource script f ile to your Delphi project, that w ay you can edit
the f ile w ithin the IDE and, more important, Delphi w ill know it has to compile this
resources by building the project. Delphi w ill place a compiler directive into the
project f ile (*.dpr).
Now w e have to f ill the resource script, the example show s an include of the f ile
(sto_cursor.cur). At the begin of the line has to be the id of the resource (my_cursor),
it is def ined by yourself . The id is f ollow ed by a keyw ord (CURSOR), w hich describes
the type of the resource, and the third parameter is the f ilename.
Af ter this is done, you can build your project and the resource w ill be compiled
(sto_cursor.res) and added to the exe. To display the new cursor, you f irst have to
load the resource. In the example, the unused cursor "crSqlWait" w ill be replaced by
our cursor. Normally you w ould have to unload a resource loaded w ith "LoadImage"
but in this case "Screen.Cursor" w ill handle this f or you.
Note: the f unction "LoadCursor" has been superseded by "LoadImage".
const
crMyCursor = crSqlWait; // replace an unneeded cursor
12 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
13 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
////////////////////////////////////////////////////////////////
// this function returns the long path from a short 8.3 DOS path.
// if it was not possible to get the long path, the function gives
// back "ShortPathName" as the result.
function Sto_ExtractLongPathName(const ShortPathName: String): String;
var
bSuccess: Boolean;
fncGetLongPathName: function (lpszShortPath: LPCTSTR;
lpszLongPath: LPTSTR; cchBuffer: DWORD): DWORD stdcall;
szBuffer: array[0..MAX_PATH] of Char;
pDesktop: IShellFolder;
swShortPath: WideString;
iEaten: ULONG;
pItemList: PItemIDList;
iAttributes: ULONG;
begin
bSuccess := False;
// try to use the function "GetLongPathNameA" (Win98/2000 and up)
@fncGetLongPathName := GetProcAddress(
GetModuleHandle('Kernel32.dll'), 'GetLongPathNameA');
if (Assigned(fncGetLongPathName)) then
begin
bSuccess := fncGetLongPathName(PChar(ShortPathName), szBuffer,
SizeOf(szBuffer)) <> 0;
if bSuccess then
Result := szBuffer;
end;
// use an alternative way of getting the path (Win95/NT). the function
// "SHGetFileInfo" (as often seen in examples) only converts the
// filename without the path.
if (not bSuccess) and Succeeded(SHGetDesktopFolder(pDesktop)) then
begin
swShortPath := ShortPathName;
iAttributes := 0;
if Succeeded(pDesktop.ParseDisplayName(0, nil, POLESTR(swShortPath),
iEaten, pItemList, iAttributes)) then
begin
bSuccess := SHGetPathFromIDList(pItemList, szBuffer);
if bSuccess then
Result := szBuffer;
// release ItemIdList (SHGetMalloc is superseded)
CoTaskMemFree(pItemList);
end;
end;
// give back the original path if unsuccessful
if (not bSuccess) then
Result := ShortPathName;
end;
14 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
uses SysUtils;
////////////////////////////////////////////////////////////////
// this function determines the size of a file in bytes, the size
// can be more than 2 GB.
function Sto_GetFileSize(const FileName: String): Int64;
var
myFile: THandle;
myFindData: TWin32FindData;
begin
// set default value
Result := 0;
// get the file handle.
myFile := FindFirstFile(PChar(FileName), myFindData);
if (myFile <> INVALID_HANDLE_VALUE) then
begin
Windows.FindClose(myFile);
Int64Rec(Result).Lo := myFindData.nFileSizeLow;
Int64Rec(Result).Hi := myFindData.nFileSizeHigh;
end;
end;
uses FileCtrl;
////////////////////////////////////////////////////////////////
// opens the directory browse dialog with a pre-selected node.
procedure TForm1.Button1Click(Sender: TObject);
var
sDirectory: String;
begin
sDirectory := 'C:\Program Files';
if SelectDirectory('Text above the tree', '', sDirectory) then
ShowMessage(sDirectory);
end;
15 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
////////////////////////////////////////////////////////////////
// - "Filenames" is a list of files and directories you want to delete.
// the user will be asked for confirmation and the progress dialog will
// be displayed if necessary.
// - "ParentWindow" is the parent window for message boxes, you can pass "0".
// after executing, you have to check, which files where really deleted,
// because the user can cancel the deleting procedure.
procedure Sto_DeleteFiles(const ParentWindow: HWND; const Filenames: TStrings;
const ToRecycleBin: Boolean = True);
var
iFile: Integer;
sFilenames: String;
myFileOp: SHFILEOPSTRUCT;
begin
if (Filenames.Count = 0) then Exit;
// create a #0 delimited string with two trailing #0
sFilenames := '';
for iFile := 0 to Filenames.Count - 1 do
sFilenames := sFilenames + ExcludeTrailingPathDelimiter(Filenames.Strings[iFile]) + #0;
sFilenames := sFilenames + #0;
// prepare the SHFILEOPSTRUCT
FillChar(myFileOp, SizeOf(myFileOp), 0);
myFileOp.Wnd := ParentWindow;
myFileOp.wFunc := FO_DELETE;
myFileOp.pFrom := PChar(sFilenames);
// could be moved to the recyclebin, even if "ToRecycleBin" is false.
if ToRecycleBin then
myFileOp.fFlags := myFileOp.fFlags or FOF_ALLOWUNDO;
SHFileOperation(myFileOp);
end;
Searching a directory
You can use this code as a base to your ow n procedure, the procedure lists the
content of a direcotry.
16 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
////////////////////////////////////////////////////////////////
// "Directory": will be searched for files and directories, the results will
// be added with the full path to "List". directories are written with a
// trailing "\" at the end.
// "Mask": can contain one or several masks, delimited with a semikolon. to
// ignore directory names, add an extension to the mask. for more detailed
// information see the delphi function "FindFirst".
// "Recursive": if true, subdirectories will be searched too.
// "Append": if true, existing entries remain in "List".
procedure Sto_SearchDirectory(List: TStrings; const Directory: String;
const Mask: String = '*.*'; Recursive: Boolean = True;
Append: Boolean = False);
17 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
begin
// skip "." and ".."
if (mySearchRec.Name <> '.') and (mySearchRec.Name <> '..') then
begin
sDirectory := IncludeTrailingPathDelimiter(DelimitedDirectory +
mySearchRec.Name);
_SearchDirectory(List, sDirectory, Masks, Recursive);
end;
// find next directory
bFoundFile := FindNext(mySearchRec) = 0;
end;
FindClose(mySearchRec);
end;
end;
var
slMasks: TStringList;
begin
// prepare list
if (not Append) then
List.Clear;
List.BeginUpdate;
slMasks := TStringList.Create;
try
// prepare masks
if (Mask = '') then
slMasks.Add('*')
else
begin
slMasks.Delimiter := ';';
slMasks.DelimitedText := Mask;
end;
// start recursive loop
_SearchDirectory(List, IncludeTrailingPathDelimiter(Directory),
slMasks, Recursive);
finally
slMasks.Free;
List.EndUpdate;
end;
end;
18 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
////////////////////////////////////////////////////////////////
// copies filenames from the clipboard to "Filenames" if there
// are any. the clipboard can contain file- and directory names.
procedure Sto_PasteFilenamesFromClipboard(Filenames: TStrings);
var
hDropHandle: HDROP;
szBuffer: PChar;
iCount, iIndex: Integer;
iLength: Integer;
begin
// check entry conditions
if (Filenames = nil) then Exit;
Filenames.Clear;
// lock clipboard
Clipboard.Open;
try
// does clipboard contain filenames?
if (Clipboard.HasFormat(CF_HDROP)) then
begin
// get drop handle from the clipboard
hDropHandle := Clipboard.GetAsHandle(CF_HDROP);
// enumerate filenames
iCount := DragQueryFile(hDropHandle, $FFFFFFFF, nil, 0);
for iIndex := 0 to iCount - 1 do
begin
// get length of filename
iLength := DragQueryFile(hDropHandle, iIndex, nil, 0);
// allocate the memory, the #0 is not included in "iLength"
szBuffer := StrAlloc(iLength + 1);
try
// get filename
DragQueryFile(hDropHandle, iIndex, szBuffer, iLength + 1);
Filenames.Add(szBuffer);
finally // free the memory
StrDispose(szBuffer);
end;
end;
end;
finally
// unlock clipboard
Clipboard.Close;
end;
end;
////////////////////////////////////////////////////////////////
// copies filenames from "Filenames" to the clipboard.
// "Filenames" can contain file- and directory names.
function Sto_CopyFilenamesToClipboard(Filenames: TStrings): Boolean;
var
19 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
sFilenames: String;
iIndex: Integer;
hBuffer: HGLOBAL;
pBuffer: PDropFiles;
begin
// check entry conditions
Result := (Filenames <> nil) and (Filenames.Count > 0);
if (not Result) then Exit;
// bring the filenames in a form,
// separated by #0 and ending with a double #0#0
sFilenames := '';
for iIndex := 0 to Filenames.Count - 1 do
sFilenames := sFilenames +
ExcludeTrailingPathDelimiter(Filenames.Strings[iIndex]) + #0;
sFilenames := sFilenames + #0;
// allocate memory with the size of the "DropFiles" structure plus the
// length of the filename buffer.
hBuffer := GlobalAlloc(GMEM_MOVEABLE or GMEM_ZEROINIT,
SizeOf(DROPFILES) + Length(sFilenames));
try
Result := (hBuffer <> 0);
if (Result) then
begin
pBuffer := GlobalLock(hBuffer);
try
// prepare the "DROPFILES" structure
pBuffer^.pFiles := SizeOf(DROPFILES);
// behind the "DROPFILES" structure we place the filenames
pBuffer := Pointer(Integer(pBuffer) + SizeOf(DROPFILES));
CopyMemory(pBuffer, PChar(sFilenames), Length(sFilenames));
finally
GlobalUnlock(hBuffer);
end;
// copy buffer to the clipboard
Clipboard.Open;
try
Clipboard.SetAsHandle(CF_HDROP, hBuffer);
finally
Clipboard.Close;
end;
end;
except
Result := False;
// free only if handle could not be passed to the clipboard
GlobalFree(hBuffer);
end;
end;
String functions
20 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
You don't remember exactly the f unctionname, but you know there w as a f unction...?
(uses SysUtils, StrUtils, Classes).
Functionames Category
Length, Delete, Insert, Copy, StringReplace Standard procedures
Pos, PosEx, LastDelimiter Find substring
ExtractStrings, TStrings.CommaText,
Splitting strings
TStrings.DelimitedText
SameText, CompareStr, CompareText Compare strings
Trim, TrimLeft, TrimRight Remove whitespaces
UpperCase, LowerCase
Format, FormatFloat, FormatDateTime Formatting
IntToStr, StrToInt, StrToIntDef, TryStrToInt, FloatToStr,
Datatype conversion
StrToFloat, StrToFloatDef, TryStrToFloat
StringOfChar, FillChar
If a f unction expects a variable of type PChar but does only read the variable (const
parameter) you can w ork w ith normal strings and constant strings. In the example the
w indow s f unction "MessageBox" expects tw o PChar arguments.
procedure SayHello;
var
sNormalString: String;
begin
sNormalString:= 'hello';
MessageBox(0, PChar(sNormalString), 'ConstantString', MB_OK);
end;
If a f unction expects a PChar to place a result in it, then you have to allocate the
memory yourself . Af ter the f unction call it's very easy to convert the PChar to a String,
you can simply assign the PChar to a String. In Delphi, the variable szBuff er (array of
char) is compatible to PChar but w ith allocated memory.
The method above has tw o major draw backs, f irst you have to know the necessary
21 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
buff ersize, and then you can run into an unpredictable situation, if a next version of
the f unction accepts a bigger buff er. In this case the buff er w ould contain random
data (although a missing #0 character w ouldn't lead to a buff er overrun).
Of course there is a better w ay, and you can even w ork w ith normal strings. If you
later have to sw itch to unicode, the code w orks the same w ay w ith WideStrings. Most
Window s API f unctions w ill return the needed size shouldn't there be enough
allocated memory, otherw ise they return the length of the resulting string.
22 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
/// <summary>
/// Searches for a text resource in a Windows library.
/// Sometimes, using the existing Windows resources, you can
/// make your code language independent and you don't have to
/// care about translation problems.
/// </summary>
/// <example>
/// btnCancel.Caption := Sto_LoadWindowsStr('user32.dll', 801, 'Cancel');
/// btnYes.Caption := Sto_LoadWindowsStr('user32.dll', 805, 'Yes');
/// </example>
/// <param name="LibraryName">Name of the windows library like
/// 'user32.dll' or 'shell32.dll'</param>
/// <param name="Ident">Id of the string resource.</param>
/// <param name="DefaultText">Return this text, if the resource
/// string could not be found.</param>
/// <returns>Desired string if the resource was found, otherwise
/// the DefaultText</returns>
function Sto_LoadWindowsStr(const LibraryName: String; const Ident: Integer;
const DefaultText: String = ''): String;
const
BUF_SIZE = 1024;
var
hLibrary: THandle;
iSize: Integer;
begin
hLibrary := GetModuleHandle(PChar(LibraryName));
if (hLibrary <> 0) then
begin
SetLength(Result, BUF_SIZE);
iSize := LoadString(hLibrary, Ident, PChar(Result), BUF_SIZE);
if (iSize > 0) then
SetLength(Result, iSize)
else
Result := DefaultText;
end
else
Result := DefaultText;
end;
ColorToHtml / HtmlToColor
In HTML, colors are stored as text in the RGB (red green blue) f ormat. Each part of the
color has a hex value w ith tw o digits. With the f ollow ing f unctions you can convert a
TColor value to an HTML-color-string and reverse.
23 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
////////////////////////////////////////////////////////////////
// converts a color to an html color string
// clRed => #FF0000
function Sto_ColorToHtml(const Color: TColor): String;
var
iRgb: Longint;
iHtml: Longint;
begin
// convert system colors to rgb colors
iRgb := ColorToRGB(Color);
// change BBGGRR to RRGGBB
iHtml := ((iRgb and $0000FF) shl 16) or // shift red to the left
( iRgb and $00FF00) or // green keeps the place
((iRgb and $FF0000) shr 16); // shift blue to the right
// create the html string
Result := '#' + IntToHex(iHtml, 6);
end;
////////////////////////////////////////////////////////////////
// converts an html color string to a color,
// can raise an EConvertError exception
// #0000FF -> clBlue
function Sto_HtmlToColor(Color: String): TColor;
var
iHtml: Longint;
begin
// remove the preceding '#'
if (Length(Color) > 0) and (Color[1] = '#') then
Delete(Color, 1, 1);
// convert html string to integer
iHtml := StrToInt('$' + Color);
// change RRGGBB to BBGGRR
Result := ((iHtml and $FF0000) shr 16) or // shift red to the right
( iHtml and $00FF00) or // green keeps the place
((iHtml and $0000FF) shl 16); // shift blue to the left
end;
Writing events
You can w rite your ow n events in your classes. As soon as you place the event
property in the "published" part of the class declaration, the Delphi IDE w ill show the
event in the objectinspector by adding the component. It's a common practice to w rite
a virtual method f or calling the event, of ten it's called like the event w ith "Do" at the
begin. In this example, the object passes a boolean parameter to the event, w hich can
be altered in the event procedure. If you don't have to pass parameters you can use
the predef ined type "TNotif yEvent" instead of your ow n event declaration
24 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
("TOnEvent").
interface
type
TOnEvent = procedure(Sender: TObject; var Continue: Boolean) of object;
TEventDemo = class(TObject)
protected
FOnEvent: TOnEvent;
procedure DoEvent; virtual;
published
property OnEvent: TOnEvent read FOnEvent write FOnEvent;
end;
implementation
procedure TEventDemo.DoEvent;
var
bContinue: Boolean;
begin
// set default values
bContinue := True;
// call the event
if Assigned(FOnEvent) then
FOnEvent(Self, bContinue);
// do other things
if bContinue then
begin
// ...
end;
end;
Index properties
A special kind of properties are the indexed properties, they act like an array and can
be used in the same f ashion. How ever, the class has to take care of the memory
management and the perf ormed actions by implementing the methods
("GetIndexProperty" and "SetIndexProperty").
25 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
interface
type
TIndexPropertyDemo = class(TObject)
protected
function GetIndexProperty(Index: Integer): String; virtual;
procedure SetIndexProperty(Index: Integer; const Value: String); virtual;
public
property IndexProperty[Index: Integer]: String read GetIndexProperty write SetIndexPropert
end;
implementation
In the example you can see a small class w hich implements a transparent panel. There
are tw o methods implemented "WMEraseBkgnd" and "CreateParams", they show the
diff erence betw een a normal virtual method (CreateParams) and a message handler
(WMEraseBkgnd).
26 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
interface
uses Windows, Messages;
type
TStoTransparentPanel = class(TWinControl)
private
procedure WMEraseBkgnd(var Message: TWmEraseBkgnd); message WM_ERASEBKGND;
protected
procedure CreateParams(var Params: TCreateParams); override;
end;
implementation
Delphi VCL
Coloring a StringGrid
You can bring colors to your grids w ith a small amount of w ork. A comf ortable w ay to
do this is, to w rite your ow n "OnDraw Cell" event and paint the text manually. The
example w orks w ith a TStringGrid, though it's not very diff erent f or TDraw Grid's.
27 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
////////////////////////////////////////////////////////////////
// draws the text for one single cell in a grid
procedure Sto_DrawCellText(const Canvas: TCanvas; const Rect: TRect;
const Text: String; const BackColor, TextColor: TColor;
const Alignment: TAlignment);
const
BORDER_WIDTH = 2;
var
iLeftBorder: Integer;
begin
// calculate the left border
iLeftBorder := 0;
case Alignment of
taLeftJustify : iLeftBorder := Rect.Left + BORDER_WIDTH;
taRightJustify: iLeftBorder := Rect.Right - BORDER_WIDTH - Canvas.TextWidth(Text) -1;
taCenter : iLeftBorder := Rect.Left + (Rect.Right - Rect.Left - Canvas.TextWidth(Text
end;
// set colors
Canvas.Font.Color := TextColor;
Canvas.Brush.Color := BackColor;
// paint the text
ExtTextOut(Canvas.Handle, iLeftBorder, Rect.Top + BORDER_WIDTH, ETO_CLIPPED or ETO_OPAQUE,
@Rect, PChar(Text), Length(Text), nil);
end;
////////////////////////////////////////////////////////////////
// the OnDrawCell event of the grid
procedure TForm1.grdColoredStringGridDrawCell(Sender: TObject; ACol,
ARow: Integer; Rect: TRect; State: TGridDrawState);
var
grdColored: TStringGrid;
sText: String;
myBackColor: TColor;
myTextColor: TColor;
myAlignment: TAlignment;
begin
grdColored := TStringGrid(Sender);
// set default values for the parameters,
// get the values depending on the grid settings.
sText := grdColored.Cells[ACol, ARow];
if (ARow < grdColored.FixedRows) or (ACol < grdColored.FixedCols) then
myBackColor := grdColored.FixedColor
else
myBackColor := grdColored.Color;
myTextColor := grdColored.Font.Color;
myAlignment := taLeftJustify;
// set your own values...
myTextColor := clGreen;
// draw the text in the cell
Sto_DrawCellText(grdColored.Canvas, Rect, sText, myBackColor, myTextColor, myAlignment);
end;
28 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
////////////////////////////////////////////////////////////////
// hides all tabs (not the tabsheet itself) in the pagecontrol,
// and selects the first tabsheet as the active displayed.
procedure Sto_HideTabsInPageCtrl(PageControl: TPageControl);
var iPage: Integer;
begin
// hide the tabs
for iPage := 0 to PageControl.PageCount - 1 do
PageControl.Pages[iPage].TabVisible := False;
// set the active tabsheet
if (PageControl.PageCount > 0) then
PageControl.ActivePage := PageControl.Pages[0];
// hide the border of the pagecontrol
PageControl.Style := tsButtons;
end;
Shortcuts of the f orm <alt><?> are normally generated w ith the caption property of the
menu item. Insert a "&" character to generate an <alt> shortcut w ith the next letter,
"Hello &w orld" w ould generate the shortcut <alt><w >.
29 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
////////////////////////////////////////////////////////////////
// this procedure sets the with of the dropdown box, if the
// content is to large for the standard width.
procedure Sto_AutoWidthCombobox(Combobox: TCombobox);
const
HORIZONTAL_PADDING = 4;
var
iWidth: Integer;
iIndex: Integer;
iLineWidth: Integer;
begin
iWidth := 0;
// calculate the width of the drop down content
for iIndex := 0 to Combobox.Items.Count - 1 do
begin
iLineWidth := Combobox.Canvas.TextWidth(Combobox.Items[iIndex]);
Inc(iLineWidth, 2 * HORIZONTAL_PADDING);
if (iLineWidth > iWidth) then
iWidth := iLineWidth;
end;
// set the calculated width if necessary
if (iWidth > Combobox.Width) then
SendMessage(Combobox.Handle, CB_SETDROPPEDWIDTH, iWidth, 0);
end;
Delphi IDE
Shortcut Description
<ctrl><shift><0..9> Set or remove the bookmark 0..9 .
<ctrl><0..9> Jump to the bookmark 0..9 in the currently opened file.
<ctrl><shift><i> Indent the selected rows.
<ctrl><shift><u> Unindent the selected rows.
<ctrl><shift><up>
Switch between declaration and implementation.
<ctrl><shift><down>
<alt><up>
<ctrl> Find the implementation of a procedure or function.
<left_mouse_button>
<ctrl><e> Incremental search mode.
30 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
Output directories
In the project options you can set an output path f or the executable and one f or the
compiled units (*.dcu). It's very usef ul to have the dcu's in a separate directory, but
every programmer has to have this directory, otherw ise he can't compile the project.
Since Delphi6 (?) it's possible to have global variables and relative paths in the output
directories. Because all programmers w ill have at least a global temp path, you can set
this variable as the dcu output directory. If you set a relative path as the executable
directory, it's easier to move or copy the project.
Path Setting
..\bin Output path for the executable in a relative directory
$(TEMP) Output path for units (*.dcu)
31 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
uses Variants;
procedure VariantDemo;
var
vDemo: Variant;
bTest: Boolean;
begin
// EMPTY
vDemo := Unassigned; // assign EMPTY to variant
bTest := VarIsEmpty(vDemo); // check if variant is EMPTY
// NULL
vDemo := NULL; // assign NULL to variant
bTest := VarIsNull(vDemo); // check if variant is NULL
// numeric
vDemo := 8.8; // assign a float to variant
bTest := VarIsNumeric(vDemo); // check if variant is numeric
// text
vDemo := 'demo'; // assign a string to variant
bTest := VarIsStr(vDemo); // check if variant contains text
// COM methods can define obtional parameters. if you are
// working with typelibraries you have to pass a parameter
// nevertheless, then you can pass "EmptyParam"
vDemo := EmptyParam;
bTest := VarIsEmptyParam(vDemo);
end;
Register Unregister
in-process (MyServ er.dll) regsvr32 MyServer.dll regsvr32 /u MyServer.dll
out-of-process (MyServ er.exe) MyServer.exe /regserver MyServer.exe /unregserver
If you of ten have to do w ith COM servers, it is very usef ul to be able to (un)register
them in the explorer. It's not diff icult to extend the explorer's context menu, you can
use this small reg f ile to add a "Register" and an "UnRegister" entry.
32 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
Def ining a def ault property is very easy, provided that you know w here to look. In the
typelibrary editor you go to the page "Attributes" and set the "ID" f ield to 0, done.
33 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
////////////////////////////////////////////////////////////////
// if you display a form from inside a COM server, you will miss the
// automatic navigation between the controls with the "TAB" key.
// the "KeyPreview" property of the form has to be set to "True".
procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);
var
bShift: Boolean;
begin
// check for tab key and switch focus to next or previous control.
// handle this in the KeyPress event, to avoid a messagebeep.
if (Ord(Key) = VK_TAB) then
begin
bShift := Hi(GetKeyState(VK_SHIFT)) <> 0;
SelectNext(ActiveControl, not(bShift), True);
Key := #0; // mark as handled
end;
end;
////////////////////////////////////////////////////////////////
// if you display a form from inside a COM server, you will miss the
// support of the menu- and action- shortcuts like "<Ctrl><S>".
// the "KeyPreview" property of the form has to be set to "True".
procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
const
AltMask = $20000000;
var
myMessage: TWMKey;
begin
// recreate the original "KeyUp" message
FillChar(myMessage, SizeOf(TWMKey), 0);
myMessage.Msg := WM_KEYUP;
myMessage.CharCode := Key;
if (ssAlt in Shift) then
myMessage.KeyData := AltMask;
// find and execute matching shortcut
if IsShortCut(myMessage) then
Key := 0; // mark as handled
end;
You can use interf aces f or your conventional objects too, w ithout supporting
ref erence counting and automatically f reeing. Doing this, you have to pay attention to
some special f acts.
34 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
ITest = interface(IInterface)
// press <ctrl><shift><g> to create your own GUID for each interface.
// this is necessary to implement the "QueryInterface" method.
['{CA51B752-0DF5-40D2-945C-A5CF2EAA3B31}']
procedure ShowText;
end;
Because every interf ace inherites f rom the parent interf ace "IInterf ace" (same as
w indow s specif ic "IUnknow n"), you have to support at least the three methods of
"IInterf ace". This example show s a standard implementation.
procedure TTest.ShowText;
begin
ShowMessage(FText);
end;
When you later use the object, you have to be caref ul, that at the moment of
destruction, no ref erence to the interf ace remains.
35 of 36 3/8/2019, 8:35 PM
Delphi tips https://www.martinstoeckli.ch/delphi/delphi.html#AppName
procedure WellUsed;
var
myTestObject: TTest;
pTestInterface: ITest;
begin
// creating the object
myTestObject := TTest.Create;
// get a reference to the interface of the object, this will implicitly call "_AddRef"
pTestInterface := myTestObject;
// ...
// release the reference to the interface, this will implicitly call "_Release"
pTestInterface := nil;
// freeing the object itself
myTestObject.Free;
end;
If you f ree the object, bef ore the last ref erence to the interf ace is released, then the
implicit call to "_Release" w ill call to a not existing object (Delphi w ill release the
interf ace automatically, if you don't do it yourself ).
procedure WrongUsed;
var
myTestObject: TTest;
pTestInterface: ITest;
begin
myTestObject := TTest.Create;
pTestInterface := myTestObject;
// ...
// freeing the object with an existing reference
myTestObject.Free;
// this releasing of the interface will implicitly call "_Release", but there
// is no living object anymore.
pTestInterface := nil;
end;
Normally, calling a method of a not existing object w ill cause a runtime error, not in our
example. That's because no member of the object is used inside "_Release", as soon
as you access a member, you w ill get the expected error. So, f irst make sure you
don't call a "_Release" on a not existing object, then don't access members inside of
"_Release".
36 of 36 3/8/2019, 8:35 PM