这是一个再普通不过的Button1Click执行体:

procedure TForm1.Button1Click(Sender: TObject);
begin
ShowMessage('I am Button1');
end;

点击Button1以后,具体过程是:Form收到Button1发来的WM_COMMAND,然后发一个CN_COMMAND给Button1,这个过程就不描述了。这里研究的是VCL在接下去是如何执行的:

procedure TButton.CNCommand(var Message: TWMCommand);
begin
if Message.NotifyCode = BN_CLICKED then Click;
end; procedure TButton.Click;
var
Form: TCustomForm;
begin
Form := GetParentForm(Self);
if Form <> nil then Form.ModalResult := ModalResult;
inherited Click;
end; 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); // 这里的Self代表当前TControl,也就是Button1对象
end;

说白了就是这么简单啊。

注意,每一个具有Sender参数的函数,都是由VCL框架提供的。但每一个不同的事件,分别由VCL不同函数来提供Sender参数的具体内容。以上分析仅仅分析了OnClick事件这一种情况。

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

那么TImage1的OnClick的Sender是谁提供的呢?在Form1上只放置一个Image1,载入图片后双击增加以下代码,经过测试:

procedure TForm1.Image1Click(Sender: TObject);
begin
ShowMessage(Sender.ClassName); // 显示结果TImage
if Sender is TImage then
ShowMessage(TImage(Sender).Name); // 显示结果是Image1
end;

而且鼠标点击图片后,不松手,就不会执行以上代码,这充分说明不是WM_LBUTTONDOWN触发此事件。于是忽略所有有关WM_LBUTTONDOWN的过程。

仔细查看TCustomForm的代码,并没有发现WM_LBUTTONUP的覆盖消息函数,这说明Image1的触发过程,仍是VCL框架提供的功能(要么是TControl,要么是TWinControl)。继续仔细查看,发现只有TControl对WM_LBUTTONUP有直接的响应函数:

procedure WMLButtonUp(var Message: TWMLButtonUp); message WM_LBUTTONUP;

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
Click;
end;
DoMouseUp(Message, mbLeft);
end;

同时TWinControl.WndProc里也有对WM_LBUTTONUP的处理,那么谁先谁后呢?当然还是WndProc优先运行,因为它是Form1这个窗口所直接对应的窗口函数。只有在这里找不到处理,才会调用Dispatch去寻找消息处理函数。

procedure TWinControl.WndProc(var Message: TMessage);
var
Form: TCustomForm;
begin
case Message.Msg of
WM_SETFOCUS:
begin
Form := GetParentForm(Self);
if (Form <> nil) and not Form.SetFocusedControl(Self) then Exit;
end;
WM_KILLFOCUS:
if csFocusing in ControlState then Exit;
WM_NCHITTEST: // 注意这里,鼠标移动时,也会不停的执行
begin
inherited WndProc(Message);
if (Message.Result = HTTRANSPARENT) and (ControlAtPos(ScreenToClient(
SmallPointToPoint(TWMNCHitTest(Message).Pos)), False) <> nil) then
Message.Result := HTCLIENT;
Exit;
end;
WM_MOUSEFIRST..WM_MOUSELAST:
begin
if Message.Msg = WM_LBUTTONUP then // 不这样改造,WM_MOVE消息会不停的来干扰
begin
tag := ; // 下断点,可以准确捕捉鼠标点击图片后弹起时的消息
end;
if IsControlMouseMsg(TWMMouse(Message)) then // 检测并转发鼠标消息。如果是直接点击WinControl,那么此处捕捉无效,会继续通过TControl.WndProc和Dispatch继续传递消息
begin
{ Check HandleAllocated because IsControlMouseMsg might have freed the
window if user code executed something like Parent := nil. }
if (Message.Result = ) and HandleAllocated then
DefWindowProc(Handle, Message.Msg, Message.wParam, Message.lParam);
Exit; // 图形子控件对消息处理完毕,直接退出了。不给父控件处理的机会
end;
end;
WM_KEYFIRST..WM_KEYLAST:
if Dragging then Exit;
WM_CANCELMODE:
if (GetCapture = Handle) and (CaptureControl <> nil) and
(CaptureControl.Parent = Self) then
CaptureControl.Perform(WM_CANCELMODE, , );
end;
inherited WndProc(Message);
end; function TWinControl.IsControlMouseMsg(var Message: TWMMouse): Boolean;
var
Control: TControl;
P: TPoint;
begin
if GetCapture = Handle then
begin
if (CaptureControl <> nil) and (CaptureControl.Parent = Self) then
Control := CaptureControl
else
Control := nil;
end
else
Control := ControlAtPos(SmallPointToPoint(Message.Pos), False); // 检测鼠标正在点击自己的哪个图形子控件
Result := False;
if Control <> nil then
begin
P.X := Message.XPos - Control.Left;
P.Y := Message.YPos - Control.Top;
Message.Result := Control.Perform(Message.Msg, Message.Keys, Longint(PointToSmallPoint(P))); // 此时调试器显示msg的值是514,经过查询WM_LBUTTONUP = $0202;正是514,说明WM_LBUTTONUP消息被Image1的父控件正确转发给Image1
Result := True;
end;
end; procedure TControl.WMLButtonUp(var Message: TWMLButtonUp);
begin
inherited; // 注意,如果是直接点击Form1,会执行TCustomForm.DefaultHandler(var Message);相当于给子类控件提供了新的处理消息的机会
if csCaptureMouse in ControlStyle then MouseCapture := False;
if csClicked in ControlState then
begin
Exclude(FControlState, csClicked);
if PtInRect(ClientRect, SmallPointToPoint(Message.Pos)) then
Click; // 先执行OnClick,后执行MouseUp
end;
DoMouseUp(Message, mbLeft);
end; 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); // 执行Image1Click,注意它的Sender参数

end;

OnClick事件的Sender参数的前世今生——TWinControl.WinProc优先捕捉到鼠标消息,然后使用IsControlMouseMsg函数进行消息转发给图形子控件(意外发现OnClick是由WM_LBUTTONUP触发的)的更多相关文章

  1. TCustomControl绘制自己和图形子控件共四步,TWinControl关键属性方法速记

    TCustomControl = class(TWinControl) private FCanvas: TCanvas; procedure WMPaint(var Message: TWMPain ...

  2. 关于Asp.net事件,如何在触发子控件的事件时,同步触发父页面的事件

    对页面引用自定义控件后,通过绑定自定义事件,页面绑定子控件的事件,在子控件做了某些修改动作后,如何同步操作父页面的方法:下面我煮了个栗子,同学们可以来尝一尝试一试 a.aspx 引用 UserCont ...

  3. 记录下帮助一位网友解决的关于android子控件的onTouch或onClick和父OnTouch 冲突的问题。

    前三天收到位网友的私信求助,问题大概如标题所示.具体是下面的情况,个人感觉,这个问题挺有趣,也会在实际项目开发中很常见.不想看前奏的请直接跳至解决方法. 问题原型: 父控件是自定义的 LinearLa ...

  4. 解决ListView中Item的子控件与Item点击事件冲突

    常常会碰到在ListView中点击当中一个Item.会一并触发其子控件的点击事件.比如Item中的Button.ImageButton等.导致了点击Item中Button以外区域也会触发Button点 ...

  5. 截取scrollview的滑动事件,传递给子控件

    重写一个ScrollView public class MyScrollView extends ScrollView{ public MyScrollView(Context context, At ...

  6. JQuery 点击子控件事件,不会触发父控件的事件

     $('.order-delete').on('tap', function (e) {                  console.log('删除1');                  c ...

  7. HTML 控件和web控件 OnClientClick和OnClick OnServerClick区别

      ^_^ 本来对html控件,服务器控件的知识模模糊糊的.今天特地查了相关的知识. 下面是我写代码总结的. 这些事件   主要用于在客户端执行验证,然后决定是否执行服务端事件   (没接触之前就为此 ...

  8. Android开发之解决父控件拦截子控件事件问题

    以ViewPager为例: public class TopNewsViewPager extends ViewPager { public TopNewsViewPager(Context cont ...

  9. onclick事件对动态参数类型为字符串的处理

    onclick="solveRow("'+row.isbesolve+'")"

随机推荐

  1. sqlite3 脚本的使用

    本来想找个专门查看数据库的工具的,后来发现脚本也非常easy,就直接用脚本了. sqlite3的脚本能够方便的查看当前数据库里的内容,使用起来事实上非常easy,这一列举一些最简单的使用方法,以后假设 ...

  2. LeetCode——Path Sum II

    Given a binary tree and a sum, find all root-to-leaf paths where each path's sum equals the given su ...

  3. 使用CAShapeLayer和UIBezierPath画一个自定义半圆弧button

    通常我们使用系统自带的UIButton时,一般都是Rect矩形形式的,或则美工给出一张半圆弧的按钮,如图为一张半圆加三角形的按钮,而此时,如果给按钮添加点击事件时,响应事件依然为矩形区域,不符合我们的 ...

  4. ThinkPHP - I 函数

    ThinkPHP函数详解:I方法   浏览:144722 发布日期:2013/06/01 分类:文档教程 关键字: 函数 ThinkPHP的I方法是3.1.3版本新增的,如果你是之前的3.*版本的话, ...

  5. MacBook外接显示器设置方法(新手入门贴)

    小屏幕的MacBook/MacBook Pro放在桌上长时间使用,眼睛比较累,而且,长时间低头看屏幕,易得颈椎病,绝对有损健康.配一台大屏幕的外置显示器不失为两全其美的好办法. 首先,得买一台中意的大 ...

  6. 【转】centos安装memcached+php多服务器共享+session多机共享问题

    参考博文: centos安装memcached 源码安装 Yum安装Memcache Memcached内存分配优化及使用问题 <转>php+memcached 实现session共享 P ...

  7. PyQt中如何隐藏Menu

    PyQt中隐藏一个Menu Item,可以通过QAction的setVisible(False)来设置,而QMenu的setVisible(False)是不管用的. 现在问题来了,我们有一个菜单,它有 ...

  8. 分享一个 CSDN 自动评论角本

    先修改帐户名和密码 import requests from BeautifulSoup import BeautifulSoup import time def commitfunc(source_ ...

  9. H面试程序(28):字符串处理转换

    //2 字符串处理转换 //问题描述: //在给定字符串中找出单词( “单词”由大写字母和小写字母字符构成, //其他非字母字符视为单词的间隔,如空格.问号.数字等等:另外单个字母不算单词): //找 ...

  10. LDAP研究

    一.ldap介绍 ldap是轻量级的文件夹訪问协议.重点是文件夹訪问协议.更为重点的是协议.好吧他是一个协议.也就是一个标准. 那么实现这款协议的软件当中有一款就是openldap. 二.安装wind ...