测试,在按钮事件里写上 Button1.Repaint;(包括TWinControl.Invalidate;和procedure TWinControl.Update;两个函数,会被TButton所继承),跟踪一下就什么都明白了。

1. 执行TWinControl.Invalidate;后,执行Perform函数,调用WndProc(比如TButton的WndProc),然后依次向上传递。
如果程序员没有处理CM_INVALIDATE消息,则最后会在TControl.WndProc的末尾执行Dispatch CM_INVALIDATE消息。
Dispatch是个虚函数,每个类都有。而TWinControl.CMInvalidate函数能处理CM_INVALIDATE的本事也被TButton继承。
最后相当于执行了TButton的CMInvalidate函数(最后确实会调用API重画自己,但这时还不行),发现Button1有父控件,就必须递归先通知所有父控件。越高级别的父控件越先处理。
父控件TCustomForm也这样来一遍,因为没有被程序员拦截事件处理,所以还是没有效果。
此时上一次递归完成,继续往下执行调用API InvalidateRect使得自己的特定区域无效,至此Button1.Invalidate函数执行过程完成。

2. 以上步骤完成后,相当于只是声明了无效区域,系统会在没有消息的时候自动发送WM_PAINT消息来真正绘制,但我们不希望等待,于是执行第二个函数TWinControl.Update(否则程序员只需要调用Button1.Invalidate就可以了,而不是Button1.Repaint;),后者会调用API UpdateWindow更新窗口,也就是说会发WM_PAINT消息但不经过队列立即重绘。又会经过一系列复杂的执行过程。

procedure TForm1.Button1Click(Sender: TObject);
begin
Button1.Repaint;
end;

至于为什么执行Button1.Repaint;请看这里:
http://www.cnblogs.com/findumars/archive/2012/10/29/2744335.html

于是Invalidate;执行:

procedure TWinControl.Invalidate;
begin
Perform(CM_INVALIDATE, , ); // 注意,第二个参数即WParam是0。
end; // Perform是函数,不是直接发送Windows消息到队列里,只是处理消息而已(所以不会产生消息队列),最后调用WndProc处理。
// 如果程序员没有在WndProc里进行处理,就等于没效果。
function TControl.Perform(Msg: Cardinal; WParam, LParam: Longint): Longint;
var
Message: TMessage;
begin
Message.Msg := Msg;
Message.WParam := WParam;
Message.LParam := LParam;
Message.Result := ;
if Self <> nil then WindowProc(Message); // 虚函数,会调用TWinControl或者其子类的同名函数,直接执行,跟消息队列没有关系
Result := Message.Result;
end; procedure TButtonControl.WndProc(var Message: TMessage);
begin
// 如果在这个窗口函数里找不到消息处理,就向上发送
inherited WndProc(Message);
end; procedure TWinControl.WndProc(var Message: TMessage);
begin
// 最后一定向上传递消息,不管被处理过没有。
inherited WndProc(Message); // 对于CM_INVALIDATE消息来说,这里没有被处理
end; procedure TControl.WndProc(var Message: TMessage);
begin
// 到了这里,以及无法再使用WndProc方法向父类传递消息了,所以使用Dispatch。而且必定向上传递(一般情况下上级不响应这些消息)
Dispatch(Message); // 也没有找到此消息
end; // 终于找到CM_INVALIDATE处理(注意,会递归先重绘其父控件)
procedure TWinControl.CMInvalidate(var Message: TMessage);
var
I: Integer;
begin
if HandleAllocated then // 这是大前提,反正句柄控件都应该有句柄
begin
// 相当于子控件要重画,必须通知一下父控件,而且是挨个通知,父控件想处理就处理,不处理就算了。
if Parent <> nil then Parent.Perform(CM_INVALIDATE, , ); // 递归,先重画父类。第二个参数就是WParam
// 父控件通知完了,就该重画自己了。因为是Invalidate这里过来的
if Message.WParam = then
begin
InvalidateRect(FHandle, nil, not (csOpaque in ControlStyle)); // API, 第二个参数为NULL的话,则重画整个客户区;第三个参数,不透明则保持背景
{ Invalidate child windows which use the parentbackground when themed }
// fixme 只有应用主题的时候才重画子控件。那么蓝天白云算不算应用主题呢?
if ThemeServices.ThemesEnabled then
for I := to ControlCount - do
if csParentBackground in Controls[I].ControlStyle then
Controls[I].Invalidate;
end;
end;
end;

Update的执行过程:

procedure TWinControl.Update;
begin
if HandleAllocated then UpdateWindow(FHandle); // API,发送WM_PAINT立即重绘
end; function TWinControl.HandleAllocated: Boolean;
begin
Result := FHandle <> ;
end; // 执行流程:判断有句柄后执行UpdateWindow,即发消息WM_PAINT立刻执行(这里不是程序员手动调用WndProc,而是要经过Windows系统处理,但不经过消息队列),进入TWinControl的窗口函数MainWndProc
procedure TWinControl.MainWndProc(var Message: TMessage);
begin
WindowProc(Message);
end; procedure TButtonControl.WndProc(var Message: TMessage);
begin
inherited WndProc(Message); // 在WndProc里找不到WM_PAINT消息处理
end; procedure TWinControl.WndProc(var Message: TMessage);
begin
inherited WndProc(Message); // 在WndProc里找不到WM_PAINT消息处理
end; procedure TControl.WndProc(var Message: TMessage);
begin
Dispatch(Message); // 在WndProc里找不到WM_PAINT不要紧,还可以使用Dispatch寻找WM_PAINT消息处理
end; // 找到了WM_PAINT消息处理,既然找到了,那么对WM_PAINT消息也算有个交代了,于是上面所有的WndProc全部正常结束。
// 除非TWinControl.WMPaint里面再继续执行复杂的调用,否则Button1.Update就算执行结束了。
procedure TWinControl.WMPaint(var Message: TWMPaint);
var
DC, MemDC: HDC;
MemBitmap, OldBitmap: HBITMAP;
PS: TPaintStruct;
begin
// 注意,这里是重画句柄控件,重画图形控件不在这里
// fixme 不知道什么时候是双缓冲
if not FDoubleBuffered or (Message.DC <> ) then
begin
// 自己不把自己算做子控件,所以这个函数是用来重画子控件的。
// 想画自己,就得另外覆盖Paint;函数,但是发消息给子控件重画这件事情,这里已经帮助程序员写好了(子控件自己还是要覆盖Paint;才能保证正确画自己)
// 注意csCustomPaint这个风格
if not (csCustomPaint in ControlState) and (ControlCount = ) then
inherited // 假牙,父类根本就没有相关函数,什么都不做。fixme,好像是通过父类来重画自己
else
PaintHandler(Message); // 一般走这里,给所有子控件做剪裁并重画(挨个发送WM_PAINT消息)
end
else
begin
// 准备内存画板,此时还没有Canvas,所以用API旧方法
DC := GetDC(); // 参数0代表取得整个屏幕的DC
MemBitmap := CreateCompatibleBitmap(DC, ClientRect.Right, ClientRect.Bottom); // 创建当前DC的画板(大概是为了保留除了当前窗口以外的显示内容,跟画板的底板一样)
ReleaseDC(, DC); // 留下MemBitmap,然后释放整个屏幕的DC
MemDC := CreateCompatibleDC(); // 创建当前DC的兼容DC
OldBitmap := SelectObject(MemDC, MemBitmap); // 把MemBitmap画板放到MemDC里去,就可以准备在MemDC里画了
try
DC := BeginPaint(Handle, PS); // 返回值是指定Window的DC
// 双缓冲工作真正开始
Perform(WM_ERASEBKGND, MemDC, MemDC); // 当前控件使用MemDC擦除背景
Message.DC := MemDC; // 构建一个消息,把MemDC传入,当前控件和子控件都在MemDC上画
WMPaint(Message); // 递归调用函数(构建了一个消息,但不是发生消息),而且此时的DC不等于0,因此条件成立,进入块执行PaintHandler
Message.DC := ; // 消息使用完毕,消息参数复位,但是通过消息得到的MemDC所有数据都在
// 画完了内存画板,准备切换
BitBlt(DC, , , ClientRect.Right, ClientRect.Bottom, MemDC, , , SRCCOPY); // 把画好所有控件的MemDC一次性拷贝到指定Window的DC
EndPaint(Handle, PS); // 结束画图过程
finally
SelectObject(MemDC, OldBitmap);
DeleteDC(MemDC);
DeleteObject(MemBitmap);
end;
end;
end;

1. 结果发现执行
if not (csCustomPaint in ControlState) and (ControlCount = 0) then
inherited;
2. 然后调用(不会因为没有在祖先中找到一样的函数或者方法而将inherited失效,他会传入当前类缺省的消息处理)
procedure TWinControl.DefaultHandler(var Message);
Result := CallWindowProc(FDefWndProc, FHandle, Msg, WParam, LParam); // API调用本类的属性,就这一处调用。好像是这句处理了Button1的WM_PAINT并使之重绘
3. 然后执行
procedure TWinControl.MainWndProc(var Message: TMessage);
    WindowProc(Message); // 虚函数
执行(在它们的WndProc里都找不到WM_PAINT消息处理)
procedure TCustomForm.WndProc(var Message: TMessage);
procedure TWinControl.WndProc(var Message: TMessage);
procedure TControl.WndProc(var Message: TMessage);

5. 使用Dispatch寻找,即使找不到也会被同一个类的DefaultHandler函数处理(如果这个类有覆盖这个函数的话)
procedure TCustomForm.DefaultHandler(var Message);
     inherited DefaultHandler(Message)
6. 继续向上寻找(算是找到消息了)
procedure TWinControl.DefaultHandler(var Message);
case Msg of
WM_CTLCOLORMSGBOX..WM_CTLCOLORSTATIC: (一共7个消息)
Result := SendMessage(LParam, CN_BASE + Msg, WParam, LParam);
7. 发送消息后,即消息进入窗口函数,找到窗口函数,
procedure TWinControl.MainWndProc(var Message: TMessage);
    WindowProc(Message);
执行(在它们的WndProc里都找不到CN_CTLCOLORBTN消息处理)
procedure TCustomForm.WndProc(var Message: TMessage);
procedure TWinControl.WndProc(var Message: TMessage);
procedure TControl.WndProc(var Message: TMessage);

8. 通过Dispatch会找到 CN_BASE + Msg
procedure TButton.CNCtlColorBtn(var Message: TWMCtlColorBtn);
     inherited;
9. 向上传递后,会执行后,所有调用过程结束。
procedure TWinControl.DefaultHandler(var Message);
CN_CTLCOLORMSGBOX..CN_CTLCOLORSTATIC:
begin
SetTextColor(WParam, ColorToRGB(FFont.Color)); // API,会发送WM_SETTEXT消息吗?
SetBkColor(WParam, ColorToRGB(FBrush.Color)); // API,会发送WM_BACKGROUND消息吗?
Result := FBrush.Handle;
end;

先留下一个爪印,熟悉熟悉整个过程,以后有时间再回来详加注释。

TButton.Repaint的执行过程的更多相关文章

  1. 点击TButton后的执行OnClick和OnMouseDown两个事件的过程(其实是通过WM_COMMAND执行程序员的代码)

    问题的来源:在李维的<深入浅出VCL>一书中提到了点击TButton会触发WM_COMMAND消息,正是它真正执行了程序员的代码.也许是我比较笨,没有理解他说的含义.但是后来经过追踪代码和 ...

  2. ASP.NET Web API 过滤器创建、执行过程(二)

    ASP.NET Web API 过滤器创建.执行过程(二) 前言 前面一篇中讲解了过滤器执行之前的创建,通过实现IFilterProvider注册到当前的HttpConfiguration里的服务容器 ...

  3. ASP.NET Web API 过滤器创建、执行过程(一)

    ASP.NET Web API 过滤器创建.执行过程(一) 前言 在上一篇中我们讲到控制器的执行过程系列,这个系列要搁置一段时间了,因为在控制器执行的过程中包含的信息都是要单独的用一个系列来描述的,就 ...

  4. ASP.NET Web API 控制器执行过程(一)

    ASP.NET Web API 控制器执行过程(一) 前言 前面两篇讲解了控制器的创建过程,只是从框架源码的角度去简单的了解,在控制器创建过后所执行的过程也是尤为重要的,本篇就来简单的说明一下控制器在 ...

  5. Struts2拦截器的执行过程浅析

    在学习Struts2的过程中对拦截器和动作类的执行过程一度陷入误区,特别读了一下Struts2的源码,将自己的收获分享给正在困惑的童鞋... 开始先上图: 从Struts2的图可以看出当浏览器发出请求 ...

  6. 通过源码了解ASP.NET MVC 几种Filter的执行过程

    一.前言 之前也阅读过MVC的源码,并了解过各个模块的运行原理和执行过程,但都没有形成文章(所以也忘得特别快),总感觉分析源码是大神的工作,而且很多人觉得平时根本不需要知道这些,会用就行了.其实阅读源 ...

  7. Hadoop MapReduce执行过程详解(带hadoop例子)

    https://my.oschina.net/itblog/blog/275294 摘要: 本文通过一个例子,详细介绍Hadoop 的 MapReduce过程. 分析MapReduce执行过程 Map ...

  8. 高程(4):执行环境、作用域、上下文执行过程、垃圾收集、try...catch...

    高程三 4.2.4.3 一.执行环境 1.全局执行环境是最外层的执行环境. 2.每个函数都有自己的执行环境,执行函数时,函数环境就会被推入一个当前环境栈中,执行完毕,栈将其环境弹出,把控制器返回给之前 ...

  9. saltstack命令执行过程

    saltstack命令执行过程 具体步骤如下 Salt stack的Master与Minion之间通过ZeroMq进行消息传递,使用了ZeroMq的发布-订阅模式,连接方式包括tcp,ipc salt ...

随机推荐

  1. hdu6058[链表维护] 2017多校3

    用一个双向链表来查找比当前元素大的前k-1个元素和后k-1个元素 ,从小到大枚举x,算完x的贡献后将x从链表中删除,优化到O(nk). /*hdu6058[链表维护] 2017多效3*/ #inclu ...

  2. [Vijos1308]埃及分数(迭代加深搜索 + 剪枝)

    传送门 迭代加深搜索是必须的,先枚举加数个数 然后搜索分母 这里有一个强大的剪枝,就是确定分母的范围 #include <cstdio> #include <cstring> ...

  3. P3799 妖梦拼木棒 (组合数学)

    题目背景 上道题中,妖梦斩了一地的木棒,现在她想要将木棒拼起来. 题目描述 有n根木棒,现在从中选4根,想要组成一个正三角形,问有几种选法? 输入输出格式 输入格式: 第一行一个整数n 第二行n个整数 ...

  4. 【2018.11.22】CTSC2018(模拟赛!)

    太蠢了……$noip$ 后第一次模拟赛竟然是这样的……完全就是打击自信 / 降智…… 1. 假面 一道神仙概率 $dp$!第一次写…… 拿到题就发现血量 $m_i$ 的上限只有 $100$! 然后 $ ...

  5. Ionic 如何把左上角的按钮去掉?

    代码实现: <ion-header > <ion-toolbar> <ion-buttons start> <a href="#"> ...

  6. lnux 下 core文件

    1. core文件的简单介绍在一个程序崩溃时,它一般会在指定目录下生成一个core文件.core文件仅仅是一个内存映象(同时加上调试信息),主要是用来调试的. 2. 开启或关闭core文件的生成用以下 ...

  7. perl学习之精髓中的精髓

    1.是函数就有返回值: 比如:chomp函数,其可以除去换行符,但其也有返回值 chomp($xx) #这是去除xx的换行符 $yy=chomp($xx)  #这是看这次除去了几个换行符,也就是函数运 ...

  8. mysql合并和时间函数

    sql:利用group_concat()方法,参数为需要合并的字段,合并的字段分隔符默认为逗号,可通过参数separator指定,该方法往往配合group by 一起使用.利用group_concat ...

  9. hdu 4857 逆拓扑+大根堆(priority_queue)

    题意:排序输出:在先满足定约束条件下(如 3必需在1前面,7必需在4前面),在满足:1尽量前,其次考虑2,依次.....(即有次约束). 开始的时候,只用拓扑,然后每次在都可以选的时候,优先考虑小的, ...

  10. php 处理大文件方法 SplFileObject

    $csv_file = 'tmp.csv'; $start = isset($_GET['start']) ?intval($_GET['start']) : 1; // 从第几行开始读取 $num ...