D1 bug: Freeing tBitBtns on visible panels on MDI children causes GPFs

I'm working in Delphi 1, version 1.47.136.0, under Windows 3.1.  Here is
a new bug I've found, one that was not on the Delphi Bug List maintained
in the Netherlands, and I have now reported it to them.

I'm dynamically creating MDI child forms, putting panels on the forms,
and putting tBitBtns on the panels.  I was getting GPFs under some
circumstances when trying to free the tBitBtns.  These GPFs do not occur
if the panel is made invisible or if it is given a non-MDI-Child parent
before the button is deleted.

Here is the code for a small test project that creates the forms,
panels, and buttons and that gives the user a choice of circumstances to
try deleting the tBitBtn.  Further operating instructions are in the
comments at the top of the program.  Note that sometimes the first
attempt to delete the button works without a problem, and that the
problem occurs only on the second attempt.

------------------ start code here -----------------------------------

unit Bug02;

{Demonstration of a very obscure Delphi 1 bug.  If a tBitBtn is placed
on a
 tPanel on an MDI child form, then freeing the button causes a GPF if
the
 panel is visible and its parent is the child form.  However, changing
 either the visibility or the parentage of the panel avoids the GPF.
The
 problem does not occur if the panel is on the base form, only on one of
 the child forms.

 To see the demonstration of the problem, click the "Create MDI child"
button
 at least six times.  Set the radio button to "Visible, on MDI parent"
and
 click "Delete top MDI child".  It will work delete properly.  Set the
radio
 button to "Invisible, on MDI child" and click "Delete top MDI child"
again.
 It will still work.  Set the radio button to "Visible, on MDI child"
and
 press "Delete fixed panel".  It will work. Now click on "Delete top MDI
 child" one last time.  You will not always get a GPF the first time,
but
 you will eventually get one.  Also, if you have only one child form at
 the time, you may not get it.}

interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, StdCtrls, ExtCtrls, Buttons, Menus;

type
  TForm1 = class(TForm)
    CreateButton: TButton;
    Scroller: TMemo;
    DelTopButton: TButton;
    FixedPanel: TPanel;
    DelFixedButton: TButton;
    DeletionModeGroup: TRadioGroup;
    procedure CreateButtonClick(Sender: TObject);
    procedure RefocusButtonClick(Sender: TObject);
    procedure DelTopButtonClick(Sender: TObject);
    procedure FormActivate(Sender: TObject);
  private
    { Private declarations }
    procedure Beep(Sender: tObject);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  Child1: TForm;

implementation

{$R *.DFM}

type ItProc=Procedure(Sender: Pointer);

procedure TForm1.Beep(Sender: tObject);
begin
   Scroller.Lines.Add((Sender as tForm).Caption+' activated');
   MessageBeep(0)
end;

procedure TForm1.CreateButtonClick(Sender: TObject);
const N: integer=0;
var Form:  tForm;
    Panel: tPanel;
    Thing: tBitBtn;
begin
   if (Sender=nil) then
      Panel:=FixedPanel
   else begin
      Inc(N);
      Form:=tForm.Create(Self);
      Form.Name:='Form_'+IntToStr(N);
      Form.FormStyle:=fsMDIChild;
      Form.Height:=200;
      Form.Width:=200;
      Form.Top:=20*MDIChildcount;
      Form.Left:=20*MDIChildcount;
      Form.OnActivate:=Beep;
      if (N=1) then
         Child1:=Form;
      Panel:=tPanel.Create(Self);
      Panel.Align:=alClient;
      Panel.Parent:=form
      end;
   Thing:=tBitBtn.Create(Self);
   Thing.Parent:=Panel;
   Thing.Top:=0;
   Thing.Caption:='Do nothing';
end;

procedure TForm1.RefocusButtonClick(Sender: TObject);
var N,
    Loops:  integer;
    Target: tForm;
begin
   if (MDIChildCount<2) then
      Exit;
   for Loops:=1 to 10 do begin
      Application.ProcessMessages;
      N:=Random(MDIChildCount);
      if (MDIChildren[N]<>ActiveMDIChild) then begin
         Target:=MDIChildren[N];
         Scroller.Lines.Add('');
         Scroller.Lines.Add('--- refocusing to '+Target.Caption+' ---');
         Target.BringToFront;
         Scroller.Lines.Add('Brought to front');
         Target.SetFocus;
         Scroller.Lines.Add('Focus now reset');
         Break
         end
      end
end;

procedure TForm1.DelTopButtonClick(Sender: TObject);
var  Form:  tForm;
     Panel: tPanel;
begin
   Scroller.Lines.Add('');
   if (Sender=DelTopButton) then begin
      Form:=MDIChildren[0];
      Panel:=Form.Controls[0] as tPanel;
      Scroller.Lines.Add('About to free the items on '+Form.Caption)
      end
   else begin
      Panel:=FixedPanel;
      Scroller.Lines.Add('About to free the items on the fixed panel')
      end;
   if (DeletionModeGroup.ItemIndex=1) then begin
      Scroller.Lines.Add('Deleting button from invisible panel on MDI
child');
      Panel.Visible:=false
      end
   else if (DeletionModeGroup.ItemIndex=2) then begin
      Scroller.Lines.Add('Deleting button from visible panel on MDI
parent');
      Panel.Parent:=Form1
      end
   else
      Scroller.Lines.Add('Deleting button from visible panel on MDI
child');
   (Panel.Controls[0] as tBitBtn).Free;
   Panel.Free;
   if (Sender=DelTopButton) then begin
      Scroller.Lines.Add('About to free the form');
      Form.Free;
      Scroller.Lines.Add('Done freeing the form')
      end
end;

procedure TForm1.FormActivate(Sender: TObject);
begin
   CreateButtonClick(nil);
   OnActivate:=nil
end;

initialization
end.

------------- end code here, start form here ------------------------

object Form1: TForm1
  Left = 129
  Top = 549
  Width = 756
  Height = 393
  Caption = 'Form1'
  Font.Color = clWindowText
  Font.Height = -13
  Font.Name = 'System'
  Font.Style = []
  FormStyle = fsMDIForm
  PixelsPerInch = 96
  Position = poScreenCenter
  OnActivate = FormActivate
  OnClick = RefocusButtonClick
  TextHeight = 16
  object CreateButton: TButton
    Left = 228
    Top = 16
    Width = 157
    Height = 33
    Caption = 'Create MDI child'
    TabOrder = 0
    OnClick = CreateButtonClick
  end
  object Scroller: TMemo
    Left = 392
    Top = 8
    Width = 345
    Height = 201
    TabOrder = 1
  end
  object DelTopButton: TButton
    Left = 228
    Top = 56
    Width = 157
    Height = 33
    Caption = 'Delete top MDI child'
    TabOrder = 2
    OnClick = DelTopButtonClick
  end
  object FixedPanel: TPanel
    Left = 504
    Top = 216
    Width = 225
    Height = 97
    Caption = 'Fixed Panel'
    TabOrder = 3
  end
  object DelFixedButton: TButton
    Left = 507
    Top = 320
    Width = 174
    Height = 33
    Caption = 'Delete fixed panel'
    TabOrder = 4
    OnClick = DelTopButtonClick
  end
  object DeletionModeGroup: TRadioGroup
    Left = 256
    Top = 216
    Width = 201
    Height = 105
    Caption = 'Delete button with panel ...'
    ItemIndex = 0
    Items.Strings = (
      'Visible, on MDI child'
      'Invisible, on MDI child'
      'Visible, on MDI parent')
    TabOrder = 5
  end
end

------------------------- end form here
-----------------------------------

I do not have the Delph1 1 VCL source, and I do not know what causes
this problem.  The workaround I am using is to make the panel invisble
first.  The workaround is easy; the difficult part was figuring out what
the problem was.

--
Howard L. Kaplan
Psychopharmacology and Dependence Research Unit
Women's College Hospital
76 Grenville Street, 9'th floor
Toronto, Ontario
Canada  M5S 1B2
(416)323-6400, ext 4915
howard.kap...@utoronto.ca