• 终于懂了:WM_PAINT 与 WM_ERASEBKGND(三种情况:用户操作,UpdateWindow,InvalidateRect产生的效果并不相同),并且用Delphi代码验证 good


    一直对这两个消息的关系不是太了解,借重新深刻学习windows编程的机会研究一番。

    1)当窗口从无效变为有效时,比方将部分覆盖的窗口恢复时会重绘窗口时:程序首先会通过发送其他消息调用DefWindowProc,它内部会发送WM_ERASEBKGND消息然后才会发送WM_PAINT消息,而且不经过消息队列(笔记:这结论从而何来?)。用Delphi的代码当场验证:

    procedure TWinControl.WMSize(var Message: TWMSize);
    begin
      UpdateBounds; // 类函数
      inherited;
      Realign; // 类函数
      if not (csLoading in ComponentState) then Resize; // 类函数,简单调用程序员事件
    end;
    
    procedure TWinControl.WMMove(var Message: TWMMove);
    begin
      inherited;
      UpdateBounds;
    end;

    果然发现两个inherited,会把WM_SIZE和WM_MOVE消息发送到DefWindowProc,从而触发WM_ERASEBKGND。

    2)诸如UpdateWindow也会先调用WM_ERASEBKGND消息的处理过程,然后才会调用WM_PAINT消息的处理过程

    procedure TControl.Update;
    begin
      if Parent <> nil then Parent.Update;
    end;
    
    procedure TControl.Repaint;
    var
      DC: HDC;
    begin
      if (Visible or (csDesigning in ComponentState) and
        not (csNoDesignVisible in ControlStyle)) and (Parent <> nil) and
        Parent.HandleAllocated then
        if csOpaque in ControlStyle then
        begin
          DC := GetDC(Parent.Handle);
          try
            IntersectClipRect(DC, Left, Top, Left + Width, Top + Height);
            Parent.PaintControls(DC, Self);
          finally
            ReleaseDC(Parent.Handle, DC);
          end;
        end else
        begin
          Invalidate;
          Update;
        end;
    end;
    
    procedure TWinControl.Update;
    begin
      if HandleAllocated then UpdateWindow(FHandle); // API,只有这一处使用这个API函数,但是有另外三处会来调用当前函数,因为这有这个API函数可以真正做到立刻刷新
    end;
    
    procedure TWinControl.Repaint;
    begin
      Invalidate;
      Update;
    end;

    3)当调用InvalidateRect时(三个参数:hWnd,RECT,bErase),如果bErase参数为TRUE时会先调用WM_ERASEBKGND消息的处理过程。为False时,只会发送WM_PAINT消息到队列。所以InvalidateRect不是立刻调用WM_PAINT消息的处理过程,他只是给程序窗口增加一个更新区域(参阅MSDN)。

    Delphi里一共有三处调用这个API函数:

    procedure TControl.InvalidateControl(IsVisible, IsOpaque: Boolean);
    var
      Rect: TRect;
    begin
      if (IsVisible or (csDesigning in ComponentState) and
        not (csNoDesignVisible in ControlStyle)) and (Parent <> nil) and
        Parent.HandleAllocated then
      begin
        Rect := BoundsRect;
        InvalidateRect(Parent.Handle, @Rect, not (IsOpaque or // 注意,处理的是父窗口的句柄
          (csOpaque in Parent.ControlStyle) or BackgroundClipped));
      end;
    end;
    
    procedure TWinControl.CMInvalidate(var Message: TMessage);
    var
      I: Integer;
    begin
      if HandleAllocated then
      begin
        if Parent <> nil then Parent.Perform(CM_INVALIDATE, 1, 0);
        if Message.WParam = 0 then
        begin
          InvalidateRect(FHandle, nil, not (csOpaque in ControlStyle)); // 注意,处理的是自己的句柄,nil表示自己的整个客户区都重绘
          { Invalidate child windows which use the parentbackground when themed }
          if ThemeServices.ThemesEnabled then
            for I := 0 to ControlCount - 1 do
              if csParentBackground in Controls[I].ControlStyle then
                Controls[I].Invalidate;
        end;
      end;
    end;
    
    procedure TWinControl.InvalidateFrame;
    var
      R: TRect;
    begin
      R := BoundsRect;
      InflateRect(R, 1, 1);
      InvalidateRect(Parent.FHandle, @R, True); // 也是重绘自己,但却是无条件重绘,而不需要考虑是否透明之类的问题,因为设计期也要绘制边框。其次,边框绘制出来以后,其内容可以被覆盖,因此不用管内容是否需要使用Theme
    end;

    另外关于WM_ERASEBKGND处理函数的返回值,如果处理WM_ERASEBKGND消息时返回FALSE,BeginPaint标记pt.fErase为TRUE, 如果处理WM_ERASEBKGND时返回TRUE,BeginPaint标记pt.fErase为FALSE。注意,Delphi就是这样做的,意思就是Delphi已经处理过了,不需要进一步处理。看这里:

    procedure TWinControl.WMEraseBkgnd(var Message: TWMEraseBkgnd);
    begin
      with ThemeServices do
      if ThemesEnabled and Assigned(Parent) and (csParentBackground in FControlStyle) then
        begin
          { Get the parent to draw its background into the control's background. }
          DrawParentBackground(Handle, Message.DC, nil, False);
        end
        else
        begin
          { Only erase background if we're not doublebuffering or painting to memory. }
          if not FDoubleBuffered or
             (TMessage(Message).wParam = TMessage(Message).lParam) then
            FillRect(Message.DC, ClientRect, FBrush.Handle);
        end;
    
      Message.Result := 1; // 表示已经处理过了
    end;

    如果pt.fErase标记为TRUE,指示应用程序应该处理背景,但是应用程序不一定需要处理,pt.fErase只是作为一个标记(笔记:留给程序员的灵活性依然很强)

    http://www.aichengxu.com/view/2426114

  • 相关阅读:
    微信支付退款部分代码
    Raspberry PI 点亮LED
    Raspberry PI 摄像头
    RaspberryPI 3B系统烧录
    MySQL基础使用
    MySQL数据库管理系统概述
    Eclipse创建JAVA项目
    Brup Suite拦截https请求
    Github 第一个仓库
    python os模块主要函数
  • 原文地址:https://www.cnblogs.com/findumars/p/5221931.html
Copyright © 2020-2023  润新知