back to Inner-Smile.com .. Richey's DELPHI-BOX   Software Tools  Hot Links  Suggest a link  Contact us   
 
Translate
into

Delphi-Box Home

Delphi News

Delphi WWW Sites

Delphi FTP sites

Delphi Tips, Tricks & Docs

Delphi Tools & Updates

Delphi Information resources

Delphi Books

Delphi User Groups

Delphi Job Offers

Delphi-Box reSearch

 


Delphi:
My personal Tips 4U


Let me share some experiences with you which I've found out during the years !
..and if you have tips to add, correct or improve: don't hesitate to tell me !


Howto:




Dealing with DsgnIntf.pas code trouble

Many of you might have experienced problems when trying to use DsgnIntf.pas in applications that are compiled under Delphi 6 or 7. Since the original DsgnIntf.pas code has been splitted up in those newer versions, the following trick helps in most situations:

{$UNDEF DEL345}
{$IFDEF VER100} {$DEFINE DEL345} {$ENDIF}
{$IFDEF VER120} {$DEFINE DEL345} {$ENDIF}
{$IFDEF VER130} {$DEFINE DEL345} {$ENDIF}

uses
	{$IFDEF DEL345}
	DsgnIntf
	{$ELSE}
	DesignIntf, DesignEditors, VCLEditors, RTLConsts
	{$ENDIF};
Put the above code at the place that pointed to just DsgnIntf before, and you should be a happy person again. :-)

How to add columns to a TListBox

Put in the TabWidth property the width of the column in pixels (test it with 100, for example). When you add items to the listbox, separate the columns with ^I.

ListBox1.Items.Add('FirstColumn'^I'Second'^I'Third');
(found at: About.com forums)


How to use True Type fonts without installing?

Setting an alternative font for your forms is easy. But how to use your specialized fonts without the need to fill up your user's FONTS directory? Well, it's just a few lines, so here we go:

// first load it in the OnCreate event of a form:
procedure TForm1.FormCreate(Sender: TObject);
begin
  AddFontResource('c:\FONTS\MyFont.TTF');
  SendMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0);
end;

// before application terminates, we must free it:
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  RemoveFontResource('C:\FONTS\MyFont.TTF');
  SendMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0);
end;
(found at: About.com forums)


How to create shortcuts?

There is no single API call to create a shortcut. You rather have to use IShellLink OLE interface for this purpose. See the following sample code:

uses
  Windows, Ole2, ShlObj, SysUtils, Forms;

procedure CreateLink(ObjectPath: string;
// path of the file/folder to create a shortcut
					 LinkPath: string;
// path of the newly created link/shortcut
                     Description : string);
var  ShellLink   : IShellLink;
var  PersistFile : IPersistFile;
var  WidePath    : array[0..259] of WideChar;
begin
  // initialize COM library
  CoInitialize;
  // Get a pointer to the IShellLink interface.
  if Failed(CoCreateInstance(CLSID_ShellLink,nil,CLSCTX_INPROC_SERVER,
  		    IID_IShellLink,ShellLink)) then
	 raise Exception.Create('Unable to create an IShellLink instance');
  try
	// set the path to the shortcut target
	ShellLink.SetPath(PChar(ObjectPath));
	// set the Link description
	ShellLink.SetDescription(PChar(Description));
	// Query IShellLink for the IPersistFile interface for saving the
	// shortcut in persistent storage.
	if Failed(ShellLink.QueryInterface(IID_IPersistFile,PersistFile)) then
		raise Exception.Create('Unable to create an IPersistFile instance');
	try
		// ensure that the string is ANSI.
		MultiByteToWideChar(CP_ACP, 0, PChar(LinkPath), -1,WidePath, 259);
		// save the link by calling IPersistFile::Save.
		if Failed(PersistFile.Save(WidePath, TRUE)) then
			raise Exception.Create('Unable to save link');
	finally
		// free the IPersistFile interface
		PersistFile.Release;
	end;
    // unitialize COM library
    CoUninitialize;
  finally
	// free the IShellLink interface
	ShellLink.Release;
  end;
end;
If you want to create your shortcut in a "special" directory, check the WinAPI helpfile for a description of the ShGetSpecialFolderLocation() function.


What is FOO?

You can see "foo" mentioned in many programming examples, but what the hell is "foo" ?

Well, it is a bit of an inside joke. Programming examples frequently use identifiers Foo and Bar. The inside joke is that these are short for the acronym FUBAR which is f*'ed up beyond all recognition... ;) For details, see the New Hacker's Dictionary and search for "foo".




How to retrieve UNC paths?

see the ExpandUNCFilename function in Delphi help which returns the full path of a file name with the network drive portion in UNC format. Syntax:

function ExpandUNCFileName(const FileName: string): string;




How to send a QuickReport via Outlook
(send mail with attachement)

USES [..],
	 Outlook8, OleServer, COMobj, ActiveX;
[..]
procedure TfrmKuProt.sbtMailClick(Sender: TObject);
VAR
	Outlook		 : OutlookApplication;
	Unknown		 : IUnknown;
	Result		 : HResult;
	MI			 : MailItem;
	Insp		 : Inspector;
	x			 : Integer;
	aExportFilter: TQRExportFilter;
	wmf			 : TQRWMFFilter;
CONST
	olMailItem = 0;
BEGIN
	qr.Prepare;
	try
	  wmf := TQRWmfFilter.Create(NIL);
	  wmf.Enhanced := TRUE;
	  aExportFilter := TQRExportFilterLibraryEntry(
		  QRExportFilterLibrary.Filters[4]).ExportFilterClass.Create('c:\rpt.bmp');
	  qr.QRPrinter.ExportToFilter(aExportFilter);
	  wmf.Free;
	finally
	  aExportFilter.Free
	end;

	// via early binding:
	Result := GetActiveObject(CLASS_OutlookApplication, nil, Unknown);
	if Result = MK_E_UNAVAILABLE then
		Outlook := CoOutlookApplication.Create
	else begin
		// make sure no other error occurred during GetActiveObject
		OleCheck(Result);
		OleCheck(Unknown.QueryInterface(OutlookApplication, Outlook));
	end;
	MI := Outlook.CreateItem(olMailItem) as MailItem;
	MI.Recipients.Add(' @');
	MI.Attachments.Add('c:\rpt.bmp',EmptyParam, EmptyParam, EmptyParam);
	MI.Subject := 'Your sheet Y';
	MI.Body := 'Dear Mr. Dredd,'#13#10#13#10'Enclosed please find ..'#13#10;
	Insp := MI.GetInspector;
	Insp.Display(False);

	Showmessage('Please click OK when Outlook is finished.');
	Screen.Cursor := crHourglass;
	Outlook.Quit;
	Outlook := nil;
end;
Thanks to Deborah Pate (see her URL on my Delphi Tips page) for pointing me in the right direction with Outlook.




Hidden features of the Delphi IDE

Some undocumented registry settings of Delphi 5 (which -slightly adapted- might also work with Delphi 4 and below) modify the behavior of the Delphi component palette in a manner you may like!
Most values are stored as strings, and boolean values are represented as "1" for true and "0" for false. All values are stored in HKEY_CURRENT_USER.
As always, use of this information is at your own risk... ;-)

Software\Borland\Delphi\5.0\Extras\AutoPaletteSelect
will cause a tab on the component palette to be automatically selected when the mouse is hovering over it. If the mouse is in the top two-thirds (2/3) of the tab, the palette for that tab will automatically be displayed.
Software\Borland\Delphi\5.0\Extras\AutoPaletteScroll
will make you scroll left and right automatically whenever the mouse is positioned over the relevant arrow.
Software\Borland\Delphi\5.0\Editor\Options\NoCtrlAltKeys
Disables menu item Ctrl+Alt key sequences for international keyboards
Software\Borland\Delphi\5.0\Form Design\AlwaysEnableMiddleEast
Forces Right-to-Left text in the form designer (?)
Software\Borland\Delphi\5.0\Extras\FontNamePropertyDisplayFontNames
Display the fonts in the object inspector dropdown in the font's actual style (slow with many fonts installed). See also DsgnIntf.FontNamePropertyDisplayFontNames in D5.
Software\Borland\Delphi\5.0\Compiling\ShowCodeInsiteErrors
Show compilation errors found by CodeInsite in the message view window
Software\Borland\Delphi\5.0\Globals\PropValueColor
Fill in with a string like "clGreen" to change the color of the right half (properties) of the Object Inspector.
Software\Borland\Delphi\5.0\Disabled Packages
This is the place you put Delphi Direct :)
Software\Borland\Delphi\5.0\Globals\TwoDigitYearCenturyWindow
Default value for TwoDigitYearCenturyWindow (see the help file)
Software\Borland\Delphi\5.0\Component Templates\CCLibDir
Alternative component templates directory (shared/network)
Software\Borland\Delphi\5.0\FormDesign\DefaultFont="Arial,8" [D4] or "Arial,8,Bold" [D5]
The default for for new forms (you might prefer using the repository's default form checkbox instead)
Software\Borland\Delphi\5.0\Wizards
Alternate key to store Expert/Wizard DLLs to load at startup
Software\Borland\Delphi\5.0\Debugging\DontPromptForJITDebugger
Don't ask to change the current JIT debugger (?)
Software\Borland\Delphi\5.0\Version Control\VCSManager
The DLL used for the version control interface in the IDE.
Software\Borland\Delphi\5.0\Globals\PrivateDir
A way to specify an alternative directory for the location for the Delphi configuration files when running the application from a network drive or the CD-ROM.
Software\Borland\Delphi\5.0\Main Window\Palette Visible
Software\Borland\Delphi\5.0\Main Window\Speedbar Visible
Software\Borland\Delphi\5.0\Main Window\Palette Hints
Software\Borland\Delphi\5.0\Main Window\Speedbar Hints
Software\Borland\Delphi\5.0\Main Window\Split Position
These seem to have no effect at runtime, but are read by the IDE. The actually used values come from HKEY_CURRENT_USER\Software\Borland\Delphi\5.0\Toolbars.
Software\Borland\Delphi\5.0\ProjectManager\Dockable
Software\Borland\Delphi\5.0\PropertyInspector\Dockable
Software\Borland\Delphi\5.0\CallStackWindow\Dockable
Software\Borland\Delphi\5.0\ModuleWindow\Dockable
Read but unused settings. Used values come from DSK files.

There are lots of other interesting registry keys that aren't modifiable in the IDE, but they all have values written by default, so you can find and play with them much easier.
For easier use, I have created a .REG file for you - download it, adjust it to meet your needs and simply double click it in Explorer to update the registry with your preferred configuration...

(Tips revealed by John Kaster (Borland) in his related article and Erik Berry (Co-Author of GExperts).


Retrieve the version number of a program


procedure GetBuildInfo(var V1, V2, V3, V4: Word);
function strBuildInfo: String;

procedure GetBuildInfo(var V1, V2, V3, V4: Word);
var
   VerInfoSize,
   VerValueSize,
   Dummy       : DWORD;
   VerInfo	   : Pointer;
   VerValue	   : PVSFixedFileInfo;
begin
	 VerInfoSize := GetFileVersionInfoSize(PChar(ParamStr(0)), Dummy);
	 GetMem(VerInfo, VerInfoSize);
	 GetFileVersionInfo(PChar(ParamStr(0)), 0, VerInfoSize, VerInfo);
	 VerQueryValue(VerInfo, '\', Pointer(VerValue), VerValueSize);
	 With VerValue^ do
	 begin
	 	V1 := dwFileVersionMS shr 16;
		V2 := dwFileVersionMS and $FFFF;
		V3 := dwFileVersionLS shr 16;
		V4 := dwFileVersionLS and $FFFF;
	end;
	FreeMem(VerInfo, VerInfoSize);
end;

function strBuildInfo: String;
var
  V1, V2, V3, V4: Word;
begin
  GetBuildInfo(V1, V2, V3, V4);
  Result := IntToStr(V1) + '.' +
            IntToStr(V2) + '.' +
            IntToStr(V3) + '.' +
            IntToStr(V4);
end;




Turn off the monitor

// turn it off:
SendMessage(Application.Handle, WM_SYSCOMMAND, SC_MONITORPOWER, 0); 
// turn it on again:
SendMessage(Application.Handle, WM_SYSCOMMAND, SC_MONITORPOWER, -1); 
Caution: you must ensure the monitor being turned on again after you use this command, otherwise the system must be rebooted!




Loading your glyphs from resources

If you want static bitmaps in your forms and the users are not allowed to change them you can use a RES file instead of loading Bitmaps in the Delphi IDE. This also makes your EXE smaller! Example:

	Unit Unit1;
	Interface
	Uses
		x,y,z;
	Type
		...
	Implementation
	{$R BitIco.res}
	...
	procedure ...
	begin
		// Watch out for upper and lower case
		Image1.Picture.Bitmap.Handle := LoadBitmap(HInstance, 'Name of Bitmap');
		// or:
		Image1.Picture.Icon.Handle := LoadIcon(HInstance, 'Name of Icon');
		...
	end;
Don't forget that the names are treated case-sensitive!

More links about this topic can be found in the section named How to store virtually "everything" in your .EXE file of this FAQ.


How to display columns in a simple Listbox

The TabWidth property of the TListbox component sets the number of dialog base units, usually pixels, for each tab character. Set it, to say, a half of the ListBox' width to display two columns. When adding strings to the ListBox, use the tab character (^I) at the desired position:

ListBox1.Items.Add('Column1'^I'Column2');
The imperfection of such approach is that the width of column is not set automatically depending on the width of the string displayed, which is simple to improve. Let's look at TextWidth method of TCanvas class: it returns a width in pixels of a string passed as a parameter. Then we can write
with ListBox do begin
  W := Canvas.TextWidth(Str);
  if W > TabWidth then
    TabWidth := W;
end;

(don't remember where I found this tip, sorry)


Preventing Ctrl-Alt-Del

Your program might require to disable Ctrl-Alt-Del key sequence, for example if you don't want your program to be unloaded from memory (it's up to you to decide if your users will love you for that): and it is possible by using the SystemParametersInfo API function. This function is used by Control Panel to customize the Windows environment like setting keyboard-, display-, sound setting parameters and others. Its syntax is as follows:

BOOL SystemParametersInfo(
	UINT uiAction,  // system parameter to query or set
	UINT uiParam,   // depends on action to be taken
	PVOID pvParam,  // depends on action to be taken
	UINT fWinIni    // user profile update flag
);
The meaning of each parameter is described in Win32 Developer's Reference (kbase.hlp). Now, to do what we want, we must call this function like in the following procedure:
procedure DisableCtrlAltDel;
var
  i : integer;
begin
  i := 0;
  {Disable Ctrl-Alt-Del}
  SystemParametersInfo( SPI_SCREENSAVERRUNNING, 1, @i, 0);
end.
Don't forget to place WinProcs unit in "uses" clause of your unit. Remark: To disable the Alt-Tab sequence, you must use the first parameter SPI_SETFASTTASKSWITCH and others are the same as above.




Bringing Delphi 4 to work under Win2000

MS ackowledges that the current install problems are their bug and they have stated that it will be addressed in a future beta (it should finally be fixed in the already available Win2k, though!). All InstallShield programs that use password protected CABs will probably have problems. However, here's a workaround how to bring Delphi 4 to work under Windows 2000:

  1. install Delphi 4 on Windows 2000 and ignore all error messages
  2. copy the runtime image directly from the CD
  3. go to the command line and change to
    Common Files\Borland\Shared\Debugger
    From there, type regsvr32 -u bordbk40.dll
    and then regsvr32 bordbk40.dll
..this should do the trick!




Preventing task switching

Preventing a user from switching to another application is possible under Windows 3.x and 9x by tricking Windows into thinking a screen saver is running. This method does not work under Windows NT and is not guaranteed to be available in future versions of Windows. Many versions of Windows may also respond to a task switching trap installed by a CBT (Computer Based Training) application hook. To prevent task switching under Windows NT, you will need access to a third party keyboard device device driver.

  VAR
	OldValue: Longint;
  [..]
	// turns trap on:
	SystemParametersInfo(97, True, OldValue, 0);
	// turns trap off:
	SystemParametersInfo(97, False, OldValue, 0);




Ensuring your program runs just one instance

Modify your *.DPR project file with the example below:

Program PrevInst;
USES
	Forms, Windows,				// Add Windows
	Unit1 in 'Unit1.pas' {Form1};
{$R *.RES}
VAR								// Add Vars
	MutexHandle: THandle;
	hwind:HWND;
BEGIN
	// Add below code
	MutexHandle := CreateMutex(nil, TRUE, 'MysampleAppMutex'); // should be a unique string
	IF MutexHandle <> 0 then
    begin
		IF GetLastError = ERROR_ALREADY_EXISTS then
		begin
		  // MessageBox(0, 'Instance of this application is already running.',
		  //			'Application already running', mb_IconHand);
		  CloseHandle(MutexHandle);
		  hwind := 0;
		  repeat
			// The string 'My app' must match your App Title (below)
            hwind:=Windows.FindWindowEx(0,hwind,'TApplication','My app');
		  until (hwind<>Application.Handle);
	      IF (hwind<>0) then
          begin
			Windows.ShowWindow(hwind,SW_SHOWNORMAL);
			Windows.SetForegroundWindow(hwind);
		  end;
          Halt;
		end
	end;
	// your/Delphi's window create / run code is below here:
	Application.Initialize;
	Application.Title := 'My app'; // this matches to above
	Application.CreateForm(TForm1, Form1);
	Application.Run;
END.
A shorter method (without automatic switching to your app's original instance) would be:
	CreateMutex(nil,FALSE,'AnyNameHere');
	IF GetLastError = ERROR_ALREADY_EXISTS THEN
	begin
	 	MessageDlg('Program is already running. You can not start more than one instance',
	    mterror,[mbOK], 0);
		Halt(0);
	end;
	Application.Initialize;




Ensuring correct font display under all screen resolutions

If you have developed your program using Delphi's default settings and then started it on a Computer running in a different Screen resolution, you will already know that this can turn out to a problem causing not only short headaches.
But here is the solution that should fix your problems:

Design on large fonts, use only truetype fonts, set the forms Scaled property to false. That works pretty well on smallfont systems. You just have to make sure that your form does not become larger than the users screen size. This can be handled by a simple check in the forms OnCreate procedure, call

	SystemparametersInfo( SPI_GETWORKAREA, 0, @aRect, 0 );
Check if your forms width or height exceed the dimensions of aRect, if so you set your forms Boundsrect := aRect; and the forms AutoScroll property to true. It then gets scrollbars but that is better than having parts of the form not accessible by the user.

More tips on this topic are linked from my "Delphi Tips" page.




Debugging AV's

Debugging is a pain, especially when you get uninformative errors from the operating system. The following edited and partial answer demonstrates a generally useful technique when you are getting AV's in "bad" situations like after closing your app etc.:

The access violation dialog should say that an access violation happened at XXXXXX: read of address YYYYYY. Try running your program in the debugger. Pause the program and bring up the view CPU pane. In the upper left pane, right click and select Go To Address. Go to the address that caused the access violation. Look around a little bit and see if you can find where you are in the program.

Insert some breakpoints and try to close the program. When you hit a breakpoint, view the call stack and see if it gives any clues.

(Tip provided by Harold Howe)




Giving your application real-time priority

This is not a good thing for an application to do unnecessarily...

	Super := Application.Handle;
	if SetPriorityClass(Super,REALTIME_PRIORITY_CLASS) THEN
		Application.MessageBox('Didn''t work','',MB_OK);




Speed up Delphi

Delphi 4 was the slowest Delphi we have ever seen, and Delphi 5 is not really faster, nor was Delphi 3. Beside these versions, I'm still using Delphi 2 for some special tasks and every time I start it I'm amazed by its incredible loading and general processing speed compared to the newer versions. And although the raw compile and linking times have not increased measuruable, the required resources Delphi 3/4/5 occupies make even that quite slow, especially at the first compile after starting Delphi. Why?

Well, the main reason for Delphi's behaviour is the design of the OS under which it runs: Microsoft Windows. Windows can be seen as the most "bloated" of today's operating systems, containing an incredible amount of useless code, "funny" graphics, animations and all the stuff which is required to keep even foreground threads slow and resource-hungry.
Beside that, especially Delphi 3/4/5 itself has many built-in features which make it slow, which can produce quite long "thinking pauses" from time to time and susceptible for memory overflows. The goal of this section in my FAQ is to provide you a number things you could check and avoid to prevent unnecessary resource consumption of the IDE.

Note: this section is not thought to provide you with tips that help to avoid unnecessary resource consumption of your apps themselves. For that, please check the sections "speed up your app and save resources" or "reduce the .EXE-size of your app" on this page.

  • develop under Windows NT.
    Windows NT comes with a better memory management than Windows 95/98.
  • put Resource-Meter (rsrccmtr.exe) in the tray area of your Windows Desktop.
    This helps you to gain control about the point where Windows's resources are filled up and after which the desktop "dies" so that you can close other apps or opened forms before this happens.
  • don't keep too many forms open while you are working in the IDE.
    Each opened unit and form eats up resources - especially complex multi-page forms can easily fill up to the half (!) of the available memory. After that, not only your harddisks will have to do heavy work, but also you risk hanging your PC when you run your app from within the IDE.
  • Check "Minimize on run" in the Environment options of the IDE.
    This will help to minimize fragmention of the Windows resources.
  • disable unneeded "Code Insight" features
    for these features, Delphi has to constantly open and lookup libraries, pop up hint windows etc. etc. .. all stuff which will slow down your working speed a lot. If you need these features, check out the related keyboard shortcuts - they will give your Delphi the same power, but prevent making it unnecessarily slow.
  • remove all unneeded components and packages from the IDE.
    Each installed package and component uses valuable resources - even if you never need it and it's "just a button" in your Delphi toolbar. And not only that - it also slows down your Delphi startup a lot since Delphi does a lot of internal reference checking when loading the required stuff.
  • turn off Code Explorer.
    Treeviews are slow by design. Turn off Delphi's built-in Treeview if you don't really need it (you can still turn it on manually via "View"-"Code Explorer").
  • finally: give Windoze at least 64 MB.
    with less than that, programming with Delphi 3/4/5 is a pain and you might end up as a trembling individual in a mental clinic.
  • more tips? Please let me know!




Save configuration in .INI files or the registry?

There are 2 quite easy methods to save configuration data of your program: using INI files (all Windows versions) or the Windows registry (for 32 bit programs).
If your program (or external parts of your program) have to run under Windows 3.x, too, this is already my first argument why I'm always preferring INI files against the registry. Another reason is easier maintenance: imagine having to tell a computer-newbie how to change a damaged part of his program configuration in the registry during a support call - horrible! Other reasons are much easier backup of the program including program configuration, easiest overview about the saved settings, easy "low-level" editing capabilities of the configuration (Notepad is enough). A commonly underestimated part is that INI files won't "blow up" your registry - as you might know, Windoze doesn't really delete registry keys as they are "deleted" -> it just "hides" them from your view. This leads to registry files of 2-5 MB after a year of happily installing and "uninstalling" one application after each other.... Unfortunately, there are still "programmers" out there who even use the registy to save large amounts of binary data or even localized strings for their complete applications (!) in the registry .. aargh ... let's talk about nicer things again.

My personal favourite for opening the INI files is:

MyIni := TIniFile.Create(ChangeFileExt(Application.ExeName,'.ini'));
This has the advantage that the INI file will always "follow" the program, even if I change the name of the EXE file.

There also exist 2 possible traps when using INI files:

  • Windows 95/98 caches INI data. This might lead to annoying situations if you want to access the INI file as a text file while it is opened or something like that. In this case, add a TIniFile.UpdateFile; to your code which will flush the INI data to disk (Delphi 4. With earlier versions, you can use TIniFile.WriteString(nil,nil,nil);.)
  • INI files bigger than 32kb. If there is much data to save, Windows is overwhelmed ;-). You will have to use a 3rd party component like TIniFile32 or BigIni.zip (get it at the Delphi Super Page) to read and write INI files of this size.
Issues which could lead to a decision against the use of INI files are: speed (using the registry is faster due to its structure), better organization (tree structure), if you want to hide something (it's easier to hide a "secret" registration string "somewhere" in the registry than in an INI file, even if it's saved in another directory -> for better tips please read my Anti-Cracking FAQ) and the M$ Guidelines which suggest to just use the registry under 32bit-Windows. Well, Bill's wishes and visions have never influenced me too much, so I could easily resist until now. ;-)




Creating cool transparent splash forms with Delphi

Here is a very cool technique that not only gives you transparent forms but transparent forms with insanely complex shapes (or, I should say, the appearance of). The following is only tested in Delphi 1/2, but should also work without problems when using D3/4. And this is probably only good for splash screens, because moving a window doesn't always force a redraw, which is the trick.

The trick is altering your form like this: Add the following procedures to your form:

procedure paint; override;
procedure WMERASEBKGND(var Msg:TMEssage); message wm_erasebkgnd;

procedure TSplash.WMERASEBKGND(var Msg:TMEssage);
begin
  msg.result:=1;
end;
Here is where the trickery comes - For this example I am using a component that I wrote whose only function is holding a number of bitmaps. With this component, several of my graphic controls can share the same bitmaps resources but not be forced into the standard palette - as you would have to if you were using .RES files. Anyways, I have two bitmaps - the first bitmap is my splash form and the second bitmap is an 8-bit grayscale mask. In the mask, black areas will be transparent, white areas will be clear and any shades of gray will be semi transparent. It's important to note, however, that the copymode settings used below do some weird crap, so it's best to use a mask with little to no gray, although a little gray will work and give your edges a more smooth anti-aliased appearance.
You also don't have ot use bitmaps, you can draw or whatever you want to do.
procedure TSplash.Paint;
var
  t:TBitmap;
begin

 // create an in-memory bitmap the same dimensions as my form 
 t:=TBItmap.Create;
 t.width:=width;
 t.height:=height;

 // copy the area my form takes up from the desktop's image onto my drawing bitmap

 BitBlt(t.canvas.handle,0,0,width,height,GetDC(GetDesktopWindow),left,top,SRCCOPY);

 with t.canvas do
  begin
    copymode:=cmNotSrcErase;
    draw(0,0,splashrez.bitmap2);  // draw my bitmap mask first
    copymode:=cmSrcErase;
    draw(0,0,splashrez.bitmap1);  // Draw my splash logo/regular bitmap
  end;
  canvas.draw(0,0,t); // draw the entire thing to the form's canvas in one shot to reduce flicker
  t.free;
end;

And that's it! ;-)
(Tip provided by Jon Gilkinson)


Saving your complete Delphi 2/3/4 environment

Ever lost your Delphi 4 IDE setup?
In 32bit-Delphi, all IDE settings are stored in the registry under the branch

HKEY_CURRENT_USER\Software\Borland\Delphi\?.0
where ?.0 is 4.0 for Delphi 4, for example.
Since Regedit.exe can easily export and re-import branches of the registry, you have an easy way to protect yourself against losing your carefully customized IDE environment after crashes or when installing "your" Delphi on a new computer.




Extract icon from file

USES
  ShellAPI;
  ..
procedure ExtractIcon(IcoFileName: String);
VAR
    IcoFileName	: String;
    IcoHandle	: THandle;
    MyIcon	: TIcon;
BEGIN
    // the last parameter is the index of the icon
    IcoHandle := ExtractIcon(Application.Handle, PChar(IcoFilename), Word(0));
    // is there an icon in this file?
   IF IcoHandle = 0 then begin
       ShowMessage('No icon in this file!');
       exit;
   END;
   // okay, so there is an icon in this file
   MyIcon := TIcon.Create;
   MyIcon.Handle := IcoHandle;
   // how much space are we going to need for the icon?
   BitBtn1.Glyph.Height := MyIcon.Height;
   BitBtn1.Glyph.Width := MyIcon.Width;
   // now draw the icon on the button
   BitBtn1.Glyph.Canvas.Draw(2, 2, MyIcon);
   // lets draw it on a TImage too...
   Image1.Picture.Icon := MyIcon;
   // oh, starts to make fun .. lets change the applications icon too! ;-)
   Application.Icon := MyIcon;
   // free the icon
   MyIcon.Free;
END;




"Quiet" online checking

Sometimes it's required to check if a user has an active Internet connection, but you don't want the DUN dialog box to popup by your code. Well, it was not easy to find out a method for that, but finally a hint by Henri Fournier in the Newsgroups gave me the idea for the following 2 powerful lines:

USES
  WinInet;
  ..
  ..
  
function InternetConnected: Boolean;
CONST
  INTERNET_CONNECTION_MODEM      = 1; // local system uses a modem to connect to the Internet.
  INTERNET_CONNECTION_LAN        = 2; // local system uses a local area network to connect to the Internet.
  INTERNET_CONNECTION_PROXY      = 4; // local system uses a proxy server to connect to the Internet.
  INTERNET_CONNECTION_MODEM_BUSY = 8; // local system's modem is busy with a non-Internet connection.
VAR  
  dwConnectionTypes : DWORD;
BEGIN
  dwConnectionTypes :=
  	 INTERNET_CONNECTION_MODEM +
	 INTERNET_CONNECTION_LAN +
	 INTERNET_CONNECTION_PROXY;
  Result := InternetGetConnectedState(@dwConnectionTypes,0);
END;

Note: this solution only works if IE is installed, so it would fail on 'older' machines, like most Windows NT 4 computers. You app would then display an error during program startup if you referred to Wininet.

Since today, there are many ways to connect to the Internet (via LAN, Dialup/RAS, ADSL, ..) propably the best way would be to test for certain IPs. Here is a link to more information on the topic, including a list of ways to find out whether an Internet connection seems to be active or not.




Duplicating Records

Need to make exact copies of records with some slight variations? This in itself wouldn't be a problem, but if there are many tables that need this functionality and they had a variety of types and structures, it can hit your nerves. The XBase solution would have been to store all the fields into memory variables, append a new record, then replace all the fields with the memory variables. As it turns out, that can be the solution to the problem with Delphi as well.

The procedure introduced here, AppendCurrent, works this way:

  • it creates a Variant Array
  • it populates the array with each of field's values from the selected table
  • it calls the Append method of the table
  • it populates the table's fields with the values from the variant array
  • it passes it back in the edit state ready for changing

The following listing shows you the code.

{************************************************
// procedure AppendCurrent
//
// Will append an exact copy of the current 
// record of the dataset that is passed into 
// the procedure and will return the dataset 
// in edit state with the record pointer on 
// the currently appended record.
************************************************}
Procedure AppendCurrent(Dataset:Tdataset);
VAR
  aField : Variant ;
  i      : Integer ;
BEGIN
  // create a variant Array
  aField := VarArrayCreate([0,DataSet.Fieldcount-1],VarVariant);

  // read values into the array
  for i := 0 to (DataSet.Fieldcount-1) do 
     aField[i] := DataSet.fields[i].Value ;

  DataSet.Append ;

  // put array values into new the record
  for i := 0 to (DataSet.Fieldcount-1) do
     DataSet.fields[i].Value := aField[i] ;
END;

(Tip found in Delphi Developer)



Cool Delphi Videos

For me, a nice add-on of each new Delphi version has always been the inclusion of the latest Delphi advertisement video clips on the Delphi CD's. Borland/Inprise has always had really cool and entertaining videos, you should definitely check them out.
The Delphi 4 CD, for example, contains the following clips:

  • "Speed is.." (long version) in \Runimage\Delphi40\Demos\Coolstuf\SpeedIs.avi
  • "The right tools.." in \Delphi16\Videos\Borland.avi
  • "Speed is.." (short version) in \Delphi16\Videos\Delphi.avi
On the Delphi 3 CD, you can find
  • "Just want to.." in \Info\Borland\Bor_GG.avi
and my personal favourites are definitely:
  • "Racing Car" in DelCar2.avi and
  • "Hans and Gunter" in DelCom2.avi
Unfortunately I don't remember where I found those initially. :-}

Btw., don't forget to browse your Delphi CD's for other goodies, too! You can find plenty of interesting stuff like FAQ's, add-on libraries, Delphi mags (unfortunately just on the earlier CD's) and, not the least, updated versions of Delphi 1 on them.



Add cool icons to your application

Maybe you've already asked yourself, how you get Delphi to accept two (or more) Icons ? A 16x16 icon for when the small icon is required for display (ie. small icons in Explorer), or a 32x32 icon for display when the large one is required (ie. large icons in Explorer). Also, if you peek in M$ (and other) applications, you will see multiple icons of differing sizes and palettes all under the same title.

Just check for the current resolution and change the icon handle of the application... Of course, you have to create new icons in your resource (for this "how to", please see tip "How to store "everything" in your EXE file").

Put this in the project (.DPR) file of your application source:

  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  CASE GetDeviceCaps(GetDC(Form1.Handle), HORZRES) of
     640 : Application.Icon.Handle := LoadIcon (hInstance, 'ICON640');
     800 : Application.Icon.Handle := LoadIcon (hInstance, 'ICON800');
    1024 : Application.Icon.Handle := LoadIcon (hInstance, 'ICON1024');
    1280 : Application.Icon.Handle := LoadIcon (hInstance, 'ICON1280');
  END;
  Application.Run;
Well, that's all !     (Tip found in a msg of Gerry Jacobs.)




How to store "EVERYTHING" in your EXE file

By supporting resource files, Delphi gives you a great way to store static file contents like animated cursors, AVI videos, pictures or other nice typa things inside your .EXE files. In the following example, the .AVI video file myavi.avi will be stored inside the .EXE file:

  • Define a constant to be used to refer to the AVI:
        CONST
    	   ID_AVI_FILE = 123;    { assign whatever number you want.}
    	
  • Create a resource file MyRes.RC. In the file, have the following line (you can repeat steps 1 & 2 to add multiple AVI's):
    	IDS_AVI_FILE AVI myavi.avi
    	
  • Compile the RC file to a RES with the command:
    	BRC32 -r MyRes.RC
    	
  • Include the resulting MyRes.RES file in your project:
    	{$R MyRes.RES}
    	
  • Now we're going to add code to access the contained AVI video: put a TAnimate component on the form, and assign it a name (in this example, the AVI is named "AviClip"). Then, in the FormCreate() event, add the following to start playing the clip:
        
    	WITH AviClip DO BEGIN
    	   ResID := IDS_AVI_FILE;	{ Load AVI }
    	   ResHandle := hInstance; { this line must be placed after assigning ResID }
    	   Active := TRUE;	{ start playing immediately }
    	END;
    	
Well.. that's all !
After popular demand, here comes a similar procedure to load a JPEG file from a resource:
	
procedure LoadJPEGfromEXE;
var
  MyJPG : TJPEGImage; // JPEG object
  ResStream : TResourceStream; // Resource Stream object
begin
  try
    MyJPG := TJPEGImage.Create; 
    ResStream := TResourceStream.CreateFromID(HInstance, 1, RT_RCDATA);
    MyJPG.LoadFromStream(ResStream); // What!? Yes, that easy!
    Canvas.Draw(12,12,MyJPG); // draw it to see if it really worked!
  finally
    MyJPG.Free;
    ResStream.Free;
  end;
end;
See the second parameter of the CreateFromID procedure of the TResourceStream component? It's simply the resource index. You can include more than one jpeg in your executable just by adding a line for each jpeg (with a different index) in the resource script (.RC) file.
And finally, a totally different procedure to play a WAVE file stored as resource:
var
  FindHandle, ResHandle: THandle;
  ResPtr: Pointer;
begin
  FindHandle:=FindResource(HInstance, '<Name of your Ressource>', 'WAVE');
  if FindHandle<>0 then begin
    ResHandle:=LoadResource(HInstance, FindHandle);
    if ResHandle<>0 then begin
      ResPtr:=LockResource(ResHandle);
      if ResPtr<>Nil then
        SndPlaySound(PChar(ResPtr), snd_ASync or snd_Memory);
      UnlockResource(ResHandle);
    end;
    FreeResource(FindHandle);
  end;
end;

Another use for this technique can be to use this technique for program loaders like Setup programs, self-patching or self-checking utilities.
Just add the second program to the first one as a RCDATA resource. When the first program is started, it automagically extracts the second program to a temp file and starts it. Here's the code for the magic:

-----SECOND.RC file listing
SECONDAPPEXE RCDATA "c:\Apps\Second\Second.EXE"
------ EOF
In a DOS Window:
C:\>BRCC32 FIRST.RC
In first.dpr add the following line:
{$R SECOND.RES}
Then, whenever you want to save Second.Exe to file, do the following:
VAR
   SecRes : TResourceStream;
   pTemp : pchar;
   TempPath : string;
BEGIN
   SecRes := TResourceStream.Create(hInstance,'SECONDAPPEXE',RT_RCDATA);
   pTemp  := StrAlloc(MAX_PATH);
   GetTempPath(MAX_PATH, pTemp);
   TempPath := String(pTemp);
   StrDispose(pTemp);
   SecRes.SaveToFile(TempPath+'Second.EXE');
   SecRes.Free;
   WinExec(PChar(TempPath+'Second.EXE'), SW_SHOW);
END;
Optionally, you can use CreateProcess instead of WinExec. You can also use the API call GetTempFileName to make sure Second.EXE receives a unique filename in the directory returned by GetTempPath. Also, in the code above, I am presuming that the path returned by GetTempPath ends with a backslash ( \ ) character. You should also check for that.
Last Tip partially provided by Alex Simonetti.

More information can be found at the following pages:


Create an elliptic window

VAR
  h: THandle;
begin
  h := CreateEllipticRgn(40, 40, 300, 200);
  SetWindowRgn(Handle,h,TRUE);
Well.. that's all !




Search your help file for a key word

Usually, you will search for a key word like the following:

 procedure HelpSearch(sHelpName,sSearchKey:string);
 var
   pc:PChar;
 begin
   Application.HelpFile:=sHelpName;
   pc:=StrAlloc(100);
   StrPCopy(pc,sSearchKey);
   Application.HelpCommand(HELP_PARTIALKEY, LongInt(pc));
   StrDispose(pc)
 end;
Instead, you could use following code:
 procedure HelpSearch(sHelpName,sSearchKey:string);
 var
   pc:array[0..99] of char;
 begin
   Application.HelpFile:=sHelpName;
   StrPCopy(pc,sSearchKey);
   Application.HelpCommand(HELP_PARTIALKEY, LongInt(@pc))
 end;
The important things is the low bounds of array must been 0.

Tip provided by Christo Tsvetanov.




Optimizing File Name display length

In some situations you might have to display file names at a limited length - whether this length is enough to show the full name or not. Delphi's function MinimizeName helps you to "fit" this space by truncating and adding a "continue"-symbol to it.




INI file caching

Personally, I prefer using INI files for saving configuration data of my programs. This gives me an easy way of helping them if I want to prevent that they had to hack around in the dark deeps of the ever-growing Windows registry. Here are two tips from my experiences with using INI files:

  • Save your INI files in the main directory of your program
    I think that preventing to spread files all over our user's PC's is a good habit. Filling up his or her Windows directory with one more INI file (often forgotten when removing programs !) isn't required anymore if you just open/create your INI file(s) with the line:
    MyIni := TIniFile.Create(ExtractFilePath(Application.ExeName)+'MyApp.INI');
  • Opening INI files as text
    In some cases it could be required that you open your INI file as text. This might lead into problems if you concurrently do this after your file has also been opened using the INI file functions since Windows works on a cached version of the INI file once it was opened. It could happen that your changes won't be saved correctly... I have spent many hours with searching for the reason for those problems until I found the following way to flush Windows' cache:
    WritePrivateProfileString(NIL,NIL,NIL,'MyIni.INI');
    If all three parameters are NULL, the function flushes the cache. Note that the function always returns False after flushing, regardless of whether the flush succeeds or fails.




Interesting Delphi Keystrokes !

  • To indent/outdent a block of text:
    1. Mark the text
    2. [Control]-[Shift]-[I] to indent entire block
    3. [Control]-[Shift]-[U] to indent entire block
    Bonus: It autorepeats! Cool..
  • Keyboard recorder:
    [Control]-[Shift]-[R]: record your keystrokes (press again to stop recording)
    [Control]-[Shift]-[P]: playback recorded keystrokes
  • [Control]-[click] on a token to navigate to the declaration
  • [Control]-[Shift]-[Up/Down arrow] to navigate between function interface and implementation
  • [Control]-[Shift]-[G] to insert a new GUID in the editor
  • Hold down [Control] while dragging a window in the IDE to prevent it from docking
  • [Esc] while dragging a window in the IDE to cancel the move/dock
  • [Control]-[Space] to force Code Completion
  • [Control]-[Shift]-[Space] to force Code Parameter Insight
  • [Control]-[J] to use a Code Template
  • [Alt]-[Shift]-[Up/Down arrow] to move cursor up/down and select the column above/beyond
  • Use the wheel on your IntelliMouse (or compatible) to navigate in the editor
  • All of the debugger window hot keys are [Control]-[Alt]-something - making them easy to remember
  • [Control]-[Shift]-[C] early and often to complete class declarations and method implementations!
  • [Control]-[E]: incremental search. When some letter is wrong - click [Backspace], this will delete it, and continue typing. Delphi also remembers your last searched word, and you can repeate search with [F3]!
  • Mark string in modile with [Ctrl]+[Shift]+<number from 0 to 9> (or [Control]+[K]+<number from 0 to 9>). This will show a small green square with your number on the gutter. To find this, mark click [Control]+<your number>. To delete it, back to the [Control]+[Shift]+<your number> when the cursor on the marked string.
  • [Control]+[Shift]+[T] deletes the word right to cursor position
  • [Control]+[Shift]+[Y] deletes the whole string right to cursor position
  • [Control]+[Backspace] deletes the word left to cursor position
  • To put multiple copies of the same component on a form:
    1. Hold down shift and click on the desired component (it goes flat)
    2. Then each click on the form leaves a new component!
    3. click on the 'cursor' component to turn it off!
Some of these tips found here, thanks also for some contributions by Stas Kashepava and don't forget to read the interesting article by Marco Cantu about Delphi 5-related stuff.

Hidden Secrets

- In Delphi 1 go to 'about' and do ALT + AND and see a picture of a "Mr. Anders H." ;-) winking !
- or ALT + TEAM and see names of the team
- or ALT + DEVELOPERS and see developers
- or ALT + QUALITY and see names of quality team (Delphi 3..5 C/S only)
- or ALT + VERSION to see the internal version number (Delphi 2 only)
- or ALT + CHUCK and see something like an animated picture of Chuck J (D3)
- or ALT + JEDI to see a list of the Team JEDI members (Delphi 5 only - then press Cursor down to see a secret message.. ;)

A webpage dedicated to those "Easter Eggs" can be found here and here.

But now: Fun over .. back to work !<g>

(and don't forget to tell me if you found more secrets and "unknown" keystrokes !)
These tips were discovered by Frank Cowan and Tim Little.


Translating Delphi 3 VCL messages

To translate the Delphi 3.0 VCL messages, you need additional source files. They are stored at Borland's website from where you can download them by clicking on the link at my "Delphi updates and patches" page.

For example, to translate the button captions displayed by the MessageDlg function:

  • copy consts.pas
  • translate the SMsgDlg... strings
  • put consts.pas in the project search path
  • recompile the project

See the information on ResourceString in the help for more information.
(From the Borland website)




Simultaneous 16- and 32bit-Development

If you need to develop 16bit- and 32bit-versions of the same program simultaneously, you just have to consider the following:

  • Delphi 1 DFM files can be read by Delphi 2 and 3, but not vice versa. Start your work in Delphi 1, then port to Delphi 2/3.
  • Any Delphi 2/3 enhancements can be "protected" with IFDEFs in the .PAS files, so the same .PAS file can be used by all Delphi versions:
    	{$IFDEF ver80}
    	   this is Delphi 1 specific..
    	{$ENDIF}
    	{$IFDEF ver90}
    	   this is Delphi 2 specific..
    	{$ENDIF}
    	{$IFDEF ver100}
    	   this is Delphi 3 specific..
    	{$ENDIF}
    	{$IFDEF ver120}
    	   this is Delphi 4 specific..
    	{$ENDIF}
    	{$IFDEF ver130}
    	   this is Delphi 5 specific..
    	{$ENDIF}
    	{$IFDEF ver140}
    	   this is Delphi 6 specific..
    	{$ENDIF}
    	{$IFDEF ver150}
    	   this is Delphi 7 specific..
    	{$ENDIF}
    	{$IFDEF WIN32}
    	   this is specific to Delphi 2 and 3..
    	{$ENDIF}
    	{$IFDEF ver93}
    	   this is specific to C++ Builder..
    	{$ENDIF}
    	{$IFDEF ver110}
    	   this is specific to C++ Builder 3..
    	{$ENDIF}
    	

  • The only serious incompatibility are the .DCR-files. You will have to load them with the Delphi 2/3 Image-Editor, and save them again to convert a 16bit .DCR-file to a 32bit one.




CodeInsight saves time

If you type the name of a unit in the Delphi code editor and add a point, the code completion function enters in action and displays the types, variables, procedures and function in the interface part of the named unit.
You can get the same effect by just pressing Ctrl+«space».

Tip: if you type 'self.' in an obect's method, you also get a list of properties, variable, events, etc. - related to the "current" object.



Adding the Compile date automatically to a project

Create a mini app (30k) called TODAY.EXE with the following code
(note this is a .DPR-file with the main form removed from the project !):
 Program Today;
 USES
   Windows,SysUtils;
 {$R *.RES}
 VAR
   f       : TextFile;
   i       : Integer;
   dd,mm,yy: word;
 BEGIN
   DecodeDate(Date,yy,mm,dd);
   AssignFile(f,'c:\windows\Today.inc');
   Rewrite(f);
   WRITELN(f,'CONST');
   WRITELN(f,'_Day  : WORD = '+IntToStr(dd)+';');
   WRITELN(f,'_Month: WORD = '+IntToStr(mm)+';');
   WRITELN(f,'_Year : WORD = '+IntToStr(yy)+';');
   CloseFile (F);
 END.
 
Compile this project and move the resulting TODAY.EXE to the Windows directory. Edit WIN.INI and add the following line to the [windows] section:
 { assuming "windows" as the name of your Win-Dir: }
 run=c:\windows\today.exe
 
This executes TODAY.EXE each time windows is booted (presumed daily) and updates the constants which are stored in "c:\today.inc" (an include file).
Now all you have to do to access these constants is to add the include file to the unit you wish to use them in with the following statement:
 {$I c:\windows\today.inc}
 { Now you could do the following...}
 LblCompileDate.Caption :=
     IntToStr(_Day)+'/'+IntToStr(_Month)+'/'+IntToStr(_Year);
 
(found somewhere on the web - don't remember where, sorry.)


How to speed up your app and save resources

When using Tables and Databases:

  • index your table to speed up your searches !
  • avoid reading the whole thing, better use
    a) conditional indexes (preferred) or
    b) queries
  • sometimes using special algorithms is better than doing manual searches / sorting on tables
  • don't use too many fields per table !
    Having more than 100, 120 fields in one table can slow down your app because of exhaustive memory useage and forced data transfer. Better split up the informations you need in more tables and
  • open only the tables you need !
    This one is a bit tricky to decide. Opening a table takes its time, so if you had to open and close tables very often in a special part of your program, better keep them opened permanently.
    If your app is built of several parts (for example: one part for maintenance of your clients, one for articles, one for suppliers, one for statistics etc.), then you shouldn't keep ALL of your Tables opened permanently - instead open just the tables you need in the FormCreate-procedure of the individual Form and close them again in its OnClose procedure. This can also prevent locking and update-problems.
  • when using ranges with Server databases:
    Server databases are set oriented and, therefore, do not understand record navigation very well. If you are trying to change the records in the range you should get much better performance by using an UPDATE query (to see the SQL generated by SetRange use the SQL Explorer).
    The fastest way to change a bunch of records in a server table is alway with an UPDATE query that runs entirely on the server and doesn't require you to read the records to client and send the updates back.
  • Generally: at some database operations using TQuery is much faster than performing all and everything only by TTable methods. Try it !
  • Make use of TTable.DisableControls !
    Updating all of the controls connected to your dataset takes its time - save this time by calling DisableControls .. after performing all the required operations, call TTable.EnableControls (evt. followed by TTable.Refresh) and everything will be updated again !
  • Avoid OnCalcFields unless necessary ! This function gets called like you wouldn't believe - that's why it can slow your app down enourmously...
    Instead you could make use of the TDataSource.OnDataChange event to update anything special on your form when a record changes.
  • Order the results of queries by indexed fields !
    Using dBase Tables with SQL can be very slow, because the Server processes the query locally (= on the server) and then returns only the result, which is not the case with dBase tables, which must be transported entirely through the net to your client, processed on your client, which then shows the result. If you have a slow net and large dBase tables this becomes very, very slow. Using Paradox files instead can really speed up things if you have a primary index in which you search for the value (the TQuery looks up for the primary index and tries to use it !).
  • Use QuickReports with tQuery !
    It's faster than ReportSmith and gives unlimited queries. (tQuery with ChartFX is also pretty cool!)
  • Avoid tQuery in Database Grids and so forth
    - it appears too slow.
  • Could you make use of the SetRange function ?
    It beats anything around for speed. Although it works on an IndexFieldName, you can trick it by creating filtered indexes. This allows you to have special choice combo-boxes that only display appropriate data.
    (Table1.SetRange([Cust_A],[Cust_A]) will only show Cust_A data...) For example IndexName: 'LiveOrders' (index on Cust_No for Paid=0 TAG LiveOrders) 'paidorders' (INDEX on Cust_No for Paid=1 TAG PaidOrders)
    Both work as IndexFieldName='Cust_No', but you set the index as follows: Table1.IndexName='LiveOrders';... Table1.SetRange([var],[var]);
    (Apollo has a similar function that is even a bit more flexible.)
  • Consider using 3rd party database engines (see Link at my Delphi Tools area)!
    Engines like Advantage Database or Apollo not only use optimized search and query algorithms, they also support combined, conditional and full-text indexes. Additionally you'll only need 2, relatively small DLL-files instead of the big-sized BDE.
Other tip collections about program optimization:

Save resources in TNoteBooks, TTabbedNotebooks and TTabSheets

NoteBooks, TTabbedNotebooks and TabSheets inhale a relatively large amount of a computer's resources. To free the resources of all components which are currently not displayed (because they are on a "hidden" page), place the following piece of code in the TNotebook's TTabset TabClick-Event:
   FOR i := 0 TO nbAdresses.Pages.Count-1 DO
       IF i <> nbAdresses.PageIndex THEN
          TfrmAdressForm(nbAdresses.Pages.Objects[i]).DestroyHandle;
Because Delphi keeps the objects stored internally, all of them will nevertheless correctly be displayed when you change to the "cleaned" pages again. To accomplish a similar task with TTabbedNotebooks, put the following at the end of your tTabbedNotebook's onChange event (From Sid Gudes):
   IF AllowChange THEN BEGIN
      WITH TabbedNotebook1 DO BEGIN
         LockWindowUpdate(Handle);
         THintWindow(Pages.Objects[PageIndex]).ReleaseHandle;
         TWinControl(Pages.Objects[NewTab]).HandleNeeded;
         LockWindowUpdate(0);
      END;
      TabAt[nbPacket.PageIndex] := TabPacket.TabIndex;
   END;
And to restore the handles:
   IF Sender is TTabbedNotebook THEN 
      WITH TTabbedNotebook(Sender) DO BEGIN
         CurrentPage := TWinControl(Pages.Objects[PageIndex]);
         LockWindowUpdate(Handle);
         TWinControl(Pages.Objects[NewTab]).HandleNeeded;
         LockWindowUpdate(0);
   END;
Some caveats on these methods:
  1. if you have any tComboBoxes with style of csDropDownList, you'll lose the values in them (csDropDown works OK); you can write some code to cycle through the components and save the values beforehand, then restore them afterwards.
  2. this frees resources as the user clicks through the pages/tabs, but at FormCreate time all pages will be created, so the resource hit will be high at the beginning.
  3. carefully watch the edit mode of the tables of the form - in my apps I always lost the edit mode after changing the pages/tabs and had to recall it.

General speed issues

  • Use BeginUpdate / EndUpdate when doing large screen operations ! (btw: Since WM_SETREDRAW and LockWindowUpdate seem to accomplish the same task, I think it is easier to stick with the BeginUpdate/EndUpdate Delphi methods than dropping to the Windows API. Not to mention the fact that LockWindowUpdate can cause horrendous flickering in any other apps visible on the screen :). )

General: saving resources

Some tips which - for example - can help you prevent this hated message "Out of resources - can't create window" ...

  • Avoid using Form-Autocreation !
    If you create new forms for you project by selecting File..New.. with Delphi, it creates code in your project file (.DPR - you can open it by pressing Ctrl-F4), which will create each form automatically at the startup of your Application:
    	USES
    	  MainForm in 'MainForm.pas',
    	  ChildFrm1 in 'ChildFrm1.pas',
    	  ChildFrm2 in 'ChildFrm2.pas',
    	  (..etc..);
    	[..]
    	BEGIN
       	  Application.CreateForm(TMainForm ,MainForm);
       	  Application.CreateForm(TChildFrm1,ChildFrm1);
       	  Application.CreateForm(TChildFrm2,ChildFrm2);
    	  (..etc..);
    	END;
    	
    This doesn't only slow down the startup, but also uses huge amounts of memory !
    Instead, let Delphi create only your "Main-Form" automatically, and remove any other forms from the USES-clause, remove any other auto-creations from the project source and create those other forms in the source of the main-form "by hand" - and only where you really need it:
    	procedure MyMainForm.mnuClientsClick(Sender: TObject);
    	BEGIN
    	   ChildForm1 := TChildForm1.Create(Self);
    	   ChildForm1.ShowModal; { if it's a MDI-form, use ChildForm1.Tile; }
    	   ChildForm1.Release;  { will save additional resources ! }
    	END;
    	
  • Even applications less than 200kb in size use approx. 1 MB of RAM because the Delphi RTL obtains much of it's variant support from OLEAUT32.DLL. However, if you're not using OLE you can free those libs by putting
    	FreeLibrary(GetModuleHandle('OleAut32'));
    	
    in the OnCreate event of your MainForm or your project source. It will unload OleAut32.dll and OLE32.dll, which use about one meg.
  • Release memory of closed forms by putting either
    	MyForm1.Release;
    	
    in the calling form (after closing it) or by placing the line
    	Action := caFree;
    	
    in the OnClose-Event of your Form.
  • Check: in your program there should never be more than 4 or 5 windows opened at the same time. Each window consumes quite a lot of dynamic resources.
  • Use TBevel's rather than TPanels !
    The TPanel-component needs a separate window-handle whereas a TBevel is only a "painted" are on your form. So you can again save resources by just "painting" a Bevel on it instead of filling it with resource-eating Panels...
  • Set "ParentFont=TRUE" on your forms !
    By doing that, your app doesn't have to load and keep font-instances for each individual component anymore.



How to fix "Data segment too large" errors

Many of us had to find out that Windows (not a Delphi problem !) has serious limitations about how many controls you can use on forms. Especially large forms with over 100, 120 controls (i.e.: Edit-Controls, Buttons, but also Panels and Images) can produce loading problems ("error creating form xx").
Another problem is the limited size of the Data Segment, which is only 64 KB in size and cannot be enlarged anyhow. Since the Delphi RTL already uses a few KB's of this space, many of us already had contact to "Compiler error 49 ("Data Segment too large")...
How can you minimize the use of the Data Segment ?

The Data Segment commonly consists of the following:
  • Global Variables
  • Stack space (1 MB automatically -virtually- allocated)
  • Local Heap
  • PChar Literals
  • Typed Constants
  • Virtual Method Tables of objects & classes
You can :
  1. Try to make your strings smaller
    Use Mystring: String[size] instead of unqualified strings.
    Example:
    	VAR StrX: String;    { takes 255 bytes }
    	VAR StrX: String[5]; { takes up on ly 5 or 6 }
    	
  2. Try to reduce your Stack space in the Linker options
    Usually you can reduce it to a much smaller value without troubles.
  3. Split the data into different units.
  4. Put your things (see list above) in group blocks !
    The Delphi compiler is smart enough not to link in any data that isn't used, but this is only possible if everything in the block where this data was declared isn't used. So, put your things that belong together in their own VAR.., TYPE.. and CONST.. blocks to avoid linking them in if you might use only one part of it.
  5. Allocate static data (see list above) on the heap and make it dynamic !
    Means: use pointers and GetMem/New ! This will again decrease execution speed and requires another layer of indirection (a pointer dereference).
    Example: instead of coding
    	TYPE
    	   a1: ARRAY[1..] OF
    	   a2: ..
    	
    you should code:
    	TYPE 
    	   TArrRec = RECORD
    	               a1: ARRAY[1..] OF
    		 		   a2: ..
    		 		 END;
    	VAR
    	   ArrRec: ^TArrRec;
    	BEGIN
    	   TRY
       	     NEW(ArrRec);
    		 { use your Data like   ArrRec^.a1[3];   here }
    	   FINALLY
    	     DISPOSE(ArrRec);
    	   END;
    	END;
        
  6. Calculate values each time you need them and don't use lookup tables instead.
    Well, this uses more code space and execution time, but fixes your bottleneck..
  7. Windows programs can eliminate the Data Size used up by PChar literals by using String Tables. This also enables the translation (internationalisation) of the application itself - Delphi already uses String Tables heavily !
  8. Move all your strings to a resource .RES file and load them dynamically.
  9. "What I usually do is to define one "global" object. It's usually a class - created when the program starts and destroyed as it goes away.
    All of the things that the various units of the program need to talk among themselves and to each other are stored in Global. When the things that are global need to be larger than 32k or so (even in Win95..transportability is still important to me..), it becomes a method. Voila, now the class hides all of the details of physically locating and storing/retrieving the global information - exactly, of course, as a class ought to do.
    It works. Even in Win95-land, it works." (by Sundial Services)
  10. More questions ? Maybe Klaus Hartnegg's 64 KB-FAQ can help you !
  11. Find related information on Borland's Pages: [1], [2] !
(Some hints to this topic came from Bob Swart)



Fix "Stack Overflow" errors (=Runtime Error 202)

  • In Delphi 1, under "Options"/"Compiler"/"Linker", you set stack amd local heap sizes. This is equivalent to the $M compiler directive in TP - and probably in Delphi, too.
    The trick is to fit these and your data area into 64k, i.e. make them both 16.384, and you have 32k left for variables. If you then get "Data segment too large" (see above!), you have to shove some of your variables onto the heap with Getmem() etc.
    (by Jim Redmond)



Reduce the .EXE-size of your app

One default blank form in an application produces the following EXE size:
  • Delphi 2: 157,184 bytes
  • Delphi 3: 179,712 bytes
  • Delphi 4: 282,112 bytes
  • Delphi 5: 294,912 bytes
  • Delphi 6: 359,424 bytes
  • Delphi 7: 368,128 bytes (+134.2% against Delphi 2!!)
Frightening, isn't it? - so, how could you make your EXE files smaller?
  • Use CASE.. statments rather than IF..ELSE.. clauses.
  • In every .PAS-file that will be linked in your project, place the following line to the top of your code:
    	{$D-,L-,O+,Q-,R-,Y-,S-}
    	
    Also add this line to your project source (.DPR).
    {$D-} will prevent placing Debug info to your code.
    {$L-} will prevent placing local symbols to your code.
    {$O+} will optimize your code, remove unnecessary variables etc.
    {$Q-} removes code for Integer overflow-checking.
    {$R-} removes code for range checking of strings, arrays etc.
    {$S-} removes code for stack-checking. USE ONLY AFTER HEAVY TESTING !
    {$Y-} will prevent placing smybol information to your code.
    After doing this, recompile the whole project - your .EXE-size should magically have been reduced by 10-20 %..
  • If you link in graphics, they don't need to have more than 16 or 256 colors.
  • If the goal is really and only .EXE size, load your graphics by code ("manually") instead of embedding them already during design time in each and every form which uses them. A detailled description of this tip can be found on this page after clicking here.
  • If you include resource-files, they should only content the resources you really need - and nothing more.
  • If you use Delphi 1, finally run W8LOSS.EXE on your .EXE file (not required for 32bit-Apps).
  • Delphi 2 produces larger .EXE sizes than Delphi 1 - 32 bit code demands its tribute..
  • Delphi 3 produces larger .EXE sizes than Delphi 2 - main reason: market pressure leads to more "fundamental" support of "bells and whistles" - quality and usefulness/efficiency/productivity doesn't seem to be a real criteria in M$-times...
  • Delphi 4 produces larger .EXE sizes than Delphi 3 - main reason: market pressure leads to more "fundamental" support of "bells and whistles" - (..to be continued like above)
  • check the "Show Hints" and "Show Warnings" options on the Project|Options|Compiler page, then rebuild your project. It will show you every variable and proc/func that isn't being used. You might be able to trim a little there, too.
  • Clean your USES.. clauses for all unneeded units!
    The so-called "SmartLinking" doesn't always remove all unused code. In a large project you could possibly spare 100k in the EXE by that.
  • Place Bitmaps/Glyphs/etc. in DLL's instead of in *.RES/*.DCR files !
    If you place fx some large bitmaps in a .RES, these will compiled into the EXE. Remove the .RES-declarations, instead place them in a new project like this:
        LIBRARY My_Extern_RESes;
    	USES EmptyUnit;
        {$R MyRes1.RES}
        {$R MyRes2.RES}
        ...
        INITIALIZATION
        END.
    	
    The unit AEmptyUnit is a completely empty unit. You have to use a unit like this because any other unit will place unnecessary code in the final DLL. When you want to use an image (and typically, you only want to load it once), you can do it like this :
        MyHandle := LoadLibrary('My_Extern_RESes.DLL');
        TButton1.Glyph.Handle := LoadBitmap(MyHandle,'My_Glyph');
    	
    Placing Glyphs, Bitmaps, String-const's etc. in DLL's can really reduce a projects EXE-size. (Tip came from David Konrad)
  • Basis rule: the more forms and units you add, the larger your exe becomes. When you had one huge form, you only had one form class - even when you had 500+ things on it! This creates only one runtime type information for that class. Now, when you split this into 17 units, each class in that unit requires its own RTTI. Now, this runtime information can get large, collectively, with a lot of redundant information compared to when you only had one huge class. Also, if each of the 17 units had its own form, your end product must contain resource information for all 17 of those forms even though most of that information may be redundant. One reason why this happens is because the Delphi compiler doesn't optimize resources - but I don't know of any compiler that does. So, if you had two separate forms which are identical in look, you'll have 2 copies of the resource in your .EXE.
    This leaves some work for the programmer to be creative in maximizing resource reuseability. (Tip came from Young Chung)
  • Delphi 3 and up allows you to use packages (runtime libraries) which can be easily enabled in your Project options. Especially if you have to update your application often, this could be interesting for you: the runtime libraries just have to be deployed once (there is even a chance that important Delphi packages like vcl30.dpl or vcl40.bpl already exist on your user's computer) and to update your (then shrinked) executable whenever an update is required (please consult the helpfile for further details).
  • If your EXE file has to be super-small and you can go without Delphi's powerful IDE and design features, you can also program without including the VCL (visual component library) in your USES clause. Here is a small EXE program which compiles to about 30 kb (by Frank Peelo):
    Program HelloWin;
    { Standard Windows API application written in Object Pascal. }
    Uses
      Windows,
      messages,
      MMSystem;
    Const
      AppName : pChar = 'HelloWin';
    
    Function WindowProc(Window:HWnd; AMessage, WParam, LParam:LongInt):
    	LongInt; StdCall; Export;
    { The message handler for the new window }
    Var
      h : hdc;
      ps: tPaintStruct;
      r : tRect;
    Begin
      Result := 0;
      Case AMessage of
      WM_Create:Begin
    			PlaySound('C:\WINDOWS\MEDIA\Musica Windows Start.wav', 0,
    			Snd_FileName or Snd_Async);
    			Exit;
    			End;
      WM_Paint: Begin
    			h := BeginPaint(Window, Ps);
    			GetClientRect(Window, r);
    			DrawText(h, 'Hello Winblows!', -1, r,
    			DT_SingleLine or DT_Center or DT_VCenter);
    			EndPaint(Window, ps);
    			Exit;
    			End;
      WM_Destroy:Begin
    			PostQuitMessage(0);
    			Exit;
    			End;
      End;
      Result := DefWindowProc(Window, AMessage, WParam, LParam);
    End;
    { Register the window class }
    Function WinRegister:Boolean;
    Var
      WindowClass: TWndClass;
    Begin
      WindowClass.Style := cs_HRedraw or cs_VRedraw;
      WindowClass.lpfnWndProc := @WindowProc;
      WindowClass.cbClsExtra := 0;
      WindowClass.cbWndExtra := 0;
      WindowClass.hInstance := HInstance;
      WindowClass.hIcon := LoadIcon(0, idi_Application);
      WindowClass.hCursor := LoadCursor(0, idc_Arrow);
      WindowClass.hbrBackground := HBrush(GetStockObject(White_Brush));
      WindowClass.lpszMenuName := NIL;
      WindowClass.lpszClassName := AppName;
      Result := RegisterClass(WindowClass)<>0;
    End;
    
    Function WinCreate:HWnd;
    Var
      HWindow:HWnd;
    Begin
      hWindow := CreateWindow(AppName,
                              'The Hello Program',
                              WS_OverlappedWindow,
                              CW_UseDefault,CW_UseDefault,
                              CW_UseDefault,CW_UseDefault,
                              0,0,HInstance,NIL);
      If hWindow<>0 then Begin
        ShowWindow(hWindow, CmdShow);
        UpdateWindow(hWindow);
      End;
      Result := hWindow;
    End;
    { Main: Set up window, then give it messages until done.}
    Var
      AMessage: TMsg;
      hWindow: HWnd;
    Begin
      If not WinRegister then Begin
        MessageBox(0, 'Register Failed', NIL, mb_Ok);
        Exit;
      End;
      hWindow := WinCreate;
      If hWindow=0 then begin
        MessageBox(0, 'Register Failed', NIL, mb_Ok);
        Exit;
      End;
      While GetMessage(AMessage, 0, 0, 0) do begin
        TranslateMessage(AMessage);
        DispatchMessage(AMessage);
      End;
      Halt(AMessage.WParam);
    End.
    
  • Learning how to read a detailed .map file could easily allow you to reduce code size. First you can scan it for VCL units that you no longer use, as mentioned above (e.g., you had a grid on one form in the past, but have grids on no forms now, Delphi is not smart enough to remove the reference, hence all the VCL classes in Grids unit bloat your EXE). Second you can scan it to see which units are the largest. You may find ways to specifically optimize those units for size to gain the greatest benefit.
    See this link, or below for some info: Understanding Linker Generated 32-bit MAP files
    A sample map file line would be:
    	0001:000C2340 00012E74 C=CODE     S=.text    G=(none)   M=jpeg
    	
    Headers for this would look like:
    	Address       Size     Segment    SegType    SegGroup   Unit
    	
    The most important part to you is the size. For example, the jpeg unit above has a size of $12E74 or about 77K! Are you using Jpeg support, or do you only have one small JPEG? Might a BMP be a more compact solution when the unit's file size is considered as part of the equation? Of course, if you have many images, JPEG is sure to pay for itself many times over (in most cases, anyway).
    Also important is the SegType. '.text' segments are compiled code, '.data' segments are initialized constants and global variables, '.bss' are zero initialized variables. Both '.text' and '.data' segments use up about the same amount of space in the .EXE. '.bss' segments use virtually zero space in the .exe, but they do use the listed amount of space at run-time. (explanation by Colin P. Sarsfield given in the Borland Newsgroups)

    Tip: an IDE expert is included in the Jedi Code Library which adds a new menu item to the Delphi Project menu: "Analyze Project". It creates a detailed MAP file, rebuilds the project and displays the list of used units, forms and their size! [Screenshot]
  • a VCL replacement project named XCL exists, linked from my "Delphi Tools" page, which could be of help here, too.
  • If your main goal is distributable size, you could also consider
    • using EXE/DLL packers like Blinker, ASPack, NeoLite etc. for program compression
    • or at least stripping the relocation section from Win32 PE EXE files with utilities like StripReloc
    • and, to shrink the complete package to a minimum: use alternative setup programs like our award-winning INF-Tool to create your distribution package. INF-Tool creates today's smallest setup packages available! Since users usually prefer to download smaller programs from the Internet (rather than to 'control' the installed program size after setup), you can focus on functionality and features instead of having to think about where to save a few code lines or if using a helpful VCL component "really pays".
    (links to all mentioned programs can be found on my Delphi Tools page.)



How to start Delphi without Splash Screen

Start DELPHI32.EXE with parameter -ns ... and save invaluable millisecs !! :)
(works only with Delphi2 and Delphi3).

More good reasons why Delphi is called a RAD tool..

  • You can drag Tables AND Fields onto your Form using the Database Explorer: Try it !
  • By using bookmarks in the code editor you can jump around like you need it.
    Set bookmarks by pressing <Ctrl>+<Shift>+"0".."9" and jump to them by pressing <Ctrl>+"0".."9". (see Configuration help when you use other keyboard settings than the default).
  • In the Object designer you can jump to the parent of the highlighted component by pressing <Esc>.
  • You can easily change the "Source" of your forms by pressing Alt-F12 !


Using DataSources in other Forms

If you want to use Tables, Queries or DataSources in secondary forms, you could place a new DataSource and write in the OnCreate-Event of the second form:
DataSource1.DataSet := Form1.Table1;
or you can assign the DataSource of the data-aware components to Form1.DataSource1. From Delphi 2 on this is also possible with the Object Inspector as well. In Delphi 1 you loose design time view of the data.
Another way would be to use DataModules (Delphi 2 and higher). See the manual.


Richey's Delphi-Box at http://come.to/Delphi-Box


[e-mail] [PGP] [Copyright]
Do not copy to other sites or include in commercial compilations without the written authorization from the author.