您现在的位置:首页 >> VCL >> VCL >> 内容

Delphi组件开发教程指南(四)组件生成过程(TWinControl)

时间:2011/9/3 16:22:52 点击:

  核心提示:还记得在第二章的时候,我用到了procedure CreateParams(var Params: TCreateParams);这个函数的吧!为什么我会使用这个函数来实现那个对齐的问题呢!现在就来追...
还记得在第二章的时候,我用到了procedure CreateParams(var Params: TCreateParams);这个函数的吧!为什么我会使用这个函数来实现那个对齐的问题呢!现在就来追根底的来看看!这个过程其实是在构建窗口的时候会调用的,当然我说的这个是针对TWincontrol继承过来的组件说的,从TGraphicontrol等继承过来的是没有这个的。这个函数的产生也是Windows组件库所特有的,如果列为看官有Windows编程的基础,那么这个就很容易理解了,记得,在Windows编程的时,注册这个窗口类之前,我们都会为一个窗口类指定一系列的参数,而这个CreateParams函数就是产生在这个注册过程之前,目的是用来为创建过程指定参数。

     在讲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;

可见,这个重建的过程,如果你是一个容器控件,内部有很多子控件的话,使用这个方式来实现某些效果,效率是灰常低下的,所以容器类不建议频繁使用重建方法! 至此为止,组件的生成过程就讲解完毕,欢迎专家指正!

作者:不得闲 来源:转载
共有评论 0相关评论
发表我的评论
  • 大名:
  • 内容:
本类推荐
  • 没有
本类固顶
  • 没有
  • 盒子文章(www.2ccc.com) © 2024 版权所有 All Rights Reserved.
  • 沪ICP备05001939号