控件的Destroy出错问题:无法释放相关的对像 (50分)

  • 主题发起人 主题发起人 南腔北调
  • 开始时间 开始时间
在 createwnd 中创建的对象,要在 DestoryWnd过程中释放,而不是在 Destory过程。
 
有一点很奇怪
就是在调用Destroy时,里面加句
showmessage(inttostr(tag));
可以运行成功
也不会出错
可加
showmessage(items[0]);
就不行了,会提示出现上面我说的错误
按理来说tag与items都是一个变量而已,不同的只是存储的内容
为什么到Destroy时,tag可用,items却不行了?
 
再补充一句,跟什么tid释放无关
就算不用addobtect,只用add,也是一样的
例:
unit CHOPCombobox;

interface

uses
SysUtils, Classes, Controls, StdCtrls,Forms,messages,CHManageDBConn,shareproc,
CustomDataClass,Dialogs;

type
TComboBoxTest = class(TComboBox)
private
{ Private declarations }
protected
{ Protected declarations }
public
procedure CreateWnd;override;
// constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
{ Public declarations }
published
{ Published declarations }
end;
procedure Register;

implementation

procedure Register;
begin
RegisterComponents('CHGroupware', [TComboBoxTest]);
end;

{ TComboBoxTest }
procedure TComboBoxTest.CreateWnd;
begin
inherited CreateWnd;
if not (csDesigning in ComponentState) then
begin
self.Tag:=111;
items.Add('bbbbbbbb');
items.Add('aaaaaaa');
items.Add('ccccc');
end;
end;

destructor TComboBoxTest.Destroy;
begin
if not (csDesigning in ComponentState) then
showmessage(items[0]);//这里会出错

// showmessage(inttostr((self.items.Objects[1] as tid).id));

inherited Destroy;
end;
 
Items属性只有在Combobox有Handle(已经创建了窗口)下有效,而且要创建Handle必须有
Parent.你可以试试下面代码:
var
a:TComboBox;

begin
a:= TComboBox.Create(nil);

a.Parent := form1; //去掉这一行会出错。
a.Items.Add('a');

end;
 
TO xeen
再看了看TCustomComboBox源码,感觉的确是这个问题
一直弄不清楚FORM的释放顺序是怎么样的?
是先释放FORM再释放上面的组件呢,还是先释放组件再释放FORM?
我觉得是后者

弄清楚原因就好了,省得一直想不通
 
肯定是先释放组件再Form,这个没问题。
关键是控件执行 Destroy 之前,已经执行了 DestroyWnd过程了,这样控件的
Handle就失效了,这才是这个错误的原因。
 
DestroyWnd怎么用呢?似乎并不执行,谁说说?

在form的destructor TWinControl.Destroy;中,remove子控件时:
while I <> 0 do
begin
Instance := Controls[I - 1];
Remove(Instance);

procedure TWinControl.Remove(AControl: TControl);
begin
if AControl is TWinControl then
begin
ListRemove(FTabList, AControl);
ListRemove(FWinControls, AControl);
end else
ListRemove(FControls, AControl);
AControl.FParent := nil; //完蛋,Parent没了。
end;

用CreateWnd,会在Destroy时调用form的CreateWnd,最后因为ComponentState的值不包含csReading,而出错,我曾试图通过修改ComponentState来避免这个错误,但却触发了其它的错误,唉,VCL真是复杂。

又测试了一些,反正TComboBoxTest.Destroy时,肯定取不到items.Count的值,这是TStrings的特性决定的,所以,只有在Owner的Destroy事件中访问items才有可能(即在Remove(Instance);之前动作),你要达到你的目的,最好是从TCustomComboBox继承,然后重写TComboBoxStrings,增加在Destroy时释放那些指针,替换TCustomComboBox.FItems,大概这样才行吧。

那位高手来说说CreateWnd的用途。
 
createwnd不要去覆盖它,除非你要自己创建控件句柄,这就是你的根本错误.

实际上完全可以覆盖create方法来做同样的事情,不知道你为什么要现拽似的在createwnd中做,简直是乱搞呵呵
 
大家想的太复杂了,改成“for I := Items.Count - 1 downto 0 do”这样是可以的,
也可以改成“while Items.Count > 0 do (Items as TObject).Free”。
下面说一下原理,如果你写“for I := 0 to Items.Count - 1 do”这样会出错,因为Items是一个TList对象,你可以把它看成一个链表。当第一次执行把Items[0]释放掉之后,Items会用Items[1]代替Items[0]的位置,然后将Count - 1。这时Items[0]指向原来的Items[1],也就是把整个链表向前挪了一个位置。你认为for循环将Items[0]释放后会去释放Items[1],其实这时它释放的是原来的Items[3]。你这样做只相当于把原来Items中的单数对象释放掉了,而偶数对象并没有释放。当执行到Items.Count / 2时(也就是中间时),便自然会出现下标越界。这样说可能你不会很明白,你可以跟到VCL控件内部看一下TComboBox的释放过程。
 
不好意思一时手快打错了,不是Items[3],应该是Items[2]才对。[:D]
 
yangying_2000说的对也不对,问题不是出在Create,而是在Items.Count上,在CreateWnd中写这些代码是有问题,在Destory时,CreateWnd会被再次调用到。

Kevin.C,问题不要想当然,你说的根本是另一码事,楼主的循环修改了Items的长度了吗?

TComboBox.Items不是普通的TStrings,而是TComboBoxStrings,它不是用FCount来保存Count,取Count是用Result := SendMessage(ComboBox.Handle, CB_GETCOUNT, 0, 0);,请看VCL源码。

我的想法是:

重写TComboBoxStrings,照抄全部过程

TComboBoxStrings = class(TStrings)
private
ComboBox: TCustomComboBox;
FList: array of TObject;
FCount: Integer; // 这两个是自己增加的
protected
function Get(Index: Integer): string; override;
function GetCount: Integer; override;
function GetObject(Index: Integer): TObject; override;
procedure PutObject(Index: Integer; AObject: TObject); override;
procedure SetUpdateState(Updating: Boolean); override;
public
function Add(const S: string): Integer; override;
procedure Clear; override;
procedure Delete(Index: Integer); override;
function IndexOf(const S: string): Integer; override;
procedure Insert(Index: Integer; const S: string); override;
destructor Destroy; override;
end;

然后仿照TStringList,增加FCount、FList,在Add、Delete、PutObject等Count、FList发生变化的地方手工修改FCount、FList,然后在TComboBoxTest.Create中,替换原来的Items,

constructor TComboBoxTest.Create(AOwner: TComponent);
var
cid:tid;
ss:TComboBoxStrings;
begin
inherited Create(AOwner);
if not (csDesigning in ComponentState) then
begin
ffitem:=items; //ffitem定义在private,在destory中再释放
ss:=TComboBoxStrings.Create ;
ss.ComboBox := Self;
items :=ss;
CreateWnd;
cid:=tid.Create;
cid.id:=1;
items.AddObject('bbbbbbbb',cid);
cid:=tid.Create;
cid.id:=2;
items.AddObject('aaaaaaa',cid);
cid:=tid.Create;
cid.id:=3;
items.AddObject('ccccc',cid);
end;
end;


然后TComboBoxStrings.Destory中(注意不是TComboBoxTest),根据FCount、FList,增加释放对象的代码,

destructor TComboBoxStrings.Destroy;
var
i:integer;
begin
for i:=0 to FCount-1 do
if FList<>nil then FreeAndNil(FList);
inherited;
end;

这样应该可以满足楼主的需求,但还有个问题解决不了,就是那个ffitem,不知该怎么释放,TComboBoxStrings中有个ComboBox,TComboBoxStrings的各种方法都是从往这个对象发消息得来的返回值,可能在释放时ComboBox也做些什么,老是出错。

费了这么多周折,我看楼主还是放弃用items来保存、释放对象吧,因为这些对象是要在comboboxtest的owner的destroy过程中操作的,还不如在tcomboboxtest中另外增加方法来操作省事的多。
 
//没用
//我加了下面一句:
//if not (csDesigning in ComponentState) then
//begin
// showmessage('aaaaaaaaa');//根本就不会运行。。。
先解决你的小小疑惑.
ShowMessage()调用过程中有
.......
repeat
Application.HandleMessage;
if Application.FTerminate then ModalResult := mrCancel else
if ModalResult <> 0 then CloseModal;
until ModalResult <> 0;
......
所以你如果是在程序还没TERMINATE之前释放COMBOBOXTEST则会象你所说的运行(不管如何它是运行了的)显示出一个窗口来.如果是在主窗口DESTROY时释放COMBOBOXTESST则不会显示窗口,尽管执行了很长很长的代码(我也是跟踪才找到原因的^_^)
 
疑惑ing...............[:(][:(][:(].
 
结分吧,还是没说到错误,不过也谢谢你帮我看问题
更感谢猛禽、hygsxy、kouchun(第一个顶)
分不多,还请各位笑纳
 
to dirk
我完全同意您的说法
刚才没注意,结完贴才看到您的回贴,不好意思,只给了2分
我上面说了,先想办法绕过再说(正如你最后所说)
在这个控件中,我最后的确是放弃了用items来保存、释放对象,
我在COMBOBOX中另外加了方法与TSTRINGS来跟踪ID值,实现很顺利
感觉现在大富翁上的高手真的是越来越少了
或者说高手几乎是上来晃晃,就随手Close了IE

不过幸亏还不致于冷场,真希望后浪胜前浪
希望我们这些后浪加油吧
对技术了解更深一些
 
这个分拿得汗,应该给dirk才对[:)]

看来还是另外保存是最好的办法。
 
to Kevin.C:
我想你是看错代码了,最后释放中的循环调用只是为了释放对象,不是清除list,并没有调用delelte,所以不会出现你说的问题,
以后回答问题的时候看清楚先,否则会闹笑话的
 
to 猛禽
分乃身外之物。。。
 
后退
顶部