Delphi控件的显示内容与显示边框是两回事

没有内容,不代表没有边框。比如设计期一个空的TImage仍是有边框的。

if (csOpaque in image1.ControlStyle) then ShowMessage('不透明')
else ShowMessage('透明') // image1没有内容的时候,就是透明;有内容的时候,就是不透明

再比如:

procedure TWinControl.PaintControls(DC: HDC; First: TControl);
var
  I, Count, SaveIndex: Integer;                                    
  FrameBrush: HBRUSH;
begin
  // 这个DC其实是父Win控件的句柄
  // 一共有2处调用此函数。分别是TControl.Repaint和TWinControl.PaintHandler,分别用来重绘图形控件和Win控件(后者包括了图形子控件,也正因为这个才需要执行这个函数)
  if DockSite and UseDockManager and (DockManager <> nil) then
    DockManager.PaintSite(DC);
  // 重画所有子控件(图形和句柄控件)
  // FControls和FWinControls在TControl.SetParent里调用TWinControl.Insert里增加元素
  if FControls <> nil then // 专指图形控件,不包含windows控件
  begin
    I := 0;
    if First <> nil then
    begin
      I := FControls.IndexOf(First);
      if I < 0 then I := 0;
    end;
    Count := FControls.Count;
    while I < Count do
    begin
      with TControl(FControls[I]) do
        if (Visible or (csDesigning in ComponentState) and not (csNoDesignVisible in ControlStyle)) and
          RectVisible(DC, Rect(Left, Top, Left + Width, Top + Height)) then // API
        begin
          if csPaintCopy in Self.ControlState then Include(FControlState, csPaintCopy);
          SaveIndex := SaveDC(DC);      // API,重画前,保存父控件的DC
          MoveWindowOrg(DC, Left, Top); // 调用2个API
          IntersectClipRect(DC, 0, 0, Width, Height); // API,新建一个完全的区域
          // 原本图形控件不能接受Windows消息的,现在也接受了。注意传递了父控件的DC
          Perform(WM_PAINT, DC, 0);     // important7,图形控件已经把WM_PAINT消息内容已经填好,就等程序员填写Paint函数加上真正要执行的内容。
          RestoreDC(DC, SaveIndex);     // API,恢复父控件的DC
          Exclude(FControlState, csPaintCopy); // 画完之后,去除标记
        end;
      Inc(I);
    end;
  end;
  // 除此以外,还要给Windows子控件额外画边框(因为实体已经画好了)(注意不是给自己画边框)
  if FWinControls <> nil then // 专指windows控件,不包含图形控件
    for I := 0 to FWinControls.Count - 1 do
      with TWinControl(FWinControls[I]) do
        if FCtl3D and (csFramed in ControlStyle) and
          (Visible or (csDesigning in ComponentState) and not (csNoDesignVisible in ControlStyle)) then
        begin
          // fixme 可以试试屏蔽这里的语句,看看效果
          FrameBrush := CreateSolidBrush(ColorToRGB(clBtnShadow)); // API
          FrameRect(DC, Rect(Left - 1, Top - 1, Left + Width, Top + Height), FrameBrush); // API 画矩形边框
          DeleteObject(FrameBrush); // API
          FrameBrush := CreateSolidBrush(ColorToRGB(clBtnHighlight));
          FrameRect(DC, Rect(Left, Top, Left + Width + 1, Top + Height + 1), FrameBrush); // 画两条线
          DeleteObject(FrameBrush); // API
        end;
end;

又看到一个函数:

procedure TWinControl.WMWindowPosChanged(var Message: TWMWindowPosChanged);
var
  Framed, Moved, Sized: Boolean;
begin
  // 三明治手法,这里使边框失效
  // 判断是否有边框,是否移动了,是否改变了尺寸
  Framed := FCtl3D and (csFramed in ControlStyle) and (Parent <> nil) and (Message.WindowPos^.flags and SWP_NOREDRAW = 0);
  Moved := (Message.WindowPos^.flags and SWP_NOMOVE = 0) and IsWindowVisible(FHandle); // API
  Sized := (Message.WindowPos^.flags and SWP_NOSIZE = 0) and IsWindowVisible(FHandle);
  // 如果有边框,并且已经移动或者改变了尺寸,那么使边框无效
  if Framed and (Moved or Sized) then InvalidateFrame;  // 类函数 fixme 这不是重复了吗?
  // 仅仅调整边框不够,更主要是调整控件自己的位置
  if not (csDestroyingHandle in ControlState) then UpdateBounds; // 类函数,使用API调整控件在屏幕上的位置

  inherited; // super 三明治手法,调用程序员潜在的消息函数,并重新计算最大化最小化的限制和坞里的尺寸

  // fixme 根据消息的内容,再次使边框无效(如果有显示或隐藏标记的话)
  if Framed and ((Moved or Sized) or (Message.WindowPos^.flags and (SWP_SHOWWINDOW or SWP_HIDEWINDOW) <> 0)) then
    InvalidateFrame; // 类函数,简单调用API
end;
procedure TWinControl.InvalidateFrame;
var
  R: TRect;
begin
  R := BoundsRect; // 类属性,调用方法,简单计算
  InflateRect(R, 1, 1); // API
  InvalidateRect(Parent.FHandle, @R, True); // API
end;

留个爪,以后再详细研究~

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