Delphi对于控件的SuperClassing(封装并扩展Button,使之变成TButton)
写博客写了这么久,但是一直不知道应该怎么样写函数之间的调用关系和执行顺序,因为不停的跳来跳去的,但是写的时候却只能顺序写调用关系,直到今天发现这种写法很不错:
TButton创建窗口是在CreateWnd方法开始,下面是创建的一个大概流程:
TButton.CreateWnd;
| TWinControl.CreateWnd;
| | TButton.CreateParams(var Params: TCreateParams);
| | | TButtonControl.CreateParams(var Params: TCreateParams);
| | | | TWinControl.CreateParams(var Params: TCreateParams);
| | | TWinControl.CreateSubClass(var Params: TCreateParams; ControlClassName: PChar);
| | Windows.RegisterClass(WindowClass)
| | TWinControl.CreateWindowHandle(const Params: TCreateParams);
当然这篇文章总体内容也是很不错,学习记录之。
------------------------------------------------------------------------------------
第零步,问题的提出:
Windows内部预定义了一些通用的控件,我们在用这些控件的时候不必再调用RegisterClass注册一个窗口类,只要直接调用CreateWindow,并指定一个预定义的窗口类就可以,比如,我们要创建一个Button,只要用如下形式即可:
CreateWindow(.., ‘BUTTON’,...)。
但用Delphi写出来的程序,用SPY看它的某个控件的窗口类名,却是这个控件类的ClassName,比如,一个按钮控件,它的窗口类名是TButton,TButton控件也是封装系统预定义的BUTTON控件,按理它的窗口类名应该是BUTTON才对啊,怎么会变成TButton呢。这个问题长期困扰着我。
昨晚看了一下《Window 95程序设计指南》,其中讲到了Superclassing的技术,可以以系统标准控件为基础,设计新的控件。这个技术使我恍然大悟,赶紧看了一下VCL的代码,果然是用了这样的技术,大喜,以此文记之。
第一步,窗口的总体创建过程:
我以一个TButton控件的创建过程,说明Superclassing技术在Delphi控件创建的应用。
TButton创建窗口是在CreateWnd方法开始,下面是创建的一个大概流程:
TButton.CreateWnd;
| TWinControl.CreateWnd;
| | TButton.CreateParams(var Params: TCreateParams);
| | | TButtonControl.CreateParams(var Params: TCreateParams);
| | | | TWinControl.CreateParams(var Params: TCreateParams);
| | | TWinControl.CreateSubClass(var Params: TCreateParams; ControlClassName: PChar);
| | Windows.RegisterClass(WindowClass)
| | TWinControl.CreateWindowHandle(const Params: TCreateParams);
第二步,设置新类名
在TWinControl.CreateParams(var Params: TCreateParams)中设定了窗口的一些基本的风格,其中有一句重要,是:
with Params do
begin
StrPCopy(WinClassName, ClassName);
end;
里面的WinClassName即是窗口类名,这时被赋为控件的类名,即是TButton。
TButton覆盖CreateParams,作一些自己的风格设置,其中调用到TWinControl的CreateSubClass,是这样调用的:
CreateSubClass(Params, 'BUTTON');
BUTTON就是系统的预定义按钮控件的窗口类名。那么这个方法有什么作用呢,它在里面调用了数个GetClassInfo函数,以获得该窗口类的类风格,类风格填充到Params参数的WindowClass结构成员中。这时WindowClass这个结构就有了BUTTON标准窗口的类风格的数据了。
第三步,设置窗口函数
设置完窗口类的风格后,回到TWinControl的CreateWnd方法,打开这个方法的代码来看,有一句非常重要:
FDefWndProc := WindowClass.lpfnWndProc;
因为我们前面已经调用了CreateSubClass获得了BUTTON的窗口类的类风格,并填充在Params参数的WindowClass结构中。所以上面我们看到的这一句是将BUTTON的窗口过程保存到FDefWndProc这个函数指针里面,它是TWinControl的一个成员,请记住这个成员。
接下来调用:
ClassRegistered := GetClassInfo(WindowClass.hInstance, WinClassName, TempClass);
还记得WinClassName吗,是它是Params的成员,在上面它已经被为’TButton’了。检查有这个名字的窗口类是否已经被注册了,如果没有则有下面三句非常重要的代码:
WindowClass.lpfnWndProc := @InitWndProc;
WindowClass.lpszClassName := WinClassName;
if Windows.RegisterClass(WindowClass) = 0 then RaiseLastOSError;
首先将BUTTON窗口的窗口过程设定为InitWndProc。再将它的窗口类名字设定为WinClassName也即是TButton。最后注册窗口类。
好了窗口类注册完了,就调用以下两句:
CreateWindowHandle(Params);
CreateWindowHandle根据Params里面的参数创建窗口,代码比较简单。
第四步,TWinControl的消息最后会流到TWinControl.DefaultHandle并调用Button原先的默认窗口函数
这一个TButton的创建过程就完了,那么它是如何享有系统预定义的BUTTON的那些功能的呢。接着往下看。
当在CreateWindowHandle里面调用CreateWindow函数时,系统直接发送一个WM_CREATE消息给这个新建的窗口。而上面我们已经知道这个窗口的窗口函数就是InitWndProc。在它里面调用了:
SetWindowLong(HWindow, GWL_WNDPROC, Longint(CreationControl.FObjectInstance));
将窗口过程重新指定,关于这个技术李维的《Inside VCL》已经有说明。反正窗口过程最后就到了TWinControl的WndProc这个方法去了,以后所有的消息将由这个方法来处理。那就看看这个方法吧:
TWinControl.WndProc这个方法没有看到什么信息,不过它会调用父类的WndProc,再看看TControl.WndProc; 里面最终调用了TObject的Dispatch(Message),将消息通过DMT发送到对应的消息处理方法中,最后还会调用TObject的DefaultHandler。TContorl覆盖了这个方法,而TWinControl也覆盖了这个方法。不过TControl没有我们要的信息。关键就是TWinControl的DefaultHandle。在里面终于找到最最重要的这一句:
Result := CallWindowProc(FDefWndProc, FHandle, Msg, WParam, LParam);
还记得FDefWndProc吗,在上面,它被指向了BUTTON类的原始窗口过程。这里调用了它,亦即Windows在内部会自动对TButton的一些BUTTON特性进行处理。
至此Superclassing完成。而我们也看到BUTTON控件变成了TButton控件。呵呵,如果看一下那本《Window 95程序设计指南》,再看一下VCL源码,相信会理解得更清楚。
总结:
系统内置了对Button的消息,只要能把消息送入WINAPI的DefWindowProc函数(Delphi的类属性FDefWndProc封装了它的地址)里去,就能得到正确的处理。查看TButton源码可以发现TButton对Button原有的消息处理绝大多数都没有拦截,以期消息得到正常处理并得到系统内置的功能,而只拦截了一个WM_ERASEBKGND消息,外加若干CM_和CN_消息用来与VCL框架匹配或者增强TButton的功能。同时类名也被改成了TButton。就整个过程来说,相当于TButton类包含了Button类的所有功能,并有一定的扩展,因此叫做SuperClassing。不过封装的时候却调用了CreateSubClass函数,这个函数名称是不是有点讽刺意味。。。
------------------------------------------------------------------------------------
参考:
http://blog.csdn.net/linzhengqun/article/details/529300
Delphi对于控件的SuperClassing(封装并扩展Button,使之变成TButton)的更多相关文章
- <总结>delphi WebBrowser控件的使用中出现的bug
Delphi WebBrowser控件的使用中出现的bug: 1.WebBrowser.Visible=false:Visible属性不能使WebBrowser控件不可见,暂时用 WebBrowse ...
- 修改Delphi工具控件的默认字体
修改Delphi工具控件的默认字体: 注册表: Delphi 6: HKEY_CURRENT_USER\Software\Borland\Delphi\6.0Delphi 7: HKEY_ ...
- Delphi WebBrowser控件的使用(大全 good)
Delphi WebBrowser控件的使用 WebBrowser控件属性:1.Application 如果该对象有效,则返回掌管WebBrowser控件的应用程序实现的自动化对象(IDis ...
- Delphi TcxtreeList控件说明 转
Delphi TcxtreeList控件说明 树.cxTreeList 属性: Align:布局,靠左,靠右,居中等 AlignWithMargins:带边框的布局 Anchors:停靠 (akT ...
- delphi按钮控件的default属性
delphi按钮控件的default属性用于设置默认命令按钮,.设置为true时,按[Enter键]相当于用鼠标单击了该按钮 .窗口中如果有多个按钮的default是true的话,就根据tabinde ...
- Delphi fmx控件在手机滑动与单击的问题
Delphi fmx控件在手机滑动与单击的问题 (2016-03-08 10:52:00) 转载▼ 标签: it delphi 分类: Delphi10 众所周知,fmx制作的app,对于象TEdit ...
- Delphi maskedit控件的掩码含义及用法方法
Delphi maskedit控件的掩码含义及用法方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 2 ...
- Delphi StringGrid控件的用法
Delphi StringGrid控件 组件名称:StringGrid ●固定行及固定列: StringGrid.FixedCols:=固定行之数; StringGrid.Fixe ...
- Delphi IDHTTP控件:GET/POST 请求
Delphi IDHTTP控件:GET/POST 请求 最近一直在使用IDHTTP,下面是一些关于 GET.POST 请求基本使用方法的代码 一.GET 请求 1 procedure GetDem ...
随机推荐
- XAML中ContentControl,ItemsControl,DataTemplate之间的联系和区别
接触XAML很久了,但一直没有深入学习.今天学习了如标题所示的内容,所以来和大家分享一下,或者准确的说是自我回顾一遍. 在XAML中,有两类我们常见的控件,分别是ContentControl和Item ...
- 文档对象模型(DOM)
文档对象模型(DOM) DOM可以将任何HTML或XML文档描绘成一个由多层节点构成的结构.节点分为几种不同的类型:文档型节点.元素节点.特性节点.注释节点等共有12种节点类型.DOM1级定义了 ...
- .net 常用正则表达式
Net中正则表达式的简单使用方法及常见验证判断 判断字符串是只是数字我们可以这样写:return new System.Text.RegularExpressions.Regex(@"^([ ...
- java 24点算法实现
最近闲来无事,突然怀念起小时候和堂兄表姐们经常玩24点游戏,于是就琢磨着是不是开发一个安卓手机版本.然后上网上一搜,发现已经被别人给开发烂了啊.不过这只能说明这个小游戏要想赚广告费很难了,但是拿来锻炼 ...
- Netsharp快速入门(之4) 基础档案(之C 实体建模 计量单位、商品、往来单位)
作者:秋时 杨昶 时间:2014-02-15 转载须说明出处 3.3.2 基础档案建模 1.在基础档案项目,右击,选择新建包, 2.录入包的名称,录入名称.完成后点确定 3.3.2.1 计量 ...
- Window.document对象(2)
四.操作样式 首先利用元素的ID找到该元素,存于一个变量中: var a = document.getElementById("id"): 然后可以对该元素的属性进行操作: a.s ...
- 【Python】vim7.4 配置python2.6支持Gundo
问题描述: vim7.4 配置python2.6支持Gundo 参考资料: (1) http://sjl.bitbucket.org/gundo.vim/ ...
- matrix_last_acm_2
2014年广州站网络赛 北大命题 password 123 B http://acm.hust.edu.cn/vjudge/contest/view.action?cid=97257#problem/ ...
- ZOJ Monthly, July 2015
B http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=5552 输入n,表示有n个数1到n.A先拿,B后拿,依次拿,每次可以拿任意一 ...
- 0910 noip模拟
教师节快乐: T1:勇士闯魔塔,是一道很裸的莫队题目,但在老师的催促下,出题人@syq同学修改了第一题,使之成了一道送分题,全暴力水过: T2:第二题是一道预处理+分组背包,考试中,忘了分组背包怎么敲 ...