zoukankan      html  css  js  c++  java
  • TButton.Repaint的执行过程

    测试,在按钮事件里写上 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, 0, 0); // 注意,第二个参数即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 := 0;
      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, 1, 0); // 递归,先重画父类。第二个参数就是WParam
        // 父控件通知完了,就该重画自己了。因为是Invalidate这里过来的
        if Message.WParam = 0 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 := 0 to ControlCount - 1 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 <> 0;
    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 <> 0) then
      begin
        // 自己不把自己算做子控件,所以这个函数是用来重画子控件的。
        // 想画自己,就得另外覆盖Paint;函数,但是发消息给子控件重画这件事情,这里已经帮助程序员写好了(子控件自己还是要覆盖Paint;才能保证正确画自己)
        // 注意csCustomPaint这个风格
        if not (csCustomPaint in ControlState) and (ControlCount = 0) then
          inherited // 假牙,父类根本就没有相关函数,什么都不做。fixme,好像是通过父类来重画自己
        else
          PaintHandler(Message);  // 一般走这里,给所有子控件做剪裁并重画(挨个发送WM_PAINT消息)
      end
      else
      begin
        // 准备内存画板,此时还没有Canvas,所以用API旧方法
        DC := GetDC(0);   // 参数0代表取得整个屏幕的DC
        MemBitmap := CreateCompatibleBitmap(DC, ClientRect.Right, ClientRect.Bottom); // 创建当前DC的画板(大概是为了保留除了当前窗口以外的显示内容,跟画板的底板一样)
        ReleaseDC(0, DC); // 留下MemBitmap,然后释放整个屏幕的DC
        MemDC := CreateCompatibleDC(0); // 创建当前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 := 0;  // 消息使用完毕,消息参数复位,但是通过消息得到的MemDC所有数据都在
          // 画完了内存画板,准备切换
          BitBlt(DC, 0, 0, ClientRect.Right, ClientRect.Bottom, MemDC, 0, 0, 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;

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

  • 相关阅读:
    Thinkphp3.2 cms之角色开发
    说几个你知道的设计模式?
    9种实现点击一个链接弹出一个小窗口的代码
    分享自己作为一个程序员的找工作经历
    网页设置锚点
    博客园网摘地址
    PHP面试总结
    简单的10秒倒计时
    PHP测试题目
    关键字搜索内容总结
  • 原文地址:https://www.cnblogs.com/findumars/p/3703024.html
Copyright © 2011-2022 走看看