在讲CreateParams的来源之前,我们必须简略说说组件由生成到显示在用户面前的这个过程。这是个灰常纠结的问题,纠结到我不晓得怎么去说(当然纠结的主要原因还是本人的水平有限,下面大家就简单看看吧,解说可能有错,欢迎指正)。由于组件都是依托于Form之上的,所以组件要显示出来最首要的是要组件所依托的容器显示出来,那么最首要,我们需要看看Form的创建然后显示出来的过程。至于窗口的创建过程可以参考一下以下代码:
1、TCustomForm.Create
在 TCustomForm.Create 中调用 TCustomForm.CreateNew;
2、TCustomForm.CreateNew;
调用 FCanvas := TControlCanvas.Create;
触发 TControlCanvas.Create;
触发 TControlCanvas.CreateHandle;
3、TControlCanvas.CreateHandle;
调用 FControl.GetDeviceContext(FWindowHandle);
即 TWinControl.GetDeviceContext(FWindowHandle);
4、TWinControl.GetDeviceContext(FWindowHandle);
调用 TWinControl.GetDC(Handle);
此处说明一下:
对 TWinControl 的 Handle 属性的读取会触发 TWinControl.GetHandle;
可以察看 Property Handle; 的声明。
5、第四步中对 Handle 进行读取,触发下述序列:(TWinControl)
Handle->GetHandle->HandleNeeded
6、TWinControl.HandleNeeded 检查 FHandle 的值:
if FHandle = 0 then
begin
if Parent <> nil then Parent.HandleNeeded;
CreateHandle; // 调用 CreateHandle;
end;
7、TWinControl.CreateHandle
调用 CreateWnd;
if FHandle = 0 then // 此时 FHandle 仍然为零
begin
CreateWnd;
...
end;
8、TWinControl.CreateWnd
调用 CreateParams(Params);
// 让用户有机会加入新的特征参数
CreateParams(Params);
with Params do
begin
...
// 标准的 API 使用,注册窗口类,CreateWindowEx ...
if Windows.RegisterClass(WindowClass) = 0 then RaiseLastWin32Error;
...
CreateWindowHandle(Params);
...
end;
9、CreateWindowHandle(Params);
FHandle := CreateWindowEx(ExStyle, WinClassName, Caption, Style,
X, Y, Width, Height, WndParent, 0, WindowClass.hInstance, Param);
完成真正的窗口创建,并赋予 FHandle 窗口句柄。
10、回到第一步
CreateNew 之后调用 DoCreate
try
CreateNew(AOwner);
...
if OldCreateOrder then DoCreate;
finally
...
end;
11、DoCreate
调用用户的 OnCreate 事件:
if Assigned(FOnCreate) then
try
FOnCreate(Self); // 调用 OnCreate;
except
Application.HandleException(Self);
end;
if fsVisible in FFormState then Visible := True;
在这里我在给他的细化一下,便于我们的工作的展开!这个细化应该是在他那个说明的第5步之前,也就是他说的
此处说明一下:
对 TWinControl 的 Handle 属性的读取会触发 TWinControl
.
GetHandle;
可以察看
Property
Handle; 的声明。
5
、第四步中对 Handle 进行读取,触发下述序列:(TWinControl)
Handle->GetHandle->HandleNeeded
这个HandleNeeded是在什么时候第一次调用的,其实他不是在GetHandle的时候第一次调用的,而是在窗口显示出来之前,也就是Visible变化的过程中第一次调用的,而这个Visible的变化,是在Delphi读取Form资源文件的属性了之后触发(这个属性读取过程,可以参考Delphi 的持续机制浅探)。我们看看Visible这个属性变化所触发的过程,这个属性定义在TControl中,属性变化对应的过程为
procedure TControl.SetVisible(Value: Boolean);
begin
if FVisible <> Value then
begin
VisibleChanging;
FVisible := Value;
Perform(CM_VISIBLECHANGED, Ord(Value), 0);
RequestAlign;
end;
end;
由此可以看到在属性变化的时候发送了一个CM_VISIBLECHANGE的消息出去,然后我们再去这个消息的触发过程
procedure TWinControl.CMVisibleChanged(var Message: TMessage);
begin
if not FVisible and (Parent <> nil) then RemoveFocus(False);
if not (csDesigning in ComponentState) or
(csNoDesignVisible in ControlStyle) then UpdateControlState;
end;
本过程在TControl中也有,但是在TWinControl中被重写了,所以我这里只列出了TWinControl的,在Visible变化的时候会调用UpdateControlState函数来更新控件状态,然后这个更新过程中调用了另外一个更新控件显示的函数UpdateShowing,我们来看看UpdateShowing这个过程
procedure TWinControl.UpdateShowing;
var
ShowControl: Boolean;
I: Integer;
begin
ShowControl := (FVisible and (not (csDesigning in ComponentState) or not (csDesignerHide in ControlState)) or
((csDesigning in ComponentState) and not (csDesignerHide in ControlState)) and
not (csNoDesignVisible in ControlStyle)) and
not (csReadingState in ControlState) and not (csDestroying in ComponentState);
if ShowControl then
begin //这个时候如果是第一次显示,FHandle为0,就会调用CreateHandle来创建一个窗口句柄了,也就是说
//在这个时候才真真实实的创建Windows的标准控件!
if FHandle = 0 then CreateHandle;
if FWinControls <> nil then
for I := 0 to FWinControls.Count - 1 do
TWinControl(FWinControls[I]).UpdateShowing;//之后会更新属于这个控件容器的所有子控件显示
end;
if FHandle <> 0 then
if FShowing <> ShowControl then
begin
FShowing := ShowControl;
try
SetPerformingShowingChanged(Self);
try
Perform(CM_SHOWINGCHANGED, 0, 0);
finally
ClearPerformingShowingChanged(Self);
end;
except
FShowing := not ShowControl;
raise;
end;
end;
end;
CreateHandle过程中调用了CreateWnd,然后CreateWnd得时候就调用我们上面声明的CreateParams来为标准控件传递参数。上面说了控件的最终容器Form的创建到显示过程,那么我们现在再来说说一般控件的创建显示过程,其实也就和TForm的创建显示过程一样!只是TForm的显示从读取了属性之后触发,而一般控件由他所在的容器触发,也就是上面的UpdateShowing过程中的实现过程,后面会遍历子控件,然后更新他们的显示,第一次显示的时候都会触发CreateHandle的过程,所以Windows组件的真实创建过程实际上应该是在组件的第一次显示的过程中创建,而不是我们调用Create的时候,在Delphi中,我们Create的时候,仅仅是为这个组件提供了一些初始化信息以及各种参数而已。说到这里,那么,第二章中的CreateParams的实现方法也就相当顺其自然了,因为在CreateParams中为Edit指定其他的扩展样式时,实际上Windows的真实Edit控件实际上还没有创建出来。那么当指定了新样式,当他创建出来的时候,就自然具备了我们指定的扩展样式了。然后,我在设置新样式的时候,调用了一个RecreateWnd的方法,这个方法的目的是重建句柄,也就是重建Windows组件,这个函数的实现过程相当简单,仅仅就是发送了一个组件重建的消息CM_RECREATEWND,然后我们看看这个消息过程的实现方法
procedure TWinControl.CMRecreateWnd(var Message: TMessage);
var
WasFocused: Boolean;
begin
WasFocused := Focused;//先保存控件是否是焦点状态
UpdateRecreatingFlag(True);//这个函数,我就不贴他的代码了,从他的代码中,我们可以看出来,这个函数
//的目的是为所有的子控件打上重建的标记
try
DestroyHandle;//释放句柄,同时释放所有子控件的句柄
UpdateControlState;//更新控件状态,这个函数上面已经分析,会建立句柄,同时子控件句柄。
finally
UpdateRecreatingFlag(False);//重建状态完成
end;
if WasFocused and (FHandle <> 0) then
Windows.SetFocus(FHandle);//如果重建成功,并且原先有焦点,就恢复原先的焦点状态
end;
可见,这个重建的过程,如果你是一个容器控件,内部有很多子控件的话,使用这个方式来实现某些效果,效率是灰常低下的,所以容器类不建议频繁使用重建方法! 至此为止,组件的生成过程就讲解完毕,欢迎专家指正!