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;

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

  • 相关阅读:
    word设置的密码忘了怎么办?
    Navicat Report Viewer 设置 HTTP 的方法
    如何处理Navicat Report Viewer 报表
    excel密码忘记了怎么办
    Beyond Compare文本比较搜索功能详解
    Popular Cows POJ
    Problem B. Harvest of Apples HDU
    网络流模型整理
    The Shortest Statement CodeForces
    Vasya and Multisets CodeForces
  • 原文地址:https://www.cnblogs.com/findumars/p/3703024.html
Copyright © 2011-2022 走看看