Home > Software design >  Self deleting button
Self deleting button

Time:05-14

I have a TScrollBox with a bunch of TPanels with some TButtons generated at runtime. I need to delete the TPanel when one TButton is clicked but doing that in OnClick end in an access violation...

procedure TMainForm.ButanClick(Sender: TObject);
var
  vParentPanel: TPanel;
begin
  if (string(TButton(Sender).Name).StartsWith('L')) then
  begin
    TButton(Sender).Caption := 'YARE YARE DAZE';
  end
  else
  begin
    vParentPanel := TPanel(TButton(Sender).GetParentComponent());
    TheScrollBox.RemoveComponent(vParentPanel);
    vParentPanel.Destroy();
    // access violation but the panel is removed
  end;
end;

procedure TMainForm.Button3Click(Sender: TObject);
var
  i: Integer;
  vPanel: TPanel;
  vButton: TButton;
begin
  for i := 0 to 20 do
  begin
    vPanel := TPanel.Create(TheScrollBox);
    vPanel.Align := alTop;
    vPanel.Parent := TheScrollBox;

    vButton := TButton.Create(vPanel);
    vButton.Align := alLeft;
    vButton.Parent := vPanel;
    vButton.Name := 'L_butan'   IntToStr(i);
    vButton.OnClick := ButanClick;

    vButton := TButton.Create(vPanel);
    vButton.Align := alRight;
    vButton.Parent := vPanel;
    vButton.Name := 'R_butan'   IntToStr(i);
    vButton.OnClick := ButanClick;
  end;
end;

CodePudding user response:

You cannot safely destroy the parent TPanel (or the TButton itself) from inside the TButton's OnClick event. The VCL still needs access to the TPanel/TButton for a beat after the event handler exits. So, you need to delay the destruction until after the handler exits. The easiest way to do that is to use TThread.ForceQueue() to call TObject.Free() on the TPanel, eg:

procedure TMainForm.ButanClick(Sender: TObject);
var
  vButton: TButton;
begin
  vButton := TButton(Sender);
  if vButton.Name.StartsWith('L') then
  begin
    vButton.Caption := 'YARE YARE DAZE';
  end
  else
  begin
    TThread.ForceQueue(nil, vButton.Parent.Free);
  end;
end;

The TPanel will remove itself from the TScrollBox during its destruction. You do not need to handle that step manually.

CodePudding user response:

Solved with Renate Schaaf answer:

...
const
WM_REMOVEPANEL = WM_USER   9001;
procedure ButanClick(Sender: TObject);
procedure OnCustomMessage(var Msg: TMessage); message WM_REMOVEPANEL;

...
procedure TMainForm.ButanClick(Sender: TObject);
var
  vParentPanel: TPanel;
begin
  if (string(TButton(Sender).Name).StartsWith('L')) then
  begin
    TButton(Sender).Caption := 'YARE YARE DAZE';
  end
  else
  begin
    // SendMessage = access violation again because it wait the return
    // while PostMessage return istantly
    PostMessage(Handle, WM_REMOVEPANEL, 0, THandle(@Sender));
  end;
end;

procedure TMainForm.OnCustomMessage(var Msg: TMessage);
var
  vButton: TButton;
begin
  if (Msg.Msg = WM_REMOVEPANEL) then
  begin
    vButton := TButton(Pointer(Msg.LParam)^);
    ShowMessage(vButton.Name);
    TheScrollBox.RemoveComponent(vButton.GetParentComponent());
    TPanel(vButton.GetParentComponent()).Destroy();
    Msg.Result := 1;
  end
  else
    Msg.Result := 0;
end;
  • Related