问题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

Delphi的Owner与Parent可以不一致,而且Owner不是必须存在(一共7个问题) good的更多相关文章

  1. Delphi中Owner和Parent的区别

    Owner为创建者,Parent为容器 他们的类型不同,Owner为TComponent(元件),Parent为TWinControl(窗体控件) Parent属性是指构件的包容器,构件只能在此范围内 ...

  2. 教程-关于Owner和Parent的区别

    Parent属性是指构件的包容器,构件只能在此范围内显示和移动 Owner属性是指构件的所有者,它负责构件的创建和释放.

  3. delphi中WebBrowser的parent改变时变成空白问题的解决(覆盖CreateWnd和DestroyWnd)

    这段时间在做一个delphi界面打开网页的功能,且此网页所在窗口可完整显示,可缩小到另一个窗口的panel上显示 可是在改变网页所在窗口时,WebBrowser控件变成了空白 上网google了半天, ...

  4. java中经常使用的Swing组件总结

    1.按钮(Jbutton) Swing中的按钮是Jbutton,它是javax.swing.AbstracButton类的子类,swing中的按钮可以显示图像,并且可以将按钮设置为窗口的默认图标,而且 ...

  5. Laya layout算法

    /** * <p>重置对象的 <code>X</code> 轴(水平方向)布局.</p> * @private */ public function r ...

  6. Laravel Redis分布式锁实现源码分析

    首先是锁的抽象类,定义了继承的类必须实现加锁.释放锁.返回锁拥有者的方法. namespace Illuminate\Cache; abstract class Lock implements Loc ...

  7. owner window 和 parent window 有什么区别?

    1.Pop-up窗口:   一个弹出窗口是必须具有WS_POPUP属性的窗口,弹出窗口只能是一个Top-Level窗口,不能是子窗口,弹出窗口多用于对话框和消                     ...

  8. 关于 Delphi 中的Sender和易混淆的概念(转)

    /////////////////////////////////////////////////////// Delphi 中Sender对象的定义///////////////////////// ...

  9. Delphi的组件读写机制

    Delphi的组件读写机制(一) 一.流式对象(Stream)和读写对象(Filer)的介绍在面向对象程序设计中,对象式数据管理占有很重要的地位.在Delphi中,对对象式数据管理的支持方式是其一大特 ...

随机推荐

  1. 利用mysql中的SQL_CALC_FOUND_ROWS 来实现group by后的记录数统计

    最近正在做一个显示消息的列表页,列表页中需要根据一个字段来分组显示.并且需要一个分页的效果. 大家也知道group by 后的数据是每一组一行记录,统计分组后的总的记录数又不能用count,所以SQL ...

  2. 在eclipse中部署发布web项目 和 更改eclipseweb项目发布的路径

    我的工作空间:d:workspaceweb项目名称:xxx在eclipse配置完tomcat后,发布到的路径是 d:\workspace\.metadata\.plugins\org.eclipse. ...

  3. SVN服务器的搭建 分类: 网络 2014-11-27 01:18 204人阅读 评论(4) 收藏

    一.首先来下载和安装SVN服务器 现在Subversion已经迁移到apache网站上了,下载地址: http://subversion.apache.org/packages.html 这是二进制文 ...

  4. C#中Hashtable、Dictionary详解以及写入和读取对比

    转载:http://www.cnblogs.com/chengxingliang/archive/2013/04/15/3020428.html 在本文中将从基础角度讲解HashTable.Dicti ...

  5. 判断ios是app第一次启动

    首次运行的应用程序加入一些help 或者 宣传动画 现在变的很重要了. 一个有用的例子是发送一个分析实例.这可能是一个很好的方法来确定有多少人下载实用应用程序.有人会说:“但是,嘿,苹果AppStor ...

  6. sql server获取当前日期

    SqlServer中得到当前日期(convert函数,getdate函数)函数GETDATE()的返回值在显示时只显示到秒.实际上,SQL Sever内部时间可以精确到毫秒级(确切地说,可以精确到3. ...

  7. javascript 用函数实现“继承”

    一.知识储备: 1.枚举属性名称的函数: (1)for...in:可以在循环体中遍历对象中所有可枚举的属性(包括自有属性和继承属性) (2)Object.keys():返回数组(可枚举的自有属性) ( ...

  8. 查看当前linux系统位数

    linux系统也有位数之分,所以在linux上安装一些软件,比如jdk之类的就需要注意下版本. 查看linux系统位数最简单的命令(这里以redhat为例,不同版本linux命令也许不同) 命令1:g ...

  9. 引入的iframe是跨域的, 如何控制其高度

    前提是你有编辑这个跨域的iframe的权限!! 1. main-domain.html (main display HTML) <!DOCTYPE html> <html> & ...

  10. 关于php的一些开源程序

    最好用的当属thinksns,目前更新到4.3.4, 社交型网站. 此网站提供大量源码,有时间可以去看看:http://down.admin5.com/