WM_PAINT在微软官方定义中,wParam和lParam都没有使用,所以就被Delphi给重定义了这个消息,还增加了DC(Delphi可任意改写消息的结构)

LRESULT CALLBACK WindowProc(
  HWND hwnd, 
  UINT  uMsg, 
  WPARAM wParam, 
  LPARAM lParam     
);

Parameters

wParam

This parameter is not used.

lParam

This parameter is not used.

Delphi里去掉句柄之后重定义:

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

经过测试,其大小为16:

procedure TForm1.Button3Click(Sender: TObject);
begin
  ShowMessage(IntToStr(sizeof(TWMPaint)));
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 // 消息里已经自带DC,用这个绘制就可以了
  begin
    if not (csCustomPaint in ControlState) and (ControlCount = 0) then // 针对Windows自带控件,其包含的子控件数量为0,且没有自绘标记。
      inherited // 走向1,主要是针对Windows系统原生控件,微软定义的基本控件,总不会有错,应该内含BeginPaint了吧?
    else
      PaintHandler(Message); // 走向2,主要是针对Delphi的自绘控件,内含BeginPaint
  end
  else
  begin
    DC := GetDC(0); // 走向3,消息里没有自带DC,那么就现取,内含BeginPaint
    MemBitmap := CreateCompatibleBitmap(DC, ClientRect.Right, ClientRect.Bottom);
    ReleaseDC(0, DC);
    MemDC := CreateCompatibleDC(0);
    OldBitmap := SelectObject(MemDC, MemBitmap);
    try
      DC := BeginPaint(Handle, PS);
      Perform(WM_ERASEBKGND, MemDC, MemDC);
      Message.DC := MemDC; // 其实这有这里进行赋值
      WMPaint(Message); // 中间有了DC,赶紧使用
      Message.DC := 0;
      BitBlt(DC, 0, 0, ClientRect.Right, ClientRect.Bottom, MemDC, 0, 0, SRCCOPY);
      EndPaint(Handle, PS);
    finally
      SelectObject(MemDC, OldBitmap);
      DeleteDC(MemDC);
      DeleteObject(MemBitmap);
    end;
  end;
end;

继续传递:

procedure TWinControl.PaintHandler(var Message: TWMPaint);
var
  I, Clip, SaveIndex: Integer;
  DC: HDC;
  PS: TPaintStruct;
begin
  DC := Message.DC; // 使用消息带来的句柄(没准在别处赋值的),比如子控件挨个向父控件发送重绘请求,这样就不用每次都重新给DC赋值了。但是这里不是引用,出了这个函数,Message.DC的值并没有被改变。fixme 还是没全懂
  if DC = 0 then DC := BeginPaint(Handle, PS); // 如果DC是空的,就要取得它(如果是单独的控件,肯定要执行)
  try
    if FControls = nil then PaintWindow(DC) else
    begin
      SaveIndex := SaveDC(DC);
      Clip := SimpleRegion;
      for I := 0 to FControls.Count - 1 do
        with TControl(FControls[I]) do
          if (Visible or (csDesigning in ComponentState) and
            not (csNoDesignVisible in ControlStyle)) and
            (csOpaque in ControlStyle) then
          begin
            Clip := ExcludeClipRect(DC, Left, Top, Left + Width, Top + Height);
            if Clip = NullRegion then Break;
          end;
      if Clip <> NullRegion then PaintWindow(DC);
      RestoreDC(DC, SaveIndex);
    end;
    PaintControls(DC, nil); // 还要把自己的DC带给子控件
  finally
    if Message.DC = 0 then EndPaint(Handle, PS);
  end;
end;

procedure TGraphicControl.WMPaint(var Message: TWMPaint);
begin
  if Message.DC <> 0 then // 注意,如果DC为零,根本就不自绘(没法自绘)
  begin
    Canvas.Lock;
    try
      Canvas.Handle := Message.DC; // 可见的本质上,DC就是一个控件的Canvas句柄,而且是父控件的DC句柄
      try
        Paint;
      finally
        Canvas.Handle := 0;
      end;
    finally
      Canvas.Unlock;
    end;
  end;
end;

procedure TCustomControl.WMPaint(var Message: TWMPaint);
begin
  Include(FControlState, csCustomPaint);
  inherited;
  Exclude(FControlState, csCustomPaint);
end;

procedure TWinControl.WMPrintClient(var Message: TWMPrintClient);
begin
  with Message do
    if Result <> 1 then
      if ((Flags and PRF_CHECKVISIBLE) = 0) or Visible then
        PaintHandler(TWMPaint(Message))
      else
        inherited
    else
      inherited;
end;

回头做个实验,一个TWinControl包含多个TCustomControl子控件。WM_PAINT第一次会发给父控件,然后依次重绘TCustomControl子控件,但是每个TCustomControl子控件都会通过inherited把消息传回来,当第一次传回来的时候,这个消息的DC就应该不是0了。

原文地址:https://www.cnblogs.com/findumars/p/5183638.html