对TControl和TWinControl相同与不同之处的深刻理解

TControl是图形控件,它本身没有句柄,所以不能直接使用WINAPI显示,调整位置,发消息等等,只能想办法间接取得想要的效果,但是可以直接使用一些不需要句柄的API,比如InvalidateRect。
TWinControl是含有Windows句柄的窗口,它有句柄,因此所有使用句柄的WINAPI都可以直接操作它从而取得各种效果,使得Windows窗口能够被驱动从而正常的工作。所以它顺带把它的图形子控件管理起来,让它们在自己所在的一份三分地里正常的工作(我的理解:在Delphi的世界里,此时一个WinControl就相当于扮演了整个Windows的角色,用来管理它的“子窗口”控件,即TControl和TWinControl,如果说它对WinControl的管理功能还不是那么强烈和重要,但它无疑百分百的管理起了TGraphicControl,这一点非常重要)。

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

因为这个原因,TWinControl需要定义一系列的类函数,用它们包装WINAPI,使之符合Delphi的VCL整体架构,并且还更好用TControl虽然无法直接使用WINAPI驱动工作,但VCL的作者偷梁换柱使用它的父控件句柄,并使用相同的函数名称达到了相同的效果(这就是为什么李维会提到一个VCL的缺陷——TControl.Parent必须是个TWinControl,这是因为他没有意识到是Borland在有意强迫这样做,否则难道还要处处判断这个Parent是TWinControl,那样岂不是麻烦死,很可能还要多写很多异常语句。李维大师的思路也没有错,那是一种纯面向对象理论的思路,但他本人毕竟没有深入参与VCL开发,跟Borland的大师们还有些距离,而以他的一己之力来解释整个VCL的架构与思路,而实际上估计Borland早就考虑到了这一点,对Windows编程和OO运用的如此如火纯青的人,怎么会连这一点都想不到?所以Delphi的实现方法往往已经就是理论的完美诠释,因为它对纯理论方案的一些缺陷做了最佳的弥补和平衡,而且我感觉处处都是这样,这点也是我觉得Delphi最了不起的地方。光把理论实现一遍谁不会啊,无非就是花点时间和繁琐一点。正如有朋友说的那样,C++以外的世界很精彩,不要一辈子沉迷于C++的世界,有空还是要多学几门语言并加以思考和比较一下,我就感觉自己从Delphi的一些思想和平衡性方案里受益匪浅。当然只沉迷于Delphi的世界里也是不行的,我的下一个目标是Golang和C#,哈哈哈)。这就是为什么TControl会经常有TWinControl的同名函数,因为相同名称的函数更好用嘛!比如SetBounds函数(我就是看到这个函数时想到这层意思的):

procedure TControl.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
begin
  if CheckNewSize(AWidth, AHeight) and // TControl的类函数
    ((ALeft <> FLeft) or (ATop <> FTop) or (AWidth <> FWidth) or (AHeight <> FHeight)) then
  begin
    InvalidateControl(Visible, False); // TControl的类函数,第二个参数表示暂时设置当前控件是透明的
    FLeft := ALeft;
    FTop := ATop;
    FWidth := AWidth;
    FHeight := AHeight;
    UpdateAnchorRules; // TControl的类函数,坐标和长宽设置完了,就要重新铆接一下
    // 属性设置完了,如果有API可以使之起作用就当场调用(关于显示部分,不需要句柄就有API使用,这是特殊情况)
    Invalidate; // TControl的类函数,调用TControl.InvalidateControl,再调用API声明无效区域
    // 此消息在TControl和TWinControl里都有相应的函数,图形控件使用消息再做一些自己力所能及的变化,Win控件使用消息调用类函数使之调用API真正起作用
    // 前者重新计算最大化最小化的限制和坞里的尺寸,后者使用API调整边框和控件自己的位置,当然也得重新计算最大化最小化的限制和坞里的尺寸(三明治手法) 
    Perform(WM_WINDOWPOSCHANGED, 0, 0);
    // Windows位置调整完了,还要重新对齐(本质是调用TWinControl.RequestAlign,然后调用API重新排列)
    // 但实际上是靠父Win控件重新排列自己,因为它自己没有能力拥有别的控件,当然也就不能实质上让所有控件对齐。
    RequestAlign; // TControl的虚函数,各WinControl的子类可自己改写,比如TCustomForm就改写了
    if not (csLoading in ComponentState) then Resize; // TControl的虚函数,简单调用程序员事件。子类一般不需要改写它。
  end;
end;

我们可以看到TWinControl也有相应的函数,它就可以使用API直接达到效果,它甚至还可以使用TControl定义的某些通用逻辑和效果(拜OO技术所赐):

procedure TWinControl.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
var
  WindowPlacement: TWindowPlacement; // Windows结构类型,包含最大化最小化窗口位置等6项内容
begin
  if (ALeft <> FLeft) or (ATop <> FTop) or (AWidth <> FWidth) or (AHeight <> FHeight) then
  begin
    // 如果有句柄,并且不是最小化状态,就使用API立刻调整位置
    if HandleAllocated and not IsIconic(FHandle) then // API
      SetWindowPos(FHandle, 0, ALeft, ATop, AWidth, AHeight, SWP_NOZORDER + SWP_NOACTIVATE) // API,使用完毕后Windows自己会更改WindowPlacement里的数据
    else
    // 如果还没有句柄,或者处于最小化状态下,使用API更改WindowPlacement结构的值,以备下次调用API直接显示
    begin
      // 重新设置左上角坐标以及长宽
      FLeft := ALeft; // TControl的类属性,通用属性
      FTop := ATop;
      FWidth := AWidth;
      FHeight := AHeight;
      if HandleAllocated then
      begin
        // 取得窗口的位置信息
        WindowPlacement.Length := SizeOf(WindowPlacement);
        GetWindowPlacement(FHandle, @WindowPlacement); // API
        // 更改窗口的位置信息 
        WindowPlacement.rcNormalPosition := BoundsRect; // TControl的类属性,通用属性
        SetWindowPlacement(FHandle, @WindowPlacement); // API
      end;
    end;
    // super 前面使用API设置了真实的Windows窗口属性和Delphi控件属性后,可放心大胆的调用一些函数,不是TControl已经提供了通用逻辑,就是它自己定义了一些特殊的函数,可随便使用,直接产生效果,而不再依赖别人来完成某件事情。
    UpdateAnchorRules; // TControl类函数,通用函数
    RequestAlign; // TControl类函数,通用函数
  end;
end;

TControl.GetDeviceContext这个例子也很明显,它使用父Win控件的同名函数得到DC,然后使用这个句柄调用API得到视角和增加一个新的剪裁区域,因为这两个API是和自绘有关的API,而TControl的基本设计目的之一就是显示和剪裁图形图像,因此TControl可直接使用它们实现想要的功能。

function TControl.GetDeviceContext(var WindowHandle: HWnd): HDC;
begin
  if Parent = nil then
    raise EInvalidOperation.CreateFmt(SParentRequired, [Name]);
  Result := Parent.GetDeviceContext(WindowHandle);// TWinControl的类函数,不仅仅设置DC,还从Delphi的FHandle设置参数句柄
  SetViewportOrgEx(Result, Left, Top, nil);       // API 用来改变视端口和窗口的原点,并都具有改变轴的效果,以致(0,0)不再指左上角
  IntersectClipRect(Result, 0, 0, Width, Height); // API 创建一个新的剪切区域,该区域是当前剪切区域和一个特定矩形的交集
end;

再稍微总结一下TControl的对齐过程,其实也是同理:
RequestAlign->AlignControl->DisableAlign->AlignControls(实质上干活对齐)->EnableAlign->Realign->AlignControl(nil)
整个过程的一级入口函数是:
TControl.RequestAlign;
但它只是图形控件,没有能力拥有别的控件,当然也就不能实质上让所有控件对齐,所以要执行:
TWinControl.AlignControl(AControl: TControl);
它做了整个对齐逻辑的封装,依次执行:
调用TWinControl.DisableAlign; 它执行Inc(FAlignLevel);
调用TWinControl.AlignControls(AControl: TControl; var Rect: TRect); // 所有有关控件大小、对齐、铆接的实质内容都在这里
调用TWinControl.EnableAlign; 它调用TWinControl.Realign; 它调用TWinControl.AlignControl(nil);

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

总结:
TControl有两个作用:第一是定义所有图形控件和Win控件都要用到的通用功能(比如10个鼠标按钮消息),第二是定义所有图形控件都要用到的通用功能,比如InvalidateControl,SetColor,GetText,SetText等等,数不胜数
TWinControl两个作用:第一是定义Windows句柄控件所要用到的通用功能(使用API真正起作用),第二是管理其图形子控件的显示,很多时候也得管理其WinControl(如果Windows没有很好的直接管理它们的话)。

有空还可以把TControl和TWinControl的同名函数总结一下,列个表并对比一下它们的功能和实现手法。

对TControl和TWinControl相同与不同之处的深刻理解

上一篇:WPF和Expression Blend开发实例:模拟QQ登陆界面打开和关闭特效


下一篇:C#批量导入Excel或WPS实现方法