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;