The Delphi Bug List

Visual Component Library (VCL)

General


The color codes indicate in which version(s) of Delphi the bug occurs and what its status is.
Latest update: 9 February 1999
Bug # Delphi versions Description
1 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
Stefan Hoffmeister has written some very useful comments about fixing bugs in Borland supplied code (read: the VCL and RTL units). If you intend to apply some of the bug fixes mentioned on the rest of this page, you should take note of these comments!
2 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
The VCL/RTL sources that come with the Professional and the C/S version do NOT match the compiled DCUs and packages as distributed by Borland.
3 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
Another list with VCL bugs that are not mentioned here yet: The Fookes Delphi Bug List
Here you will find a list of lesser known bugs that I (Eric Fookes) have come across in Delphi 1 and 2. I have included workarounds to many of them in my component packages.
4 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
The WM_EndSession message is not handled properly by the VCL.
105 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
The procedure ShortCutToKey (in the VCL unit Menus) has a bug.
108 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
Application adds icons to Win95 taskbar for each window.
453 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
Memory/resource leaks in Delphi 4 VCL
458 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
HintHook resource leak: Handle created by CreateThread not freed by call to CloseHandle
470 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
A Form's WindowState property is not reported correctly in the FormResize event when maximizing/minimizing/restoring. The WindowState property gets updated AFTER the FormResize event.
411 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 Drag and Drop
TDragObjects created in OnStartDrag are not freed
412 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 Drag and Drop
If the source of a drag'n'drop operation <> the focused control the complete drag'n'drop system can be killed...
408 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 Forms.pas
There's a memory loss of 4KB (or more) on every dynamic load of a DLL written in Delphi 3.0x
106 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 Printing / Printers
Printing will fail after Printer.Abort
107 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 Printing / Printers
Printing text uses wrong (too small) font size
14 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 TApplication
Run method does not honor the Run Minimized/Maximized setting.
493 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 TClipboard
TClipboard.HasFormat is declared as taking a word type parameter, but this should be a 32-bits (unsigned) integer.
29 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 TComponent
Writing a floating point property value to a stream and then reading it may go wrong.
30 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 TComponent
String property values of certain lengths will get damaged.
436 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 TControl
Anchored Controls are not resized if Form's initial WindowState set to wsMaximized
509 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 TControl
TControl.WM_CancelMode does not handle the Mouse state correctly
31 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 TCustomControl
Drag & Drop: OnStartDrag event occurs too early
32 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 TCustomControl
Drag & Drop: OnEndDrag: Target parameter doesn't always get correct value
44 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 TFont
TFont.Size and TFont.Height behave different from what's specified in the Help. This is interpreted as not a bug but a documentation error.
45 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 TForm
If you create a main form with WindowState=wsMaximized and Position=poScreenCenter, the main form displays incorrectly (sometimes about 10-15 pixels low and 20-25 pixels to the right)
46 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 TForm
TForm.Name is empty at runtime.
437 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 TForm
Forms do not honor the windows animation routine upon minimize/maximize
457 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 THintWindow
THintWindow has a resource leak: the Handle retrieved by GetWindowDC is not released by ReleaseDC
67 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 TObject
This applies to all descendants of TObject, IOW: to all class types!
Calling the Free method is not always safe
505 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 TRegistry
The on-line documentation for the TRegistry.OpenKeyReadOnly is incorrect.
506 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 TRegistry
The NT-specific comments in the on-line documentation for the TRegistry.DeleteKey method are incorrect.
532 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 TRegistry
TRegistry.ReadString does not raise an exception when the value does not exist.
90 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 TStringList
Find method does not always work.
91 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 TStringList
The Clear method does not Free the corresponding objects.
413 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 TStringList
TStringList.SaveToStream causes memory loss.
445 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 TStringList
Memory leakage with TStringList.AddObject (Sorted = TRUE; Duplicates = dupIgnore)
448 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 TThread
There's a documentation bug concerning TThread.OnTerminate. Contrary to what the Help says, it is not safe to free the thread object in the OnTerminate handler.
459 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 TThread
Race condition in TThread's implementation
102 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 TWinControl
TWinControl.Destroy destroys each of its controls before destroying itself, while it does not own its controls.
103 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 TWinControl
RecreateWnd is dangerous to API routines that rely on the window handle.

Bug #2; last modified: before April 1998
1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
N/A N/A Exists Unknown Unknown Unknown Unknown Unknown
General

The VCL/RTL sources that come with the Professional and the C/S version do NOT match the compiled DCUs and packages as distributed by Borland.

Description
This information originally appeared as part of an article in the German computer magazine c't.

Reported by Arne Schaepers; confirmed by Stefan Hoffmeister.

It appears as if the VCL source code shipped with Delphi 3 (build 5.53) does NOT match the compiled DCUs and packages as distributed by Borland.

At least one example of this mismatch can be found in MMSYSTEM.PAS, where the source code lists the TWaveOutCapsA record NOT as "packed" (which is a bug) while in fact in the DCU it *is* packed (which is correct).

Solution / workaround
Be very careful when re-compiling VCL source code; even if you manage to use the correct compiler settings (see this subtle, but nasty problem in DB.PAS), you need to be aware of the fact that the source code you use does not necessarily reflect the behaviour of the compiled DCUs and DPLs distributed by Borland.

Bug #4; last modified: before April 1998
1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
Exists Fixed Fixed Fixed Fixed Fixed Fixed Fixed
General

The WM_EndSession message is not handled properly by the VCL.

Description
Windows exit terminates delphi applications rudely.
When Windows is actualy closing down, it sends a WM_EndSession message. VCL does not handle this message.
Solution / workaround
By Hallvard Vassbotn
We add a hook to handle the message ourselves:
procedure TForm1.FormCreate(Sender: TObject);
begin
  Application.HookMainWindow(HookProc);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  Application.UnHookMainWindow(HookProc);
end;

function TForm1.HookProc(var Message: TMessage): boolean;
begin
  Result := false;
  if (Message.Msg = WM_EndSession) and WordBool(Message.wParam) then
    Halt;
end;
The cleanest solution would have been to call Application.Terminate or MainForm.Close. Unfortunately this will not always work because these are message based and Windows does not guarantee that any messages will be processed after WM_EndSession has been sent.

So my solution is to simply call Halt. This might seem drastic, but it actually calls all ExitProcs, that again call all destructors for all forms closing them down properly, calling a ExitProc that close down the BDE properly and so on.

This solution will not call your OnClose handler, but it will call your OnDestroy handler. Put all your essential clean-up code there and you should be ok.


Bug #105; last modified: before April 1998
1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
Exists Fixed Fixed Fixed Fixed Fixed Fixed Fixed
General

The procedure ShortCutToKey (in the VCL unit Menus) has a bug.

Description
I (Ray Lischner?) am having some problems using the ShortCutToKey procedure. I would like to use it to load up some arrays with the shortcuts of some menu items at runtime. However, the shift output parameter is always empty. I have followed the example in the help quite closely with some strange results. I am at wit's end!
For example, I have: MyItem: TMenuItem on a form. It has a shortcut of Ctrl+M. My code looks something like this:
procedure test;
var a: Word;
 b: TShiftState;
begin
  ShortCutToKey(MyItem.Shortcut, a, b);
  {Now, if I interrogate A, it is what I would expect, but B will be [] }
  {Now if I say :}
  MyItem.ShortCut := Shortcut(a, b) ; {MyItem no longer has a shortcut.}
end;
Solution / workaround
Ray Lischner sent in a bug report to the Delphi Bug List describing this problem. His solution (checked: it works) is to create your own procedure as follows:
procedure ShortCutToKey(ShortCut: TShortCut; var Key: Word; 
                        var Shift: TShiftState);
begin
  Key := ShortCut and not (scShift + scCtrl + scAlt);
  Shift := [];
  if ShortCut and scShift <> 0 then Include(Shift, ssShift);
  if ShortCut and scCtrl <> 0 then Include(Shift, ssCtrl);
  if ShortCut and scAlt <> 0 then Include(Shift, ssAlt);
end;

Bug #108; last modified: before April 1998
1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
Gotcha Fixed Fixed Fixed Fixed Fixed Fixed Fixed
General

Application adds icons to Win95 taskbar for each window.

Description
Solution / workaround
Copied from comp.lang.pascal.delphi.misc; not checked

When you put these extra lines to you .DPR project, then your app will have only one icon on Win95 task bar, not one for every windows of your application.
program MyProgram;
Var
  wStyle: LongInt;
begin
  { Set the Win95 to have only one icon for your app. }
  wStyle := getWindowLong(Application.Handle, GWL_Style);
  wStyle := wStyle or ws_Caption;
  setWindowLong(Application.Handle, GWL_Style, wStyle);
  .
  .

Bug #453; last modified: 5-Aug-98
1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
Unknown Unknown Unknown Unknown Unknown Exists Unknown Unknown
General

Memory/resource leaks in Delphi 4 VCL

Description
Reported by Atanas Stoyanov
Atanas Stoyanov has started a dedicated Web Page on which memory/resource leaks in the Delphi 4 VCL and sample code are listed. The URL is: http://www.nidlink.com/~astoyanov/index.htm

Bug #458; last modified: 17-Aug-98
1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
Unknown Unknown Unknown Unknown Exists Fixed Fixed Fixed
General

HintHook resource leak: Handle created by CreateThread not freed by call to CloseHandle

Description
Reported by James D. Rofkar; checked by Hallvard Vassbotn
The resource leak can be detected with BoundsChecker for Delphi.

The problem is reproducible:

  1. Create a program that uses hint windows. Set the "ShowHint" property to true, and specify some "Hint" text.
  2. Run the program.
  3. Quit the program.
The cause is related to the HookHintHooks procedure in Forms.pas creating a new thread and assigning it to the HintThread variable. Unfortunately, in the UnhookHintHooks procedure of Forms.pas, the HintThread variable is not properly freed by a call to CloseHandle().

Comment from checker:
Confirmed. This has also been fixed in D4 (they didn't bother to check that HintThread <> 0 before calling CloseHandle, btw).

Solution / workaround
To fix:
  1. Edit the Forms.pas unit in your \Delphi\Source\VCL directory.
  2. Add the following: "if HintThread <> 0 then CloseHandle(HintThread);" to the UnhookHintHooks procedure. When finished editing it should resemble:
    procedure UnhookHintHooks;
    begin
      if HintHook <> 0 then UnhookWindowsHookEx(HintHook);
      HintHook := 0;
      if HintThread <> 0 then
      begin
        SetEvent(HintDoneEvent);
        if GetCurrentThreadId <> HintThreadID then
          WaitForSingleObject(HintThread, INFINITE);
        if HintThread <> 0 then
          CloseHandle(HintThread);
        HintThread := 0;
      end;
    end;
  3. Save Forms.pas
  4. Recompile Forms.pas to a new Forms.DCU file.
  5. Place the new Forms.DCU file in your \Delphi\LIB directory.

Bug #470; last modified: 28-Oct-98
1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
Absent Absent Absent Absent Absent Exists Exists Exists
General

A Form's WindowState property is not reported correctly in the FormResize event when maximizing/minimizing/restoring. The WindowState property gets updated AFTER the FormResize event.

Description
Reported by David Willis
To reproduce, check the value of the WindowState property in the FormReize event when maximizing and restoring. The value is incorrect. Delphi 1.02 and Delphi 3 give correct values. The attached code sample updates a label on a form with the currect value. Try maximizing and restoring.

To fix this bug, you need to edit the FORMS.PAS file. I haven't attempted a workaround that doesn't involve editing the VCL code, although I'm sure it would be possible by trapping the WM_SIZE message.

You need to adjust the TCustomForm.WMSize procedure so that the "inherited" line comes AFTER the case statement that sets the WindowState property. i.e.:

procedure TCustomForm.WMSize(var Message: TWMSize);
begin
  {inherited;}  // remove this line

  if not (csDesigning in ComponentState) then
    case Message.SizeType of
      SIZENORMAL: FWindowState := wsNormal;
      SIZEICONIC: FWindowState := wsMinimized;
      SIZEFULLSCREEN: FWindowState := wsMaximized;
    end;
  inherited;    // move line to here.
  RequestAlign;
  ......
I am using US English Delphi 4.0 Professional updated with the 12 August 1998 patch.

Bug #411; last modified: 7-May-98
1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
Unknown Unknown Unknown Unknown Exists Unknown Unknown Unknown
General - Drag and Drop

TDragObjects created in OnStartDrag are not freed

Description
Reported by Thorsten Engler
The Delphi Online Help says:

Use the OnStartDrag event handler to implement special processing when the user starts to drag the control or an object it contains. Sender is self, the control that is about to be dragged, or that contains the object about to be dragged.
The OnStartDrag event handler can create a TDragObject object for the DragObject parameter to specify the drag cursor, or, optionally, a drag image list. There is no need to call the Free method for the DragObject when dragging is over.

Let's take a look at the VCL Source:
in DragDone you'll find this line of code:

    if DragFreeObject then DragSave.Free;
the only other occurrence of DragFreeObject is here:
procedure DragInitControl(Control: TControl; Immediate: Boolean);
var
  DragObject: TDragObject;
begin
  DragControl := Control;
  try
    DragObject := nil;
    DragFreeObject := False;
    Control.DoStartDrag(DragObject);
    if DragObject = nil then
    begin
      DragObject := TDragControlObject.Create(Control);
      DragFreeObject := True;
    end;
    DragInit(DragObject, Immediate);
  except
    DragControl := nil;
    raise;
  end;
end;
as you can see DragFreeObject is set to false before the call to Control.DoStartDrag (which calls OnStartDrag...). If the DragObject is set to something other than nil (as the help text sugests for implementing own dragobjects) DragFreeObject is never set to something other than false.... so the Object is never freed!!

Bug #412; last modified: 2-Feb-98
1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
Unknown Unknown Unknown Unknown Exists Exists Exists Exists
General - Drag and Drop

If the source of a drag'n'drop operation <> the focused control the complete drag'n'drop system can be killed...

Description
Reported by Thorsten Engler; checked by Sergey Mishkovskiy
To reproduce the bug:
Solution / workaround
By Greg Chapman
it is possible to restore the drag system without shutting down the application. The reason for the bug is that, when you press the spacebar with a button focused, the button control (Windows, not the VCL) either captures the mouse or calls ReleaseCapture -- at any rate, it takes the mouse capture from the Window used by the DragObject to handle mouse messages. Having lost the capture, DragObject will wait forever for the WM_LBUTTONUP message telling it the drag is over. And since there is still a DragObject, the VCL will refuse to start a new drag. However, you can restore the drag system by calling CancelDrag, which will get rid of the old DragObject.

It seems anything which causes the DragObject's window to lose the mouse capture before the end of the drag will run into this bug. The best workaround would be for TDragObject's MouseMsg window method to handle the WM_CAPTURECHANGED message so that it cancels the drag automatically if something grabs the mouse capture. Unfortunately, any fix will probably require changing controls.pas. Also note that, as it is now, CancelDrag calls DragDone which ultimately calls ReleaseCapture. If called in response to a WM_CAPTURECHANGED message, CancelDrag will release the current capture (if any), not DragObject's capture (which has already been released).


Bug #408; last modified: 15-Oct-98
1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
Unknown Unknown Unknown Exists Exists Exists Exists Unknown
General - Forms.pas

There's a memory loss of 4KB (or more) on every dynamic load of a DLL written in Delphi 3.0x

Description
Reported by Martin Djernæs; checked by Erik Sperling Johansen
The problem is easily reproduceable (see attached zip file):

Cause:
The problem happens in the MakeObjectInstance (or rather because of...)
in MakeObjectInstance is the following line

    Block := VirtualAlloc(nil, PageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
which allocates memory in 4K blocks; this is not released again :-(
Solution / workaround
The following function can release that memory (must be written inside the forms.pas implementation section):
Procedure DeleteInstBlockList;
Var
  Block : PInstanceBlock;
Begin
  Repeat
    Block := InstBlockList^.Next;
    VirtualFree(InstBlockList, 4096, MEM_DECOMMIT);
    InstBlockList := Block;
  Until Block = NIL;
end;
and must then be called from the finalization section of the forms.pas unit
finalization
  ..
  ..
  DeleteInstBlockList;

end.

Bug #106; last modified: before April 1998
1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
Exists Fixed Fixed Fixed Fixed Fixed Fixed Fixed
General - Printing / Printers

Printing will fail after Printer.Abort

Description
[reported and fixed by so many people that I think singling out somebody would not do justice to the others]

Printing will fail if only Printer.Abort is called to abort a currently printing job. To produce the problem, click on the button (below) twice...
uses
  Printers;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Printer.BeginDoc;
  if Printer.Printing then
  begin
    Printer.Abort;
    Printer.EndDoc;
    exit;
  end;
  Printer.EndDoc;
end;
This problem is due to an oversight by Borland in PRINTERS.PAS.
Solution / workaround
Either replace each call to
    Printer.Abort;
with
    Printer.Abort;
    Winprocs.AbortDoc(Printer.Handle);
or replace TPrinter.Abort in PRINTERS.PAS with
procedure TPrinter.Abort;
begin
  CheckPrinting(True);
{$IFDEF FixPrinterAbortBug}
  WinProcs.AbortDoc(DC);
{$ENDIF}
  FAborted := True;
end;

Bug #107; last modified: before April 1998
1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
Gotcha Unknown Unknown Unknown Unknown Unknown Unknown Unknown
General - Printing / Printers

Printing text uses wrong (too small) font size

Description
Reported by Isaac Hepworth; checked and edited by Stefan Hoffmeister
It is debatable whether this actually is a bug:
  Printer.BeginDoc;

  Printer.Canvas.Font := Label1.Font;
  Printer.Canvas.TextOut(10, 10, Label1.Caption);

  Printer.EndDoc;
will print the caption of the label by far too small. The reason for this is that a printer has a resolution of (say) 300dpi while the screen has a resolution of (say) 96-120dpi. You have set the printer's font to label's one, which will have it's PixelsPerInch property set to Screen.PixelsPerInch (100ish). So the font will print out about a third of the desired size.
Solution / workaround
Always assign the printer font's PixelsPerInch property, too:
  Printer.BeginDoc; 

  Printer.Canvas.Font.PixelsPerInch := 
            GetDeviceCaps(Printer.Handle, logPixelsY);
  Printer.Canvas.Font.Size := 12;
  Printer.Canvas.TextOut(10, 10, Label1.Caption);

  Printer.EndDoc;

Bug #14; last modified: before April 1998
1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
Exists Fixed Fixed Fixed Fixed Fixed Fixed Fixed
General - TApplication

Run method does not honor the Run Minimized/Maximized setting.

Description
Delphi applications do not honor the Windows shell settings for running the program in a minimized or maximized state. This happens because the TApplication object creates a hidden "application" window which is receiving the the minimize/maximize setting instead of the main form.
Solution / workaround
If you have the source to the VCL, modify the TApplication.Run method (FORMS.PAS) to account for these settings:
procedure TApplication.Run;
begin
  AddExitProc(DoneApplication);
  if FMainForm <> nil then
  begin
    {$IFDEF FixHonourRunMinMax}
    if (CmdShow = sw_ShowMinimized) or
       (CmdShow = sw_ShowMinNoActive) or { Win95 sets this }
       (CmdShow = sw_ShowMaximized)
    then ShowWindow(FMainForm.Handle, CmdShow);
    {$ENDIF}
    FMainForm.Visible := True;
    repeat
      HandleMessage
    until Terminated;
  end;
end;
If you do not have, or prefer not to modify, the VCL source, add the following to your main form's OnCreate event handler:
  if (CmdShow = sw_ShowMinimized) or
     (CmdShow = sw_ShowMaximized)
  then ShowWindow(Handle, CmdShow);
Note, that for this fix (and the old one, too) an application started with a CmdShow <> sw_ShowNormal will always override the form's WindowState setting. As this may be undesirable sometimes, you will have to set WindowState := wsXXX; in the (main) form's OnShow event handler.

Bug #493; last modified: 28-Oct-98
1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
Absent Gotcha Gotcha Gotcha Gotcha Gotcha Gotcha Gotcha
General - TClipboard

TClipboard.HasFormat is declared as taking a word type parameter, but this should be a 32-bits (unsigned) integer.

Description
Reported by Brad Stowers; checked by Reinier Sterkenburg
This bug will probably never cause any problems, see comment below
The Win32 API functions IsClipboardFormatAvailable and RegisterClipboardFormat both use UINTs (Integer in D2, D3, LongWord in D4, either way it should be taking a 32-bit value, not a 16-bit value).

This is a bug because the wrong size parameter (16-bit) is being passed to the VCL function. If you register your own clipboard format (RegisterClipboardFormat), it returns a 32-bit value and that may not fit in a 16-bit value and so it would get truncated and the function would incorrectly return false.

Comment by Chris Jobson:
While it is correct that IsClipboardFormatAvailable and RegisterClipboardFormat use UINTs, in fact clipboard formats are always 16-bit values and so I don't believe that there will ever be a problem with using TClipboard.HasFormat.

The MSDN documentation for RegisterClipbioardFormat states "Registered clipboard formats are identified by values in the range 0xC000 through 0xFFFF", and some other Win32 API calls using clipboard formats treat them as 16-bit values (for example, the DDEADVISE structure used with WM_DDE_ADVISE messages stores the clipboard format as a "short"). I think the real problem here is Microsoft's API is not consistent!

Solution / workaround
None needed; there will probably never be any problem.

Bug #29; last modified: 15-Oct-98
1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
Exists Exists Exists Exists Exists Fixed Fixed Fixed
General - TComponent

Writing a floating point property value to a stream and then reading it may go wrong.

Description
Reported by Sam Liddicott; contributions by Martin Lovell and Hallvard Vassbotn
(SL:) If you create a component with a floating point property, and set this property to a value which can be expressed with only 0..9 and then write this component to a stream, upon which you do ObjectBinaryToText, this floating point value is written without anything to indicate it is a float.
ObjectTextToBinary then converts this to an integer, as it cannot detect it is a float (lack of ".", "E" or "e").
Then when the component is read back, it fails because it tries to read an integer stored value into a float property.
But what is bugged? ObjectBinaryToText for not putting E0 on the end? Or TReader.ReadPropValue for not allowing an integer to be read into a float? (It expects to find nothing but a float there!?)

(ML/HV:) The routines ObjectBinaryToText and ObjectTextToBinary in the Classes unit convert the persistent storage of objects from binary to text and vice versa. This is used to convert DFM files to TXT files and back, for instance. When ObjectBinaryToText converts properties it does not store any type information only the name and the value of the property. So when ObjectTextToBinary converts the text back to binary format it must deduce the type of the properties by looking at how the values are formatted. String properties are recognized by the starting quote, while floating point properties are recognized by a decimal point.

In the standard VCL components, only TBCDField and TFloatField publish floating point properties: MaxValue and MinValue.
The problem can also be experienced with the Convert.EXE utility. It can convert the DFM file to TXT but gives the same error when you try to convert the TXT file back to a DFM file.

Solution / workaround
(ML:) My fix is to the classes unit, and applies to any of the released classes units.

Find TReader.ReadPropValue, change from:
tkFloat: SetFloatProp(Instance, PropInfo, ReadFloat);
to
tkFloat: If NextValue in [vaInt8,vaInt16,vaInt32]
then SetFloatProp(Instance, PropInfo, ReadInteger);
else SetFloatProp(Instance, PropInfo, ReadFloat);
(HV:) You can of course change the implementation of ObjectBinaryToText so that it always store floating-point values with a decimal part, but that will not help very much, as the problem is mostly about the version of ObjectBinaryToText that has been compiled into the Delphi IDE and Convert binaries.

Another workaround is to use values with decimal parts (i.e. 2147483648.1 instead of 2147483648) or restrict yourself to use the range -MaxInt..MaxInt. If you use CONVERT.EXE in batch-file routines you could also introduce a post-processing utility that simply adds proper floating point parts to any numerical property that exceeds the integer range.

Component writers should be aware of this bug and explain to their users how it can be avoided.


Bug #30; last modified: before April 1998
1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
Absent Absent Exists Unknown Fixed Fixed Fixed Fixed
General - TComponent

String property values of certain lengths will get damaged.

Description
Reported by Rick Fogerty; checked and edited by Hallvard Vassbotn
ObjectBinaryToText (and thus View From as Text and Convert.EXE) cannot properly convert string properties of specific lengths. Any string with a length of 64*n+1 (i.e. 65, 129, 193 etc.) are converted with a missing ' +' and newline combination. The result is that the string property will have a different value when converted back to binary format (it has an embedded single quote character in position before the last character).

Step-by-step instruction on how to re-produce the bug:

  • Drop any component that has a string property on a form (i.e. a TButton). Be sure to make it wide.
  • Enter a string that is _excactly_ 65 characters long in one of the string properties (i.e. Caption)
  • View the form as text (Alt+F12 or Right-click View form as text)
  • Notice the strange representation of the string property
  • View the form again and see that the property has changed in the Object Inspector and on the form.
  • The same thing happens if you use Convert.EXE to convert a DFM file to a TXT file and back or if you use the ObjectBinaryToText procedure in your own code.
    Solution / workaround
    If you use this procedure yourself, you might want to fix the code. This does not solve the problem in the IDE or the Convert.EXE, of course. The buggy code is in ObjectBinaryToText in Classes.Pas. In the nested procedure ConvertValue, you will find this code:
       if ((I - K) >= LineLength) and (I < L) then
       begin
         WriteStr(' +');
         NewLine;
         K := I;
       end;
    This should be changed to:
       if ((I - K) >= LineLength) and (I <= L) then
       begin
         WriteStr(' +');
         NewLine;
         K := I;
       end;
    Notice that the '<' has been changed to '<='.

    The only other known workaround is to avoid strings of length 65, 129, 193 etc in all string and stringlist properties (including TQuery.SQL property). A viable option is to fix the code for ObjectBinaryToText and then write your own Convert.Exe utility (this is fairly straightforward).


    Bug #436; last modified: 29-Dec-98
    1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
    N/A N/A N/A N/A N/A Exists Exists Exists
    General - TControl

    Anchored Controls are not resized if Form's initial WindowState set to wsMaximized

    Description
    Reported by Steve Maughan and Greg Chapman; checked by Reinier Sterkenburg
    To see the erroneous behavior, create a form, drop a control on it and set the Control's Anchor property to include all four anchor types (akLeft..akBottom). Then set the Form's WindowState to wsMaximized. When you run the app and show the Form, it maximizes, but the control dropped on it stays the same size.

    I believe this problem occurs because most of the special resizing code is turned off during form loading. Note in particular TWinControl.UpdateLastResize which appears to be crucial in saving information for the next time AlignControls is called. If I am correct, this problem should not be limited to setting WindowState:= wsMaximized; it will also show up for any property changed during form loading which causes the form to have a different size than it had in the Delphi form-designer. I'm not sure how big a problem that may be.

    Solution / workaround
    The work around for wsMaximized is fairly simple. Just leave the form's designed WindowState as wsNormal, and then set it to wsMaximized in FormCreate.

    Bug #509; last modified: 9-Dec-98
    1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
    Gotcha Gotcha Gotcha Gotcha Gotcha Gotcha Gotcha Gotcha
    General - TControl

    TControl.WM_CancelMode does not handle the Mouse state correctly

    Description
    Reported by Max Nilson
    The standard WM_CANCELMODE message handling attempts to correct a flaw in the way that Windows handles mouse up messages and fails to achieve this.
    Consider the following code snippet from Controls.pas in Delphi 2.0 and Delphi 3.02:
    procedure TControl.WMCancelMode(var Message: TWMCancelMode);
    begin
      inherited;
      if MouseCapture then
      begin
        MouseCapture := False;
        if csLButtonDown in ControlState then Perform(WM_LBUTTONUP, 0,
          $FFFFFFFF);
      end;
    end;
    What the Delphi programmers have tried to do is to correct the Windows problem whereby a mousedown triggering creation of a new window causes the associated mouse up to be delivered to the new window rather than the original window.
    Unfortunately the author has not realised that the documented behaviour of the DefWindowProc on receiving a WM_CANCELMODE is to release any mouse captures. Thus the check that occurs directly after the inherited *always* fails. This can be confirmed by placing a break point inside the begin end block in any application, and running it. No matter what you do with drop downs, MDI windows, etc. you can never trigger the break point.
    Some one at Delphi obviously noticed a problem because in Delphi 4.02 the code was modified as follows:
    procedure TControl.WMCancelMode(var Message: TWMCancelMode);
    begin
      inherited;
      if MouseCapture then
      begin
        MouseCapture := False;
        if csLButtonDown in ControlState then Perform(WM_LBUTTONUP, 0,
          Integer($FFFFFFFF));
      end
      else
        Exclude(FControlState, csLButtonDown);
    end;
    This ensures that the ControlState is maintained correctly (a good thing) but they completely missed the actual bug that was causing this problem in the first place!
    I think that this is a gotcha because anyone working with code needing correct mode handling needs to process WM_CANCELMODE anyway, but this is a rather surprising thing that has caused some confusion in all the programmers here until we discovered what our mouse ups were not arriving as expected.

    Bug #31; last modified: before April 1998
    1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
    Unknown Exists Unknown Unknown Unknown Unknown Unknown Unknown
    General - TCustomControl

    Drag & Drop: OnStartDrag event occurs too early

    Description
    Reported by Steve Fillingham
    I have two problems with the Drag & Drop code in TControl for Delphi 2.0:
    1. In the OnEndDrag event the Target parameter (as specified in the Help files) should be
    2. On accepting the drop: references the control that accepted it.
    3. On cancelling the drop (dropping it on a control that does not accept it): equal 'nil'.
    4. Drawing up a quick program with a Label1 and a ListBox1 and appropriate events in the Label1 code it is easy to see that case b) above does not work, ie when the Label1 is dropped on a control that does not accept it (ie cancelled) the OnEndDrag event has Target referencing the control it was unsuccesfully dropped on (even though that control did not get the OnDragDrop event).
    5. The OnStartDrag event is meant to occur when dragging of a control starts (so the Help says). But with a controls DragMode = dmManual, and on the OnMouseDown event we do a BeginDrag(False) which should wait until you have moved 5 pixels before starting the drag, you get the OnStartDrag event immediately from BeginDrag(False). This is also easy to see with a simple test program.

    Bug #45; last modified: before April 1998
    1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
    Exists Exists Exists Unknown Unknown Unknown Unknown Unknown
    General - TForm

    If you create a main form with WindowState=wsMaximized and Position=poScreenCenter, the main form displays incorrectly (sometimes about 10-15 pixels low and 20-25 pixels to the right)

    Description
    Solution / workaround
    Don't set Position=poScreenCenter. Leave it as poDefault. If the window is maximized there is no need to try and center the window.

    Bug #46; last modified: 7-May-98
    1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
    Exists Fixed Fixed Fixed Fixed Fixed Fixed Fixed
    General - TForm

    TForm.Name is empty at runtime.

    Description
    Even though TForm.Name always shows content at design time, it will be empty at runtime. Thus it is not possible to distinguish between forms using the .Name property.
    Solution / workaround
    Use TForm.Classname. This will always give you the form's name prepended with a "T", for example: "TForm1" if your form's name is "Form1".

    Alternative solution:
    Set the form's name property at run time e.g. in the form's OnCreate event routine.


    Bug #437; last modified: 22-Jul-98
    1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
    Gotcha Gotcha Gotcha Gotcha Gotcha Gotcha Gotcha Gotcha
    General - TForm

    Forms do not honor the windows animation routine upon minimize/maximize

    Description
    Reported by Stephen Deken; checked by Reinier Sterkenburg
    To reproduce:
    Create a blank Delphi application. Run it. Minimize the blank window.
    The form should display the animated title bar scrolling down to the taskbar and play the "Window Minimize" sound as defined in the control panel. However, the form just disappears.

    This behaviour can be explained by the fact that the application maintains a hidden 'Main Window'; your own main window is therefore not the real main window and therefore it does not minimize like 'normal' applications do.
    I do not remember where this is documented, but I am quite sure that the above explanation is right. If someone would provide a good (Borland supplied) reference to this information, this would be very much appreciated.


    Bug #457; last modified: 17-Aug-98
    1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
    Unknown Unknown Unknown Unknown Exists Fixed Fixed Fixed
    General - THintWindow

    THintWindow has a resource leak: the Handle retrieved by GetWindowDC is not released by ReleaseDC

    Description
    Reported by James D. Rofkar; checked by Hallvard Vassbotn
    The problem can be detected with BoundsChecker for Delphi.
    To reproduce:
    1. Create a program that utilizes a hint window. Set "ShowHint" property to true and set some "Hint" text.
    2. Run the program.
    3. Display the hint window.
    4. Quit the program.
    Solution / workaround
    Fix:
    1. Edit the Controls.pas unit in your \Delphi\Source\VCL directory.
    2. Goto the THintWindow.WMNCPaint procedure and add ReleaseDC(Self.Handle, Canvas.Handle) to the finally section. The resulting code will resemble:
      procedure THintWindow.WMNCPaint(var Message: TMessage);
      var
        R: TRect;
      begin
        Canvas.Handle := GetWindowDC(Handle);
        with Canvas do
        try
          R := Rect(0, 0, Width, Height);
          DrawEdge(Handle, R, BDR_RAISEDOUTER, BF_RECT);
        finally
          ReleaseDC(Self.Handle, Canvas.Handle);
          Canvas.Handle := 0;
        end;
      end;
    3. Recompile Controls.pas to generate a new Controls.DCU
    4. Place the new Controls.DCU in your \Delphi\LIB directory.

    Bug #67; last modified: before April 1998
    1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
    Gotcha Gotcha Gotcha Gotcha Gotcha Gotcha Gotcha Gotcha
    General - TObject

    This applies to all descendants of TObject, IOW: to all class types!
    Calling the Free method is not always safe

    Description
    After a call of the Free method, the value of the object (which is a pointer) remains the same, and the properties remain accessible. Calling Free again causes a GPF. The documentation says it is safe to call the Free method and it should be used instead of Destroy. That would make much more sense if Free would also set the pointer value to Nil.

    Bug #505; last modified: 4-Nov-98
    1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
    N/A N/A N/A N/A N/A Exists Exists Exists
    General - TRegistry

    The on-line documentation for the TRegistry.OpenKeyReadOnly is incorrect.

    Description
    Reported by Vadim Farberov; checked by Reinier Sterkenburg
    The on-line documentation for OpenKeyReadOnly states:
    "Call OpenKeyReadOnly to make a specified key the current key. Key is the name of the key to open. If Key is null, the CurrentKey property is set to the key specified by the RootKey property.

    Key is opened with the security access value KEY_ALL_ACCESS. ... "

    That is not true. As seen from the source code, TRegistry.OpenKeyReadOnly calles WinAPI function RegOpenKeyEx with KEY_READ, not KEY_ALL_ACCESS, which it naturally used in TRegistry.OpenKey. I suppose the bug in documentation is just a misprint.


    Bug #506; last modified: 4-Nov-98
    1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
    N/A Absent Exists Exists Exists Exists Exists Exists
    General - TRegistry

    The NT-specific comments in the on-line documentation for the TRegistry.DeleteKey method are incorrect.

    Description
    Reported by Vadim Farberov; checked by Reinier Sterkenburg
    The on-line documentation states:
    "Call DeleteKey to remove a specified key and its associated data, if any, from the registry. Under Windows 95, if the key has subkeys, the subkeys and any associated data are also removed. Under Windows NT, subkeys must be explicitly deleted by separate calls to DeleteKey."

    That is not true. The subkeys are deleted under Windows 95 as well as for Windows NT. Moreover, the source code of TRegistry.DeleteKey recursively calls WinAPI RegDeleteKey (which behaviour really IS OS dependent) and does not check the OS.


    Bug #532; last modified: 9-Feb-99
    1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
    N/A Exists Exists Exists Exists Exists Exists Exists
    General - TRegistry

    TRegistry.ReadString does not raise an exception when the value does not exist.

    Description
    Reported by Pedro Gimeno; checked by Reinier Sterkenburg
    When calling TRegistry.ReadString for a non-existing value, no exception is raised, contradicting the documented behaviour and the rest of ReadWhatever.
    Example:
    ...
    uses Registry;
    ...
    var Reg: TRegistry;
    ...
      Reg := TRegistry.Create;
      Reg.RootKey := HKEY_CURRENT_USER;
      If Reg.OpenKey('Software', False) then
      begin
        try
          ShowMessage('The value ThisNameDoesNotExist contains <'
             + Reg.ReadString('ThisNameDoesNotExist') + '>');
        except
          on ERegistryException do
            ShowMessage('Value not found');
          else raise;
        end;
      end;
      Reg.Free;
    Change Reg.ReadString(...) to IntToStr(Reg.ReadInteger(...)) to check the correct behaviour.

    It's a direct consequence of the code for TRegistry.ReadString in VCL\REGISTRY.PAS: a check is made for GetDataSize > 0 and if false the function returns '', but GetDataSize returns -1 when the value does not exist, thus causing the function to return '' instead of raising an exception as it should.


    Bug #90; last modified: 25-Oct-98
    1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
    Exists Fixed Fixed Fixed Fixed Fixed Fixed Fixed
    General - TStringList

    Find method does not always work.

    Description
    ; checked by Anders Melander
    The Find method of TStringList will fail for lists which are not sorted (i.e. MyList.Sorted = FALSE). A quick look at the VCL source code file (CLASSES.PAS) reveals that the Find method does not bother to determine if the list is sorted or not; it assumes that it is and starts a binary search.

    Erik Berry pointed out that this behaviour is as designed; it should be considered a documentation bug. In the Delphi versions after D1, this has been corrected; the documentation does mention that Find is only meant to work for sorted lists.

    Solution / workaround
    Use the IndexOf method instead of Find. The IndexOf method will automatically call Find if the list's Sorted property is true. Otherwise, it will perform a standard linear search.

    Bug #91; last modified: 15-Oct-98
    1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
    Gotcha Gotcha Gotcha Gotcha Gotcha Fixed Fixed Fixed
    General - TStringList

    The Clear method does not Free the corresponding objects.

    Description
    The documentation for TStringList.Clear, etc. strongly implies that when you free a TStringList entry (or clear the list), the "corresponding object will be freed."
    This does not appear to be the case. It is only the list-entry, and its object-pointer, that is freed. The object referenced by that pointer, if any, is not affected by the TStringList.
    Solution / workaround
    Don't believe the docs...

    Bug #413; last modified: 1-Jun-98
    1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
    Fixed Exists Fixed Fixed Fixed Fixed Fixed Fixed
    General - TStringList

    TStringList.SaveToStream causes memory loss.

    Description
    Reported by Stojan Klancar; comment by Peter Below and Jordan Russell
    Peter Below (TeamB):
    This is a bug in Delphi 2.0 that has been fixed in the 2.01 update, as far as I'm aware. TStrings.SaveToStream calls GetText in D 2.0, which returns a PChar to a buffer created on the fly. The code fails to StrDispose of this buffer.

    Jordan Russell:
    The statement that this bug was fixed in Delphi 2.01 is NOT correct -- I just tested and checked the VCL source code (classes.pas). Delphi 3.00 is the first version to fix the bug.

    Solution / workaround
    Jordan Russell:
    The best solution for the problem is what was done in Delphi 3: in TStrings.SaveToStream replace the line "S := GetText;" with "S := GetTextStr;"

    Bug #445; last modified: 27-Jul-98
    1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
    Exists Exists Exists Exists Exists Exists Exists Unknown
    General - TStringList

    Memory leakage with TStringList.AddObject (Sorted = TRUE; Duplicates = dupIgnore)

    Description
    Reported by Michael Phillips; checked by Reinier Sterkenburg
    The problem occurs when we define a TStringList with 'Sorted = TRUE', and 'Duplicates = dupIgnore'. When performing an AddObject(string, object) of a string that already exists in the TStringList, the TStringList object pointer is changed to point to the passed object which discards the previous object. The is now no pointer pointing to the discarded object so we have a memory leak.

    AddObject('string1', object1);
    AddObject('string2', object2);
    AddObject('string3', object3);
    AddObject('string2', object4);
    In the brief example above the resulting TStringList is as below:
    string1 object1
    string2 object4
    string3 object3
    and the pointer to object2 is lost.

    I'd expect it to be
    string1 object1
    string2 object2
    string3 object3
    with a fail flag returned so that I can dispose of the object I created (in this instance object4).
    eg.

    object4 := create;
    if (AddObject('string2', object4) = -1) then
      object4.Free;
    In the online help for Delphi under TStringList.Duplicates it says that dupIgnore should 'ignore attempts to add duplicate string to the sorted list'. This is not the case when doing AddObject.

    Bug #459; last modified: 18-Aug-98
    1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
    N/A Absent Absent Absent Absent Exists Exists Unknown
    General - TThread

    Race condition in TThread's implementation

    Description
    Reported by Anders Melander
    I believe I have found an anomaly (a.k.a. bug) in Delphi 4's TThread. The problem exists in Delphi 4.0 and 4.01, but not Delphi 2 and 3 or C++ Builder 1 and 3.

    The source of the problem is a race condition between a thread that is destroying and another thread that is creating and the result of the problem is that TThread.Synchronize *might* stop working.

    In short here's what happens:

    1. Thread A is asked to stop via the Terminate property.
    2. Immediately there after thread B is created
    3. Thread A decrements the thread counter, detects that it is the last thread and posts a message to the thread window asking telling the window to delete itself.
    4. Thread B, which is still "constructing", sees that it is the only thread (thread counter is zero), creates a new thread window and stores the handle in the ThreadWindow variable.
    5. The thread window received the message telling it to delete itself, and it does so by deleting the handle in ThreadWindow and setting it to 0.
    The problem now is that even though a thread window still exists, the ThreadWindow variable which where supposed to be pointing to it has been zeroed. Since the messages sent by TThread.Synchronize to implement synchronization are sent to the handle specified by ThreadWindow (which is now 0), Synchronize has stopped working.

    It might be difficult to reproduce the bug since the timing of the two threads has to be "right" for it to occur. Anyway, the following code reproduces the bug on my system (400MHz P2 running NT4):

    unit Unit1;
    interface
    uses
      Windows, Messages, SysUtils, Classes, Graphics,
      Controls, Forms, Dialogs, StdCtrls;
    
    type
      // A form with 2 buttons on it
      TForm1 = class(TForm)
        ButtonStart: TButton;
        ButtonDie: TButton;
        procedure ButtonStartClick(Sender: TObject);
        procedure ButtonDieClick(Sender: TObject);
      end;
    
      TDummyThread = class(TThread)
      protected
        procedure Execute; override;
        procedure DoCount;
      end;
    
    var
      Form1: TForm1;
      PleaseTerminate: boolean = False;
      Counter: integer = 0;
    
    implementation
    {$R *.DFM}
    
    procedure TDummyThread.Execute;
    begin
      FreeOnTerminate := True;
      while not(terminated) do
      begin
        Sleep(10);
        Synchronize(DoCount);
      end;
    end;
    
    procedure TDummyThread.DoCount;
    begin
      inc(Counter);
      Form1.Caption := IntToStr(Counter);
      if (PleaseTerminate) then
      begin
        // Acknowledge terminate request and do it
        PleaseTerminate := False;
        Terminate;
      end;
    end;
    
    procedure TForm1.ButtonStartClick(Sender: TObject);
    begin
      ButtonStart.Enabled := False;
      ButtonDie.Enabled := True;
      TDummyThread.Create(False);
    end;
    
    procedure TForm1.ButtonDieClick(Sender: TObject);
    var
      Msg                   : TMsg;
    begin
      // Request that thread terminate itself
      PleaseTerminate := True;
    
      // Wait for thread to acknowledge terminate request
      while (PleaseTerminate) do
        Application.ProcessMessages;
    
      // The following Sleep(0) is used (as an example) to
      // let the thread resume execution. While we sleep,
      // the thread will decrement the thread counter,
      // detect that it was the last thread and therefore
      // signal the thread window to delete itself by
      // posting a CM_DESTROYWINDOW message
      // to it.
      Sleep(0);
      // Once the message has been posted, execution
      // *might* continue in this thread...
    
      // Here we create a new thread. The new thread will
      // see that it is the first thread (thread counter is
      // zero), and thus create a new thread window...
      // The problem is that the CM_DESTROYWINDOW
      // message hasn't been processed yet and when it at
      // some later time does get processed, it will delete
      // the new thread window instead of the old one (which
      // by the way has now been lost/leaked because we have
      // overwritten the thread window variable with a new
      // value).
      TDummyThread.Create(False);
    end;
    
    end.
    Solution / workaround
    I have not found an *acceptable* work-around yet, but when I have one I will post it to The Delphi Bug Lists.
    One solution might be to replace the following (in ThreadWndProc of classes.pas):
      CM_DESTROYWINDOW:
      begin
        FreeThreadWindow;
        Result := 0;
      end;
    with
      CM_DESTROYWINDOW:
      begin
        DestroyWindow(Window);
        Result := 0;
      end;

    Bug #102; last modified: 25-Oct-98
    1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
    Exists Unknown Unknown Unknown Unknown Unknown Fixed Fixed
    General - TWinControl

    TWinControl.Destroy destroys each of its controls before destroying itself, while it does not own its controls.

    Description
    The following scenario leads to a GPF:

    A TWinControl component A owns a TControl component B which in turn owns a component C. If both components B and C have A as a parent the program will crash when component A is destroyed since A tries to destroy C, which has already been destroyed by B.
    If this is not clear enough, a piece of sample code is in sample17.txt
    Solution / workaround
    In TWincontrol.Destroy, replace the following lines (old code):
    destructor TWinControl.Destroy;
    {Snip}
      I := ControlCount;
      while I <> 0 do
      begin
        Dec(I);
        Instance := Controls[I];
        Remove(Instance);
        Instance.Destroy;
      end;
    {Snip}
    end;
    with (new code):
    destructor TWinControl.Destroy;
    {Snip}
      while ControlCount <> 0 do
      begin
        Instance := Controls[ControlCount-1];
        Remove(Instance);
        Instance.Destroy;
      end;
    {Snip}
    end;

    Bug #103; last modified: before April 1998
    1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02
    Gotcha Unknown Unknown Unknown Unknown Unknown Unknown Unknown
    General - TWinControl

    RecreateWnd is dangerous to API routines that rely on the window handle.

    Description
    All components that are based on TWinControl have a method, RecreateWnd, which destroys the current window and recreates it. This is necessary for certain situations, such as if the style of a window is modified after creation. However, certain Windows functions rely on a window handle, and recreating the window will prevent these functions from working correctly. For instance, the DragAcceptFiles function takes a window handle and registers it as able to accept files dropped from the file manager. If the window were recreated, the handle you passed to DragAcceptFiles would no longer be valid.
    Solution / workaround
    This isn't so much a bug as a very big gotcha. The best solution I have for the problem is to derive a new component that overrides the RecreateWnd method and re-registers the window handle after the inherited method is called. The obvious short-coming of this work-around is that you will have to do it for every component that this may happen to.

    Another possibility is to simply make sure that you do not do anything that would cause the component's window to be recreated. Some conditions which cause a window to be recreated are:

  • Changing the border style.
  • Changing the CharCase, HideSelection or OEMConvert property of an edit control.
  • Changing the Alignment or ScrollBars property of a memo control.
  • Changing the Sorted or Style property of a listbox control.
  • Changing the Alignment property of a listbox control.
  • Changing the Ctl3D appearance of a checkbox control.
  • This should NOT be considered a complete list. This is only about half of what I found in STDCTRLS.PAS alone. I think this illustrates very well that you probably should derive new components and override RecreateWnd rather than try to avoid the situation.

    Index page
    The Delphi Bug Lists are maintained by Reinier Sterkenburg, with help from the DeBug Team.
    All feedback is appreciated. See also the feedback section of the Delphi Bug List home page.