问题的来源:在李维的《深入浅出VCL》一书中提到了点击TButton会触发WM_COMMAND消息,正是它真正执行了程序员的代码。也许是我比较笨,没有理解他说的含义。但是后来经过追踪代码和仔细分析,终于明白了整个过程。结论是,自己对Win32的不够了解,其实触发按钮就是靠这个WM_COMMAND消息,而且VC里也是这样做的。

现象:有没有发现TButton既有OnClick,又有OnMouseDown,它们之间是什么区别和联系是什么呢?普通的按钮点击到底是哪个事件执行了程序员的代码,又是如何执行的呢?且看我的分析过程:

type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
m_tag: integer;
end; var
Form1: TForm1; implementation {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject);
begin
tag:=;
end; procedure TForm1.Button1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
m_tag:=;
end; procedure TForm1.Button2Click(Sender: TObject);
begin
ShowMessage(intToStr(tag));
ShowMessage(intToStr(m_tag));
end;

点击Button1后,再点击Button2,发现tag和m_tag两个值都被赋值了。看来用鼠标点击Button是一箭双雕啊,会同时触发OnClick和OnMouseDown事件。至于这两个事件哪个会先执行,则要看产生消息的先后顺序。至于到底谁先谁后,我想了好多办法:用SPY++观察不行,因为Button1和Form1是两个不同的句柄;在Application.Run里观察消息也不行,因为实在Application运行以后是太多消息了,没法调试。也许修改VCL源代码并同时加上case WM_COMMAND和case WM_LBUTTONDOWN后看先截住谁。不过后来我想了一个好办法,就是在这两个事件里加上记录时间的选项,这样简单方便,实在是不用什么高深技术。通过代码测试,我发现还是OnClick会被先执行:

  TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
m_tag: integer;
m_time_click: TTime;
m_time_mousedown: TTime;
end; var
Form1: TForm1; implementation {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject);
begin
tag:=;
m_time_click:=now;
end; procedure TForm1.Button1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
m_tag:=;
m_time_mousedown:=now;
end; procedure TForm1.Button2Click(Sender: TObject);
begin
ShowMessage(intToStr(tag));
ShowMessage(intToStr(m_tag));
if m_time_click>m_time_mousedown then ShowMessage('m_time_click is first');
end;

于是接下去自然应该分析OnClick的执行过程。不过说实话,正面分析有点难,但我还是硬着头皮上吧(其实我本人是知道答案后反推整个过程的)。如下:

1.程序员改写的OnClick事件,那么我们可以发现OnClick是TControl的事件:
property OnClick: TNotifyEvent read FOnClick write FOnClick stored IsOnClickStored;
同时可以了解一下什么是TNotifyEvent?按住Ctrl点击鼠标就可以找到它的定义:
TNotifyEvent = procedure(Sender: TObject) of object;
就是说是一个函数指针。从这个原理上来猜,就是VCL框架让这个函数指针指向了程序员定义的那个函数,才使得程序员定义的函数自动被融入到VCL框架内得以正确执行。

2.然后在TControl里搜索,是谁在调用。发现有不少地方都调用了FOnClick事件。不过我们这个例子里,没有什么action在起作用,所以只能是TControl.Click函数在调用它,代码如下:

procedure TControl.Click;
begin
{ Call OnClick if assigned and not equal to associated action's OnExecute.
If associated action's OnExecute assigned then call it, otherwise, call
OnClick. }
if Assigned(FOnClick) and (Action <> nil) and (@FOnClick <> @Action.OnExecute) then
FOnClick(Self)
else if not (csDesigning in ComponentState) and (ActionLink <> nil) then
ActionLink.Execute(Self)
else if Assigned(FOnClick) then
FOnClick(Self); // 这里,会执行函数指针指向的内容
end;

再看它的定义,发现是一个动态函数:

procedure TControl.Click; dynamic;
于是下一步就该研究,是谁调用Click函数了。

题外话:为什么要搜索Controls单元?因为它第一次定义了OnClick事件,所以嫌疑最大。如果它那里只定义不处理,那也有是可能的。不过我的整个例子只涉及到TForm和TButton,除了这两个类本身要研究,还有就是它们的父类要研究,那样就缩小研究范围、一共只有几个类了,一定可以找到OnClick事件的来龙去脉。好在我们在TControl里就发现它了,那样就变得更简单了。关于这点完全是我自己的心得,别的文章可以把原理讲的更透彻,但是对于类似我这样的白痴产生更源头上的问题,只有我这样有相同的疑惑和经历才会讲到这一点。其实关于VCL我还有大量的疑惑没有解决(当然是在仔细研读了VCL代码基础上产生的大量问题,很多都是细节里的细节),如果哪位高手愿意与我探讨,我将不甚感激。

3. 很明显,对于一个动态函数,一旦其子类有覆盖函数,那么就会执行子类的覆盖函数。没有就拉倒,变得更简单了。搜索TButton及其父类TButtonControl,我们果然在TButton里发现了它的覆盖函数:procedure Click; override; 即:

procedure TButton.Click;
var
Form: TCustomForm;
begin
Form := GetParentForm(Self);
if Form <> nil then Form.ModalResult := ModalResult;
inherited Click;
end;

有趣的是,我们发现这个覆盖函数仅仅改写父窗体的状态,它本身并不真正执行程序员事件,还是要inherited Click;也就是TControl.Click来执行程序员的事件。猜测这么做是因为放在TControl里可以让图形控件也拥有Click的能力。

4. 我们继续搜索,发现在TControl.WMLButtonUp函数里调用Click;函数(注意,到这步分析错了,大家不要往下看了,稍后纠正)

procedure TControl.WMLButtonUp(var Message: TWMLButtonUp);
begin
inherited;
if csCaptureMouse in ControlStyle then MouseCapture := False;
if csClicked in ControlState then
begin
Exclude(FControlState, csClicked);
if PtInRect(ClientRect, SmallPointToPoint(Message.Pos)) then // API
Click; // 类函数
end;
DoMouseUp(Message, mbLeft);
end;

正是它响应了WM_LBUTTONUP消息。注意啊,OnClick事件只有放开鼠标的时候才执行,否则是不会执行的;而且因为PtInRect函数判断的关系,按下按以后,鼠标是不能移到Button1范围之外,否则也不会执行Click函数,不信可以试试。

5. 至此就可以明白,只要鼠标点击Button1,鼠标就会产生WM_LBUTTONUP消息并发送给Button1,VCL内建的消息循环必然会找到TControl.WMLButtonUp从而执行Click函数。但是它会先执行TButton.Click;函数,这个Click函数做了两件事情:先通知祖先Form(一定是Form,而不是别的窗口,而且也不必是直接的父窗口,可以是间接的。所以Form里放一个TPanel,TPanel里放一个TButton也还是可以找到这个Form,这样间接的按钮照样通过改变ModalResult照样关闭一个Form)的ModalResult属性状态被改变了,然后执行要inherited Click;也就是TControl.Click;这个函数里面有这句:if Assigned(FOnClick) then FOnClick(Self); 如果FOnClick有值了,或者说不再是一个空指针了,那么它就会调用函数指针FOnClick执行的那个函数,而且还是带一个Self参数的函数。那么FOnClick是否有值了如何判断呢?简单呀,程序员双击Button1后,在Unit1.dfm里以下内容:

object Button1: TButton
Left =
Top =
Width =
Height =
Caption = 'Button1'
TabOrder =
OnClick =
Button1Click // 这里,函数指针连接了程序员的函数!
OnMouseDown = Button1MouseDown
end

也就是说OnClick已经指向了程序员定义的函数(其实是IDE为自动产生,程序员填写执行内容的函数。通过手动赋值OnClick = Func1就可以指向另一个指定函数一点问题没有)。

OnClick的分析过程到此结束。OnMouseDown的分析过程,且听下回分解(我会继续编辑此文,而不是另开博文)。

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

有兴趣的还可以参考一下这篇文章,图文并茂,挺好的:
http://ymg97526.blog.163.com/blog/static/173658160201131021911946/

点击TButton后的执行OnClick和OnMouseDown两个事件的过程(其实是通过WM_COMMAND执行程序员的代码)的更多相关文章

  1. 点击Button后,执行MouseDown的过程(使用Call Stack观察很清楚)

    Form1上放两个按钮Button1和Button2,默认输入焦点是Button1,现在点击Button2,产生WM_LBUTTONDOWN消息 procedure TForm1.Button2Mou ...

  2. 如何点击按钮后在加载外部的Js文件

    或许有朋友遇到过,想等自己点击按钮之后才执行某一个js文件,那么,你运气好,看到了我的代码了哈哈, <html> <head> <title></title& ...

  3. 消除点击连接或者按钮或者执行onclick事件时出现的边框

    css中添加 *:not(input) { font-family: sans-serif; font-size-adjust: none; -webkit-user-select: none; -w ...

  4. ASP.NET 多次点击button后事件执行多次 并发解决 频繁操作解决办法

    首先让我们体验一下频繁操作: 1)打开项目,在后台aspx.cs等服务器页面,设置断点. 2)点击页面按钮 3)调试开始进入断点,然后重复点击页面按钮 4)服务器第一次事件已经处理完毕,调试再次进入了 ...

  5. 如何让Web程序在点击按钮后出现如执行批处理程序般的效果

    在cli程序中,输入命令得到连续的输出已经是一种进度而美观的页面交互形式,好比下图: 而web程序里也有类似的场景,比如执行一个耗时任务,除了显示出等待图标外,用户还希望把执行的状态及时显示出来.如下 ...

  6. 点击按钮后到底发生了什么,Touch,LongClick或者Click?

    按钮点击事件详解 最近一个项目需要给应用初始界面上的动态按钮添加在不同状态的变换效果,如点击(俗一点也可称为按压)后实现背景图的更换或者图标的缩放等效果.由于按钮点击的时间有长有短,所以采用OnTou ...

  7. 解决Button设置disabled后无法执行后台代码问题

    一.开始调式下面的程序,发现Button在js中设置disabled后无法执行后台代码(btnsave_Click)问题 <asp:Button ID="btnsave" r ...

  8. response 后刷新页面,点击按钮后,禁用该按钮

    一,正常的点击按钮后,将其灰显,全部执行完毕再正常显示. this.btnSave.Attributes.Add("onclick", "if (typeof(Page_ ...

  9. 让超链接点击后不跳转,可以用href = "#",但是这个#就会锚点到页面最上边 点击链接后不跳转可以设置成

    让超链接点击后不跳转,可以用href = "#",但是这个#就会锚点到页面最上边     点击链接后不跳转可以设置成     1.<a href="javascri ...

随机推荐

  1. HTML <!DOCTYPE>标签 各版本对应的标签是否有无

    参考来源: http://www.w3school.com.cn/tags/html_ref_dtd.asp HTML5/HTML 4.01/XHTML 元素和有效的 DTD 下面的表格列出了所有的 ...

  2. HBase编程 API入门系列之get(客户端而言)(2)

    心得,写在前面的话,也许,中间会要多次执行,连接超时,多试试就好了. 前面是基础,如下 HBase编程 API入门系列之put(客户端而言)(1) package zhouls.bigdata.Hba ...

  3. Spring Boot (16) logback和access日志

    Spring Boot 内部采用的是Commons Logging进行日志记录,但是在底层为Java Util Logging.Log4J2.Logback等日志框架提供了默认配置. logback ...

  4. Java中Ajaxa中文乱码问题

    客户端: url='test/queryList?itemName='+itemName; //如果只转一次url=encodeURI(toUrl);   到后台时:乱码    servlet里dec ...

  5. MySQL Connector for .NET 和 EF版本匹配问题

    以下讨论的都是EF5.0, 版本号:4.4.0.0 如果装了MySQL 5.0.1 , 那么最好用MySQL Connector 6.3.6,但是创建数据库后,生成迁移历史表的时候,会报错,你不管,直 ...

  6. ie8及其以下版本兼容性问题之圆角

    解决办法:在http://css3pie.com/页面下载一个PIE.htc的文件,加载到根目录下,然后在css中加上一句behavior:url(../js/PIE.htc);如下: .border ...

  7. VMWare 安装Centos 6.9

    1.新建虚拟机 (1)点击文件-->新建虚拟机 (2)选择 自定义(高级)--> 下一步 (3)选择Workstation 12.0 --> 下一步 (4)选择 稍后安装操作系统 - ...

  8. C# Datetime 使用详解

    获得当前系统时间: DateTime dt = DateTime.Now; Environment.TickCount可以得到“系统启动到现在”的毫秒值 DateTime now = DateTime ...

  9. Deutsch lernen (05)

    1. die Wahrheit, -en 真理:  - 真言,实情 Wir sollen die Wahrheit festhalten. 坚持:紧握 Im Wein liegt Wahrheit. ...

  10. In Swift, typedef is called typealias:

    It is used to create an alias name for another data type. The syntax of the typedef declaration is:[ ...