• Delphi的Owner与Parent可以不一致,而且Owner不是必须存在(一共7个问题) good


    问题1:Owner与Parent不一致:
    新建一个Form,上面放一个Button1,一个Panel1,然后在Panel1上再放一个Button2,测试结果:
    procedure TForm1.Button2Click(Sender: TObject);
    begin
    ShowMessage(button2.Owner.Name); // 显示Form1,凡是拖控件放到Form上的控件,它们的Owner都是Form,可从TControl.ReadState进入深入研究
    ShowMessage(button2.Parent.Name); // 显示Panel1
    end;
    示例代码:
    procedure TForm1.Button1Click(Sender: TObject);
    var
    Button2: TButton;
    begin
    Button2 := TButton.Create(Nil); // 注意参数是Nil,即Owner为空
    Button2.Name := 'Button2';
    Button2.Left := 100;
    Button2.Top := 100;

    Button2.Parent := Panel1;
    //ShowMessage(button2.Owner.Name); // 注意,无法读取
    ShowMessage(button2.Parent.Name);
    end;
    分析过程:
    constructor TButton.Create(AOwner: TComponent);
    inherited Create(AOwner);

    constructor TButtonControl.Create(AOwner: TComponent);
    inherited Create(AOwner);

    constructor TWinControl.Create(AOwner: TComponent);
    inherited Create(AOwner); // 应该会调用TObject的NewInstance

    constructor TControl.Create(AOwner: TComponent);
    inherited Create(AOwner); // important 把控件放入到Owner的容器中

    procedure TControl.SetParent(AParent: TWinControl);
    begin
    if AParent = Self then
    raise EInvalidOperation.CreateRes(@SControlParentSetToSelf); // 绝对不允许父控件是控件自己
    AParent.InsertControl(Self); // important7 一级入口,子控件管理与显示都在这里处理。AParent是父控件,Self是子控件,现在是在执行子控件的函数。
    end;

    procedure TWinControl.InsertControl(AControl: TControl);
    begin
    // 以父控件的身份来执行此函数,AControl是子控件
    if AControl is TWinControl then
    begin
    AControl.Perform(CM_PARENTCTL3DCHANGED, 0, 0); // fixme 只有Win控件才有3D属性?
    UpdateControlState; // important7 二级入口,父控件刷新自己的状态。它会自己寻找最上层的Win控件,并判断是否需要显示。如果需要显示,就调用API递归显示Win子控件,然后显示自己。
    end;
    end;

    procedure TWinControl.UpdateControlState;
    var
    Control: TWinControl;
    begin
    // 以父控件的身份来执行此函数
    while Control.Parent <> nil do
    begin
    Control := Control.Parent;
    if not Control.Showing then Exit; // 如果某个祖先控件不显示,那么子控件也就不用显示了
    end;
    if (Control is TCustomForm) or (Control.FParentWindow <> 0) then
    UpdateShowing; // 显示控件的入口,里面做一系列的事情(检测控件自己是否需要显示,创建控件等等)
    end;

    procedure TWinControl.UpdateShowing;
    begin
    // 检测是否需要显示
    // 如果需要显示,那么先检测自己的FHandle是否为空,为空的话就当场创建(这里会设置当前控件的Parent)
    if ShowControl then CreateHandle;
    // 递归显示自己的所有子控件
    // 等所有Win子控件显示完毕,显示自己
    Perform(CM_SHOWINGCHANGED, 0, 0); // 五级入口:新创建的控件,会在这里调用API真正显示,并且到此为止。
    end;

    procedure TWinControl.CMShowingChanged(var Message: TMessage);
    const
    ShowFlags: array[Boolean] of Word = (
    SWP_NOSIZE + SWP_NOMOVE + SWP_NOZORDER + SWP_NOACTIVATE + SWP_HIDEWINDOW, // 一个隐藏,其它都一样
    SWP_NOSIZE + SWP_NOMOVE + SWP_NOZORDER + SWP_NOACTIVATE + SWP_SHOWWINDOW); // 一个显示,SWP_NOMOVE表示位置不变 
    begin
    // important 五级入口:调用API真正显示Windows窗口。屏蔽这句,一切Form的子控件都不显示
    SetWindowPos(FHandle, 0, 0, 0, 0, 0, ShowFlags[FShowing]); // API,改变窗口的尺寸,位置和Z序。注意最后一个参数,是否激活就看它的作用。
    end;

    其中创建窗口对象的过程如下:
    procedure TWinControl.CreateHandle;
    begin
    CreateWnd;
    SetWindowPos; // API 调整位置
    SetProp(FHandle, MakeIntAtom(ControlAtom),THandle(Self)); // 全局记录Handle
    end;

    procedure TWinControl.CreateWnd;
    begin
    CreateParams(Params); // 准备参数,包括Owner的handle,当作当前控件的Parent
    Windows.RegisterClass(WindowClass); // 注册窗口类
    CreateWindowHandle(Params); // 根据Params申请窗口对象
    end;

    procedure TWinControl.CreateWindowHandle(const Params: TCreateParams);
    begin
    // 根据之前准备的Params参数使用API创建窗口。其10个参数都是Params的参数,0表示Menu,WindowClass的十项内容只用到了hInstance一项
    // important 注意,此时才真正设置父窗口。它的父窗口是WndParent,也就是Owner的窗口句柄
    // important 控件移到正确的显示位置,就是靠这个X和Y,会移到父控件区域的相对位置(实践检测)。而VC里一般使用CW_USEDEFAULT
    with Params do
    FHandle := CreateWindowEx(ExStyle, WinClassName, Caption, Style, X, Y, Width, Height, WndParent, 0, WindowClass.hInstance, Param);
    end;

    ------------------------------------------------------------
    问题2:可使用IsChild进行探测。无论Windows还是Delphi,自己都不是自己的父窗口

    procedure TForm1.Button1Click(Sender: TObject);
    begin
    if IsChild(handle, handle) then ShowMessage('yes') else ShowMessage('no'); // 显示No, 因为Form自己不是自己的Child

    if IsChild(handle, button1.handle) then ShowMessage('yes') // 显示Yes
    else ShowMessage('no');

    if IsChild(handle, panel1.handle) then ShowMessage('yes') // 显示Yes
    else ShowMessage('no');

    if IsChild(handle, button2.handle) then ShowMessage('yes') // 显示Yes,通过实测发现,凡是具有子孙关系的控件(不仅仅是父子关系),IsChild都成立
    else ShowMessage('no');
    end;

    ------------------------------------------------------------
    问题3:而且Owner不是必须存在,比如:

    procedure TForm1.Button1Click(Sender: TObject);
    var
    Button2: TButton;
    begin
    Button2 := TButton.Create(Nil); // 这个Nil就是指Owner!
    Button2.Left := 100;
    Button2.Top := 100;
    Button2.Parent := Panel1;

    // ShowMessage(button2.Owner.Name); // 运行错误!
    ShowMessage(button2.Parent.Name);
    end;

    最有趣的是,

    ------------------------------------------------------------
    问题4:Owner的设置比较简单,但Parent到底是什么设置的呢?

    关键就是TWinControl.CreateWnd里设置:
    Params.WndParent := TWinControl(Owner).Handle; // 即把Owner的Handle作为父窗口。然而button2的Owner是Form1,所以它的Parent也是Form1

    然后调用:
    procedure TWinControl.CreateWindowHandle(const Params: TCreateParams);
    begin
    // 根据之前准备的Params参数使用API创建窗口。其10个参数都是Params的参数,0表示Menu,WindowClass的十项内容只用到了hInstance一项
    // important 注意,此时才真正设置父窗口。它的父窗口是WndParent,也就是Owner的窗口句柄
    // important 控件移到正确的显示位置,就是靠这个X和Y,会移到父控件区域的相对位置(实践检测)。而VC里一般使用CW_USEDEFAULT
    with Params do
    FHandle := CreateWindowEx(ExStyle, WinClassName, Caption, Style, X, Y, Width, Height, WndParent, 0, WindowClass.hInstance, Param);
    end;

    ------------------------------------------------------------
    问题5:碰到Owner为空的时候,什么时候才能正确设置Parent呢?

    回答:没有Owner的时候,尽管内存对象已经被创建,但是它自身的windows句柄并没有被创建,此时它也不需要父控件的句柄(即使有,也不会使用)。它的windows窗口创建要延迟到执行SetParent的时候。当指定了Parent,那么设置它的父控件句柄也是自然而然的事情。

    不需要父控件的句柄是指(在指定父控件以前,因为不会执行到CreateWnd函数,即使有Owner也是如此):
    procedure TWinControl.CreateWnd;
    begin
    WndParent := TWinControl(Owner).Handle;
    end;

    测试代码:
    procedure TForm1.Button1Click(Sender: TObject);
    var
    Button2: TButton;
    begin
    Button2 := TButton.Create(Nil);
    Button2.Left := 100;
    Button2.Top := 100;
    Button2.Parent := Panel1; // 这里调用了SetParent函数,那么它的Delphi属性FParent就有值了,然后就可以为API准备参数了

    ShowMessage(button2.Parent.Name);
    end;

    关键流程如下:
    1. 设置Delphi控件的父子关系:TControl.SetParent->TWinControl.InsertControl->TWinControl.Insert->TWinControl.UpdateControlState->TWinControl.UpdateShowing
    2. 真正创建控件 CreateHandle->CreateWnd->CreateParams(Params)->CreateWindowHandle(Params)
    3. 最后还要执行 TWinControl.CMShowingChanged 进行显示

    其中在执行Insert函数的时候,设置了Delphi的FParent属性
    procedure TWinControl.Insert(AControl: TControl);
    begin
    AControl.FParent := Self;
    end;

    其中在执行CreateParams函数的时候,通过Delphi的FParent属性,得到了父控件的句柄
    procedure TWinControl.CreateParams(var Params: TCreateParams);
    begin
    if Parent <> nil then
    Params.WndParent := Parent.GetHandle // 这里得到父控件的句柄,随时准备让API使用!
    else
    Params.WndParent := FParentWindow; // 这句什么时候会被执行?
    end;

    ------------------------------------------------------------
    问题6:控件显示之后,到底什么时候被移到了正确的坐标的呢?

    回答:这个问题就不正确,其实是创建窗口的时候就放到了正确的x,y位置,以后调整显示的时候,调用SetWindowPos函数,其风格都是SWP_NOSIZE + SWP_NOMOVE + SWP_NOZORDER + SWP_NOACTIVATE,即大小不变,位置不变,Z轴不变,不激活窗口,只改变显示或者不显示

    procedure TWinControl.CreateWindowHandle(const Params: TCreateParams);
    begin
    // 控件移到正确的显示位置,就是靠这个X和Y,会移到父控件区域的相对位置(实践检测)。而VC里一般使用CW_USEDEFAULT
    with Params do
    FHandle := CreateWindowEx(ExStyle, WinClassName, Caption, Style, X, Y, Width, Height, WndParent, 0, WindowClass.hInstance, Param);
    end;

    需要显示或隐藏的时候,调用:
    procedure TWinControl.CMShowingChanged(var Message: TMessage);
    const
    ShowFlags: array[Boolean] of Word = (
    SWP_NOSIZE + SWP_NOMOVE + SWP_NOZORDER + SWP_NOACTIVATE + SWP_HIDEWINDOW, // 一个隐藏,其它都一样
    SWP_NOSIZE + SWP_NOMOVE + SWP_NOZORDER + SWP_NOACTIVATE + SWP_SHOWWINDOW); // 一个显示,SWP_NOMOVE表示位置不变
    begin
    // 五级入口:调用API真正显示Windows窗口。屏蔽这句,一切Form的子控件都不显示
    SetWindowPos(FHandle, 0, 0, 0, 0, 0, ShowFlags[FShowing]); // API,改变窗口的尺寸,位置和Z序。注意最后一个参数,是否激活就看它的作用
    end;

    ------------------------------------------------------------

    问题7: 既然Owner可以是nil,那么当然也可以是TGraphicControl,并且随着这个图形控件的释放,其子控件(哪怕是Win子控件)也会被释放!!

    procedure TForm1.Button1Click(Sender: TObject);
    var
    Button2: TButton;
    begin

    Button2 := TButton.Create(Image1);
    Button2.Name := 'Button2';
    Button2.Left := 100;
    Button2.Top := 100;

    Button2.Parent := Panel1;
    ShowMessage(button2.Owner.Name);
    ShowMessage(button2.Parent.Name);

    image1.Free;
    end;

    ------------------------------------------------------------

    参考:http://www.cnblogs.com/del/archive/2008/03/02/1088137.html

  • 相关阅读:
    Response.Redirect、Server.Transfer、Server.Execute的区别
    js删除Array指定位置元素方法
    用Json.NET将json字符串反序列化为json匿名对象
    Asp.net中编程方式调用ashx(通过webRequest)
    Server.Transfer中传递ViewState方法
    Ext.Net中Ext.net.DirectMethods无法找到DirectMethod
    IFrame网页加载完成事件
    oracle中grant授权说明
    深度剖析Byteart Retail案例:前言
    深度剖析Byteart Retail案例:仓储(Repository)及其上下文(Repository Context)
  • 原文地址:https://www.cnblogs.com/findumars/p/4740839.html
Copyright © 2020-2023  润新知