【问题标题】:Deleting dynamic controls - Access Violations in one scenario, but not another删除动态控件 - 一种情况下的访问冲突,但不是另一种情况
【发布时间】:2012-01-23 05:24:35
【问题描述】:

我创建了这个小示例应用程序来演示如何维护一个控件列表,特别是一些动态创建的 TPanel 控件,每个控件都包含一些子控件。一切似乎都很好,除了一件奇怪的事情。当然,当我关闭我的应用程序时,它会遍历所有创建的控件并释放它们。这完美地工作。但奇怪的是,当我尝试删除其中一个时,我在同一代码中遇到访问冲突,该代码在关闭时完美运行。

只是为了稍微解释一下下面的代码,背景中有一个 TStringList,其中包含每个面板的一个对象。我还维护一个“Last 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 次访问违规之后,面板最终会删除:


编辑:

在我的代码中添加了一些内容并添加了 David 的修复程序后,它确实有效,但现在如果从左到右删除,删除 5 个面板中的第三个时会出现另一个 A/V。但是从右到左删除,一切正常。下面是我的新代码,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.

导致此访问冲突:

PS - 我知道新代码的其他部分不能正常工作,但这是另一个问题:P

【问题讨论】:

    标签: delphi delphi-7 panel


    【解决方案1】:

    您需要在销毁面板本身之前销毁面板中的组件。我不确定为什么会这样,但调试器告诉我,您的控件在面板开始自我破坏后试图处理消息。这不好。

    这个版本的 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。

    【讨论】:

    • 谢谢你的工作,但现在在删除 5 个中的第 3 个后,它又崩溃了。我会调查并报告,因为自从我在上面发布代码后,我确实做了一些其他更改......
    • 好吧,有了这个更改,我可以添加和删除任意数量的内容。对于您看不到的其他代码,我无能为力。
    • 我刚刚发布了上面的新代码,但主要是添加。当从右到左删除时,它可以工作,但是当从左到右删除时,5的三分之一给出一个A/V。
    • 我放弃了。如果我尝试修复新代码,您只需再次更改它。移动球门柱是不公平的。
    • @Jerry 好。不喜欢这些问题来战胜我。
    【解决方案2】:

    我认为你把这件事弄得太复杂了。 Delphi 是一种面向对象的编程语言,您应该利用这一事实来发挥自己的优势。首先,通过正确分配所有动态创建的组件的OwnerParent 属性,您可以确保垃圾收集器(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 派生的新类。该类包含与单个面板关联的完整逻辑。由于我们将TScrollBox 作为构造函数的Owner 参数传递,因此每个面板都是在其中创建的。每个面板的Parent 属性也设置为TScrollBox,而面板内所有包含组件的 Owner 和 Parent 属性设置为面板本身。当我们单击“删除”按钮时,它会执行OnDelClick 方法,该方法为每个TDynamicPanel 实例隔离,从而仅释放(销毁)该面板和(自动)所有包含的组件。如果您关闭表单,它还会自动释放其中的所有组件,包括每个动态创建的面板。

    请注意,此示例不包括通过Index 访问或删除任何面板。如果需要该功能,则以这种方式实现它要简单得多。

    【讨论】:

    • 谢谢,这是一个很好的例子,在平常的日子里,我通常会做一些比你的例子更复杂的事情。但我的示例实际上是为了避免必须声明自己的类(我知道我没有在我的问题中提到这一点,但我认为这无关紧要)。
    • 虽然我自己尝试在不创建新类的情况下制作东西,但这个案例是新类更适合该任务的完美示例,主要是因为功能元素的明确分离。
    • 也许我会更改我最初的示例项目打算用于的提案。在不利用其对象继承能力的情况下使用 Delphi 就像买了一辆车却从不开车......
    • 对于它的价值,这将受到与 Jerry 的代码相同的问题。您不能从其 OnClick 事件处理程序中删除按钮。
    • @DavidHeffernan 但是我可以将消息放在我的控件中以使其删除它们:D
    猜你喜欢
    • 2017-08-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-01
    • 2020-09-06
    • 1970-01-01
    • 2018-09-15
    相关资源
    最近更新 更多