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

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

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

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

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

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

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

另外,TWinControl也不仅仅代表自己,一定程度上也代表了它所有的子控件。当自己发生某些变化时,可以通知自己的子控件,子控件是否愿意响应是它自己的事情,但是作为parent的义务已经尽到了,并且相互之间也做了有效的沟通。比如TWinControl.CMColorChanged就是一个不错的例子,当自己的颜色发生变化的时候,也要通知自己的所有子控件:

  1. procedure TWinControl.CMColorChanged(var Message: TMessage);
  2. begin
  3. inherited; // 自己的颜色变化起效果,是通过这句话来实现的,它包含了一连串的执行过程。父控件变化完了再通知子控件,风格很强势。
  4. FBrush.Color := FColor;
  5. NotifyControls(CM_PARENTCOLORCHANGED); // 第二个消息,组建消息并传播,通知子控件,但没有任何子控件响应
  6. end;

再对比一下TWinControl.CMInvalidate,就会发现父控件先做变化,后通知子控件(强势);而子控件必须先通知父控件,后做变化(弱势):

  1. procedure TWinControl.CMInvalidate(var Message: TMessage);
  2. var
  3. I: Integer;
  4. begin
  5. if HandleAllocated then
  6. begin
  7. if Parent <> nil then
  8. Parent.Perform(CM_INVALIDATE, 1, 0); // 第四个消息,递归,先通知父类(父类要提前通知)。Form1的Parent是Application,它没有响应。
  9. if Message.WParam = 0 then
  10. begin
  11. // API,第二个参数为NULL的话,则重画整个客户区;第三个参数TRUE则背景重绘。
  12. InvalidateRect(FHandle, nil, not (csOpaque in ControlStyle)); // 总算初步达到目的,使Form1失效,后面还要自绘
  13. { Invalidate child windows which use the parentbackground when themed }
  14. if ThemeServices.ThemesEnabled then
  15. for I := 0 to ControlCount - 1 do
  16. if csParentBackground in Controls[I].ControlStyle then // important
  17. Controls[I].Invalidate;
  18. end;
  19. end;
  20. end;

但是无论父控件通知子控件,还是子控件通知父控件,都仅仅是通知而已,都不能强行阻止对方执行自己的变化过程(控件人格独立)。

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

TControl对TWinControl的区别和联系,主要是TControl的显示完全要依赖于TWinControl控件,主要代码在这里(红色字体):

  1. procedure TControl.Update;
  2. begin
  3. if Parent <> nil then Parent.Update; // 图形控件没法刷新自己,让父控件去刷新
  4. end;
  5.  
  6. procedure TControl.InvalidateControl(IsVisible, IsOpaque: Boolean);
  7. begin
  8. if (IsVisible or (csDesigning in ComponentState) and
  9. not (csNoDesignVisible in ControlStyle)) and (Parent <> nil) and
  10. Parent.HandleAllocated then
  11. begin
  12. Rect := BoundsRect;
  13. InvalidateRect(Parent.Handle, @Rect, not (IsOpaque or
  14. (csOpaque in Parent.ControlStyle) or BackgroundClipped));
  15. end;
  16. end;
  17.  
  18. procedure TControl.Repaint;
  19. var
  20. DC: HDC;
  21. begin
  22. if (Visible or (csDesigning in ComponentState) and
  23. not (csNoDesignVisible in ControlStyle)) and (Parent <> nil) and
  24. Parent.HandleAllocated then
  25. if csOpaque in ControlStyle then
  26. begin
  27. DC := GetDC(Parent.Handle);
  28. try
  29. IntersectClipRect(DC, Left, Top, Left + Width, Top + Height);
  30. Parent.PaintControls(DC, Self);
  31. finally
  32. ReleaseDC(Parent.Handle, DC);
  33. end;
  34. end else
  35. begin
  36. Invalidate;
  37. Update;
  38. end;
  39. end;

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

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

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

这里对TControl和TWinControl的同名覆盖函数总结了一下,并列个表对比它们的功能和实现手法:
http://www.cnblogs.com/findumars/p/4153769.html

对TControl和TWinControl相同与不同之处的深刻理解(每一个WinControl就相当于扮演了整个Windows的窗口管理角色,主要是窗口显示和窗口大小)——TWinControl就两个作用(管理子控件的功能和调用句柄API的功能)的更多相关文章

  1. 如何:对 Windows 窗体控件进行线程安全调用

    http://msdn.microsoft.com/zh-cn/library/ms171728(VS.90).aspx http://msdn.microsoft.com/zh-cn/library ...

  2. 验证控件jQuery Validation Engine调用外部函数验证

    在使用jQuery Validation Engine的时候,我们除了使用自带的API之外,还可以自己自定义正则验证.自定义正则验证上一篇已经讲过了,如果想使用自定义函数进行验证怎么办?其实这个控件有 ...

  3. 利用深搜和宽搜两种算法解决TreeView控件加载文件的问题。

    利用TreeView控件加载文件,必须遍历处所有的文件和文件夹. 深搜算法用到了递归. using System; using System.Collections.Generic; using Sy ...

  4. (转)jQuery验证控件jquery.validate.js使用说明+中文API

    官网地址:http://bassistance.de/jquery-plugins/jquery-plugin-validation jQuery plugin: Validation 使用说明 转载 ...

  5. jQuery验证控件jquery.validate.js使用说明+中文API(转)

    一导入js库<script src="../js/jquery.js" type="text/javascript"></script> ...

  6. jQuery验证控件jquery.validate.js使用说明+中文API

    官网地址:http://bassistance.de/jquery-plugins/jquery-plugin-validation jQuery plugin: Validation 使用说明 学习 ...

  7. ocx控件 编译成C#调用的dll 方法 转

      打开VS命令提示行 1.注册ActiveX控件(带上 VCbox.ocx的路径) regsvr32  VCbox.ocx 2.编译OCX文件 aximp VCbox.ocx 生成两个dll文件,项 ...

  8. 扩展GridView控件——为内容项添加拖放及分组功能

    引言 相信大家对GridView都不陌生,是非常有用的控件,用于平铺有序的显示多个内容项.打开任何WinRT应用或者是微软合作商的网站,都会在APP中发现GridView的使用.“Tiles”提供了一 ...

  9. c# 开发ActiveX控件,添加事件,QT调用事件

    c# 开发 ActiveX 的过程参考我的另一篇文章 :  https://www.cnblogs.com/baqifanye/p/10414004.html 本篇讲如何 在C# 开发的ActiveX ...

随机推荐

  1. 视差滚动(Parallax Scrolling)插件补充

    13. Windows Windows (github) 是一个让你用占据整个屏幕的section来构建单面网站的插件.该插件提供给你一些回调函数,当新的section出现在可视区并且并且处理快照时被 ...

  2. CentOS 6.5在grub界面下更改root密码

    想要更改CentOS 7 root的密码或者忘记了root的密码的时候可以在grub界面下更改root的密码. 百度了很多内容,更多方法都是适用于centos6及以前版本的,终于找到一个可以的. 1. ...

  3. 让UIButton在按下时没有高亮效果

    How are you setting the images for the different UIControlStates on the button? Are you setting a ba ...

  4. zookper3.4.6集群配置

    参考链接: http://blog.csdn.net/shirdrn/article/details/7183503 个人感觉zookeeper 安装在单机上无操作意义,所以直接记录集群配置过程. 连 ...

  5. jQuery.ajax实现根据不同的Content-Type做出不同的响应

    使用H5+ASP.NET General Handler开发项目,使用ajax进行前后端的通讯.有一个场景需求是根据服务器返回的不同数据类型,前端进行不同的响应,这里记录下如何使用$.ajax实现该需 ...

  6. 类似股软(大智慧)之键盘精灵的 vc2008--UNICODE 环境实现

    键盘精灵是指,当按下键盘上任意一个数字.字母或符号的时候,都会弹出“键盘精灵”,其类似于股票软件(如大智慧)中的.可以在这里面输入中英文和数字搜索您想要的东西.可以通过输入代码.名称或名称的汉语拼音首 ...

  7. ChemDraw 16最新版本发布 更效率科研的首选

    ChemDraw一直是全球领先的科学绘图软件,致力于为科学家.教师以及学生提供最新的智能应用程序.ChemDraw 16版本相较于15版本做出了较大的改进,大大缩短科研时间,提高科研效率.扩展Name ...

  8. 什么是代码?code?

    概念描述: 程序代码?code? 应用程序是由一系列代码构成,那么什么是代码呢? 简单来说:代码也可以理解为命令,通过这个命令告诉计算机该做什么事情. 文档创建时间:2018年3月16日15:10:5 ...

  9. 基于nodejs的开源博客

    https://github.com/hexojs/hexo https://hexo.io/zh-cn/docs/ markdown编辑器 http://pandao.github.io/edito ...

  10. HBase表的架构原理

    HBase总体架构图 Hbase Table的基本单位是Region,一个Table相应多个Region.Table层级关系例如以下: Table       (HBase table)     Re ...