ダイナミックコントロールの削除 - あるシナリオでは違反にアクセスしますが、別のシナリオではアクセスしないでください

この小さなサンプルアプリケーションを使用して、コントロールのリストを維持する方法、具体的には多数のサブコントロールを含むダイナミックに作成されたTPanelコントロールを示しました。奇妙なものを除いて、すべてがうまくいくようです。もちろん、私のアプリケーションを閉じると、それは作成されたすべてのコントロールを通過してフリーになります。これは完全に機能します。しかし、不思議なことに、私がそれらの1つを削除しようとすると、私は同じコードでアクセス違反を得る。

下のコードを少し説明すると、各パネルのオブジェクトを含むTStringListがバックグラウンドにあります。また、これらのパネルのタグ、およびパネルの子コントロールに割り当てる「最終ID」も保持します。パネルはダンプされ、スクロールボックスの内側に配置されるので、コントロール付きのパネルのリストコントロールのようなものです。各パネルは、そのインデックスまたはその一意のIDによって参照することができます。問題は、それを削除するはずの各パネルに「削除」ボタンを実装するときに始まりました。この削除ボタンをクリックすると、タグプロパティのIDがチェックされ、そのIDを削除するプロシージャが呼び出されます。デバッグでは、私はIDとインデックスをトレースし、それはそれがされているものですが、それは何をすべきかをしません...

unit uMain;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, Buttons, XPMan;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Label1: TLabel;
    BitBtn1: TBitBtn;
    pMain: TScrollBox;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure BitBtn1Click(Sender: TObject);
  private
    FLastID: Integer;
    FPanels: TStringList;
    function GetPanel(Index: Integer): TPanel;
    procedure DelPanClick(Sender: TObject);
    function GetPanelID(ID: Integer): TPanel;
  public
    function GetID: Integer;
    property Panels[Index: Integer]: TPanel read GetPanel;
    property PanelByID[ID: Integer]: TPanel read GetPanelID;
    function Add: TPanel;
    procedure Delete(const Index: Integer);
    procedure DeleteID(const ID: Integer);
    function Count: Integer;
    procedure Clear;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

function TForm1.Add: TPanel;
const
  MARGINS = 8;
var
  L1, L2: TLabel;
  E1: TEdit;
  C1: TComboBox;
  B1: TBitBtn;
begin
  Result:= TPanel.Create(nil);
  Result.Parent:= pMain;
  Result.Align:= alLeft;
  Result.Width:= 150;
  Result.ParentBackground:= True;
  Result.ParentBackground:= False; //TPanel/XPMan color trick...
  Result.Color:= clSilver;
  Result.Tag:= GetID;

  L1:= TLabel.Create(Result);
  L1.Parent:= Result;
  L1.Left:= MARGINS;
  L1.Top:= MARGINS;
  L1.Caption:= 'Some Text Box';
  L1.Font.Style:= [fsBold];
  L1.Tag:= Result.Tag;

  E1:= TEdit.Create(Result);
  E1.Parent:= Result;
  E1.Left:= MARGINS;
  E1.Top:= L1.Top + L1.Height + MARGINS;
  E1.Width:= Result.ClientWidth - (MARGINS * 2);
  E1.Anchors:= [akLeft,akTop,akRight];
  E1.Text:= 'Some String Value';
  E1.Tag:= Result.Tag;

  L2:= TLabel.Create(Result);
  L2.Parent:= Result;
  L2.Left:= MARGINS;
  L2.Top:= E1.Top + E1.Height + (MARGINS * 2);
  L2.Caption:= 'Some Combo Box';
  L2.Font.Style:= [fsBold];
  L2.Tag:= Result.Tag;

  C1:= TComboBox.Create(Result);
  C1.Parent:= Result;
  C1.Left:= MARGINS;
  C1.Top:= L2.Top + L2.Height + MARGINS;
  C1.Width:= Result.ClientWidth - (MARGINS * 2);
  C1.Style:= csDropDownList;
  C1.Items.Append('Some Selected Value');
  C1.Items.Append('Some Other Value');
  C1.ItemIndex:= 0;
  C1.Tag:= Result.Tag;

  B1:= TBitBtn.Create(Result);
  B1.Parent:= Result;
  B1.Width:= 60;
  B1.Height:= 25;
  B1.Left:= MARGINS;
  B1.Top:= Result.ClientHeight - B1.Height - MARGINS;
  B1.Anchors:= [akLeft,akBottom];
  B1.Caption:= 'Delete';
  B1.OnClick:= DelPanClick;
  B1.Tag:= Result.Tag;

  FPanels.AddObject(IntToStr(Result.Tag), Result);

end;

procedure TForm1.Clear;
begin
  while Count > 0 do
    Delete(0);
end;

function TForm1.Count: Integer;
begin
  Result:= FPanels.Count;
end;

procedure TForm1.Delete(const Index: Integer);
var
  P: TPanel;
begin
  if (Index >= 0) and (Index < FPanels.Count) then begin
    try
      P:= TPanel(FPanels.Objects[Index]);
      if assigned(P) then begin
        P.Free; //<----- AV
      end;
    except
      on e: exception do begin
        raise Exception.Create('Failed to delete panel: '+e.Message);
      end;
    end;
    FPanels.Delete(Index);
  end else begin
    raise Exception.Create('Panel index out of bounds ('+IntToStr(Index)+')');
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FLastID:= 100;
  pMain.Align:= alClient;
  FPanels:= TStringList.Create;
  Add;
  Add;
  Add;   
  Add;
  Add;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  Clear;
  FPanels.Free;
end;

function TForm1.GetPanel(Index: Integer): TPanel;
begin
  Result:= TPanel(FPanels.Objects[Index]);
end;

procedure TForm1.BitBtn1Click(Sender: TObject);
begin
  Add;
end;

procedure TForm1.DelPanClick(Sender: TObject);
begin
  if Sender is TBitBtn then begin
    DeleteID(TBitBtn(Sender).Tag);
  end;
end;

function TForm1.GetID: Integer;
begin
  Inc(FLastID);
  Result:= FLastID;
end;

procedure TForm1.DeleteID(const ID: Integer);
var
  X: Integer;
begin
  X:= FPanels.IndexOf(IntToStr(ID));
  if X >= 0 then begin
    Delete(X);
  end else begin
    raise Exception.Create('Invalid ID ('+IntToStr(ID)+')');
  end;
end;

function TForm1.GetPanelID(ID: Integer): TPanel;
var
  X: Integer;
begin   
  X:= FPanels.IndexOf(IntToStr(ID));
  if X >= 0 then begin
    Result:= TPanel(FPanels.Objects[X]);
  end else begin
    raise Exception.Create('Invalid ID ('+IntToStr(ID)+')');
  end;
end;

end.

そして、DFMコード:

object Form1: TForm1
  Left = 385
  Top = 556
  Width = 540
  Height = 247
  Caption = 'Form1'
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OldCreateOrder = False
  OnCreate = FormCreate
  OnDestroy = FormDestroy
  PixelsPerInch = 96
  TextHeight = 13
  object Panel1: TPanel
    Left = 0
    Top = 0
    Width = 524
    Height = 33
    Align = alTop
    BevelWidth = 2
    Color = clWhite
    ParentBackground = False
    TabOrder = 0
    DesignSize = (
      524
      33)
    object Label1: TLabel
      Left = 8
      Top = 6
      Width = 218
      Height = 20
      Caption = 'Sample Dynamic Panel List'
      Font.Charset = DEFAULT_CHARSET
      Font.Color = clWindowText
      Font.Height = -16
      Font.Name = 'MS Sans Serif'
      Font.Style = [fsBold]
      ParentFont = False
    end
    object BitBtn1: TBitBtn
      Left = 450
      Top = 8
      Width = 57
      Height = 17
      Anchors = [akTop, akRight]
      Caption = 'Add'
      TabOrder = 0
      OnClick = BitBtn1Click
    end
  end
  object pMain: TScrollBox
    Left = 0
    Top = 33
    Width = 475
    Height = 176
    Align = alLeft
    Anchors = [akLeft, akTop, akRight, akBottom]
    BorderStyle = bsNone
    Color = clSkyBlue
    ParentColor = False
    TabOrder = 1
  end
end

パネルは、これらの3つのアクセス違反の後、最終的に削除します。

3 Access Violations


編集:

私のコードにいくつかの追加を加え、Davidの修正を加えると、それはうまくいったが、左から右に削除すると、5つのパネルのうち3番目のパネルを削除することになる。しかし、右から左への削除から、それはすべて正常に動作します。以下は私の新しいコードですが、DFMは上記と同じです:

unit uMain;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, Buttons, XPMan;

const
  LABEL_1 =     0;
  EDIT_1 =      1;
  LABEL_2 =     2;
  COMBO_1 =     3;
  BUTTON_1 =    4;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Label1: TLabel;
    BitBtn1: TBitBtn;
    pMain: TScrollBox;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure BitBtn1Click(Sender: TObject);
  private
    FLastID: Integer;
    FPanels: TStringList;
    function GetPanel(Index: Integer): TPanel;
    procedure DelPanClick(Sender: TObject);
    function GetPanelID(ID: Integer): TPanel;
    function GetBtn1(Index: Integer): TBitBtn;
    function GetCbo1(Index: Integer): TComboBox;
    function GetEdt1(Index: Integer): TEdit;
    function GetLbl1(Index: Integer): TLabel;
    function GetLbl2(Index: Integer): TLabel;
    function GetBtn1ID(ID: Integer): TBitBtn;
    function GetCbo1ID(ID: Integer): TComboBox;
    function GetEdt1ID(ID: Integer): TEdit;
    function GetLbl1ID(ID: Integer): TLabel;
    function GetLbl2ID(ID: Integer): TLabel;
  public
    function GetID: Integer;
    property Panels[Index: Integer]: TPanel read GetPanel;
    property Lbl1[Index: Integer]: TLabel read GetLbl1;
    property Lbl2[Index: Integer]: TLabel read GetLbl2;
    property Edt1[Index: Integer]: TEdit read GetEdt1;
    property Cbo1[Index: Integer]: TComboBox read GetCbo1;
    property Btn1[Index: Integer]: TBitBtn read GetBtn1;    
    property PanelByID[ID: Integer]: TPanel read GetPanelID;
    property Lbl1ByID[Index: Integer]: TLabel read GetLbl1ID;
    property Lbl2ByID[Index: Integer]: TLabel read GetLbl2ID;
    property Edt1ByID[Index: Integer]: TEdit read GetEdt1ID;
    property Cbo1ByID[Index: Integer]: TComboBox read GetCbo1ID;
    property Btn1ByID[Index: Integer]: TBitBtn read GetBtn1ID;
    function Add: TPanel;
    procedure Delete(const Index: Integer);
    procedure DeleteID(const ID: Integer);
    function Count: Integer;
    procedure Clear;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

function TForm1.Add: TPanel;
const
  MARGINS = 8;
var
  L1, L2: TLabel;
  E1: TEdit;
  C1: TComboBox;
  B1: TBitBtn;
begin
  Result:= TPanel.Create(nil);
  Result.Parent:= pMain;
  Result.Align:= alLeft;
  Result.Width:= 150;
  Result.ParentBackground:= True;
  Result.ParentBackground:= False; //TPanel/XPMan color trick...
  Result.Color:= clSilver;
  Result.Tag:= GetID;

  //LABEL_1 =     0;
  //EDIT_1 =      1;
  //LABEL_2 =     2;
  //COMBO_1 =     3;
  //BUTTON_1 =    4;

  L1:= TLabel.Create(Result);
  L1.Parent:= Result;
  L1.Left:= MARGINS;
  L1.Top:= MARGINS;
  L1.Caption:= 'Some Text Box';
  L1.Font.Style:= [fsBold];
  L1.Tag:= Result.Tag;

  E1:= TEdit.Create(Result);
  E1.Parent:= Result;
  E1.Left:= MARGINS;
  E1.Top:= L1.Top + L1.Height + MARGINS;
  E1.Width:= Result.ClientWidth - (MARGINS * 2);
  E1.Anchors:= [akLeft,akTop,akRight];
  E1.Text:= 'Some String Value';
  E1.Tag:= Result.Tag;

  L2:= TLabel.Create(Result);
  L2.Parent:= Result;
  L2.Left:= MARGINS;
  L2.Top:= E1.Top + E1.Height + (MARGINS * 2);
  L2.Caption:= 'Some Combo Box';
  L2.Font.Style:= [fsBold];
  L2.Tag:= Result.Tag;

  C1:= TComboBox.Create(Result);
  C1.Parent:= Result;
  C1.Left:= MARGINS;
  C1.Top:= L2.Top + L2.Height + MARGINS;
  C1.Width:= Result.ClientWidth - (MARGINS * 2);
  C1.Style:= csDropDownList;
  C1.Items.Append('Some Selected Value');
  C1.Items.Append('Some Other Value');
  C1.ItemIndex:= 0;
  C1.Tag:= Result.Tag;

  B1:= TBitBtn.Create(Result);
  B1.Parent:= Result;
  B1.Width:= 60;
  B1.Height:= 25;
  B1.Left:= MARGINS;
  B1.Top:= Result.ClientHeight - B1.Height - MARGINS;
  B1.Anchors:= [akLeft,akBottom];
  B1.Caption:= 'Delete';
  B1.OnClick:= DelPanClick;
  B1.Tag:= Result.Tag;

  FPanels.AddObject(IntToStr(Result.Tag), Result);

end;

procedure TForm1.Clear;
begin
  while Count > 0 do
    Delete(0);
end;

function TForm1.Count: Integer;
begin
  Result:= FPanels.Count;
end;

procedure TForm1.Delete(const Index: Integer);
var
  P: TPanel;
begin
  if (Index >= 0) and (Index < FPanels.Count) then begin
    try
      P:= Panels[Index];
      while P.ControlCount > 0 do
        P.Controls[0].Free;
      P.Free;
    except
      on e: exception do begin
        raise Exception.Create('Failed to delete panel: '+e.Message);
      end;
    end;
    FPanels.Delete(Index);
  end else begin
    raise Exception.Create('Panel index out of bounds ('+IntToStr(Index)+')');
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  X: Integer;
begin
  FLastID:= 100;
  pMain.Align:= alClient;
  FPanels:= TStringList.Create;
  Add;
  Add;
  Add;
  Add;
  Add;
  for X:= 0 to Count - 1 do begin
    Edt1[X].Text:= IntToStr(X);
    Lbl1[X].Caption:= IntToStr(X);
    Lbl2[X].Caption:= IntToStr(X);
  end;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  Clear;
  FPanels.Free;
end;

function TForm1.GetPanel(Index: Integer): TPanel;
begin
  Result:= TPanel(FPanels.Objects[Index]);
end;

procedure TForm1.BitBtn1Click(Sender: TObject);
begin
  Add;
end;

procedure TForm1.DelPanClick(Sender: TObject);
begin
  if Sender is TBitBtn then begin
    DeleteID(TBitBtn(Sender).Tag);
  end;
end;

function TForm1.GetID: Integer;
begin
  Inc(FLastID);
  Result:= FLastID;
end;

procedure TForm1.DeleteID(const ID: Integer);
var
  X: Integer;
begin
  X:= FPanels.IndexOf(IntToStr(ID));
  if X >= 0 then begin
    Delete(X);
  end else begin
    raise Exception.Create('Invalid ID ('+IntToStr(ID)+')');
  end;
end;

function TForm1.GetPanelID(ID: Integer): TPanel;
var
  X: Integer;
begin   
  X:= FPanels.IndexOf(IntToStr(ID));
  if X >= 0 then begin
    Result:= TPanel(FPanels.Objects[X]);
  end else begin
    raise Exception.Create('Invalid ID ('+IntToStr(ID)+')');
  end;
end;

function TForm1.GetBtn1(Index: Integer): TBitBtn;
begin
  if (Index >= 0) and (Index < FPanels.Count) then begin
    Result:= TBitBtn(Panels[Index].Controls[BUTTON_1]);
  end else begin
    raise Exception.Create('Index out of bounds ('+IntToStr(Index)+')');
  end;
end;

function TForm1.GetCbo1(Index: Integer): TComboBox;
begin
  if (Index >= 0) and (Index < FPanels.Count) then begin
    Result:= TComboBox(Panels[Index].Controls[COMBO_1]);
  end else begin
    raise Exception.Create('Index out of bounds ('+IntToStr(Index)+')');
  end;
end;

function TForm1.GetEdt1(Index: Integer): TEdit;
begin
  if (Index >= 0) and (Index < FPanels.Count) then begin
    Result:= TEdit(Panels[Index].Controls[EDIT_1]);
  end else begin
    raise Exception.Create('Index out of bounds ('+IntToStr(Index)+')');
  end;
end;

function TForm1.GetLbl1(Index: Integer): TLabel;
begin
  if (Index >= 0) and (Index < FPanels.Count) then begin
    Result:= TLabel(Panels[Index].Controls[LABEL_1]);
  end else begin
    raise Exception.Create('Index out of bounds ('+IntToStr(Index)+')');
  end;
end;

function TForm1.GetLbl2(Index: Integer): TLabel;
begin
  if (Index >= 0) and (Index < FPanels.Count) then begin
    Result:= TLabel(Panels[Index].Controls[LABEL_2]);
  end else begin
    raise Exception.Create('Index out of bounds ('+IntToStr(Index)+')');
  end;
end;

function TForm1.GetBtn1ID(ID: Integer): TBitBtn;
var
  X: Integer;
begin
  X:= FPanels.IndexOf(IntToStr(ID));
  if X >= 0 then begin
    Result:= TBitBtn(PanelByID[ID].Controls[BUTTON_1]);
  end else begin
    raise Exception.Create('Invalid Panel ID ('+IntToStr(ID)+')');
  end;
end;

function TForm1.GetCbo1ID(ID: Integer): TComboBox;
var
  X: Integer;
begin
  X:= FPanels.IndexOf(IntToStr(ID));
  if X >= 0 then begin
    Result:= TComboBox(PanelByID[ID].Controls[COMBO_1]);
  end else begin
    raise Exception.Create('Invalid Panel ID ('+IntToStr(ID)+')');
  end;
end;

function TForm1.GetEdt1ID(ID: Integer): TEdit;
var
  X: Integer;
begin
  X:= FPanels.IndexOf(IntToStr(ID));
  if X >= 0 then begin
    Result:= TEdit(PanelByID[ID].Controls[EDIT_1]);
  end else begin
    raise Exception.Create('Invalid Panel ID ('+IntToStr(ID)+')');
  end;
end;

function TForm1.GetLbl1ID(ID: Integer): TLabel;  
var
  X: Integer;
begin
  X:= FPanels.IndexOf(IntToStr(ID));
  if X >= 0 then begin
    Result:= TLabel(PanelByID[ID].Controls[LABEL_1]);
  end else begin
    raise Exception.Create('Invalid Panel ID ('+IntToStr(ID)+')');
  end;
end;

function TForm1.GetLbl2ID(ID: Integer): TLabel;
var
  X: Integer;
begin
  X:= FPanels.IndexOf(IntToStr(ID));
  if X >= 0 then begin
    Result:= TLabel(PanelByID[ID].Controls[LABEL_2]);
  end else begin
    raise Exception.Create('Invalid Panel ID ('+IntToStr(ID)+')');
  end;
end;

end.

このアクセス違反の結果:

Second AV

PS - 新しいコードの他の部分はうまくいきませんが、それは別の質問の問題です:P

1

2 答え

私はあなたがこれをあまりにも複雑にしたと思います。 Delphiはオブジェクト指向プログラミング言語であり、あなたはその事実をあなたの利益に利用すべきです。まず、動的に作成されたすべてのコンポーネントの Owner Parent プロパティを適切に割り当てることで、親が破棄されたときにガベージコレクタ(GC)が自動的にこれらのコンポーネントを解放することを確認します。以下は、あなたの問題に対するオブジェクトのアプローチです。

unit MainForm;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, Buttons, StdCtrls, ExtCtrls;

type
  TDynamicPanel = class(TPanel)
  private
    procedure OnDelClick(Sender: TObject);
  public
    constructor Create(AOwner: TComponent); override;
  end;

  TfrmMain = class(TForm)
    Panel1: TPanel;
    Label1: TLabel;
    sbMain: TScrollBox;
    sbAdd: TSpeedButton;
    procedure sbAddClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  frmMain: TfrmMain;

implementation

{$R *.dfm}

{ TDynamicPanel }

constructor TDynamicPanel.Create(AOwner: TComponent);
const
  cMargin = 5;
var
  L1, L2: TLabel;
  E1: TEdit;
  CB1: TComboBox;
  B1: TButton;
begin
  inherited;
  Parent := TWinControl(AOwner);
  Width := 150;
  Align := alLeft;
  Color := clSilver;

  L1 := TLabel.Create(Self);
  L1.Parent := Self;
  L1.Left := cMargin;
  L1.Top := cMargin;
  L1.Caption := 'Some text box';
  L1.Font.Style := [fsBold];

  E1 := TEdit.Create(Self);
  E1.Parent := Self;
  E1.Left := cMargin;
  E1.Top := L1.Top + L1.Height + cMargin;
  E1.Width := 140;
  E1.Text := 'Some string value';

  L2 := TLabel.Create(Self);
  L2.Parent := Self;
  L2.Left := cMargin;
  L2.Top := E1.Top + E1.Height + cMargin;
  L2.Caption := 'Some Combo box';
  L2.Font.Style := [fsBold];

  CB1 := TComboBox.Create(Self);
  CB1.Parent := Self;
  CB1.Left := cMargin;
  CB1.Top := L2.Top + L2.Height + cMargin;
  CB1.Width := 140;
  CB1.Style := csDropDownList;
  CB1.Items.Add('Some selected value');
  CB1.Items.Add('Some other value');
  CB1.ItemIndex := 0;

  B1 := TButton.Create(Self);
  B1.Parent := Self;
  B1.Left := cMargin;
  B1.Top := Self.ClientHeight - cMargin - 25;
  B1.Width := 60;
  B1.Height := 25;
  B1.Caption := 'Delete';
  B1.OnClick := OnDelClick;
end;

procedure TDynamicPanel.OnDelClick(Sender: TObject);
begin
  Free;
end;

procedure TfrmMain.sbAddClick(Sender: TObject);
var
  dp: TDynamicPanel;
begin
  dp := TDynamicPanel.Create(sbMain);
end;

end.

これはほんの一例です。 TPanel から派生した新しいクラスを作成する方法を示しています。そのクラスには、1つのパネルに関連付けられた完全なロジックが含まれています。コンストラクタの Owner パラメータとして TScrollBox を渡すので、各パネルはその内部に作成されます。各パネルの Parent プロパティも TScrollBox に設定され、Panel内のすべてのコンポーネントのOwnerプロパティとParentプロパティがパネル自体に設定されます。 'Delete'ボタンをクリックすると、 TDynamicPanel インスタンスごとに分離された OnDelClick メソッドが実行され、そのパネルのみが解放(破棄)され、コンポーネント。フォームを閉じると、動的に作成された各パネルを含め、その内部のすべてのコンポーネントも自動的に解放されます。

この例では、 Index によるパネルへのアクセスやパネルの削除については触れていません。その機能が必要な場合は、これを実装する方がずっと簡単です。

4
追加された
@DavidHeffernanしかし、代わりにそれらを削除するように私のコントロールの中にメッセージを入れることができます:D
追加された 著者 Jerry Dodge,
良い代替方法+1
追加された 著者 Jerry Dodge,
おそらく私の元のサンプルプロジェクトが最初に意図した私の提案を変更するでしょう。 Delphiのオブジェクト継承能力を利用せずにDelphiを使用することは、車を買うようなもので、決してそれを運転するものではありません...
追加された 著者 Jerry Dodge,
ありがとう、それは素晴らしい例です。通常は、私は通常あなたの例よりもさらに複雑なことをします。しかし、私のサンプルは実際に自分のクラスを宣言する必要がないように意図されています(私は私の質問で言及していないことを知っていますが、それは無関係だと思いました)。
追加された 著者 Jerry Dodge,
それが価値あるものについては、これはJerryのコードと同じ問題の対象になります。 OnClickイベントハンドラからボタンを削除することはできません。
追加された 著者 David Heffernan,
私は自分自身で新しいクラスを作成せずに物事を作ろうとしていますが、このケースは、主に機能要素の明確な分離のために、新しいクラスがタスクに適しているという完璧な例です。
追加された 著者 LightBulb,

パネル自体を破壊する前に、パネル内のコンポーネントを破壊する必要があります。私はなぜこれがそうだとは分かりませんが、パネルがパネルを破壊し始めた後、あなたのコントロールがメッセージを処理しようとしていることをデバッガが教えてくれました。それは良いことではありません。

このバージョンの Delete メソッドは、ジョブを完了させます。パネルを殺す前に、子どもたちの周りを反復して、残さない限りそれぞれを殺します。

procedure TForm1.Delete(const Index: Integer);
var
  P: TPanel;
begin
  if (Index >= 0) and (Index < FPanels.Count) then begin
    try
      P := TPanel(FPanels.Objects[Index]);
      while P.ControlCount>0 do
        P.Controls[0].Free;
      P.Free;
    except
      on e: exception do begin
        raise Exception.Create('Failed to delete panel: '+e.Message);
      end;
    end;
    FPanels.Delete(Index);
  end else begin
    raise Exception.Create('Panel index out of bounds ('+IntToStr(Index)+')');
  end;
end;

更新

このコードは、ボタンが独自のイベントハンドラから破棄されるため、おそらくまだ破損しています。コードは破棄されたオブジェクト上で実行されるため、実行時エラーにつながる可能性があります。

イベントハンドラで削除しないことでその問題を解決してください。代わりに、フォームにメッセージを投稿し、そのメッセージの受信時に削除してください。

procedure TForm1.DelPanClick(Sender: TObject); 
begin
  if Sender is TBitBtn then begin
    PostMessage(Handle, WM_USER, TBitBtn(Sender).Tag, 0);
  end; 
end;

次に、メッセージハンドラを作成し、そこからDeleteIDを呼び出します。

3
追加された
私はちょうど上記の新しいコードを掲示した、それは主に追加でした。右から左に削除すると機能しますが、左から右に削除すると、5分の1のA/Vが削除されます。
追加された 著者 Jerry Dodge,
さて、 Delete コードを回答のコードに変更する以外に、質問に関するものは何も変更しませんでした。それ以外の場合は、追加されたプロパティのみが変更されます。
追加された 著者 Jerry Dodge,
さて、私は自分のプロジェクトに追加を続けようとしています。私はこれだけの理由でそれを書いていません。できるだけ多くの関連情報を追加します。新しい追加情報をコードから取り除こうとすると、おそらくコードが不正になるでしょう。そこで私は新しいユニット全体をペーストしました。
追加された 著者 Jerry Dodge,
ありがとうございましたが、今は5の3番目を削除した後、再びクラッシュしています。上記の私のコード投稿以来、私はいくつかの変更を加えたので、私はそれを調べて、報告します...
追加された 著者 Jerry Dodge,
HA!できます!我々が知っているようにそれはそうです。受け入れられました。
追加された 著者 Jerry Dodge,
私は私の更新が仕事を完了することを望む.....
追加された 著者 David Heffernan,
@ジェリー良い。私のより良いものを得るためにこれらの問題が好きではありません。
追加された 著者 David Heffernan,
それを自由にしてください。しかし、このQの目的のためにユニットの別のコピーを保管してください。それは単なるユニットであり、あなたは起立して走らせるのを簡単にする良い仕事をしました。変更することで問題を混乱させる必要はありません。
追加された 著者 David Heffernan,
そうでないかもしれない。しかし、その場合、なぜすべての余分なコードを投稿するのですか?なぜあなたの変更について話すのですか?それは問題を複雑にするだけです。とにかく、私はまだAVを再現できません。
追加された 著者 David Heffernan,
あきらめる。新しいコードを修正しようとすると、もう一度変更するだけです。ゴールポストを動かすことは公正ではありません。
追加された 著者 David Heffernan,
さて、この変更で、私が好きなだけ追加したり削除したりすることができました。私が見ることができないあなたの他のコードを助けることはできません。
追加された 著者 David Heffernan,