我掀起了这个小样本应用程序来演示如何维护控件列表,特别是一些动态创建的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的修复后,它确实有效,但现在另一个A / V会在删除5个面板中的第三个时从左到右删除。但从右到左删除,一切正常。以下是我的新代码,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
答案 0 :(得分:4)
我认为你这太复杂了。 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
的新类。该类包含与单个面板关联的完整逻辑。由于我们将TScrollBox
作为构造函数的Owner
参数传递,因此每个面板都在其中创建。每个面板的Parent
属性也设置为TScrollBox
,而Panel内所有包含组件的所有者和父属性都设置为面板本身。当我们单击“删除”按钮时,它会执行为每个OnDelClick
实例隔离的TDynamicPanel
方法,从而仅释放(销毁)该面板和(自动)所有包含组件。如果您关闭表单,它还会自动释放其中的所有组件,包括每个动态创建的面板。
请注意,此示例不包括Index
访问或删除任何面板。如果需要该功能,以这种方式实现它就会简单得多。
答案 1 :(得分:3)
在销毁面板本身之前,您需要销毁面板中的组件。我不确定为什么会这样,但是调试器告诉我你的控件试图在面板开始破坏之后处理消息。那不好。
此版本的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。