自删除按钮

Self deleting button

我有一个 TScrollBox,其中包含一堆 TPanel,以及一些在运行时生成的 TButton。 我需要在单击一个 TButton 时删除 TPanel,但在 OnClick 中执行此操作会导致访问冲突...

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;

通过 Renate Schaaf 答案解决:

...
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;

您无法从 TButtonOnClick 事件中安全地销毁父 TPanel(或 TButton 本身)。在事件处理程序退出后,VCL 仍然需要访问 TPanel/TButton 以获得节拍。因此,您需要将销毁延迟到处理程序退出之后。最简单的方法是使用 TThread.ForceQueue()TPanel 上调用 TObject.Free(),例如:

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;

TPanel 会在销毁过程中将自己从 TScrollBox 中移除。您不需要手动处理该步骤。