今天一直在考虑是否有一个内存泄漏的问题,看一下Delphi的源码。
结果竟然发现控件的Parent能够释放它的子控件,一直以为只有控件的AOwner才能释放,汗!!!!现在总结如下:
情况1:直接从TComponent继承下来的组件,将由它的AOwner释放(如果你不自己释放,下来的情况也一样)
情况2: 如果控件是从TControl继承下来的,你设定了他的Parent,当这个Parent释放时,它会释放掉所有的子控件。
情况3:当最后AOwner释放它的Components时,从最后一个组件开始删除,这里这个组件也会删除它内部的Comonents,如果组件是TWinControl,则它会释放掉它所有的子控件。所以说,Aowner只是负责释放所有的不可视组件,和一部分的可视控件,大部分可视控件还是由Parent来释放的。标记如上,引以为诫!!!大汗,引以为诫!!!!

解决方案 »

  1.   

    从TControl继承下来的控件,好象也是从TComponent继承的哦!
      

  2.   

    所以,好象一个这样的控件,无论是owner释放,还是parent释放,自己都会被释放!
    好象他们的释放机制是差不多的,就是bject list和notify!
      

  3.   

    这个可能很多人都知道了,我今天才发现,呵呵。我说的直接从TComponent继承下来,就是指不可视的控件,
    而从TControl继承下来的,就是可视控件,
    换句话说,就是有Parent的控件,和没有Parent的控件。举一个例子:
    var 
      btn: TButton;
    begin
      btn:= TButton.create(Self); //这里的Self指的是TForm;
      btn.Parent:= Panel1;        //将按钮的Parent设为Panel
      Panel1.Free;                //这时候,Panel会释放掉Btn
    end;
    具体来解释(跟踪一下源代码,你会理解得更清楚):
    当btn:= TButton.create(Self);之后,TForm的Components中就加入了这个Btn
    当btn.Parent:= Panel1; 之后,Panel的FWinControls就加入了Btn
    这个时候,Components和FWinControls都有Btn。
    当Panel1.Free; 之后,Panel1会负责将Btn释放掉。同时FWinControl会删除这一项,Component也会删除这一项。假设,如果btn.Parent:= Panel1; 改为btn.Parent:= Self;
    则可能会由Btn的Aowner即Form来释放。 
      

  4.   

    我觉得应该这样验证:
    var 
      paPare:TPanel;
      btn: TButton;
    begin
      paPare:=TPanel.Create(self);//动态创建一个Panel
      paPare.Parent:=self;
      paPare.SetBound(0,0,200,400);
      btn:= TButton.create(Self);//动态创建一个Button 
      btn.Parent:= paPare;        
      paPare.Free; //释放Panel
      if Not Assigned(btn) then
         ShowMessage('动态创建的按钮已经释放')
      else
         ShowMessage('动态创建的按钮没胡释放,你的结论是错误的');
      

  5.   

    我们可以看一下TWinControl的源码来证明一下destructor TWinControl.Destroy;
    var
      I: Integer;
      Instance: TControl;
    begin
      Destroying;
      if FDockSite then
      begin
        FDockSite := False;
        RegisterDockSite(Self, False);
      end;
      FDockManager := nil;
      FDockClients.Free;
      if Parent <> nil then RemoveFocus(True);
      if FHandle <> 0 then DestroyWindowHandle;
      I := ControlCount;
      while I <> 0 do
      begin
        Instance := Controls[I - 1];××××××子控件链表
        Remove(Instance);×××××××××
        Instance.Destroy;×××××××××
        I := ControlCount;
      end;
      FBrush.Free;
      if FObjectInstance <> nil then Classes.FreeObjectInstance(FObjectInstance);
      inherited Destroy;
    end;我们可以得出结论
     1.确切如楼主所说,TWinControl在Destroy时释放了它的子组件
     2.由于用了Destroy方法,会不会导致重复的释放组件呢?
    源码中我们也可以得出结论: 1.TWinControl有两个List维护了其子组件列表 FWinControlst(可视)和FControls(不可视)
     2.所有的控件的父组件都是TWinControl,可能是这个父组件要有回调函数来负责消息的传递和分发,来使的非可视组件也可以处理消息吧。
      

  6.   

    To:sxqwhxq(步青云)
    如果按你的解释,我的结论其实是没有错的,因为Assigned只有在对象为Nil的时候,才会返回False,如果不为Nil它是返回True的
    而Btn在被Panel释放了内存之后,Btn这个指针其实还是指向刚才那块内存,并不是指向了Nil,只不过区别在于,Btn刚才那块内存在没有释放之前,其他程序是不能访问的,而释放之后,其他程序就可以访问了。你可以在你的代码后面加一样关于Btn的操作,就会出错。
      

  7.   

    的确是这样,以前没有注意到……to: sxqwhxq
      if Not Assigned(btn) then
         ShowMessage('动态创建的按钮已经释放')
      else
         ShowMessage('动态创建的按钮没胡释放,你的结论是错误的');
    这样好像不能够说明btn没有被释放。
    在这儿用btn.Parent := Self;会报AV错。
      

  8.   

    To:RaulWhite(路虽远,我依然前行^_^) 
    关于你的结论:
     2.由于用了Destroy方法,会不会导致重复的释放组件呢?
    为什么会有这个结论呢,请你解释一下。另外:
    1.TWinControl有两个List维护了其子组件列表 FWinControlst(可视)和FControls(不可视)
    2.所有的控件的父组件都是TWinControl,可能是这个父组件要有回调函数来负责消息的传递和分发,来使的非可视组件也可以处理消息吧。其实,FWinControls是放TWinControl的控件,而FControls可以说是放TGraphicControl的控件,而不是不可视的控件。
    而非可视组件其实是没有父组件了,只有从TControl继承下来的才会有父组件,而TControl继承下来的都是可视的控件。
      

  9.   

    考虑:
      当时考虑到如果一个控件C即是一个可视控件A的子控件,又是一个Component B 的子组件,那么在
      A.Destroy时 会调用C.Destroy,后来当B.Destroy时,C(这时会nil)也会调用C.Destroy,
      会出现释放错误才对。后来:
      才发现如果一个组件释放时,会向调用Ower和Parent的方法发出通知,移除Ower和Parent的对自己的引用。也就是说上面的情形是不会出现的。当A.Destroy时 调用C.Destroy,B中的子组件链表中已经没有C了,也就不会再次调用C.Destroy另
      关于可视不可视是我理解有误,多谢楼主提醒。
      

  10.   

    procedure TForm1.Button1Click(Sender: TObject);
    var btn:TButton;
    begin
        btn:=TButton.Create(panel1);
        btn.Parent:=Panel1;
        btn.Left:=0;
        btn.Top:=0;
        /////////////只有如上代码:在Panel1上显示一个按钮
        btn.Parent:=panel2;
        btn.Left:=0;
        btn.Top:=0;
        showmessage(IntToStr(Panel2.ControlCount));
        /////////////只有如上代码:在Panel2上显示一个按钮
        btn.Repaint;
        sleep(3000);//明显的看到panel2上出现按钮
        panel1.Free;
        ////////////按钮和Panel一块消失了
        showmessage(IntToStr(Panel2.ControlCount));//孩子数量
    end;
    执行完了上面的代码会有更直观的了解...不知道ControlCount是怎么减去的...
      

  11.   

    明白了,都在Remove函数里
    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;
    end;procedure ListRemove(var List: TList; Item: Pointer);
    begin
      List.Remove(Item);
      if List.Count = 0 then
      begin
        List.Free;
        List := nil;
      end;
    end;
      

  12.   

    呵呵,这个太好了,这个贴子还是有点用处的。希望大家多读一读VCL的代码,里面有太多的知识值我们去学习了。
      

  13.   

    不过,基础是非常重要的,很多技术问题,有理论知识的支持就变得顺理成章了
    所以我打算看一看Window Programming。
      

  14.   

    我觉得可能是可视组件本身的层次属性决定的,而且是必须的.当一个可视组件parent不存在时,不能被创建;同样,parent被释放时,所包含的组件也只能被释放,否则,不知道如何去画这些组件了.
      

  15.   

    其实思路是很正常的,很多技术从源头上去考虑都是最基础的,你设身处地的以一个delphi设计者的身份去考虑的话,这个问题就顺理成章了。
    一个TwinControl被销毁的时候,我们为了防止内存的泄漏,我们会做什么工作那?
    1、首先,释放以他为Parent的那些控件,不然的话,那些控件将不知道自己显示在什么上面。
    2、再者,必须释放以他为owner的那些控件,所有者是一个控件存在的基础,他被销毁了,以他为owner的控件也将不存在。
    3、但是有时候一个控件的Parent和owner是不一样的,所以remove函数在销毁控件的同时,也把她在其他控件上的记录也都抹去了。这样就防止了,Parent释放的时候释放掉了一次,owner在释放一次时候可能出现的错误。