zoukankan      html  css  js  c++  java
  • WM_ERASEBKGND官方解释(翻译),以及Delphi里所有的使用情况(就是绘制窗口控件背景色,并阻止进一步传递消息)

    #define WM_ERASEBKGND                   0x0014

    Parameters

    wParam

    A handle to the device context. // 设备上下文的句柄

    lParam

    This parameter is not used.

    Return value

    Type: LRESULT

    An application should return nonzero if it erases the background; otherwise, it should return zero.

    如果它擦除背景,函数返回非零值 ;否则,函数返回零。

    Remarks

    The DefWindowProc function erases the background by using the class background brush specified by the hbrBackgroundmember of the WNDCLASS structure. If hbrBackground is NULL, the application should process the WM_ERASEBKGNDmessage and erase the background.

    DefWindowProc函数通过使用WNDCLASS结构中指定的hbrBackground背景画笔擦除背景。如果hbrBackground是空的,应用程序应该处理WM_ERASEBKGND消息擦除背景。

    An application should return nonzero in response to WM_ERASEBKGND if it processes the message and erases the background; this indicates that no further erasing is required. If the application returns zero, the window will remain marked for erasing. (Typically, this indicates that the fErase member of the PAINTSTRUCT structure will be TRUE.)

    如果应用程序响应处理WM_ERASEBKGND消息并擦除背景,应返回非零值 ;告诉Windows没有必要再擦除。如果应用程序返回零,Windows仍将标记为删除。(通常情况下,这表明PAINTSTRUCT结构中的fErase为真.)

    翻译来自:http://baike.baidu.com/view/9801207.htm

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

    这个消息在Delphi被重定义:

      TWMEraseBkgnd = packed record
        Msg: Cardinal;
        DC: HDC;
        Unused: Longint;
        Result: Longint;
      end;

    它在Controls.pas单元里的相关代码(1处定义,2处使用):

    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; // 相当于说,WM_ERASEBKGND的传递到此为止,无论如何都不要继续传递了
    end;
    procedure TWinControl.WMPaint(var Message: TWMPaint);
    var
      DC, MemDC: HDC;
      MemBitmap, OldBitmap: HBITMAP;
      PS: TPaintStruct;
    begin
      if not FDoubleBuffered or (Message.DC <> 0) then
      begin
        if not (csCustomPaint in ControlState) and (ControlCount = 0) then
          inherited
        else
          PaintHandler(Message);
      end
      else
      begin
        DC := GetDC(0); // 整个屏幕的DC,借用一下而已
        MemBitmap := CreateCompatibleBitmap(DC, ClientRect.Right, ClientRect.Bottom); // 图像不宜过大,所以设置成最大就是屏幕大小
        ReleaseDC(0, DC); // 还掉了
        MemDC := CreateCompatibleDC(0);
        OldBitmap := SelectObject(MemDC, MemBitmap);
        try
          DC := BeginPaint(Handle, PS); // 重新取得当前控件的DC
          Perform(WM_ERASEBKGND, MemDC, MemDC); // 结合上面的TWinControl.WMEraseBkgnd,可以观察到,要得就是这种效果,即双缓冲第一次绘制时候,需要删除背景
          Message.DC := MemDC; // 构建一个消息,把MemDC传入,当前控件和子控件都在MemDC上画,不在原先的DC上作画了
          WMPaint(Message); // 递归调用,会引发DC<>0的情况,从而正常绘制所有子控件(绘制子控件不需要双缓冲了),否则根本据不会进入绘制子控件的流程
          Message.DC := 0;  // 子控件绘制完毕,不管后续消息流动到何处,不要在消息包含的DC上绘制东西了
          BitBlt(DC, 0, 0, ClientRect.Right, ClientRect.Bottom, MemDC, 0, 0, SRCCOPY); // 把MemDC上的内容拷贝到DC上
          EndPaint(Handle, PS);
        finally
          SelectObject(MemDC, OldBitmap); // 还掉
          DeleteDC(MemDC); // 还掉
          DeleteObject(MemBitmap); // 还掉
        end;
      end;
    end;

    还可以打印控件到其它DC设备上:

    // Draws the windowed control to a device context.
    procedure TWinControl.PaintTo(DC: HDC; X, Y: Integer);
    var
      I, EdgeFlags, BorderFlags, SaveIndex: Integer;
      R: TRect;
    begin
      Include(FControlState, csPaintCopy);
      SaveIndex := SaveDC(DC);
      MoveWindowOrg(DC, X, Y);
      IntersectClipRect(DC, 0, 0, Width, Height);
      BorderFlags := 0;
      EdgeFlags := 0;
      if GetWindowLong(Handle, GWL_EXSTYLE) and WS_EX_CLIENTEDGE <> 0 then
      begin
        EdgeFlags := EDGE_SUNKEN;
        BorderFlags := BF_RECT or BF_ADJUST
      end else
      if GetWindowLong(Handle, GWL_STYLE) and WS_BORDER <> 0 then
      begin
        EdgeFlags := BDR_OUTER;
        BorderFlags := BF_RECT or BF_ADJUST or BF_MONO;
      end;
      if BorderFlags <> 0 then
      begin
        SetRect(R, 0, 0, Width, Height);
        DrawEdge(DC, R, EdgeFlags, BorderFlags);
        MoveWindowOrg(DC, R.Left, R.Top);
        IntersectClipRect(DC, 0, 0, R.Right - R.Left, R.Bottom - R.Top);
      end;
      Perform(WM_ERASEBKGND, DC, 0);
      Perform(WM_PAINT, DC, 0);
      if FWinControls <> nil then
        for I := 0 to FWinControls.Count - 1 do
          with TWinControl(FWinControls[I]) do
            if Visible then PaintTo(DC, Left, Top);
      RestoreDC(DC, SaveIndex);
      Exclude(FControlState, csPaintCopy);
    end;

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

    Controls.pas里还有一处定义,但是它只有在TSpeedButton里被使用:

    procedure PerformEraseBackground(Control: TControl; DC: HDC);
    var
      LastOrigin: TPoint;
    begin
      GetWindowOrgEx(DC, LastOrigin);
      SetWindowOrgEx(DC, LastOrigin.X + Control.Left, LastOrigin.Y + Control.Top, nil);
      Control.Parent.Perform(WM_ERASEBKGND, Integer(DC), Integer(DC)); // TSpeedButton自己不是Windows窗口,只能出此下策
      SetWindowOrgEx(DC, LastOrigin.X, LastOrigin.Y, nil);
    end;
    
    procedure TSpeedButton.Paint;
    const
      DownStyles: array[Boolean] of Integer = (BDR_RAISEDINNER, BDR_SUNKENOUTER);
      FillStyles: array[Boolean] of Integer = (BF_MIDDLE, 0);
    var
      PaintRect: TRect;
      DrawFlags: Integer;
      Offset: TPoint;
      Button: TThemedButton;
      ToolButton: TThemedToolBar;
      Details: TThemedElementDetails;
    begin
      if not Enabled then
      begin
        FState := bsDisabled;
        FDragging := False;
      end
      else if FState = bsDisabled then
        if FDown and (GroupIndex <> 0) then
          FState := bsExclusive
        else
          FState := bsUp;
      Canvas.Font := Self.Font;
    
      if ThemeServices.ThemesEnabled then
      begin
        PerformEraseBackground(Self, Canvas.Handle);
        // 其它代码
      end;
    end;

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

    Forms.pas单元里的定义与使用:

    procedure TCustomForm.WMEraseBkgnd(var Message: TWMEraseBkgnd);
    begin
      if not IsIconic(Handle) then inherited else
      begin
        Message.Msg := WM_ICONERASEBKGND; // 当窗口处于最小化状态的时候,改变消息值
        DefaultHandler(Message); // 当窗口是MDI时,还应该改变当前窗口的图标,否则相当于不执行
      end;
    end;
    
    procedure TCustomForm.ClientWndProc(var Message: TMessage);
    
      procedure Default;
      begin
        with Message do
          Result := CallWindowProc(FDefClientProc, ClientHandle, Msg, wParam, lParam);
      end;
    
      function MaximizedChildren: Boolean;
      var
        I: Integer;
      begin
        for I := 0 to MDIChildCount - 1 do
          if MDIChildren[I].WindowState = wsMaximized then
          begin
            Result := True;
            Exit;
          end;
        Result := False;
      end;
    
    var
      DC: HDC;
      PS: TPaintStruct;
      R: TRect;
    begin
      with Message do
        case Msg of
          WM_NCHITTEST:
            begin
              Default;
              if Result = HTCLIENT then Result := HTTRANSPARENT;
            end;
          WM_ERASEBKGND:
            begin
              FillRect(TWMEraseBkGnd(Message).DC, ClientRect, Brush.Handle);
              { Erase the background at the location of an MDI client window }
              if (FormStyle = fsMDIForm) and (FClientHandle <> 0) then
              begin
                Windows.GetClientRect(FClientHandle, R);
                FillRect(TWMEraseBkGnd(Message).DC, R, Brush.Handle);
              end;
              Result := 1;
            end;
          $3F://!
            begin
              Default;
              if FFormStyle = fsMDIForm then
                ShowMDIClientEdge(FClientHandle, (MDIChildCount = 0) or
                  not MaximizedChildren);
            end;
          WM_PAINT:
            begin
              DC := TWMPaint(Message).DC;
              if DC = 0 then
                TWMPaint(Message).DC := BeginPaint(ClientHandle, PS);
              try
                if DC = 0 then
                begin
                  GetWindowRect(FClientHandle, R);
                  R.TopLeft := ScreenToClient(R.TopLeft);
                  MoveWindowOrg(TWMPaint(Message).DC, -R.Left, -R.Top);
                end;
                PaintHandler(TWMPaint(Message));
              finally
                if DC = 0 then
                  EndPaint(ClientHandle, PS);
              end;
            end;
        else
          Default;
        end;
    end;

    进一步解释TCustomForm.WMEraseBkgnd转发来的消息:

    procedure TCustomForm.DefaultHandler(var Message);
    begin
      if ClientHandle <> 0 then
        with TMessage(Message) do
          if Msg = WM_SIZE then
            Result := DefWindowProc(Handle, Msg, wParam, lParam) else
            Result := DefFrameProc(Handle, ClientHandle, Msg, wParam, lParam)
      else
        inherited DefaultHandler(Message)
    end;
    
    // 处理图标消息 message WM_ICONERASEBKGND
    procedure TCustomForm.WMIconEraseBkgnd(var Message: TWMIconEraseBkgnd); 
    begin
      if FormStyle = fsMDIChild then
      if (FormStyle = fsMDIChild) and not (csDesigning in ComponentState) then
        FillRect(Message.DC, ClientRect, Application.MainForm.Brush.Handle)
      else inherited;
    end;

    还有TApplication对这个消息的使用:

    procedure TApplication.WndProc(var Message: TMessage);
    var
      procedure Default;
      begin
        with Message do
          Result := DefWindowProc(FHandle, Msg, WParam, LParam);
      end;
    
    begin
      try
        Message.Result := 0;
        for I := 0 to FWindowHooks.Count - 1 do
          if TWindowHook(FWindowHooks[I]^)(Message) then Exit;
        CheckIniChange(Message);
        with Message do
          case Msg of
            WM_SYSCOMMAND:
              case WParam and $FFF0 of
                SC_MINIMIZE: Minimize;
                SC_RESTORE: Restore;
              else
                Default;
              end;
            WM_CLOSE:
              if MainForm <> nil then MainForm.Close;
            WM_PAINT:
              if IsIconic(FHandle) then DrawAppIcon else Default;
            WM_ERASEBKGND: // 改变消息,然后转发
              begin
                Message.Msg := WM_ICONERASEBKGND;
                Default;
              end;
            WM_NULL:
              CheckSynchronize;
          else
            Default;
          end;
      except
        HandleException(Self);
      end;
    end;

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

    再看StdCtrls.pas单元里的定义与使用:

    procedure TCustomComboBox.WMEraseBkgnd(var Message: TWMEraseBkgnd);
    begin
      if Style = csSimple then
      begin
        FillRect(Message.DC, ClientRect, Parent.Brush.Handle);
        Message.Result := 1;
      end
      else
        DefaultHandler(Message);
    end;
    
    procedure TButtonControl.WMEraseBkGnd(var Message: TWMEraseBkGnd);
    begin
      { Under theme services the background is drawn in CN_CTLCOLORSTATIC. }
      if ThemeServices.ThemesEnabled then
        Message.Result := 1
      else
        inherited;
    end;
    
    procedure TButton.WMEraseBkgnd(var Message: TWMEraseBkgnd);
    begin
      if ThemeServices.ThemesEnabled then
        Message.Result := 1
      else
        DefaultHandler(Message);
    end;
    
    procedure TScrollBar.WMEraseBkgnd(var Message: TWMEraseBkgnd);
    begin
      DefaultHandler(Message);
    end;

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

    还有ExtCtrls.pas单元里的:

    procedure TCustomControlBar.WMEraseBkgnd(var Message: TWmEraseBkgnd);
    var
      R: TRect;
      I, J: Integer;
      Save: Boolean;
    begin
      if Message.DC <> 0 then
        Canvas.Handle := Message.DC;
      if Picture.Graphic <> nil then
      begin
    
        try
          R := ClientRect;
          Save := FDrawing;
          FDrawing := True;
          try
            { Tile image across client area }
            for I := 0 to (R.Right - R.Left) div Picture.Width do
              for J := 0 to (R.Bottom - R.Top) div Picture.Height do
                Canvas.Draw(I * Picture.Width, J * Picture.Height, Picture.Graphic);
          finally
            FDrawing := Save;
          end
        finally
          if Message.DC <> 0 then
            Canvas.Handle := 0;
          Message.Result := 1;
        end;
      end
      else
      begin
        Canvas.Brush.Color := Color;
        Canvas.Brush.Style := bsSolid;
        Canvas.FillRect(ClientRect);
        inherited;
      end;
    end;

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

    还有dbcgrids.pas和DbCtrls.pas单元里的:

    procedure TDBCtrlPanel.WMEraseBkgnd(var Message: TMessage);
    begin
      Message.Result := 1;
    end;
    
    procedure TDBCtrlGrid.WMEraseBkgnd(var Message: TMessage);
    begin
      Message.Result := 1;
    end;procedure TDBMemo.WMPaint(var Message: TWMPaint);
    var
      S: string;
    begin
      if not (csPaintCopy in ControlState) then inherited else
      begin
        if FDataLink.Field <> nil then
          if FDataLink.Field.IsBlob then
          begin
            if FAutoDisplay then
              S := AdjustLineBreaks(FDataLink.Field.AsString) else
              S := Format('(%s)', [FDataLink.Field.DisplayLabel]);
          end else
            S := FDataLink.Field.DisplayText;
        SendMessage(FPaintControl.Handle, WM_SETTEXT, 0, Integer(PChar(S)));
        SendMessage(FPaintControl.Handle, WM_ERASEBKGND, Message.DC, 0);
        SendMessage(FPaintControl.Handle, WM_PAINT, Message.DC, 0);
      end;
    end;

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

    还有ShadowWnd.pas单元里的:

    procedure TShadowWindow.WMEraseBkgnd(var Message: TWMEraseBkgnd);
    begin
      Message.Result := 0;
    end;

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

    还有Tabs.pas单元里的:

    procedure TTabSet.Paint;
    var
      TabStart, LastTabPos: Integer;
      TabPos: TTabPos;
      Tab: Integer;
      Leading: TEdgeType;
      Trailing: TEdgeType;
      isFirst, isLast, isSelected, isPrevSelected: Boolean;
      R: TRect;
    begin
      if not HandleAllocated then Exit;
    
      MemBitmap.Canvas.Font := Self.Canvas.Font;
    
      { draw background of tab area }
      with MemBitmap.Canvas do
      begin
        Brush.Bitmap := BrushBitmap;
        if ThemeServices.ThemesEnabled and ParentBackground then
          Perform(WM_ERASEBKGND, MemBitmap.Canvas.Handle, 0)
        else
          FillRect(Rect(0, 0, MemBitmap.Width, MemBitmap.Height));
    
      end;
    end;

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

    还有OleCtrls.pas单元里的:

    procedure TOleControl.WMEraseBkgnd(var Message: TWMEraseBkgnd);
    begin
      if FMiscStatus and OLEMISC_INVISIBLEATRUNTIME = 0 then
        DefaultHandler(Message) else
        inherited;
    end;

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

    还有ComCtrls.pas单元里的5处定义和2处使用:

    procedure TPageControl.WMEraseBkGnd(var Message: TWMEraseBkGnd);
    begin
      if (not ThemeServices.ThemesEnabled) or (not ParentBackground) then
        inherited
      else
        Message.Result := 1;
    end;
    
    procedure TCustomStatusBar.WMEraseBkGnd(var Message: TWMEraseBkGnd);
    var
      Details: TThemedElementDetails;
    begin
      if ThemeServices.ThemesEnabled then
      begin
        Details := ThemeServices.GetElementDetails(tsStatusRoot);
        ThemeServices.DrawElement(Message.DC, Details, ClientRect, nil);
        Message.Result := 1;
      end
      else
        inherited;
    end;
    
    procedure TTrackBar.WMEraseBkGnd(var Message: TWMEraseBkGnd);
    var
      R: TRect;
    begin
      if ThemeServices.ThemesEnabled then
      begin
        R := ClientRect;
        if Focused and ((Perform(WM_QUERYUISTATE, 0, 0) and UISF_HIDEFOCUS) = 0) then
          InflateRect(R, -1, -1);
        ThemeServices.DrawParentBackground(Handle, Message.DC, nil, False, @R);
        Message.Result := 1;
      end
      else
        inherited;
    end;
    
    procedure TToolBar.WMEraseBkgnd(var Message: TWMEraseBkgnd);
    begin
      if not Transparent then
        inherited else
        DefaultHandler(Message);
    end;
    
    procedure TCoolBar.WMEraseBkgnd(var Message: TWMEraseBkgnd);
    begin
      if not ThemeServices.ThemesEnabled and (IsBackgroundDirty or (IsAutoSized and (Bands.Count = 0))) then
        inherited;
      DefaultHandler(Message);
    end;
    
    procedure TCustomListView.WndProc(var Message: TMessage);
    begin
      if not (csDesigning in ComponentState) and ((Message.Msg = WM_LBUTTONDOWN) or
        (Message.Msg = WM_LBUTTONDBLCLK)) and not Dragging and (DragMode = dmAutomatic) then
      begin
        if not IsControlMouseMsg(TWMMouse(Message)) then
        begin
          ControlState := ControlState + [csLButtonDown];
          Dispatch(Message);
        end;
      end
      else if not (((Message.Msg = WM_PAINT) or (Message.Msg = WM_ERASEBKGND)) and
        Items.FNoRedraw) then
        inherited WndProc(Message);
    end;
    
    procedure TCoolBar.PaintWindow(DC: HDC);
    begin
      Perform(WM_ERASEBKGND, DC, 0);
      inherited;
    end;

    总结:其实做的事情不多,子类要么就是发WM_ERASEBKGND消息给父类,要么就是改变它的消息值(使它变成另外一个消息),而父控件做的事情也很少,最主要给控件上背景色,还有就是阻止它进一步传递。

    做实验:

    1. 窗体上放一个覆盖了WM_ERASEBKGND消息的控件,比如TButton

    2. 放一个不覆盖WM_ERASEBKGND消息的空间,比如TPanel,看看到底会出什么事情

    备注:其实这种消息很难调试,只能全部记录在日志里。

  • 相关阅读:
    总结的反爬虫手段(持续更新)
    爬虫类编程笔记导航
    技术开发流程小公司
    敏捷开发学习笔记(一)
    .NET Framework各版本比较
    linux shell 之 cut
    Hive insert overwrite 出现错误解决方法
    hive join
    linux shell 之 grep
    hive实现not in
  • 原文地址:https://www.cnblogs.com/findumars/p/5357657.html
Copyright © 2011-2022 走看看