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

时间:2022-09-22 16:34:08

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函数(我就是看到这个函数时想到这层意思的):

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);

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

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

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

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

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

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

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

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

procedure TControl.Update;
begin
if Parent <> nil then Parent.Update; // 图形控件没法刷新自己,让父控件去刷新
end; procedure TControl.InvalidateControl(IsVisible, IsOpaque: Boolean);
begin
if (IsVisible or (csDesigning in ComponentState) and
not (csNoDesignVisible in ControlStyle)) and (Parent <> nil) and
Parent.HandleAllocated then
begin
Rect := BoundsRect;
InvalidateRect(Parent.Handle, @Rect, not (IsOpaque or
(csOpaque in Parent.ControlStyle) or BackgroundClipped));
end;
end; procedure TControl.Repaint;
var
DC: HDC;
begin
if (Visible or (csDesigning in ComponentState) and
not (csNoDesignVisible in ControlStyle)) and (Parent <> nil) and
Parent.HandleAllocated then
if csOpaque in ControlStyle then
begin
DC := GetDC(Parent.Handle);
try
IntersectClipRect(DC, Left, Top, Left + Width, Top + Height);
Parent.PaintControls(DC, Self);
finally
ReleaseDC(Parent.Handle, DC);
end;
end else
begin
Invalidate;
Update;
end;
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. &lpar;转&rpar;jQuery验证控件jquery&period;validate&period;js使用说明&plus;中文API

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

  5. jQuery验证控件jquery&period;validate&period;js使用说明&plus;中文API&lpar;转&rpar;

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

  6. jQuery验证控件jquery&period;validate&period;js使用说明&plus;中文API

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

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

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

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

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

  9. c&num; 开发ActiveX控件&comma;添加事件&comma;QT调用事件

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

随机推荐

  1. android 关于Location of the Android SDK has not been setup in the preferences的解决方法

    今天在部署android开发环境的时候,每次打开eclipse的时候点击AVD Manager的按钮就会弹出Location of the Android SDK has not been setup ...

  2. 关于redhat6的服务说明

    服务名称 功能 默认 建议 备注说明 NetworkManager 主要用于图形网络连接管理 开启 关闭 对服务器无用 abrt-ccpp Automated Bug Reporting Tool 开 ...

  3. java volatile的一个验证反例(转)

    网上关于java volatile的资料已经不少了,但搜了好久也没看到谁用代码很好地验证过使不使用volatile的差异.最近自己写了个测试,意外的看到了两者的明显区别,为什么说意外呢,因为根据我的测 ...

  4. select2 api参数的文档

    具体参数可以参考一下: 参数 类型 描述 Width 字符串 控制 宽度 样式属性的Select2容器div minimumInputLength int 最小数量的字符 maximumInputLe ...

  5. python的django基础篇

    一.Django基础 Django 是用Python开发的一个免费开源的Web框架,可以用于快速搭建高性能,优雅的网站! Django的特点: 强大的数据库功能:拥有强大的数据库操作接口(QueryS ...

  6. zz-人生感悟

    1. 社交感想 首先来看一下聪明人和普通人的区别是什么? 普通人思考问题都是一步一步的来,由A推理出B,B推导到C,再推导出D,最后得出E,然而聪明人却可以由A直接推算到E. 这就像开车,普通人的是手 ...

  7. mariadb-5&period;5安装

    mariadb-5.5 Windows10安装 1.官网下载:https://downloads.mariadb.org/ 2.解压mariadb-5.5.58-winx64.zip,目录C:\mar ...

  8. PHP如何发送邮件

    例如使用163邮箱 SMTP,Simple Mail Transfer Protocol,简单邮件传输协议 保证我们的邮件服务器开启了SMTP服务 设置授权码   使用PHPMailer类发送邮件 1 ...

  9. JAVAEE——SSH项目实战04:联系人添加、列表显示和修改

    作者: kent鹏 转载请注明出处: http://www.cnblogs.com/xieyupeng/p/7159337.html 一.联系人添加 1.添加页面设计    linkman/list. ...

  10. ROW&lowbar;NUMBER&lpar;&rpar; OVER&lpar;PARTITION BY&rpar;

    select * from (select *,ROW_NUMBER() OVER(PARTITION BY GoodsID ORDER BY IsMain desc,OrderNum) as Mai ...